Skip to Content

How do I see every tool call in an agent built with Pydantic AI?

Rius shows every tool call in a Pydantic AI agent as its own span named execute_tool followed by the tool name, with no wrapping code. Pydantic AI emits these spans itself, and Rius reads them through the global tracer provider that rius.init() installs. Call Agent.instrument_all(True) once, run the agent, and open the trace in the console to see each tool call next to the model requests around it.

How it works

Pydantic AI is OpenTelemetry-native, so no auto-instrumentation extra is involved. rius.init() installs the global tracer provider, and Agent.instrument_all(True) tells the framework to emit its spans through it. The framework supplies the run root, the model requests and the tool spans. Rius reads their GenAI attributes and computes cost on its side.

The Pydantic AI integration maps the framework to the trace like this:

In the frameworkIn the trace
An agent runThe root span, named invoke_agent <agent name>
A model requestA generation named chat <model>, with tokens and cost
A tool callA span named execute_tool <tool name>

A run that calls the tool looks like this:

invoke_agent support_agent 6.0s ├─ chat gpt-5-mini 3.3s decides to call the tool ├─ execute_tool account_status 0ms └─ chat gpt-5-mini 2.7s answers

The tool span sits between the model request that decides to call the tool and the one that uses its result. Tools registered with the framework need no observe wrapper. If you run your own code outside the framework’s tool mechanism and want it in the trace, wrap that function with @rius.observe(kind=SpanKind.TOOL) so it gets its own tool span.

Set it up

  1. Install the SDK. No extra is needed for Pydantic AI, and the base SDK is enough:

    pip install glassflow-rius pydantic-ai
  2. Initialize Rius, switch on Pydantic AI instrumentation, and register your tools as usual. Here lookup, conversation_id and ticket are your own code and values:

    import rius from pydantic_ai import Agent rius.init(api_key="ri_...", service_name="my-agent") Agent.instrument_all(True) support_agent = Agent( "openai:gpt-5-mini", instructions="Answer support questions; check accounts with the tool.", ) @support_agent.tool_plain def account_status(email: str) -> str: """Fetch a customer's account standing by email.""" return lookup(email) with rius.session(conversation_id): result = support_agent.run_sync(ticket)
  3. Run one request that calls a tool. Open Traces and select the trace. You should see the invoke_agent root, the chat generations with model, tokens and cost, and one execute_tool span per tool call. Exploring traces covers the console views in full.

  4. To list tool calls across many runs, open the Spans tab and choose Tool in the All kinds menu. Add filters for status or session, or sort by duration.

  5. Decide what the tool spans carry. Tool arguments are controlled by Pydantic AI’s InstrumentationSettings, for example include_content, on top of the SDK’s own privacy controls. The with rius.session(...) block in step 2 groups every span of one conversation, tool spans included, under one session.

The Integrations page lists the other frameworks and how each one reaches Rius.

What you get

  • A span for every tool call. Each is named after your tool, so execute_tool account_status reads as what it is, and it sits in the tree under the agent run.
  • Order and timing in the waterfall. The trace page shows the tree on a timeline with spans badged by kind, so you see which model request triggered which tool call and how long it took. Failed spans are highlighted along the path from the root to the error.
  • Retries as one row. Back-to-back calls of the same tool collapse into one row, such as fetch_article · 3 attempts · recovered. Expand it for the individual attempts.
  • Tool arguments when captured. Select a tool span to read it in the span panel, and open Raw to see it exactly as ingested. Tool arguments appear there only if Pydantic AI’s content settings and the SDK’s privacy controls allow them.
  • Cost and tokens on the model requests around it. The chat generations carry tokens and cost, and time to first token and reasoning tokens show up for Pydantic AI runs.
  • A status quirk to know. Successful spans arrive with an UNSET status rather than OK. The trace status still reads Ok.

For the wider picture of watching agents after launch, see Monitor AI agents in production.

When something else fits

  • If your tool logic runs outside Pydantic AI’s tool mechanism, for example a helper your own code calls directly, wrap it with @rius.observe(kind=SpanKind.TOOL) if it is a tool, or a bare @rius.observe for a plain step, so it appears as a span.
  • If your Python setup does not use the Rius SDK, Pydantic AI also works with any OpenTelemetry setup pointed at the ingest endpoint, though the SDK adds sessions, heartbeats and privacy controls.

Still stuck? Get help

Last updated on