For AI agents: A markdown version of this page is available at https://docs.datadoghq.com/llm_observability/instrumentation/otel_instrumentation.md. A documentation index is available at /llms.txt.
This product is not supported for your selected Datadog site. ().

Overview

By using OpenTelemetry’s standardized semantic conventions for generative AI operations, you can instrument your LLM applications with any OpenTelemetry-compatible library or framework and visualize the traces in Agent Observability.

Agent Observability supports ingesting OpenTelemetry traces that follow either the OpenTelemetry 1.37+ semantic conventions for generative AI or the supported OpenInference semantic conventions. This allows you to send LLM traces directly from OpenTelemetry-instrumented applications to Datadog without requiring the Agent Observability SDK or a Datadog Agent.

Prerequisites

Supported features

Evaluations

To send external evaluations directly to the API for OpenTelemetry spans, include the source:otel tag in the evaluation. When referencing spans, provide span_id and trace_id as decimal strings. OpenTelemetry uses hexadecimal IDs natively, so convert them to decimal before submitting evaluations. For example, use Python’s int(hex_span_id, 16) to convert a hex span ID to its decimal equivalent.

Prompt Tracking

For information on using Prompt Tracking with OpenTelemetry spans, see Prompt Tracking - OpenTelemetry Instrumentation.

Experiments

You can use OpenTelemetry spans inside Agent Observability Experiments. By setting DD_TRACE_OTEL_ENABLED=1, OTel spans created inside an experiment task automatically appear as children of the experiment span.

Use OpenTelemetry span links on your GenAI spans to express non-parent-child relationships, such as when one span’s output feeds another span’s input. When two linked spans are in the same trace, the link appears as an edge in that trace’s Execution Graph, so you can see how data flows between sibling spans (for example, a tool’s output feeding a downstream LLM call).

Execution Graph for a multi-agent content-pipeline trace. The orchestrator contains research-agent, writer-agent, and editor-agent, connected by span-link edges that show data flowing from a search_web tool into the research LLM, then from research to writer to editor.

Use from and to attributes to indicate the direction of the data flow:

from opentelemetry import trace
from opentelemetry.trace import Link

tracer = trace.get_tracer(__name__)

# A tool span whose output feeds a downstream LLM call.
with tracer.start_as_current_span("lookup_order") as tool_span:
    tool_span.set_attribute("gen_ai.operation.name", "execute_tool")
    tool_ctx = tool_span.get_span_context()

# The LLM span links back to the tool span: its output became this span's input.
link = Link(context=tool_ctx, attributes={"from": "output", "to": "input"})
with tracer.start_as_current_span("chat gpt-4o", links=[link]) as llm_span:
    llm_span.set_attribute("gen_ai.operation.name", "chat")
A span link that points to a span in a different trace is stored, but is not drawn in the Execution Graph, which visualizes a single trace.

Setup

Any method Datadog supports for ingesting OpenTelemetry traces works with Agent Observability. For the full list of supported ingestion paths, see OpenTelemetry feature compatibility. The following is one way to configure it.

To send OpenTelemetry traces to Agent Observability, configure your OpenTelemetry exporter with the following settings:

Configuration

Set the following environment variables in your application:

OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=
OTEL_EXPORTER_OTLP_TRACES_HEADERS=dd-api-key=<YOUR_API_KEY>,dd-otlp-source=llmobs

Replace <YOUR_API_KEY> with your Datadog API key.

If your framework previously supported a pre-1.37 OpenTelemetry specification version, you also need to set:

OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental

This environment variable enables version 1.37+-compliant OpenTelemetry traces for frameworks that now support the version 1.37+ semantic conventions, but previously supported older versions (such as strands-agents).

Note:

  • If you are using an OpenTelemetry library other than the default OpenTelemetry SDK, you may need to configure the endpoint, protocol, and headers differently depending on the library’s API. See your library’s documentation for the appropriate configuration method.
  • When using OpenTelemetry instrumentation, some data sent to Agent Observability may also be written to the corresponding APM traces. If you are protecting sensitive data, consider also configuring a Restricted Dataset on APM to match your Agent Observability access controls. See Data Access Control for more information.

Using strands-agents

If you are using the strands-agents library, you need to set an additional environment variable to enable traces that are compliant with OpenTelemetry v1.37+:

OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental

