Docs menuOpenTelemetry recipes

Docs Integrations

OpenTelemetry recipes

Send CrewAI, LlamaIndex, Pydantic AI or Google ADK traces to Runback with the standard OpenTelemetry exporter, and no Runback package in your code. About ten minutes, plus a minute for the first run to appear.

Before you begin

  • A Runback account and a Send only key. In the app, open Settings › Keys and access › API key, keep Send only selected, select Generate API key and copy it (it is shown once and starts rb_cloud_). The quickstart walks through it.
  • Python 3.9 or later (python3 --version), and your framework already installed.

Steps

  1. Install the OpenTelemetry SDK and exporter

    terminalShell
    pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http

    Then install your framework's instrumentor from the table in step 4. For LlamaIndex, for example:

    terminalShell
    pip install openinference-instrumentation-llama-index
    Expected output
    pip ends with a Successfully installed line and no errors.
  2. Point the exporter at Runback

    Two standard variables send traces to Runback, authenticated with your key.

    terminalShell
    export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://runback.dev/api/otel/v1/traces"
    export OTEL_EXPORTER_OTLP_TRACES_HEADERS="authorization=Bearer <RUNBACK_API_KEY>"
    Expected output
    Nothing is printed. To check, run env | grep OTEL_: it should list both variables, with your key in the second.
  3. Add the tracer setup

    Save this as otel_setup.py and import it once, before your agent runs. It creates the tracer provider tp that the instrumentor in step 4 uses.

    otel_setup.pyPython
    # otel_setup.py: pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
    # The exporter reads OTEL_EXPORTER_OTLP_TRACES_ENDPOINT and _HEADERS from the environment.
    from opentelemetry import trace
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    
    tp = TracerProvider()
    tp.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
    trace.set_tracer_provider(tp)
    
    # Then instrument your framework with tp, for example Pydantic AI:
    #   from pydantic_ai import Agent
    #   from pydantic_ai.models.instrumented import InstrumentationSettings
    #   Agent.instrument_all(InstrumentationSettings(tracer_provider=tp))
  4. Instrument your framework

    Add the line for your framework after the setup, passing tp:

    FrameworkInstrumentationStill missing from the stored run
    Pydantic AIAgent.instrument_all(InstrumentationSettings(tracer_provider=tp))Nothing known
    LlamaIndexOpenInference LlamaIndexInstrumentor().instrument(tracer_provider=tp)A duplicate model step per call, without tokens or tools
    CrewAIOpenInference CrewAIInstrumentor and OpenAIInstrumentor, both with tracer_provider=tp; crewai 1.10.1 or laterNothing known
    Google ADKOpenInference GoogleADKInstrumentor().instrument(tracer_provider=tp); google-adk 2.10 or laterProvider shows as gcp.vertex.agent

    Each gap in the last column is checked by Runback's test suite, so this table changes the day the behaviour does.

  5. Run your agent and check the run arrived

    Run your agent as usual. Runback writes the run once the trace has been quiet for about a minute, so wait a minute, then open Runs in the app. The run is named after your agent's root span.

Troubleshooting

You seeWhat it meansFix
The exporter logs 401 or Failed to exportThe authorization header is missing or the key is wrong.Export both OTEL_EXPORTER_OTLP_TRACES_* variables in this terminal; the key starts rb_cloud_, with a space after Bearer.
ModuleNotFoundError: No module named 'opentelemetry'The packages went into a different Python environment.Activate the environment you installed into, or install with the same python you run the agent with.
The agent finished, but the run is not in Runs yetRunback writes an OpenTelemetry run once the trace has been quiet for about a minute.Wait a minute and refresh. If it is still missing, check the workspace name in the top right of the app is the one the key came from.

What Runback records

The endpoint takes protobuf or JSON, gzipped or not. A trace exported in several batches is still one run: spans are buffered and the run is written once the trace goes quiet.

