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
pip install trace-agent-sdk trace-agent-pydantic-aiuv add trace-agent-sdk trace-agent-pydantic-aiRequirements: 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.
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(...):
| Option | Default | Description |
|---|---|---|
record_model_events | True | Record each model request as an LLM call (model, provider, tokens, requested tools) |
record_tool_events | True | Record each tool execution (arguments, output summary, duration, errors) |
record_node_events | False | Record every node of the agent graph as an agent_action |
redact_fields | [] | Argument and output keys whose value is replaced with [redacted] |
failure_cooldown | 30.0 | Seconds to stop sending after a failed delivery; events produced meanwhile are dropped. 0 retries every event |
flush_timeout | 5.0 | Maximum 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 forfailure_cooldownseconds. - When the outermost agent run ends, it waits up to
flush_timeoutseconds for pending events, sorun.finish()normally reaches the server after them. If the server hangs for longer than that, there is no such guarantee.
Good to Know
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?
- Pydantic AI Quickstart — Runnable example, no API key needed
- OpenTelemetry Ingestion — Trace Pydantic AI without any TraceAgent code in the agent
- Hooks or OpenTelemetry? — Which route to choose
- SDK Reference — Manual instrumentation