This environment variable ensures that strands-agents emits traces following the OpenTelemetry v1.37+ semantic conventions for generative AI, which are required by Agent Observability.

Instrumentation

To generate traces compatible with Agent Observability, do one of the following:

After your application starts sending data, the traces automatically appear in the Agent Observability Traces page. To search for your traces in the UI, use the ml_app attribute, which is automatically set to the value of your OpenTelemetry root span’s service attribute.

Tested frameworks and libraries

These frameworks and libraries have been tested with Agent Observability. Frameworks that emit the supported attributes from the OpenTelemetry 1.37+ GenAI semantic conventions or OpenInference semantic conventions can send spans to Agent Observability.

Examples

Using Strands Agents

The following example demonstrates a complete application using Strands Agents with the OpenTelemetry integration. This same approach works with any framework that supports OpenTelemetry version 1.37+ semantic conventions for generative AI.

from strands import Agent
from strands_tools import calculator, current_time
from strands.telemetry.config import StrandsTelemetry
import os

# Configure AWS credentials for Bedrock access
os.environ["AWS_PROFILE"] = "<YOUR_AWS_PROFILE>"
os.environ["AWS_DEFAULT_REGION"] = "<YOUR_AWS_REGION>"

# Enable latest GenAI semantic conventions (1.37)
os.environ["OTEL_SEMCONV_STABILITY_OPT_IN"] = "gen_ai_latest_experimental"

# Configure OTLP endpoint to send traces to Agent Observability
os.environ["OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"] = "http/protobuf"
os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = ""
os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = f"dd-api-key={os.getenv('DD_API_KEY')},dd-otlp-source=llmobs"

# Initialize telemetry with OTLP exporter
telemetry = StrandsTelemetry()
telemetry.setup_otlp_exporter()

# Create agent with tools
agent = Agent(tools=[calculator, current_time])

# Run the agent
if __name__ == "__main__":
    result = agent("I was born in 1993, what is my age?")
    print(f"Agent: {result}")

Custom OpenTelemetry instrumentation

The following example demonstrates how to instrument your LLM application using custom OpenTelemetry code. This approach gives you full control over the traces and spans emitted by your application.

import os
import json
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from openai import OpenAI

# Configure OpenTelemetry to send traces to Datadog
os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = ""
os.environ["OTEL_EXPORTER_OTLP_TRACES_HEADERS"] = "dd-api-key=<YOUR_DATADOG_API_KEY>,dd-otlp-source=llmobs"
os.environ["OTEL_SEMCONV_STABILITY_OPT_IN"] = "gen_ai_latest_experimental"

# Initialize OpenTelemetry SDK
resource = Resource(attributes={SERVICE_NAME: "simple-llm-example"})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

tracer = trace.get_tracer(__name__)

# Make LLM call with OpenTelemetry tracing
with tracer.start_as_current_span(
    "chat gpt-4o",
    kind=trace.SpanKind.CLIENT,
) as span:
    model = "gpt-4o"
    max_tokens = 1024
    temperature = 0.7
    messages = [{"role": "user", "content": "Explain OpenTelemetry in one sentence."}]

    # Set request attributes
    span.set_attribute("gen_ai.provider.name", "openai")
    span.set_attribute("gen_ai.request.model", model)
    span.set_attribute("gen_ai.operation.name", "chat")
    span.set_attribute("gen_ai.request.max_tokens", max_tokens)
    span.set_attribute("gen_ai.request.temperature", temperature)

    # Add input messages as event
    input_messages_parts = []
    for msg in messages:
        input_messages_parts.append({
            "role": msg["role"],
            "parts": [{"type": "text", "content": msg["content"]}]
        })

    span.add_event(
        "gen_ai.client.inference.operation.details",
        {
            "gen_ai.input.messages": json.dumps(input_messages_parts)
        }
    )

    # Make actual LLM call
    client = OpenAI(api_key="<YOUR_OPENAI_API_KEY>")
    response = client.chat.completions.create(
        model=model,
        max_tokens=max_tokens,
        temperature=temperature,
        messages=messages
    )

    # Set response attributes from actual data
    span.set_attribute("gen_ai.response.id", response.id)
    span.set_attribute("gen_ai.response.model", response.model)
    span.set_attribute("gen_ai.response.finish_reasons", [response.choices[0].finish_reason])
    span.set_attribute("gen_ai.usage.input_tokens", response.usage.prompt_tokens)
    span.set_attribute("gen_ai.usage.output_tokens", response.usage.completion_tokens)

    # Add output messages as event
    output_text = response.choices[0].message.content
    span.add_event(
        "gen_ai.client.inference.operation.details",
        {
            "gen_ai.output.messages": json.dumps([{
                "role": "assistant",
                "parts": [{"type": "text", "content": output_text}],
                "finish_reason": response.choices[0].finish_reason
            }])
        }
    )

    print(f"Response: {output_text}")

