Skip to main content
GitHub

Pydantic AI

Auto-instrument Pydantic AI for type-safe agents.

Risicare automatically instruments Pydantic AI for type-safe agent development.

Python only

This framework integration is available in the Python SDK only. No JavaScript package exists for Pydantic AI.

Installation

pip install 'risicare[pydantic-ai]'
# or
pip install risicare pydantic-ai

Version Compatibility

Requires pydantic-ai >= 0.1.0.

Auto-Instrumentation

import risicare
from pydantic_ai import Agent
 
risicare.init()
 
agent = Agent(
    "openai:gpt-4o",
    system_prompt="You are a helpful assistant."
)
 
# Automatically traced
result = agent.run_sync("Hello!")

What's Captured

FeatureDescription
Agent Runsrun, run_sync and run_stream calls (run_stream works from Python 0.5.1; in 0.5.0 it raised TypeError) — see Streaming
Model CallsUnderlying LLM API calls, when the model uses an API that the SDK traces — see the notice below

Only the model is captured on current Pydantic AI

The agent span reliably carries one framework attribute, framework.pydantic_ai.model. Three others the integration tries to set — framework.pydantic_ai.result_type, .tools and .system_prompt_length — read attribute names (result_type, tools/_tools, system_prompt) that Pydantic AI no longer exposes on the Agent object. On a current release (measured against pydantic-ai 2.54.0 with Python 0.6.0) all three are silently absent: the reads are defensive, so nothing errors and nothing is recorded.

Tool executions and result validation are not captured as framework attributes or as child spans.

In 0.6.0 you get the underlying LLM calls as provider spans only when the model uses an API that the SDK traces. An openai: model uses the OpenAI Responses API, which 0.6.0 does not trace: the run then has no LLM-call span and no cost. The agent spans still carry the run's token counts (gen_ai.usage.prompt_tokens, gen_ai.usage.completion_tokens, from result.usage()). Agent("openai-chat:gpt-4o") uses the Chat Completions API, and the provider spans arrive. The value of framework.pydantic_ai.model is the model class (for example OpenAIResponsesModel()), not the model id.

Span Hierarchy

pydantic_ai.agent.run/{name}                    (AGENT kind)
└── pydantic_ai.agent.run/{name}                (run_sync only: the inner span is operation "run")
    └── openai.chat.completions.create/{model}  (chat-completions models only, one per model call)

An async run() sends one agent span. Without name=, the first call on an agent names the outer span Agent (and, for run_sync(), the inner span instance); Pydantic AI then keeps the inferred name instance for later run() and run_sync() calls. Set name= for stable span names. Both agent spans carry the run's token counts.

Provider Spans

Pydantic AI instrumentation creates the agent spans. A model call is traced by the provider instrumentation when the model uses an API that the SDK traces (for OpenAI, the Chat Completions API), and its span is a child of the agent span.

Structured Outputs

A structured output works with tracing on. In 0.6.0 the span does not record the output type or the schema:

from pydantic import BaseModel
 
class CityInfo(BaseModel):
    name: str
    country: str
    population: int
 
agent = Agent(
    "openai:gpt-4o",
    output_type=CityInfo
)
 
result = agent.run_sync("Tell me about Paris")
# result.output is a CityInfo

Tools

A tool runs with tracing on. In 0.6.0, tool executions are not traced: no span is sent for a tool call.

from pydantic_ai import Agent, RunContext
 
agent = Agent("openai:gpt-4o")
 
@agent.tool
def get_weather(ctx: RunContext, location: str) -> str:
    """Get weather for a location."""
    return f"Sunny in {location}"
 
# Tool executions are not traced: no per-tool child spans are emitted, and on
# current Pydantic AI the framework.pydantic_ai.tools attribute is not set
# either (see "What's Captured" above).
result = agent.run_sync("What's the weather in Paris?")

Dependencies

Dependency injection works normally with Risicare — but nothing about your dependencies is recorded:

from dataclasses import dataclass
 
@dataclass
class Deps:
    user_id: str
    api_key: str
 
agent = Agent("openai:gpt-4o", deps_type=Deps)
 
@agent.tool
def get_user_data(ctx: RunContext[Deps]) -> str:
    return f"Data for {ctx.deps.user_id}"
 
result = agent.run_sync(
    "Get my data",
    deps=Deps(user_id="123", api_key="secret")
)
# No dependency is recorded on the span — not the values, not the field names,
# not the type. There is no deps capture anywhere in the SDK, so there is also
# no secret-filtering step to rely on.

Streaming

run_stream() works with the SDK active (Python 0.5.1 and later) and sends one agent span, pydantic_ai.agent.run_stream/{name}. The span opens when the async with block starts and ends when the block ends; its status is error when your code raises inside the block. For a Chat Completions model, the provider span is a child of the agent span. In Python 0.5.0, run_stream() raised TypeError: 'coroutine' object does not support the asynchronous context manager protocol when the SDK was active; with 0.5.0, use agent.run() or agent.run_sync().

async with agent.run_stream("Write a story") as response:
    async for chunk in response.stream_text(delta=True):
        print(chunk, end="")

Multiple Models

# OpenAI (needs OPENAI_API_KEY)
agent = Agent("openai:gpt-4o")
 
# Anthropic (needs ANTHROPIC_API_KEY)
agent = Agent("anthropic:claude-sonnet-5-5")
 
# Gemini (needs GOOGLE_API_KEY)
agent = Agent("google:gemini-2.5-pro")

Next Steps