OpenTelemetry tracing
How to wire DRAgent spans and view tracing in the deployment's Monitoring -> Data exploration tab in DataRobot.
What gets traced
Two independent span sources reach DataRobot, each wired through its own switch:
- NAT lifecycle spans — workflow runs, tool calls, and other
IntermediateStep-derived events NAT emits as yourworkflow.yamlexecutes. Enabled by a block inworkflow.yaml(see below). - Framework auto-instrumentor spans — spans emitted by
opentelemetry-instrumentation-crewai,-langchain,-llamaindex, and-openai. HTTP-client and OpenAI SDK spans are enabled by calling the coreinstrument(); framework spans are enabled by calling the matching frameworkinstrument()(e.g.datarobot_genai.langgraph.telemetry.instrument) from your own code. - Mem0 memory spans —
update_memory,search_memory, anddelete_memoryspans emitted by thedr_mem0_memoryNAT provider whenstreaming_memory_agent/auto_memory_agentstore or retrieve long-term memory. Enabled automatically once the OTel SDK bootstrap frominstrument()is active (same env vars as above); no extra YAML config.
When both the NAT exporter and SDK bootstrap are active, datarobot_otelcollector mirrors NAT span hierarchy into the OTel SDK context and the SDK bootstrap wraps the global TracerProvider so framework, HTTP, and memory spans nest under the active workflow trace instead of exporting as separate trees.
You generally want both NAT lifecycle and framework spans; mem0 spans appear automatically when memory is configured and tracing is enabled.
workflow.yaml: enable the NAT exporter
Add a general.telemetry.tracing block. The exporter _type: datarobot_otelcollector is registered as a NAT plugin and discovered automatically when the dragent extra is installed.
general:
telemetry:
tracing:
otelcollector:
_type: datarobot_otelcollector
project: "<your-agent-name>" # becomes the OTel service.name
Fields:
| Field | Required? | Default | Description |
|---|---|---|---|
project |
yes | — | OTel service.name for spans emitted by this workflow. |
endpoint |
no | <DATAROBOT_(PUBLIC_)ENDPOINT>/otel/v1/traces |
Full OTLP/HTTP endpoint override. |
datarobot_api_key |
no | DATAROBOT_API_TOKEN env var |
Sent as the X-DataRobot-Api-Key header. |
datarobot_entity_id |
no | deployment-<MLOPS_DEPLOYMENT_ID> |
Sent as the X-DataRobot-Entity-Id header. Non-empty values must keep the deployment- prefix. |
extra_headers |
no | {} |
Additional headers; keys here win on collision with the DataRobot defaults. |
resource_attributes |
no | {} |
Extra OTel resource attributes; keys here win on collision. |
Batch-tuning knobs (batch_size, flush_interval, max_queue_size, etc.) are inherited from NAT's BatchConfigMixin; defaults are fine for most agents.
register.py: call instrument()
The core instrument() sets up HTTP-client, OpenAI SDK, and threading instrumentation plus the DataRobot OTel SDK bootstrap. The NAT exporter only carries NAT's own spans. To also route framework auto-instrumentor spans (CrewAI / LangChain / LlamaIndex) to DataRobot, call the matching framework instrument() at module-import time in your agent's register.py, before the framework constructs any agents:
from datarobot_genai.core.telemetry.agent import instrument
from datarobot_genai.langgraph.telemetry import instrument as langgraph_instrument
instrument() # HTTP clients + OpenAI SDK + OTel SDK bootstrap
langgraph_instrument() # framework auto-instrumentor spans
The per-framework helpers live alongside each framework package:
| Framework | Import |
|---|---|
| CrewAI | from datarobot_genai.crewai.telemetry import instrument |
| LangChain / LangGraph | from datarobot_genai.langgraph.telemetry import instrument |
| LlamaIndex | from datarobot_genai.llama_index.telemetry import instrument |
All of these are idempotent — repeat calls are no-ops — and safe to keep in register.py during local development: when the DataRobot deployment environment variables below are not all set, the underlying bootstrap_otel_provider_for_datarobot() silently skips installing the SDK provider, so framework spans simply go nowhere instead of erroring.
Required environment
The export endpoint and auth headers are configured through the standard OpenTelemetry env vars — this is the primary mechanism, and in practice every environment (deployments, notebooks, local, CI) relies on it. Both span paths (the NAT datarobot_otelcollector exporter and the instrument() SDK bootstrap) read them first.
| Variable | Description |
|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP/HTTP base URL; /v1/traces is appended. Point it at <host>/otel (not <host>/otel/v1/traces) to hit the DataRobot ingest path. |
OTEL_EXPORTER_OTLP_HEADERS |
Comma-separated key=value list sent as request headers, e.g. X-DataRobot-Api-Key=<token>,X-DataRobot-Entity-Id=deployment-<id>. Used verbatim; |
When the OTLP vars are not set, the runtime falls back to deriving the endpoint and headers from the DataRobot deployment env (populated for you inside a deployment):
| Fallback variable | Used for | Missing → |
|---|---|---|
DATAROBOT_API_TOKEN |
X-DataRobot-Api-Key header |
Silent no-op; no spans reach DataRobot. |
MLOPS_DEPLOYMENT_ID |
X-DataRobot-Entity-Id (auto-prefixed deployment-<id>) |
Silent no-op; no spans reach DataRobot. |
DATAROBOT_ENDPOINT (or DATAROBOT_PUBLIC_API_ENDPOINT) |
endpoint base; /otel/v1/traces appended |
Silent no-op; no spans reach DataRobot. |
Two caveats regardless of which path supplies the endpoint/headers:
- The
instrument()SDK bootstrap (framework /datarobot_otel_conventionsspans) is gated onMLOPS_DEPLOYMENT_IDbeing set, so set it (any value) even when you configure the export viaOTEL_EXPORTER_OTLP_*. - Optional: set
OTEL_SERVICE_NAMEto override the resourceservice.nameused by the SDK bootstrap (the NAT exporter usesprojectfrom the YAML instead).
Troubleshooting
- Data Exploration tab is empty: Confirm the export is configured —
OTEL_EXPORTER_OTLP_ENDPOINT/OTEL_EXPORTER_OTLP_HEADERS(primary) or theDATAROBOT_*fallback. Both span paths silently skip when neither supplies an endpoint and headers. - NAT lifecycle spans appear but framework spans don't: the framework
instrument()(e.g.datarobot_genai.langgraph.telemetry.instrument) was not called, or was called after the framework imported. Move the call to the top ofregister.py. - Framework or memory spans appear in a separate trace from workflow spans: confirm
datarobot_otelcollectoris enabled inworkflow.yamlandinstrument()is called inregister.pybefore the framework imports. The exporter bridges NAT context into the SDK and the bootstrap wraps the globalTracerProviderso LangChain/LangGraph, HTTPPOST, and memory spans share the active workflow trace. datarobot_entity_id must be of the form 'deployment-<id>': You setdatarobot_entity_idmanually without thedeployment-prefix. Either add the prefix or omit the field inside a deployment — it auto-derives fromMLOPS_DEPLOYMENT_ID.