# Flush spans before exit
provider.force_flush()

After running this example, search for ml_app:simple-llm-example in the Agent Observability UI to find the generated trace.

Using OpenLLMetry

The following example demonstrates using OpenLLMetry to automatically instrument OpenAI calls with OpenTelemetry.

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
import openai
from opentelemetry.sdk.resources import Resource

resource = Resource.create({
    "service.name": "simple-openllmetry-test",
})

provider = TracerProvider(resource=resource)
trace.set_tracer_provider(provider)

exporter = OTLPSpanExporter(
    endpoint="",
    headers={
        "dd-api-key": "<YOUR_DATADOG_API_KEY>",
        "dd-ml-app": "simple-openllmetry-test",
        "dd-otlp-source": "llmobs",
    },
)

provider.add_span_processor(BatchSpanProcessor(exporter))

OpenAIInstrumentor().instrument()

# Make OpenAI call (automatically traced)
client = openai.OpenAI(api_key="<YOUR_OPENAI_API_KEY>")
client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "What is 15 multiplied by 7?"}]
)

provider.force_flush(timeout_millis=5000)

After running this example, search for ml_app:simple-openllmetry-test in the Agent Observability UI to find the generated trace.

Using OpenInference

The following example uses the OpenInference OpenAI instrumentation to automatically instrument OpenAI calls with OpenTelemetry.

Configure the OpenTelemetry exporter and instrument the OpenAI client:

import openai
from openinference.instrumentation.openai import OpenAIInstrumentor
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

resource = Resource.create({
    "service.name": "simple-openinference-test",
})

provider = TracerProvider(resource=resource)
trace.set_tracer_provider(provider)

exporter = OTLPSpanExporter(
    endpoint="",
    headers={
        "dd-api-key": "<YOUR_DATADOG_API_KEY>",
        "dd-ml-app": "simple-openinference-test",
        "dd-otlp-source": "llmobs",
    },
)

provider.add_span_processor(BatchSpanProcessor(exporter))

OpenAIInstrumentor().instrument(tracer_provider=provider)

# Make OpenAI call (automatically traced)
client = openai.OpenAI(api_key="<YOUR_OPENAI_API_KEY>")
client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "What is 15 multiplied by 7?"}]
)

provider.force_flush(timeout_millis=5000)

After running this example, search for ml_app:simple-openinference-test in the Agent Observability UI to find the generated trace.

Attribute mapping reference

This section provides the mappings from OpenTelemetry GenAI semantic conventions (v1.37+), OpenLLMetry, OpenInference, and Langfuse to Datadog’s Agent Observability span schema.

Provider-specific mappings are documented separately in the OpenLLMetry attribute mappings, OpenInference attribute mappings, and Langfuse attribute mappings sections.

OpenTelemetry 1.37+ attribute mappings

Base span attributes

OTLP FieldAgent Observability FieldNotes
resource.attributes.service.nameml_app, tags.service
namenameOverridden by gen_ai.tool.name if present
parent_span_idparent_id
start_time_unix_nanostart_ns
end_time_unix_nanodurationCalculated: end - start
status.codestatuserror if > 0, else ok
status.messagemeta.error.message
attributes.error.typemeta.error.type

Span kind resolution

gen_ai.operation.nameAgent Observability span.kind
generate_content, chat, text_completion, completionllm
embeddings, embeddingembedding
execute_tooltool
invoke_agent, create_agentagent
rerank, unknown, (default)workflow

Model information

OTel AttributeAgent Observability FieldNotes
gen_ai.operation.namemeta.span.kindSee resolution table above
gen_ai.provider.namemeta.model_providerFalls back to gen_ai.system, then custom
gen_ai.response.modelmeta.model_name
gen_ai.request.modelmeta.model_nameFallback if response.model absent

Token usage metrics

