> ## Documentation Index
> Fetch the complete documentation index at: https://laminar.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Observability for Pydantic AI

## Overview

Laminar is an open-source, OpenTelemetry-native observability platform for AI agents. Trace, debug, and monitor every [Pydantic AI](https://ai.pydantic.dev/) agent run, model turn, and tool call with a single `Laminar.initialize()` call. Self-host via Helm or use managed cloud.

Pydantic AI is a Python agent framework built by the Pydantic team. `Agent` drives the loop, typed tools are added with `@agent.tool`, and the framework emits its own OpenTelemetry GenAI semconv spans for agent runs, model calls, and tool executions. Laminar wires those spans to its tracer provider so the full conversation lands in your project with no manual exporter setup.

What Laminar captures:

* The root `agent run` span with the prompt you sent and the final output.
* Every `chat <model>` turn with prompts, responses, token counts, latency, and cost.
* Every `execute_tool <name>` call with arguments and return value.
* Multi-agent handoffs and sub-agent runs nested under the call that spawned them.

## Getting started

<Steps>
  <Step title="Install">
    Ensure you have `lmnr` version `0.7.49` or higher and `pydantic-ai-slim >= 1.0` (or the full `pydantic-ai` distribution):

    ```bash theme={null}
    pip install -U lmnr "pydantic-ai-slim>=1.0"
    ```
  </Step>

  <Step title="Set environment variables">
    ```bash theme={null}
    export LMNR_PROJECT_API_KEY=your-laminar-project-api-key
    export OPENAI_API_KEY=your-openai-api-key
    ```
  </Step>

  <Step title="Initialize Laminar">
    `Laminar.initialize()` auto-instruments Pydantic AI when `pydantic-ai-slim` or `pydantic-ai` is importable. No `Agent.instrument_all()` call is needed: Laminar installs itself as the default instrumentation for every `Agent` you construct afterwards.

    ```python {4-6} theme={null}
    import asyncio

    from pydantic_ai import Agent
    from lmnr import Laminar, observe

    Laminar.initialize()

    agent = Agent(
        "openai:gpt-5-mini",
        system_prompt="You are a concise assistant. Answer in one sentence.",
    )

    @observe(name="capital-lookup")
    async def main():
        result = await agent.run("What is the capital of France?")
        print(result.output)

    if __name__ == "__main__":
        asyncio.run(main())
    ```

    <Note>
      Wrapping your entry point in `@observe()` is optional but recommended: it creates a root span that captures inputs and outputs and makes the trace easy to find in the UI.
    </Note>
  </Step>
</Steps>

<Warning>
  When Laminar auto-enables the Pydantic AI instrument, it auto-removes the overlapping raw-provider instrumentors (OpenAI, Anthropic, Google GenAI, Groq, Mistral, Cohere, Bedrock) from the default set. Pydantic AI already emits GenAI-semconv spans at the model abstraction layer, so running the provider instrumentors alongside would double-count every model call. If you call the provider SDK directly elsewhere in the same process and want both traced, pass an explicit `instruments` set to `Laminar.initialize(instruments={Instruments.PYDANTIC_AI, Instruments.OPENAI, ...})`.
</Warning>

## See what happened in a trace

Open a multi-agent trace and switch to tree view to see how Pydantic AI nests sub-agents and tool calls. The example below is a concierge agent that delegates flight and hotel booking to two sub-agents, each running its own model turns and tool calls, so the full hierarchy is visible at a glance.

<Frame caption="Tree view of a multi-agent Pydantic AI run.">
  <img src="https://mintcdn.com/laminarai/6XrcpP0N-JHhzaOH/images/traces/pydantic-ai-tree.png?fit=max&auto=format&n=6XrcpP0N-JHhzaOH&q=85&s=5675a528586c065664f8d23b3d198cf4" alt="Pydantic AI multi-agent trace in Laminar, tree view showing nested sub-agents" width="1512" height="982" data-path="images/traces/pydantic-ai-tree.png" />
</Frame>

More on the trace UX: [Viewing traces](/platform/viewing-traces).

## Multi-agent with tool delegation

Register tools with `@agent.tool_plain` (no dependency state) or `@agent.tool` (with injected `RunContext`). A coordinator agent can delegate to specialist sub-agents by calling `Agent.run()` inside a tool. Pydantic AI emits an `execute_tool <name>` span for each invocation, and Laminar nests the sub-agent's run underneath it so the hierarchy mirrors the conversation.

```python theme={null}
import asyncio

from pydantic_ai import Agent, RunContext
from lmnr import Laminar, observe

Laminar.initialize()

flight_agent = Agent(
    "openai:gpt-5-mini",
    system_prompt="You are a flight search specialist. Return one recommended flight.",
)

@flight_agent.tool_plain
def search_flights(origin: str, destination: str, date: str) -> str:
    return f"Options {origin}->{destination} on {date}: DL204 Delta $412, UA880 United $438."

hotel_agent = Agent(
    "openai:gpt-5-mini",
    system_prompt="You are a hotel booking specialist. Return one recommended hotel.",
)

@hotel_agent.tool_plain
def search_hotels(city: str, check_in: str, check_out: str) -> str:
    return f"Hotels in {city}: Kimpton Gray $285/nt, Hyatt Place $199/nt."

concierge = Agent(
    "openai:gpt-5-mini",
    system_prompt="You are a travel concierge. Use the tools to book a flight and a hotel.",
)

@concierge.tool
async def book_flight(ctx: RunContext, origin: str, destination: str, date: str) -> str:
    result = await flight_agent.run(f"Find a flight from {origin} to {destination} on {date}.")
    return result.output

@concierge.tool
async def book_hotel(ctx: RunContext, city: str, check_in: str, check_out: str) -> str:
    result = await hotel_agent.run(f"Find a hotel in {city} from {check_in} to {check_out}.")
    return result.output

@observe(name="plan-trip")
async def main():
    result = await concierge.run(
        "Plan a trip from JFK to Chicago on 2026-05-12, returning 2026-05-15."
    )
    print(result.output)

if __name__ == "__main__":
    asyncio.run(main())
```

The transcript view reads as a conversation: the input prompt, each `book_flight` / `book_hotel` tool call with its sub-agent's input and output, and the final concierge summary.

<Frame caption="Transcript view of the multi-agent run above. Each `book_flight` / `book_hotel` tool call expands inline with the sub-agent's input and output, and the final LLM turn shows the concierge's summary response.">
  <img src="https://mintcdn.com/laminarai/6XrcpP0N-JHhzaOH/images/traces/pydantic-ai.png?fit=max&auto=format&n=6XrcpP0N-JHhzaOH&q=85&s=a438cad5924ad856a0678a802f88c5f1" alt="Pydantic AI multi-agent trace in Laminar, transcript view" width="1512" height="982" data-path="images/traces/pydantic-ai.png" />
</Frame>

## Track outcomes with Signals

Traces answer *what happened on this run*. **[Signals](/signals/introduction) answer the cross-trace question**: *how often does the agent call a tool with missing arguments, when does the model hallucinate a booking code, how many runs exceed three turns without a final answer*. A Signal pairs a plain-language prompt with a JSON output schema. Laminar runs it live on new traces (Triggers) or backfills it across history (Jobs) and records a structured event every time it matches. From there you [query](/platform/sql-editor), [cluster](/signals/clusters), and [alert](/signals/alerts) on events across every trace.

<Note>
  Every new project ships with a **Failure Detector** Signal that categorizes issues on any trace over 1000 tokens. Open it from the Signals sidebar to see events as soon as your Pydantic AI traces arrive.
</Note>

## Query across traces

* **[SQL editor](/platform/sql-editor)** for ad-hoc queries across traces, spans, signals, and evals.
* **SQL API** for programmatic access from scripts and pipelines.
* **[CLI](/platform/cli)** (`lmnr-cli sql query`) for terminal-driven queries and piping JSON into shell tools or coding agents.
* **[MCP server](/platform/mcp)** to query Laminar directly from Claude Code, Cursor, or Codex.

## Troubleshooting

<AccordionGroup>
  <Accordion title="I don't see any traces in Laminar">
    * Confirm `LMNR_PROJECT_API_KEY` is set in the same process that runs the agent.
    * `pydantic-ai-slim` (or `pydantic-ai`) must be importable when `Laminar.initialize()` runs. Install with `pip install "pydantic-ai-slim>=1.0"`.
    * The integration requires `pydantic-ai-slim >= 1.0.0` and `lmnr >= 0.7.49`.
  </Accordion>

  <Accordion title="I'm getting duplicate spans for every model call">
    Laminar auto-removes the raw provider instrumentors (OpenAI, Anthropic, Google GenAI, Groq, Mistral, Cohere, Bedrock) when Pydantic AI is auto-enabled, so this should not happen with a default setup. If you passed an explicit `instruments` set that includes both `Instruments.PYDANTIC_AI` and one of the overlapping providers, drop the provider entry or use `disabled_instruments={Instruments.PYDANTIC_AI}` to keep only the raw spans.
  </Accordion>

  <Accordion title="I want to disable the Pydantic AI integration">
    Pass `disabled_instruments={Instruments.PYDANTIC_AI}` to `Laminar.initialize()`. The raw provider instrumentors will be auto-enabled again.

    ```python theme={null}
    from lmnr import Laminar, Instruments

    Laminar.initialize(disabled_instruments={Instruments.PYDANTIC_AI})
    ```
  </Accordion>

  <Accordion title="Self-hosting Laminar">
    Set `base_url` and the ports of your instance when initializing. For a local OSS deployment:

    ```python theme={null}
    Laminar.initialize(
        base_url="http://localhost",
        http_port=8000,
        grpc_port=8001,
    )
    ```
  </Accordion>
</AccordionGroup>

## What's next

<CardGroup cols={2}>
  <Card title="Viewing traces" href="/platform/viewing-traces">
    Read the transcript view, filter, and search across traces.
  </Card>

  <Card title="Signals" href="/signals/introduction">
    Detect behaviors and failures across every run, then query, cluster, and alert on them.
  </Card>

  <Card title="SQL editor and MCP server" href="/platform/sql-editor">
    Query traces programmatically from the UI, API, or your IDE.
  </Card>

  <Card title="Tracing structure" href="/tracing/structure/overview">
    Sessions, metadata, and tags for deeper control.
  </Card>

  <Card title="OpenAI SDK" href="/tracing/integrations/openai">
    Using the OpenAI SDK directly without Pydantic AI? Trace it here.
  </Card>
</CardGroup>
