CopilotKit

Pydantic AI Agents

Learn how to implement Human-in-the-Loop (HITL) using Pydantic AI Agents.


What is this?#

Flow based agents are stateful agents that can be interrupted and resumed to allow for user input.

CopilotKit lets you to add custom UI to take user input and then pass it back to the agent upon completion.

Why should I use this?#

Human-in-the-loop is a powerful way to implement complex workflows that are production ready. By having a human in the loop, you can ensure that the agent is always making the right decisions and ultimately is being steered in the right direction.

Flow based agents are a great way to implement HITL for more complex workflows where you want to ensure the agent is aware of everything that has happened during a HITL interaction.

Implementation#

Run and connect your agent#

You'll need to run your agent and connect it to CopilotKit before proceeding. If you haven't done so already, you can follow the instructions in the Getting Started guide.

If you don't already have an agent, you can use the coagent starter as a starting point as this guide uses it as a starting point.

Add a useFrontendTool to your Frontend#

First, we'll create a component that renders the agent's essay draft and waits for user approval.

ui/app/page.tsx

function YourMainContent() {
  // ...

  useFrontendTool({
    name: "write_essay",
    available: "frontend",
    description: "Writes an essay and takes the draft as an argument.",
    parameters: z.object({
      draft: z.string().describe("The draft of the essay"),
    }),
    renderAndWaitForResponse: ({ args, respond, status }) => {
      return (
        <div>
<Markdown content={args.draft || 'Preparing your draft...'} />

          <div className={`flex gap-4 pt-4 ${status !== "executing" ? "hidden" : ""}`}>
            <button
              onClick={() => respond?.("CANCEL")}
              disabled={status !== "executing"}
              className="border p-2 rounded-xl w-full"
            >
              Try Again
            </button>
            <button
              onClick={() => respond?.("SEND")}
              disabled={status !== "executing"}
              className="bg-blue-500 text-white p-2 rounded-xl w-full"
            >
              Approve Draft
            </button>
          </div>
        </div>
      );
    },
  });

  // ...
}

Setup the Pydantic AI Agent#

Now we'll setup the Pydantic AI agent. The flow is hard to understand without a complete example, so below is the complete implementation of the agent with explanations.

Some main things to note:

  • The agent's state inherits from CopilotKitState to bring in the CopilotKit actions.
  • CopilotKit's actions are bound to the model as tools.
  • If the writeEssay action is found in the model's response, the agent will pass control back to the frontend to get user feedback.
agent/sample_agent/agent.py
from pydantic_ai import Agent

agent = Agent('openai:gpt-5.4-mini')

@agent.tool_plain
async def write_essay(topic: str) -> str:
    """Write an essay on the given topic."""
    # This would typically generate an essay
    # The agent will wait for user feedback before proceeding
    return f"Essay draft on '{topic}' has been generated. Please review."

app = agent.to_ag_ui()

Give it a try!#

Try asking your agent to write an essay about the benefits of AI. You'll see that it will generate an essay, stream the progress and eventually ask you to review it.