OpenTelemetry Python SDK
Using the OpenTelemetry Python SDK
This guide shows you how to directly instrument your Python applications with the OpenTelemetry SDK to send trace data to Opik.
Installation
First, install the required OpenTelemetry packages:
Full Example
Here’s a complete example that demonstrates how to instrument a chatbot application with OpenTelemetry and send the traces to Opik:
Using thread_id as a span attribute allows you to group related spans into a single conversational thread.
Created threads can be used to evaluate multi-turn conversations as described in the Multi-turn conversations guide.
Linking OpenTelemetry spans to an existing Opik trace
If a service instrumented with OpenTelemetry is invoked by another service that is already producing an Opik trace (via the Opik SDK or @track), you can link the OpenTelemetry spans to the existing Opik trace and parent span by propagating two HTTP headers (opik_trace_id, opik_parent_span_id) and calling the Opik bridging helper on the receiving side. The helper sets the opik.trace_id / opik.parent_span_id attributes on the OpenTelemetry boundary span so the Opik OTLP ingest endpoint attaches the span to the right parent.
Recommended setup: register OpikSpanProcessor
distributed_trace.attach_to_parent only sets the Opik attributes on the boundary span. Children created inside that span via start_as_current_span inherit OTel context but not those attributes — without extra wiring they end up orphaned in a synthetic Opik trace. Register OpikSpanProcessor on the same TracerProvider as your OTLP exporter to propagate the Opik IDs down the entire attached subtree:
The processor stamps Opik IDs onto a span when any of the following holds, in order of precedence:
- the parent span already carries Opik attributes (set by
attach_to_parenton the boundary, or by an earlier processor pass); - they are inherited from upstream W3C
baggage(cross-process); - an
@trackspan is active in the current process (see below).
Spans that match none of these are left untouched, so OpenTelemetry-only traces are unaffected.
Same-process: OpenTelemetry spans inside an @track function
When an OpenTelemetry-instrumented library (for example logfire / PydanticAI) emits spans from inside an @track-decorated function, there is no boundary span and no baggage to inherit from — @track keeps its own context, not an OpenTelemetry one. With OpikSpanProcessor registered, those spans are attached to the active @track span automatically, producing a single trace instead of a separate synthetic one. No attach_to_parent call or header propagation is needed:
This is an in-process bridge. The cross-process case (service A → service B) still requires header propagation via attach_to_parent, since there is no shared context to read from.
For the full client/server pattern with Python and TypeScript examples, see Distributed Traces with a Remote Service Using OpenTelemetry.