Part 8: PydanticAI, Z.AI, and next steps
In the last part we keep the same coding-agent idea and change the framework or provider. Different models are better at coding, and different agent frameworks make different tradeoffs.
PydanticAI setup
Install PydanticAI:
pip install pydantic-ai
Import the agent class:
from pydantic_ai import Agent
Copy a fresh Django template:
cp -r django_template todo-pydantic
Create the tools object.
from pathlib import Path
import tools
agent_tools = tools.AgentTools(Path("todo-pydantic"))
With PydanticAI, you can pass the methods directly:
coding_agent_tools_list = [
agent_tools.execute_bash_command,
agent_tools.read_file,
agent_tools.search_in_files,
agent_tools.see_file_tree,
agent_tools.write_file,
]
We use a helper in the notebooks to collect public methods automatically:
from toyaikit.tools import get_instance_methods
coding_agent_tools_list = get_instance_methods(agent_tools)
This is one reason PydanticAI saves work here: we don't need to
decorate or wrap every method.
PydanticAI with OpenAI
Create the agent with OpenAI.
coding_agent = Agent(
"openai:gpt-4o-mini",
instructions=DEVELOPER_PROMPT,
tools=coding_agent_tools_list,
)
Use the ToyAIKit runner for the notebook UI:
from toyaikit.chat import IPythonChatInterface
from toyaikit.chat.runners import PydanticAIRunner
chat_interface = IPythonChatInterface()
runner = PydanticAIRunner(
chat_interface=chat_interface,
agent=coding_agent,
)
Run it in the notebook.
await runner.run()
Type:
to-do list
With OpenAI gpt-4o-mini, the agent generates an app, but some visible
items may be hard to see. You fix that by continuing
the same chat with a more specific request.
PydanticAI with Anthropic
In this variant you swap the provider with a one-line change.
If you have an Anthropic API key, you can change the model string:
coding_agent = Agent(
"anthropic:claude-3-5-sonnet-latest",
instructions=DEVELOPER_PROMPT,
tools=coding_agent_tools_list,
)
The developer prompt can stay the same for this example. Claude produces a
nicer todo interface than the gpt-4o-mini attempt in this workshop. The app
works better from the first run.
Set the Anthropic key the same way you set OPENAI_API_KEY, but with the
environment variable expected by your Anthropic client setup.
Try another app request
After the todo app, try a harder request:
Anki cards project
The generated design looked good, but one action failed because a route was missing or wrong. You learn something useful from that failure rather than hitting a dead end, since the request is more complex than a todo list. Paste the error back into the agent and ask it to fix the project.
Use this pattern for your own projects:
- Ask for a small app.
- Run it manually.
- Copy the error or broken behavior back into the chat.
- Let the agent read and patch the project.
- Repeat until the app is usable.
Z.AI through chat completions
For the last provider, we try Z.AI with glm-4.5. Z.AI follows an
OpenAI-compatible API, so we can use the OpenAI Python client with a
different base_url.
Set a ZAI_API_KEY environment variable, then create the client:
import os
from openai import OpenAI
zai_client = OpenAI(
api_key=os.getenv("ZAI_API_KEY"),
base_url="https://api.z.ai/api/paas/v4/",
)
For this path, we use the OpenAI-compatible chat completions API rather than the newer Responses API.
Switch to the older chat completions runner:
from toyaikit.tools import Tools
from toyaikit.chat import IPythonChatInterface
from toyaikit.chat.runners import OpenAIChatCompletionsRunner
from toyaikit.llm import OpenAIChatCompletionsClient
Create the tools the same way as before:
agent_tools = tools.AgentTools(Path("todo-zai"))
tools_obj = Tools()
tools_obj.add_tools(agent_tools)
Create the chat completions client and runner:
llm_client = OpenAIChatCompletionsClient(
model="glm-4.5",
client=zai_client,
)
chat_interface = IPythonChatInterface()
runner = OpenAIChatCompletionsRunner(
tools=tools_obj,
developer_prompt=DEVELOPER_PROMPT,
chat_interface=chat_interface,
llm_client=llm_client,
)
Run it in the notebook.
runner.run()
Even if Z.AI is slow or times out, the same client structure still works. Many providers expose OpenAI-compatible chat completions, so you can often swap only the base URL, model name, and runner.
Provider fallback
When a provider times out, the practical fix is usually not inside your code. The service may be overloaded. A production application should be able to switch to another provider or model when the current one is unavailable.
For this workshop, retrying the same request is enough. If the model partially edited files before timing out, the next run can read the current project state. Then it can continue from there.
Next steps
In the extra multiple-agents.ipynb notebook, we sketch the next version of
the idea.
Instead of one coding agent taking a vague prompt directly, it splits the work into stages:
- a requirements agent asks clarifying questions and produces functional requirements
- a naming step extracts a filesystem-safe project slug
- a planning agent turns requirements into an implementation plan
- a coding agent executes that plan with file tools
The requirements agent uses a REQUIREMENTS_READY marker so the notebook
knows when to stop asking questions:
def stop_on_ready(messages):
if len(messages) == 0:
return False
entry = messages[-1]
if entry.type == "message":
text = entry.content[0].text
if "REQUIREMENTS_READY" in text:
return True
return False
In that notebook we make the coding agent prompt stricter. It tells the model to execute a supplied plan step by step and report the step it finished. It also tells the model to avoid template logic, then run migrations and tests.
Treat that multi-agent notebook as a follow-up path rather than a required part of the workshop.
Deferred improvements
We leave several product-grade features for later:
- streaming output for every runner so the user sees progress live
- a browser preview that can be checked by the agent
- safer command execution and stronger sandboxing
- patch-based edits instead of whole-file rewrites
- model fallback when a provider times out
- tests that the agent runs after every generated change
- guardrails for prompt injection and unintended access
- a requirements and planning stage before the coding agent writes files
Even this small version earns its keep. It shows how coding agents work under the hood without hiding everything inside a finished product.
Continue with Q&A: side discussions.