Part 4: From toyaikit to PydanticAI
The last step is to run the same agent with PydanticAI instead of ToyAIKit. We keep the same prompt and the same underlying tool implementations, but register the tools directly in PydanticAI and run the Q&A loop ourselves.
Installing PydanticAI
Install the PydanticAI package:
uv add pydantic-ai
Creating the Agent
We remove ToyAIKit completely and use PydanticAI's Agent directly:
from pydantic_ai import Agent, RunUsage
coding_agent = Agent(
'openai:gpt-5.4-mini',
instructions=AGENT_WITH_SKILLS_INSTRUCTIONS,
tools=[
agent_tools.read_file,
agent_tools.write_file,
agent_tools.see_file_tree,
agent_tools.execute_bash_command,
agent_tools.search_in_files,
skills_tool.skill,
],
)
The agent gets the same instructions, the same tools, and the same model. Only the framework changes.
The print_messages helper
To show progress in the notebook, we use a helper that prints each message kind and its content:
def print_messages(messages):
for m in messages:
print(m.kind)
for p in m.parts:
part_kind = p.part_kind
if part_kind == "user-prompt":
print("USER:", p.content)
if part_kind == "tool-call":
print("TOOL CALL:", p.tool_name, p.args)
if part_kind == "tool-return":
print("TOOL RETURN:", p.tool_name)
if part_kind == "text":
print("ASSISTANT:", p.content)
print()
The Q&A loop with message_history
PydanticAI supports message_history so we can maintain conversation state
across turns:
messages = []
usage = RunUsage()
while True:
user_prompt = input(">> ").strip()
if not user_prompt or user_prompt.lower() == "stop":
break
result = await coding_agent.run(
user_prompt,
message_history=messages,
)
usage = usage + result.usage()
print_messages(result.new_messages())
messages.extend(result.new_messages())
Type stop to end the chat loop. After the conversation, inspect total
usage:
usage
This is the same coding agent, now running with plain PydanticAI instead of ToyAIKit. The prompt, the tools, and the behavior are identical - only the framework plumbing changed.
Continue with Where to go from here for what was intentionally not covered.