OTel AttributeAgent Observability Field
gen_ai.usage.input_tokensmetrics.input_tokens
gen_ai.usage.output_tokensmetrics.output_tokens
gen_ai.usage.prompt_tokensmetrics.prompt_tokens
gen_ai.usage.completion_tokensmetrics.completion_tokens
gen_ai.usage.total_tokensmetrics.total_tokens

Request parameters

All gen_ai.request.* parameters map to meta.metadata.* with the prefix stripped.

OTel AttributeAgent Observability Field
gen_ai.request.seedmetadata.seed
gen_ai.request.frequency_penaltymetadata.frequency_penalty
gen_ai.request.max_tokensmetadata.max_tokens
gen_ai.request.stop_sequencesmetadata.stop_sequences
gen_ai.request.temperaturemetadata.temperature
gen_ai.request.top_kmetadata.top_k
gen_ai.request.top_pmetadata.top_p
gen_ai.request.choice.countmetadata.choice.count

Tool attributes

OTel AttributeAgent Observability FieldNotes
gen_ai.tool.namenameOverrides span name
gen_ai.tool.call.idmetadata.tool_id
gen_ai.tool.descriptionmetadata.tool_description
gen_ai.tool.typemetadata.tool_type
gen_ai.tool.definitionsmeta.tool_definitionsParsed JSON array
gen_ai.tool.call.argumentsinput.value
gen_ai.tool.call.resultoutput.value

Session and conversation

OTel AttributeAgent Observability FieldNotes
gen_ai.conversation.idsession_idAlso added to metadata.conversation_id and tags

When an APM trace’s top-most span is not a gen_ai span (for example, an HTTP handler that invokes several LLMs in parallel), Agent Observability produces a separate Agent Observability trace for each top-level gen_ai span in that APM trace. To keep these split traces grouped together in the UI, set gen_ai.conversation.id to the same value on each gen_ai span within the APM trace: Agent Observability groups by session_id, so the resulting traces appear together even though they have distinct Agent Observability trace IDs. This is the same attribute used for cross-request conversation grouping.

Span links you set on a GenAI span appear as span_links on the corresponding Agent Observability span.

OTel span link fieldAgent Observability FieldNotes
trace_idspan_links[].trace_id128-bit trace IDs are emitted as hex. A link to a span in the same trace resolves to that span’s Agent Observability trace ID.
span_idspan_links[].span_idDecimal
attributesspan_links[].attributesDots in attribute keys are replaced with underscores (for example, messaging.operation becomes messaging_operation).

Links between spans in the same trace are drawn as edges in that trace’s Execution Graph.

Response attributes

OTel AttributeAgent Observability Field
gen_ai.response.modelmeta.model_name
gen_ai.response.finish_reasonsmetadata.finish_reasons

Input and output messages

Input and output messages are extracted from the following sources, in priority order:

  1. Direct attributes: gen_ai.input.messages, gen_ai.output.messages, gen_ai.system_instructions
  2. Span events (meta["events"]) with name gen_ai.client.inference.operation.details
OTel SourceAgent Observability FieldNotes
gen_ai.input.messagesmeta.input.messages (llm) / meta.input.value (others)
gen_ai.output.messagesmeta.output.messages (llm) / meta.output.value (others)
gen_ai.system_instructionsPrepended to inputAdded as system role messages
Embedding spans
OTel SourceAgent Observability Field
gen_ai.input.messagesmeta.input.documents
N/Ameta.output.value = [N embedding(s) returned]

Tags

Tags are placed directly on the span:

  • Non-gen_ai.* attributes are converted to key:value tags
  • Unknown gen_ai.* keys are added with prefix stripped
  • Filtered out: _dd.*, llm.*, ddtags, events, and already specifically mapped gen_ai.* keys
Any gen_ai.* attributes that are not explicitly mapped to Agent Observability span fields are placed in the LLM span's tags, with a 256 character limit per value. Values exceeding this limit are truncated. All other non-gen_ai attributes are dropped.

Custom metadata

To add structured metadata to a span’s meta.metadata field instead of its tags, set the _dd.ml_obs.metadata attribute to a JSON object string. Its keys and values (including nested objects and arrays) are merged into meta.metadata and rendered as JSON in the UI.

import json

span.set_attribute("_dd.ml_obs.metadata", json.dumps({
    "experiment": "a/b",
    "config": {
        "retry": {"max": 3, "backoff": "exp"},
        "feature_flags": ["new_ranker", "fast_path"],
    },
}))

