Ratel Docs
Python SDK

Telemetry & OTel

Capture Ratel's search and invoke funnel as local trace events, or export it as OpenTelemetry spans — greenfield or alongside an existing provider.

Ratel emits telemetry on two deliberately separate streams (ADR 0007): local trace events recorded into a sink you configure, and OpenTelemetry spans exported as stock OTLP. Both are off by default.

This page is the Python wiring. The full event-type vocabulary and span table live on Telemetry.

Local trace sinks

Pass trace to the ToolCatalog (or SkillCatalog) constructor and the core records every search, invoke, capability-tool, upstream-MCP, and auth event into the sink:

from ratel_ai import ToolCatalog, TraceSinkConfig

catalog = ToolCatalog(
    trace=TraceSinkConfig(kind="jsonl", session_id="session-1", path="/tmp/ratel.jsonl"),
)
# Every catalog.invoke, capability-tool call, and register_mcp_server call now
# appends one JSON line per event to /tmp/ratel.jsonl.

TraceSinkConfig is a dataclass: kind is "noop" | "memory" | "jsonl"; session_id is required for memory and jsonl, path for jsonl. Misconfiguration raises ValueError (for example "jsonl sink requires path").

Sink kinds:

  • kind="noop" — drop everything (the default when no trace is passed).
  • kind="memory" — keep events in memory; drain via catalog.drain_trace_events(), which returns the buffered events and removes them. Useful in tests.
  • kind="jsonl" — append one JSON line per event to path (mode 0600 on Unix). Best-effort, lossy on backpressure.

Inject your own events with catalog.record_event(event): the dict must deserialize to a core trace event, else the native layer raises ValueError ("invalid trace event: ...").

search_capabilities_tool records its own gateway_search event and tags the underlying catalog search's search event with origin="agent"; direct callers (catalog.search(query, k)) default to "direct". Override per call via catalog.search(query, k, "agent").

OpenTelemetry export

Independently of the local sink, the SDK emits OpenTelemetry spans for the same funnel — execute_tool, ratel.search, ratel.skill.load, ratel.upstream.register, ratel.auth.flow (the gen_ai.* / ratel.* vocabulary, catalogued in Telemetry).

The base ratel-ai install has zero dependencies. If opentelemetry or ratel_ai_telemetry is missing, every telemetry helper is a straight pass-through — zero overhead, no spans. The [otlp] extra brings both; with them installed, spans go to whatever provider is registered.

Greenfield: ship spans to Ratel Cloud

No OpenTelemetry in your stack yet? One call at startup registers a global tracer provider with a batch OTLP exporter pointed at Ratel Cloud. The exporter ships behind the [otlp] extra (without it, configure_telemetry raises ModuleNotFoundError with this install hint):

pip install 'ratel-ai[otlp]'
import os
from ratel_ai import configure_telemetry

handle = configure_telemetry(
    endpoint=os.environ.get("RATEL_URL", "https://cloud.ratel.sh/v1/traces"),
    api_key=os.environ["RATEL_API_KEY"],
)
# ... run your agent: every ratel.* / execute_tool span now exports ...
handle.shutdown()  # flush the exporter on exit (handle.force_flush() mid-run)

The full keyword surface is configure_telemetry(*, api_key=None, endpoint=None, headers=None, service_name=None, capture_content=None, include_span_and_events=None) — the last two set the content-capture gate in code. It returns a per-call shutdown handle (handle.shutdown() / handle.force_flush()) over the shared Ratel-owned provider; shutting any handle down stops export for every caller. Resolution rules:

  • An explicit endpoint= beats the RATEL_URL env var. Either way it is the full OTLP traces URL including /v1/traces; with neither set it raises ValueError.
  • api_key is sent as Authorization: Bearer <key> on top of any headers. When omitted it falls back to the RATEL_API_KEY env var (unless you already set an Authorization header explicitly).
  • service_name defaults to "ratel".

configure_telemetry owns the global provider. Calling it again once Ratel's provider is registered is idempotent — you get a handle to the same shared provider. It raises RuntimeError in two cases: a foreign tracer provider is already registered (you are on the wrong path — dual-export instead), or telemetry was already shut down in this process (shutdown is terminal).

Already on OpenTelemetry: dual-export

If Langfuse or your own collector already owns the provider, the SDK's spans are already flowing to it. Skip configure_telemetry and add the span processor from ratel-ai-telemetry to send a copy of the Ratel cut to Cloud:

import os

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from ratel_ai_telemetry.otlp import ratel_span_processor

provider = trace.get_tracer_provider()  # the provider your existing setup registered
if not isinstance(provider, TracerProvider):
    raise RuntimeError("register your OpenTelemetry SDK provider before adding processors")
provider.add_span_processor(
    ratel_span_processor(
        endpoint=os.environ.get("RATEL_URL", "https://cloud.ratel.sh/v1/traces"),
        api_key=os.environ["RATEL_API_KEY"],
    )
)

ratel_span_processor(*, api_key=None, endpoint=None, headers=None, span_filter=None, enabled=True) (pass enabled=False for a no-op processor); ratel_span_exporter(...) is the bare OTLP exporter if you wire your own processor. The default filter forwards a span if its name starts with ratel. or any attribute key starts with gen_ai. or ratel.; pass span_filter=lambda _s: True to forward everything.

Content capture

Message and tool content never leaves the process by default. The gated payloads (ratel.search.query, gen_ai.tool.call.arguments / gen_ai.tool.call.result) ride span attributes only when the capture gate selects a span mode (SPAN_ONLY or SPAN_AND_EVENT); EVENT_ONLY does not put content on spans.

Set the gate in code:

  • Greenfield path: configure_telemetry(capture_content=...).
  • Dual-export path: set_content_capture from ratel_ai_telemetry.

Code wins over the OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT env var, the fallback when nothing is set in code. The full mode table is on Telemetry.

Next steps

On this page