Pydantic AI's own instrumentation, exported over OTLP, scripted model. 5 events, captured 9 Oct 2026.

  1. Runstart · agent
    {
      "schema_version": 1,
      "run_id": "e9d431b7baf6d0e86430ab8373b43b49",
      "span_id": "run:e9d431b7baf6d0e86430ab8373b43b49",
      "parent_span_id": null,
      "seq": 0,
      "ts_start": "2026-10-09T10:56:14.447Z",
      "ts_end": null,
      "type": "run",
      "phase": "start",
      "name": "agent",
      "input": null,
      "output": null,
      "status": "running",
      "error": null,
      "metadata": {
        "source": "otel",
        "trace_id": "e9d431b7baf6d0e86430ab8373b43b49"
      }
    }
  2. Modelgpt-4o-mini → weather17 tokens · 44 ms · tool_call
    {
      "schema_version": 1,
      "run_id": "e9d431b7baf6d0e86430ab8373b43b49",
      "span_id": "2f7dbc4b853f9c26",
      "parent_span_id": "run:e9d431b7baf6d0e86430ab8373b43b49",
      "seq": 1,
      "ts_start": "2026-10-09T10:56:14.450Z",
      "ts_end": "2026-10-09T10:56:14.494Z",
      "type": "llm",
      "model": {
        "provider": "openai",
        "model_id": "gpt-4o-mini"
      },
      "request": {
        "system": "You are a weather bot.",
        "messages": [
          {
            "role": "user",
            "content": "What is the weather in Paris? cc [redacted:email]"
          }
        ],
        "tools": [
          {
            "name": "weather",
            "description": "Weather for a city.",
            "parameters": {
              "additionalProperties": false,
              "properties": {
                "city": {
                  "type": "string"
                }
              },
              "required": [
                "city"
              ],
              "type": "object"
            }
          }
        ],
        "params": {}
      },
      "response": {
        "text": null,
        "reasoning": null,
        "finish_reason": "tool_call",
        "tool_calls": [
          {
            "tool_call_id": "call_40",
            "tool_name": "weather",
            "input": {
              "city": "Paris"
            }
          }
        ]
      },
      "usage": {
        "input_tokens": 12,
        "output_tokens": 5,
        "total_tokens": 17
      },
      "latency_ms": 44,
      "error": null
    }
  3. Toolweather1 ms
    {
      "schema_version": 1,
      "run_id": "e9d431b7baf6d0e86430ab8373b43b49",
      "span_id": "b5929b8cfe877dd6",
      "parent_span_id": "run:e9d431b7baf6d0e86430ab8373b43b49",
      "seq": 2,
      "ts_start": "2026-10-09T10:56:14.498Z",
      "ts_end": "2026-10-09T10:56:14.499Z",
      "type": "tool",
      "tool_name": "weather",
      "tool_call_id": "call_40",
      "input": {
        "city": "Paris"
      },
      "output": {
        "city": "Paris",
        "forecast": "sunny"
      },
      "latency_ms": 1,
      "error": null
    }
  4. Modelgpt-4o-mini28 tokens · 3 ms · finished
    {
      "schema_version": 1,
      "run_id": "e9d431b7baf6d0e86430ab8373b43b49",
      "span_id": "231f2135b08ec268",
      "parent_span_id": "run:e9d431b7baf6d0e86430ab8373b43b49",
      "seq": 3,
      "ts_start": "2026-10-09T10:56:14.500Z",
      "ts_end": "2026-10-09T10:56:14.504Z",
      "type": "llm",
      "model": {
        "provider": "openai",
        "model_id": "gpt-4o-mini"
      },
      "request": {
        "system": "You are a weather bot.",
        "messages": [
          {
            "role": "user",
            "content": "What is the weather in Paris? cc [redacted:email]"
          },
          {
            "role": "assistant",
            "content": [
              {
                "type": "tool_call",
                "id": "call_40",
                "name": "weather",
                "arguments": {
                  "city": "Paris"
                }
              }
            ]
          },
          {
            "role": "user",
            "content": [
              {
                "type": "tool_call_response",
                "id": "call_40",
                "name": "weather",
                "result": "{\"city\": \"Paris\", \"forecast\": \"sunny\"}"
              }
            ]
          }
        ],
        "tools": [
          {
            "name": "weather",
            "description": "Weather for a city.",
            "parameters": {
              "additionalProperties": false,
              "properties": {
                "city": {
                  "type": "string"
                }
              },
              "required": [
                "city"
              ],
              "type": "object"
            }
          }
        ],
        "params": {}
      },
      "response": {
        "text": "It is sunny in Paris.",
        "reasoning": null,
        "finish_reason": "stop",
        "tool_calls": []
      },
      "usage": {
        "input_tokens": 20,
        "output_tokens": 8,
        "total_tokens": 28
      },
      "latency_ms": 3,
      "error": null
    }
  5. Runend · agentsucceeded
    {
      "schema_version": 1,
      "run_id": "e9d431b7baf6d0e86430ab8373b43b49",
      "span_id": "run:e9d431b7baf6d0e86430ab8373b43b49~end",
      "parent_span_id": "run:e9d431b7baf6d0e86430ab8373b43b49",
      "seq": 4,
      "ts_start": "2026-10-09T10:56:14.505Z",
      "ts_end": "2026-10-09T10:56:14.505Z",
      "type": "run",
      "phase": "end",
      "name": "agent",
      "input": null,
      "output": null,
      "status": "success",
      "error": null,
      "metadata": {}
    }

A real OpenTelemetry export, as Runback stores it after mapping and redaction. Runback's test suite re-checks it on every change.

What it can and cannot do

OpenTelemetry

QuestionAnswerWhy
Code changesNoneYour framework's own instrumentation sends the spans; no Runback package.
ContextWhat's exportedMessages, system prompt and tool definitions when the instrumentor exports them (GenAI, OpenLLMetry, OpenInference).
RedactionOn arrivalRedacted by Runback when the spans arrive, not inside your process.
Stop before it runsNoSpans arrive after the call ran, so nothing can stop it.
ReplayRe-issue onlyA model step can be re-sent to a live model. The whole run can't be re-run with the same tool results: they aren't kept.
ArrivesAbout a minuteA run is written once its trace goes quiet, about a minute after the root span.
Signed recordYesEach run is hash-chained and can be exported as a signed record.

Next steps

  • Want to stop calls, or redact inside your process? Use the Python SDK instead.
  • See what a run holds: Capture.
Last updated 2 Nov 2026.