---
title: Pydantic AI
description: Auto-instrument Pydantic AI agents so each agent run lands in AgentPing with the provider, model, tokens, cost, and latency of the whole run.
section: frameworks
order: 4
---

# 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

```bash
pip install "agentping-io[pydantic-ai]"
```

```python
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:

```python
@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](/docs/spend).

## Source / notes

- Python: `agentping.instrument_pydantic_ai()` in [agent-ping-python](https://github.com/agent-ping/agent-ping-python)

Pydantic AI can also emit OpenTelemetry spans natively (`Agent(..., instrument=True)`), one per model request and tool call. Export those to the [OpenTelemetry](/docs/integrations/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.
