Pydantic AI

Auto-instrument Pydantic AI (pydantic-ai on PyPI) so every Agent.run and Agent.run_sync inside an active AgentPing run emits one llm_call with the provider, model, total token usage and latency of that agent run. Python only.

Install / enable

pip install "agentping-io[pydantic-ai]"
import agentping
from pydantic_ai import Agent

agentping.init()
agentping.instrument_pydantic_ai()

triage = Agent("anthropic:claude-sonnet-4-5", instructions="Classify the ticket.")

with agentping.run("support-triage", customer_id="acme-corp"):
    result = triage.run_sync("My invoice is wrong")

instrument_pydantic_ai() patches Agent.run and Agent.run_sync at the class level. Idempotent and global; call it once at module load. Supported pydantic-ai versions: 1.x. Outside that range the patch is skipped with a single warning.

Events

Pydantic AI call AgentPing event What is recorded
Agent.run, Agent.run_sync returning llm_call provider from the model's system (anthropic, openai, google-gla, and so on), model from model_name, input and output tokens summed across every model request in the run, cached and cache-creation tokens when reported, the number of tool calls the model made, request_count (how many model requests the run took), latency for the whole run.
Agent.run, Agent.run_sync raising llm_call with status: "error" Provider, model, the exception message and class, latency. The exception is re-raised.

One agent run is one llm_call, whatever the number of model round trips inside it: Pydantic AI reports usage for the run as a whole, and that summed figure is what the rate card prices. request_count tells you how many round trips it took. Individual tool executions are not recorded as tool_call events; add your own from inside the tool function if you need them:

@triage.tool_plain
def lookup_order(order_id: str) -> dict:
    r = agentping.active_run()
    ...
    if r:
        r.event("tool_call", {"tool": "lookup_order", "status": "success", "latency_ms": 41})
    return order

Agent.run_stream and Agent.iter are not patched, so streamed and manually iterated runs emit nothing; report those yourself with run.event("llm_call", {...}) from result.usage().

Naming

provider is whatever Pydantic AI's model object reports as its system. For the Anthropic and OpenAI providers that matches the rate card as shipped. For Gemini it is google-gla or google-vertex, and for other providers it is their Pydantic AI name; add rate card rows under that provider name so the calls are priced. See Spend.

Source / notes

Pydantic AI can also emit OpenTelemetry spans natively (Agent(..., instrument=True)), one per model request and tool call. Export those to the OpenTelemetry endpoint when you want per-request detail; the patch and the OTLP route can run together, but then each model request is counted twice, so pick one for spend.