Workshops ... Part 8: PydanticAI, Z.AI, and next steps

Part 8: PydanticAI, Z.AI, and next steps

The last part of the workshop keeps the same coding-agent idea and changes 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:

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,
]

The workshop notebooks use a helper 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 feels convenient here. We do not need to decorate or wrap every method.

PydanticAI with OpenAI

Create the agent:

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:

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. That is the kind of issue you fix by continuing the same chat with a more specific request.

PydanticAI with Anthropic

Switching providers is the main reason this variant is interesting. 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, and 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. That is a useful result, not a failure of the workshop: the request is more complex than a todo list, and the next step is to paste the error back into the agent and ask it to fix the project.

Use this pattern for your own projects:

  1. Ask for a small app.
  2. Run it manually.
  3. Copy the error or broken behavior back into the chat.
  4. Let the agent read and patch the project.
  5. Repeat until the app is usable.

Z.AI through chat completions

The final provider in the workshop is 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, so we 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:

runner.run()

If Z.AI is slow or times out, the same client shape is still worth knowing: 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 and continue from there.

Next steps

The extra multiple-agents.ipynb notebook sketches 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

The coding agent prompt in that notebook is stricter. It tells the model to execute a supplied plan step by step, report the step it finished, avoid template logic, and run migrations and tests.

Treat that multi-agent notebook as a follow-up path rather than a required part of the workshop.

Deferred improvements

The workshop version leaves 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

The small version is still useful. It shows how coding agents work under the hood without hiding everything inside a finished product.

Continue with Q&A: side discussions.

Questions & Answers (0)

Sign in to ask questions