Notes:

  • Values may be arbitrarily nested; unlike tags, metadata is not subject to the 256 character per-value limit.
  • Keys that collide with metadata derived from gen_ai.* attributes (for example, temperature) are overwritten by your values, with the exception of model_name and model_provider, which are reserved.
  • The value must be a JSON object. A value that is not valid JSON, or that is a top-level array or scalar, is dropped.

OpenLLMetry attribute mappings

This section documents OpenLLMetry-specific attribute mappings that differ from or extend the standard OpenTelemetry GenAI semantic conventions.

Span kind resolution

llm.request.type is used as a fallback when gen_ai.operation.name is absent.

llm.request.typeAgent Observability span.kind
chatllm
completionllm
embeddingembedding
rerankworkflow
unknown, (default)workflow

Model information

OpenLLMetry AttributeAgent Observability FieldNotes
gen_ai.systemmeta.model_providerFallback when gen_ai.provider.name absent

Token usage metrics

OpenLLMetry AttributeAgent Observability FieldNotes
llm.usage.total_tokensmetrics.total_tokensFallback when gen_ai.usage.total_tokens absent

Input and output messages

OpenLLMetry uses indexed attributes instead of JSON arrays. These are the lowest priority source and are only used when no OTel standard sources exist.

Prompt attributes (input)
OpenLLMetry AttributeDescription
gen_ai.prompt.<index>.roleMessage role (user, system, assistant, tool)
gen_ai.prompt.<index>.contentMessage content
gen_ai.prompt.<index>.tool_call_idTool call ID for tool response messages
Completion attributes (output)
OpenLLMetry AttributeDescription
gen_ai.completion.<index>.roleMessage role
gen_ai.completion.<index>.contentMessage content
gen_ai.completion.<index>.finish_reasonCompletion finish reason
Mapping

Messages are converted to OTel-compatible format and processed normally:

OpenLLMetry SourceLLMObs Field
gen_ai.prompt.*meta.input.messages (llm) / meta.input.value (others)
gen_ai.completion.*meta.output.messages (llm) / meta.output.value (others)

Tool calls

Tool calls are nested within completion attributes.

OpenLLMetry AttributeMaps To
gen_ai.completion.<index>.tool_calls.<idx>.nametool_calls[].name
gen_ai.completion.<index>.tool_calls.<idx>.idtool_calls[].tool_id
gen_ai.completion.<index>.tool_calls.<idx>.argumentstool_calls[].arguments
Tool response messages

When role = "tool" and tool_call_id are present, the message is converted to a tool result:

OpenLLMetry AttributeMaps To
gen_ai.prompt.<index>.tool_call_idtool_results[].tool_id
gen_ai.prompt.<index>.contenttool_results[].result

Embedding spans

For embedding spans, documents are extracted from prompt content attributes.

OpenLLMetry SourceAgent Observability Field
gen_ai.prompt.<index>.contentmeta.input.documents[].text

Tags filtering

The following OpenLLMetry-specific attributes are filtered from tags:

  • gen_ai.prompt.*
  • gen_ai.completion.*
  • llm.*

OpenInference attribute mappings

Agent Observability recognizes an OpenInference span when the openinference.span.kind attribute is present and non-empty. The following sections document the OpenInference attributes that map to dedicated Agent Observability fields.

Span kind resolution

If both gen_ai.operation.name and openinference.span.kind are present, gen_ai.operation.name takes precedence.

openinference.span.kindAgent Observability span.kind
LLMllm
EMBEDDINGembedding
TOOLtool
AGENTagent
RETRIEVERretrieval
CHAIN, RERANKER, GUARDRAIL, EVALUATOR, PROMPT, other valuesworkflow

Model information

OpenInference AttributeAgent Observability FieldNotes
llm.providermeta.model_providerPreferred OpenInference provider source
llm.systemmeta.model_providerFallback when llm.provider is absent
llm.model_namemeta.model_name
embedding.model_namemeta.model_nameFallback for embedding spans

For llm and embedding spans, missing provider or model values are set to unknown.

Token usage metrics

OpenInference AttributeAgent Observability Field
llm.token_count.promptmetrics.prompt_tokens
llm.token_count.completionmetrics.completion_tokens
llm.token_count.totalmetrics.total_tokens
llm.token_count.prompt_details.cache_readmetrics.cache_read_input_tokens
llm.token_count.prompt_details.cache_writemetrics.cache_write_input_tokens
llm.token_count.completion_details.reasoningmetrics.reasoning_output_tokens

