Skip to content

Document running a Pydantic AI agent inside a reasoner with Logfire - #1065

Merged
AbirAbbas merged 2 commits into
mainfrom
docs/997-pydantic-ai-logfire
Sep 21, 2026
Merged

AbirAbbas merged 2 commits into
mainfrom
docs/997-pydantic-ai-logfire

Conversation

@AbirAbbas

Copy link
Copy Markdown
Contributor

Issue #997 asked for Pydantic AI + Logfire integration. Working through it, the Logfire half turns out to need no AgentField code at all — a Pydantic AI Agent runs inside a reasoner as ordinary Python, and logfire.configure() + instrument_pydantic_ai() + instrument_litellm() + instrument_fastapi(app) already put the Pydantic AI spans and the app.ai completions in one trace, because agentfield.Agent is a FastAPI subclass and the reasoner request becomes the root span. What was missing was the recipe and its boundaries.

So this is docs + a runnable example, not new SDK surface:

  • docs/pydantic-ai.md — the wiring, what the resulting trace looks like, how the AgentField run id gets onto every span (OpenTelemetry baggage, which Logfire copies into span attributes), how to fold Pydantic AI's tokens and cost into AgentField's per-execution usage, how this differs from the existing AGENTFIELD_LITELLM_CALLBACKS=logfire path, and what it deliberately does not cover.
  • examples/python_agent_nodes/pydantic_ai_logfire/ — the same thing as a running agent node.
  • Pointers from docs/llm-observability.md and examples/README.md.

The page is explicit that Pydantic AI's durable-execution adapters are not integrated: their checkpointing and replay sit on top of AgentField's own run DAG, X-AgentField-Replay-* and pause/resume rather than plugging into them. That part of #997 is still an open design question, so the issue should stay open.

Validation

Ran the example as a real agent node against a control plane built from this branch, in an isolated HOME/AGENTFIELD_HOME on a random port, with a real OpenRouter-backed model on both sides and Logfire in local mode (LOGFIRE_SEND_TO_LOGFIRE=false, console exporter plus an extra span processor dumping spans to JSONL for assertions).

Each reasoner execution produced exactly one trace, for both POST /api/v1/execute/... and POST /api/v1/execute/async/...:

POST /reasoners/triage_ticket            opentelemetry.instrumentation.fastapi
├── invoke_agent triager                 pydantic-ai
│   └── chat openai/gpt-4o-mini          pydantic-ai
└── acompletion                          openinference.instrumentation.litellm

Every non-root span carried agentfield.run_id, agentfield.execution_id, agentfield.node_id and agentfield.reasoner matching the ids the control plane returned for that execution.

The reasoner's usage envelope carried both calls, the Pydantic AI leg included:

{"total_cost_usd": 5.1e-05, "total_input_tokens": 112, "total_output_tokens": 57,
 "entries": [
   {"provider": "openrouter", "model": "openrouter/openai/gpt-4o-mini", "total_tokens": 117, "cost_usd": 3.42e-05, "cost_source": "genai-prices"},
   {"provider": "openai", "model": "openai/gpt-4o-mini", "total_tokens": 52, "cost_usd": 1.68e-05, "cost_source": "provider"}]}

Also verified the comparison table's claims directly: with AGENTFIELD_LITELLM_CALLBACKS=logfire and LOGFIRE_BASE_URL pointed at a local OTLP receiver, LiteLLM joined the active Logfire trace (same trace id) and emitted a single raw_gen_ai_request child through its own exporter; without LOGFIRE_TOKEN it logs a non-blocking init error and the callback does nothing.

The example is ruff check / ruff format clean. Docs and examples only, so no SDK or control-plane gates apply.

🤖 Generated with Claude Code

A Pydantic AI Agent runs inside an AgentField reasoner with no adapter, but
wiring the observability so one Logfire trace covers both the Pydantic AI run
and the app.ai completion is not obvious. This example does it: logfire.configure()
once per process, instrument_pydantic_ai() + instrument_litellm() on that provider,
and instrument_fastapi() on the Agent itself (it is a FastAPI subclass) so the
inbound reasoner request is the root span.

The AgentField run identifiers ride along as OpenTelemetry baggage, which Logfire
copies onto every descendant span, so a trace joins back to a run without threading
anything through either library's API. Pydantic AI's own token usage and cost are
folded into the per-execution cost tracker so they reach AgentField's usage
accounting alongside app.ai's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@AbirAbbas
AbirAbbas requested a review from a team as a code owner September 21, 2026 17:23
Issue #997 asked for Pydantic AI and Logfire integration. The Logfire half of
it needs no AgentField code — what was missing is a written-down recipe for the
combination, and the honest boundary around it.

This documents running a Pydantic AI agent inside a reasoner with one Logfire
trace per execution covering both the Pydantic AI spans and the app.ai
completions, correlated to the AgentField run through OpenTelemetry baggage;
how to fold Pydantic AI's tokens into AgentField's per-execution usage; how the
recipe differs from AGENTFIELD_LITELLM_CALLBACKS=logfire; and what it does not
give you — Pydantic AI's durable-execution adapters are not wired into
AgentField's run DAG, pause/resume or replay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@AbirAbbas
AbirAbbas force-pushed the docs/997-pydantic-ai-logfire branch from 1cbebb8 to 2c8b800 Compare September 21, 2026 17:26
@AbirAbbas
AbirAbbas merged commit d310f75 into main Sep 21, 2026
13 checks passed
@AbirAbbas
AbirAbbas deleted the docs/997-pydantic-ai-logfire branch September 21, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant