Skip to main content

Pydantic AI Integration

TraceAgent plugs into Pydantic AI as a capability: one extra line in your agent records every model call and every tool call into a run.

Installation​

pippip install trace-agent-sdk trace-agent-pydantic-ai
uvuv add trace-agent-sdk trace-agent-pydantic-ai

Requirements: Pydantic AI 2.x (>=2,<3) and Python 3.11+.

It works with whichever model your agent already uses: nothing in the integration depends on the provider. The examples use "openai:gpt-4o" only as an example model string.

Basic Usage​

Start the TraceAgent server first (it listens on http://127.0.0.1:8000, see the Quick Start), then:

from pydantic_ai import Agent
from trace_agent_sdk import TraceAgentClient

client = TraceAgentClient("http://127.0.0.1:8000")
run = client.start_run("weather-agent", "What is the weather in Madrid?")

agent = Agent(
"openai:gpt-4o",
capabilities=[run.as_pydantic_ai_capability()], # the TraceAgent line
)

try:
result = agent.run_sync("What is the weather in Madrid?")
run.finish(result.output)
except Exception as exc:
run.fail(type(exc).__name__, str(exc))
raise

Create the run, add capabilities=[run.as_pydantic_ai_capability()] to your Agent, and close the run at the end. Nothing else in your agent changes.

You open and close the run

If the agent raises, the capability records the agent's failure, but the run stays running until you call run.fail(...). That is what the try/except above is for.

Works with run_sync, run and run_stream, with parallel tools, and with nested agents. For a version that runs without an API key, see the Pydantic AI quickstart.

One Capability, One Run​

A capability belongs to one run. If you reuse the same Agent for many requests (a web server, for example), don't put the capability in the constructor. Pass it on each call instead. Pydantic AI accepts capabilities= in run, run_sync, run_stream and iter:

agent = Agent("openai:gpt-4o") # one agent, reused

run = client.start_run("weather-agent", "Request 1")
result = agent.run_sync(
"What is the weather in Madrid?",
capabilities=[run.as_pydantic_ai_capability()],
)
run.finish(result.output)

Each request then gets its own run, and events never mix between them.

What Gets Recorded​

For the first example, the run contains these events, in order:

run.started → chain.started → llm.started → llm.completed →
tool.started → tool.succeeded → llm.started → llm.completed →
chain.completed → run.completed

The run also gets its provider, model, token totals and the real duration of every tool. Model calls and tools appear nested under the agent run, in the order they happened.

The provider is taken from the model string (for example openai:gpt-4o). A custom model object may show it as unknown.

Options​

Pass them to run.as_pydantic_ai_capability(...):

OptionDefaultDescription
record_model_eventsTrueRecord each model request as an LLM call (model, provider, tokens, requested tools)
record_tool_eventsTrueRecord each tool execution (arguments, output summary, duration, errors)
record_node_eventsFalseRecord every node of the agent graph as an agent_action
redact_fields[]Argument and output keys whose value is replaced with [redacted]
failure_cooldown30.0Seconds to stop sending after a failed delivery; events produced meanwhile are dropped. 0 retries every event
flush_timeout5.0Maximum seconds the end of the agent run waits for pending events
capability = run.as_pydantic_ai_capability(
redact_fields=["api_key", "password"],
record_node_events=True,
)

redact_fields is a list of key names. A key is matched by its exact name, at any depth, inside the arguments and results of your tools, and its value is replaced with [redacted]. It does not detect patterns or values. Independently of this option, long values are always shortened: text inside tool arguments at 160 characters, and the summaries of prompts and outputs at 220.

It Never Gets in the Agent's Way​

Tracing must not break or slow down your agent, so:

  • Events are queued and sent by a background thread.
  • If the TraceAgent server is down, the agent keeps working. It prints a single line ([trace-agent] could not deliver trace data ... The agent is not affected.) and pauses sending for failure_cooldown seconds.
  • When the outermost agent run ends, it waits up to flush_timeout seconds for pending events, so run.finish() normally reaches the server after them. If the server hangs for longer than that, there is no such guarantee.

Good to Know​

Silence the Logfire banner

Pydantic AI prints a startup banner suggesting Logfire. It is unrelated to TraceAgent. Set PYDANTIC_AI_NO_BANNER=1 to hide it.

  • Only the agent's regular tools are recorded. If the agent's output goes through an output function (output_type=my_function), that function is not recorded as a tool. This is a known difference with the OpenTelemetry route; see Hooks or OpenTelemetry?.
  • Use either this capability or OpenTelemetry for a given agent. Using both records the run twice.
  • Tested with real agents through OpenAI-compatible and Anthropic-compatible APIs.

What's Next?​

💬 Comments