Session, user, and metadata

OpenInference AttributeAgent Observability FieldNotes
session.idsession_idAlso adds session_id and conversation_id tags and propagates the session to the trace root
user.idtagsAdded as user_id:<value>
tag.tagstagsEach list item becomes a span tag
llm.invocation_parametersmeta.metadataParsed as a JSON object
metadatameta.metadataParsed as a JSON object

Reserved Agent Observability fields in llm.invocation_parameters and metadata do not override dedicated span fields.

Tool attributes

OpenInference AttributeAgent Observability FieldNotes
tool.namenameOverrides the span name
tool.idmeta.metadata.tool_id
tool.descriptionmeta.metadata.tool_description
tool.parametersmeta.metadata.tool_parameters
input.valuemeta.input.valueUsed directly for tool, agent, and workflow spans
output.valuemeta.output.valueUsed directly for tool, agent, and workflow spans

Input and output messages

In these attributes, <direction> is input or output. Input and output are extracted from the following sources, in priority order:

  1. OpenTelemetry gen_ai.* direct attributes and span events
  2. OpenLLMetry indexed attributes
  3. OpenInference indexed attributes
  4. OpenInference input.value and output.value
OpenInference SourceAgent Observability Field
llm.input_messages.<index>.*meta.input.messages (llm) / meta.input.value (other span kinds)
llm.output_messages.<index>.*meta.output.messages (llm) / meta.output.value (other span kinds)
input.valueInput fallback
output.valueOutput fallback

The following indexed message attributes are supported:

OpenInference AttributeMapping
llm.<direction>_messages.<message-index>.message.roleMessage role
llm.<direction>_messages.<message-index>.message.contentText content
llm.<direction>_messages.<message-index>.message.contents.<content-index>.message_content.textOrdered text content
llm.<direction>_messages.<message-index>.message.contents.<content-index>.message_content.image.image.urlOrdered image content
llm.<direction>_messages.<message-index>.message.tool_calls.<tool-index>.tool_call.*Tool call ID, name, and arguments
llm.<direction>_messages.<message-index>.message.contents.<content-index>.tool_call.*Tool call within ordered content
llm.<direction>_messages.<message-index>.message.tool_call_idTool result ID when the message role is tool

Image content maps to an image URI while preserving its position among other message content.

Embedding spans

OpenInference SourceAgent Observability Field
embedding.embeddings.<index>.embedding.textmeta.input.documents[].text
N/Ameta.output.value = [N embedding(s) returned]

Retrieval spans

OpenInference SourceAgent Observability Field
input.valuemeta.input.value
retrieval.documents.<index>.document.contentmeta.output.documents[].text
retrieval.documents.<index>.document.idmeta.output.documents[].id
retrieval.documents.<index>.document.scoremeta.output.documents[].score
retrieval.documents.<index>.document.metadatameta.output.documents[].metadata (parsed JSON object)

Tags filtering

OpenInference attributes with llm.*, retrieval.*, embedding.*, and reranker.* prefixes are excluded from tags. Specifically mapped values such as input.value, output.value, metadata, tag.tags, and tool.parameters are also excluded from duplicate tags.

Other non-empty OpenInference attributes with values of 256 characters or fewer are added as key:value tags. The tag.tags list is promoted directly to span tags.

Langfuse attribute mappings

This section documents Langfuse-specific attribute mappings for applications using Langfuse’s native OpenTelemetry instrumentation.

Detection

A span is treated as a Langfuse span when it carries a non-empty langfuse.observation.type attribute. This attribute is also used as a fallback for span kind resolution when gen_ai.operation.name is absent.

Span kind resolution

langfuse.observation.typeAgent Observability span.kind
generationllm
embeddingembedding
tooltool
agentagent
retrieverretrieval
span, event, chain, evaluator, guardrail, (default)workflow

Model information

Langfuse AttributeAgent Observability FieldNotes
langfuse.observation.metadata.ls_providermeta.model_provider
langfuse.observation.model.namemeta.model_nameFallback when gen_ai.response.model and gen_ai.request.model are absent

Token usage metrics

