OpenTelemetry
Opik provides native support for OpenTelemetry (OTel), allowing you to instrument your ML/AI applications with distributed tracing. This guide will show you how to directly integrate OpenTelemetry SDKs with Opik.
OpenTelemetry integration in Opik currently supports HTTP transport. We’re actively working on expanding the feature set - stay tuned for updates!
OpenTelemetry Endpoint Configuration
Base Endpoint
To start sending traces to Opik, configure your OpenTelemetry exporter with one of these endpoints:
Opik Cloud
Self-hosted deployment
Enterprise deployment
Signal-Specific Endpoint
If your OpenTelemetry setup requires signal-specific configuration, you can use the traces endpoint. This is particularly useful when different signals (traces, metrics, logs) need to be sent to different endpoints:
Custom via OpenTelemetry SDKs
You can use any OpenTelemetry SDK to send traces directly to Opik. OpenTelemetry provides SDKs for many languages (C++, .NET, Erlang/Elixir, Go, Java, JavaScript, PHP, Python, Ruby, Rust, Swift). This extends Opik’s language support beyond the official SDKs (Python and TypeScript). For more instructions, visit the OpenTelemetry documentation.
Here’s a Python example showing how to set up OpenTelemetry with Opik:
In order to track OpenAI calls, you need to use the OpenTelemetry instrumentations for OpenAI:
And then instrument your OpenAI client:
Make sure to import the http trace exporter (opentelemetry.exporter.otlp.proto.http.trace_exporter), if you use
the GRPC exporter you will face errors.
How Opik builds traces from spans
OpenTelemetry sends spans only. Opik creates a trace from the root span of each OpenTelemetry trace: the span that has no parent span. The name, input, output, metadata, tags and errors of the root span become the values of the trace.
Opik treats a span as a root span when one of these conditions is true:
- The
parentSpanIdof the span is empty. - The
parentSpanIdof the span is equal to itstraceId.
Every trace must have one root span. If all the spans of a trace have a parent, Opik stores the spans but does not create the trace. The spans then show in the Spans tab, but the Traces tab stays empty and the span details do not load. See Troubleshooting for how to fix it.
A span with the opik.trace_id attribute attaches to an Opik trace that already exists, so Opik does not create a
trace for it. See Distributed traces.
Supported attribute conventions
Opik reads the attributes of each span and maps them to the fields of the Opik span. Opik supports the OpenTelemetry GenAI semantic conventions, the OpenInference conventions and a set of general Opik attributes. Opik also reads the attributes of some framework instrumentations, such as Logfire, Pydantic AI, LiteLLM, smolagents, LiveKit and Langfuse.
Some other rules apply:
- A more specific attribute has priority over a prefix. For example,
gen_ai.request.modelsets the model, and the othergen_ai.request.*attributes go to the input. - Opik renames the
input_tokensandoutput_tokensusage keys toprompt_tokensandcompletion_tokens. If the span has nototal_tokens, Opik calculates it. - Opik sets the span type to
llmwhen the span has a GenAI or OpenInference model or token usage attribute. Opik sets the span type totoolwhen the span hasgen_ai.tool.call.argumentsorgen_ai.tool.call.result. - When the value of
input,outputoropik.metadatais a JSON object in a string, Opik adds the keys of the object to the field. - Opik adds the span events to the metadata of the span. An
exceptionevent, or a span status ofERROR, becomes the error of the span.
Opik puts each attribute that it does not recognize in the Input field of the span, with the attribute name as
the key. If the input of your spans shows only attribute names from your framework, set input.value and
output.value (or the GenAI attributes) on the spans that you want to read in Opik.
Opik-specific span attributes
Opik reads a small set of span attributes and maps them to Opik fields. Set them with the standard OpenTelemetry API on any span.
Tagging traces and spans
Opik accepts three formats for the opik.tags attribute:
- A list of strings:
["production", "chatbot"] - A JSON array in a string:
'["production", "chatbot"]' - A comma-separated string:
"production,chatbot"
Set opik.tags on the root span if you want to filter traces by tag. Tags on a child span apply to that span only.
Routing spans to a project
Some OpenTelemetry Collector processors, such as tail_sampling, do not forward the HTTP headers of the incoming
request. In that case, set the project with the opik.project_name attribute instead of the projectName header.
Opik picks the project of each span on its own, in this order:
- The
opik.project_nameattribute of the span - The
opik.project_nameattribute of the resource of the span - The
projectNameheader - The
Default Project
Opik ignores a value longer than 150 characters.
Every span must have the opik.project_name attribute. Opik does not copy the project from the root span to the
other spans: OpenTelemetry exports the spans of a long trace in more than one batch, and the root span usually comes
last. If you set the attribute on the root span only, the other spans go to the project in the header, and they do
not show under any trace. Set the project as a resource attribute, so that every span has it.
Set the project on every span
A resource attribute applies to every span that the process sends. Use one of these methods:
Environment variable
Python SDK
OpenTelemetry Collector
The OpenTelemetry SDK reads the resource attributes from OTEL_RESOURCE_ATTRIBUTES:
All the spans of a trace must have the same project. If one trace goes through services that send to different projects, the spans that are not in the project of the root span do not show under any trace.
Send spans to Opik and to other backends
One OpenTelemetry TracerProvider can send each span to more than one backend. Add one BatchSpanProcessor for each
exporter. The frameworks that use OpenTelemetry (such as Strands Agents, LangGraph, LlamaIndex and CrewAI) send their
spans to the global tracer provider, so one setup covers all of them.
New tracer provider
Existing tracer provider
Create the tracer provider, and add one exporter for Opik and one for your other backend:
Give the Opik endpoint and headers to the exporter in code. Do not use the OTEL_EXPORTER_OTLP_ENDPOINT and
OTEL_EXPORTER_OTLP_HEADERS environment variables for Opik: all the OTLP exporters in the process read them, so the
other backend also gets the Opik endpoint and your Opik API key.
When the platform continues an incoming trace, the first span of your agent has a parent. In this case, add a root span for each invocation, as shown in Troubleshooting.
Troubleshooting
The exporter gets a 401 or 403 error
- Make sure that the
Authorizationheader contains your Opik API key, without theBearerprefix. - Make sure that
Comet-Workspaceis the name of a workspace that your API key can access.
Spans show in the Spans tab, but the Traces tab is empty
No span of the trace is a root span, so Opik did not create the trace.
This problem occurs when the instrumentation continues a trace that started in a different process. For example, a runtime such as Amazon Bedrock AgentCore or an API gateway sends a trace context with each request. The first span of your agent is then a child of a span that Opik never receives.
To find out if you have this problem, print your spans with the ConsoleSpanExporter and look at the parent_id
field. At least one span of each trace must have "parent_id": null.
To fix the problem, start a new root span at the start of each agent invocation. Pass an empty Context so that
the span does not use the incoming trace context:
All the spans that the agent creates in the with block are children of agent_invocation, so Opik creates one
trace for each invocation.
Spans show under a different trace, or under no trace
All the spans of a trace must go to the same project. If you set opik.project_name, set it as a resource
attribute. See Routing spans to a project.
The input of a span shows framework attributes and no model or token usage
Your instrumentation uses attribute names that Opik does not recognize. Opik puts these attributes in the input. Set the attributes of one of the supported conventions on your spans.
The exporter fails with a protocol error
Opik accepts OTLP over HTTP only. Use the http exporter
(opentelemetry.exporter.otlp.proto.http.trace_exporter), not the gRPC exporter.