langfuse.observation.usage_details is a JSON object. Each key maps to an Agent Observability metric, used as a fallback for any metric not already set from gen_ai.usage.* attributes:

Langfuse Usage KeyAgent Observability Field
input, input_tokensmetrics.input_tokens
output, output_tokensmetrics.output_tokens
total, total_tokensmetrics.total_tokens
prompt_tokensmetrics.prompt_tokens
completion_tokensmetrics.completion_tokens
cache_creation_input_tokensmetrics.cache_write_input_tokens
cache_read_input_tokens, cached_tokensmetrics.cache_read_input_tokens
reasoning_tokensmetrics.reasoning_output_tokens

Input and output messages

langfuse.observation.input and langfuse.observation.output carry a JSON-encoded value that can be a chat-message array, a single message object, or arbitrary JSON/string content. These are the lowest-priority sources and are only used when no gen_ai.* message attributes exist.

Each message is converted to the parts-based message shape:

  • A content string becomes a text part.
  • A content array of blocks converts image_url blocks to uri parts and text blocks to text parts; any other block is kept as serialized text.
  • A tool_calls array on a message becomes tool_call parts.
  • A message with role: tool and a tool_call_id becomes a tool_result part.
Tool spans

For tool, agent, and workflow spans, langfuse.observation.input/langfuse.observation.output are used directly as input.value/output.value, after the standard gen_ai.tool.call.* fallback.

Retrieval spans

For retrieval spans, langfuse.observation.input is used as the query value in meta.input.value. langfuse.observation.output is parsed as a document collection (an array of document objects, an array of strings, or a single document object) into meta.output.documents. Each document object’s text, content, or page_content key (checked in that order) maps to text, along with any id, score, and metadata keys.

Embedding spans

For embedding spans, langfuse.observation.input is parsed the same way as retrieval documents into meta.input.documents[].text, keeping only documents that carry non-empty text.

Session, user, metadata, and tags

Langfuse AttributeAgent Observability FieldNotes
langfuse.session.idsession_idFallback when gen_ai.conversation.id is absent
langfuse.user.iduser_id: tagFallback when the standard user ID attribute is absent
langfuse.observation.model.parametersmeta.metadata.*JSON object, merged into metadata, skipping reserved keys (model_name, model_provider)
langfuse.trace.metadata.*, langfuse.observation.metadata.*meta.metadata.*Prefix stripped, merged into metadata, skipping reserved keys
langfuse.trace.tagsAppended to tagsJSON array of strings

Tags filtering

The following Langfuse-specific attributes are filtered from tags because they’re consumed elsewhere:

  • langfuse.internal.*, langfuse.observation.metadata.*, langfuse.trace.metadata.* (prefixes)
  • langfuse.observation.input, langfuse.observation.output, langfuse.observation.model.name, langfuse.observation.model.parameters, langfuse.observation.usage_details, langfuse.observation.cost_details, langfuse.observation.completion_start_time
  • langfuse.trace.input, langfuse.trace.output, langfuse.trace.metadata, langfuse.trace.tags
  • langfuse.experiment.item.expected_output, langfuse.experiment.item.metadata, langfuse.experiment.metadata

Supported semantic conventions

Agent Observability supports spans that follow the OpenTelemetry 1.37+ semantic conventions for generative AI, including:

  • LLM operations with gen_ai.provider.name, "gen_ai.operation.name", gen_ai.request.model, and other gen_ai attributes
  • Operation inputs/outputs on direct span attributes or via span events
  • Token usage metrics (gen_ai.usage.input_tokens, gen_ai.usage.output_tokens)
  • Model parameters and metadata

For the complete list of supported attributes and their specifications, see the OpenTelemetry semantic conventions for generative AI documentation.

Disabling Agent Observability conversion

If you’d only like your generative AI spans to remain in APM and not appear in Agent Observability, you can disable the automatic conversion by setting the dd_llmobs_enabled attribute to false. Setting this attribute on any span in a trace prevents the entire trace from being converted to Agent Observability.

Using environment variables

Add the dd_llmobs_enabled=false attribute to your OTEL_RESOURCE_ATTRIBUTES environment variable:

OTEL_RESOURCE_ATTRIBUTES=dd_llmobs_enabled=false

Using code

You can also set the attribute programmatically on any span in your trace:

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("my-span") as span:
    # Disable Agent Observability conversion for this entire trace
    span.set_attribute("dd_llmobs_enabled", False)