Try it free

Get started with OpenInference and AI Observability

  • Latest Dynatrace
  • Getting started guide
  • 5-min read

OpenInference is an open standard for AI observability developed by Arize AI. OpenInference defines semantic conventions such as llm.model_name and llm.token_count.*, which differ from the gen_ai.* attributes that AI Observability AI Observability natively understands. Supported instrumentors can also emit OpenTelemetry GenAI semantic-convention attributes alongside OpenInference attributes.

Learn how to instrument your AI application with OpenInference, enable native gen_ai.* attributes where available, and normalize remaining OpenInference attributes with an OTel Collector or Dynatrace OpenPipeline.

Who is this for?

This getting started guide is for:

  • AI engineering teams building agent- and LLM-powered applications and services.
  • Site Reliability Engineers responsible for monitoring AI workloads in production.
  • Platform engineers integrating OpenTelemetry (OTel) data from AI apps into Dynatrace.

What will you learn?

By following this guide, you'll learn:

  • How to instrument an AI application with OpenInference.
  • How to emit or normalize OpenInference attributes as gen_ai.* by using native GenAI semantic-convention support, an OTel Collector, or Dynatrace OpenPipeline.
  • How to view model name, token usage, and message content in AI Observability AI Observability.

Before you begin

Prerequisites

To follow this guide, you need:

  • A running AI application, or use one of the OpenInference sample apps:

    • OpenAI
    • Amazon Bedrock
    • LangGraph
    • Anthropic
    • Cohere
    • Groq
    • Mistral
    • Ollama
    • Haystack
  • Dynatrace SaaS with a Dynatrace Platform Subscription (DPS) license that has Traces powered by Grail enabled.

  • OTLP ingestion enabled, see OpenTelemetry and Dynatrace.

  • A platform token with the openpipeline:traces:ingest scope, or a Classic access token with the Ingest OpenTelemetry traces (openTelemetryTrace.ingest) scope.

  • Any credentials required by your selected sample.

  • For the Collector option: Docker, and an OTel Collector distribution that bundles the genainormalizer processor. Either of the following works:

    • Dynatrace Collector version 0.53.1 or later.
    • Bindplane Distro for OpenTelemetry (BDOT) version 1.104.0 or later.

OpenInference samples

Choose the sample that matches your provider or framework. Each README contains the provider-specific prerequisites, instrumentation, and processing configuration.

TechnologyOpenInference instrumentorData preparation in the sample

OpenAI

openinference-instrumentation-openai

Native gen_ai.* emission, with sample processing for metric derivation

Amazon Bedrock

openinference-instrumentation-bedrock

Collector normalization and provider-specific message transforms

LangGraph

openinference-instrumentation-langchain

Collector normalization

Anthropic

openinference-instrumentation-anthropic

Collector or OpenPipeline normalization, with a provider-specific output-message transform

Cohere

openinference-instrumentation-cohere

Native gen_ai.* emission, with sample processing for metric derivation

Groq

openinference-instrumentation-groq

Collector or OpenPipeline normalization

Mistral

openinference-instrumentation-mistralai

Collector or OpenPipeline normalization

Ollama

openinference-instrumentation-ollama

Collector or OpenPipeline normalization

Haystack

openinference-instrumentation-haystack

Collector or OpenPipeline normalization

Prior knowledge

It's helpful to have some basic knowledge of:

  • Python.
  • OTel concepts like SDKs, spans, exporters, and Collectors.
  • Dynatrace permissions and data ingestion.

About OpenInference and AI observability

OpenInference captures and transmits AI model or agentic-workflow KPIs as OpenTelemetry spans. Its instrumentors emit OpenInference attributes and can, depending on the instrumentor, also emit OpenTelemetry gen_ai.* attributes. Dynatrace AI Observability requires gen_ai.* attributes. Enable native GenAI semantic-convention emission when the instrumentor supports it. Otherwise, normalize the OpenInference attributes with an OTel Collector or OpenPipeline. For more information, see Use OpenTelemetry GenAI semantic conventions when available and Normalize your OpenInference attributes.

Get started with OpenInference and Dynatrace

This section describes how to start ingesting OpenInference attributes into Dynatrace, which can then be normalized into gen_ai.* attributes.

1. Create a Dynatrace token

To authenticate, create one of the following:

  • Platform token:
    1. Go to My platform tokens.
    2. Create a platform token with the required scope (listed below). See Platform tokens for details.
  • Classic access token:
    1. Go to Access tokens Access Tokens.
    2. Select Generate new token.
    3. Enter a name for your token.
    4. Select the required scopes listed below.
    5. Select Generate token.
    6. Copy the generated token. Store it securely — you can only access it once.

Required scopes:

  • Platform token: openpipeline:traces:ingest
  • Classic access token: Ingest OpenTelemetry traces (openTelemetryTrace.ingest)

For more information, see Tokens and permissions.

2. Set environment variables

The sample app and scripts read credentials from environment variables. Create a .env file in your project directory:

DT_ENDPOINT=https://<your-environment-id>.live.dynatrace.com
DT_API_TOKEN=dt0XXX.<your-token-value>
OPENAI_API_KEY=<your-openai-api-key>
OPENAI_API_BASE=https://your-endpoint.openai.azure.com/
MODEL=gpt-5.5
OPENAI_API_VERSION=2024-07-01-preview
OPENINFERENCE_ENABLE_GENAI_SEMCONV=true

Set DT_API_TOKEN to your platform token or Classic access token.

DT_ENDPOINT is your base environment URL, not the /api/v2/otlp path. For more information, see Dynatrace OTLP endpoints.

3. Install dependencies

pip install -r requirements.txt

The key packages are openinference-instrumentation-openai and opentelemetry-sdk.

4. Instrument your application

Use the OpenInference auto-instrumentation for OpenAI. The instrumentation patches the OpenAI client and records spans with OpenInference semantic conventions automatically. Each example's README identifies its instrumentor package, dependencies, application entry point, and Collector configuration. Follow that README because providers and frameworks don't all emit the same attributes or use the same configuration filenames.

from openinference.instrumentation.openai import OpenAIInstrumentor
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
import openai
import os
# Configure the tracer provider
service_name = os.getenv("OTEL_SERVICE_NAME", "openinferenceapp")
tracer_provider = TracerProvider(
resource=Resource.create(
{
SERVICE_NAME: service_name,
}
)
)
# Configure the OTLP exporter with the appropriate endpoint and credentials (see the option you've chosen below)
exporter = OTLPSpanExporter(
endpoint=os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"],
headers={"Authorization": f"Bearer {os.environ['DT_API_TOKEN']}"},
)
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
# Instrument the OpenAI client
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
response = client.chat.completions.create(
model=os.environ.get("MODEL", "gpt-5.5"),
messages=[{"role": "user", "content": "Write a haiku about observability."}],
)
print(response.choices[0].message.content)

The required scope is openpipeline:traces:ingest.

from openinference.instrumentation.openai import OpenAIInstrumentor
from opentelemetry.sdk.resources import Resource, SERVICE_NAME
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
import openai
import os
# Configure the tracer provider
service_name = os.getenv("OTEL_SERVICE_NAME", "openinferenceapp")
tracer_provider = TracerProvider(
resource=Resource.create(
{
SERVICE_NAME: service_name,
}
)
)
# Configure the OTLP exporter with the appropriate endpoint and credentials (see the option you've chosen below)
exporter = OTLPSpanExporter(
endpoint=os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"],
headers={"Authorization": f"Api-Token {os.environ['DT_API_TOKEN']}"},
)
tracer_provider.add_span_processor(BatchSpanProcessor(exporter))
# Instrument the OpenAI client
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
response = client.chat.completions.create(
model=os.environ.get("MODEL", "gpt-5.5"),
messages=[{"role": "user", "content": "Write a haiku about observability."}],
)
print(response.choices[0].message.content)

The required scope is Ingest OpenTelemetry traces (openTelemetryTrace.ingest).

Control message-content capture

OpenInference Python instrumentors that use TraceConfig capture content by default. Prompts and completions can contain sensitive data, so review the content that your application sends before exporting spans to Dynatrace.

Use TraceConfig to suppress content programmatically with hide_inputs, hide_outputs, hide_input_messages, and hide_output_messages. You can set the equivalent environment variables to true: OPENINFERENCE_HIDE_INPUTS, OPENINFERENCE_HIDE_OUTPUTS, OPENINFERENCE_HIDE_INPUT_MESSAGES, and OPENINFERENCE_HIDE_OUTPUT_MESSAGES.

If you intentionally want message content sent to Dynatrace, ensure that the applicable controls are false or unset. These controls can't restore data that the instrumentation didn't capture.

Use OpenTelemetry GenAI semantic conventions when available

Set the following environment variable when using an instrumentor that supports OpenTelemetry GenAI semantic conventions:

OPENINFERENCE_ENABLE_GENAI_SEMCONV=true

OPENINFERENCE_ENABLE_GENAI_SEMCONV emits gen_ai.* attributes alongside OpenInference attributes. The OpenAI and Cohere examples use this setting. Other instrumentors might not emit complete gen_ai.* coverage, so coverage varies by provider and framework. For those instrumentors, normalize attributes to gen_ai.* with the Collector genainormalizer and transform processors or with Dynatrace OpenPipeline, as described below.

Normalize your OpenInference attributes

If your instrumentor doesn't emit every attribute that Dynatrace needs as gen_ai.*, normalize the remaining OpenInference attributes in one of two ways.

  • Recommended OTel Collector with the genainormalizer processor
  • Dynatrace OpenPipeline
OTel Collector + genainormalizerOpenPipeline

Where transforms run

In the Collector process, locally

Server-side, in your Dynatrace tenant

Requires Docker?

Yes

No

Requires Dynatrace configuration?

No

Yes; one-time deployment

Full message history

Reconstructs gen_ai.input.messages / gen_ai.output.messages when the instrumentor emits sufficient message attributes

Uses interim fallback from input.value / output.value

Good for

Full pipeline control and complete message history when source attributes are available

Simpler operations; no Collector to manage

Collector image

Any distribution bundling genainormalizer, such as the Dynatrace Collector or the Bindplane distro

Not applicable

OTel Collector with the genainormalizer processor

The Collector intercepts spans and normalizes OpenInference attributes to gen_ai.* with the genainormalizer processor before forwarding them to Dynatrace. No Dynatrace configuration is needed, and you can use any OTel Collector distribution that bundles the genainormalizer processor, for example:

  • Dynatrace Collector: ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector:0.53.1
  • Bindplane Distro for OpenTelemetry (BDOT): ghcr.io/observiq/bindplane-agent:1.104.0

The Collector:

  1. Listens on port 4318 for incoming OTLP/HTTP spans from the app. The app only sends spans to http://localhost:4318 and doesn't need Dynatrace credentials.
  2. Applies the genainormalizer processor (source: openinference, remove_originals: true) to map OpenInference attributes to gen_ai.* and reconstruct message history. When the instrumentor emits sufficient indexed per-message attributes, the genainormalizer processor reconstructs the full gen_ai.input.messages / gen_ai.output.messages.
  3. Applies the transform/response_model processor to mirror gen_ai.request.model to gen_ai.response.model.
  4. Authenticates with Dynatrace using DT_ENDPOINT and DT_API_TOKEN. It forwards processed spans to $DT_ENDPOINT/api/v2/otlp authenticated with your platform token or Classic access token.

The following steps describe how to normalize attributes with the Collector.

1. Start the Collector

Run your chosen Collector image with the configuration file supplied by your sample. The OpenAI sample uses otel-collector-config.yaml. The Amazon Bedrock and Cohere samples use otelcol-config.yaml. The remaining listed samples use otel-collector-config.yaml.

These examples use the Dynatrace Collector distro. To use the Bindplane distro instead, set COLLECTOR_IMAGE to ghcr.io/observiq/bindplane-agent:1.104.0.

macOS/Linux:

source .env
COLLECTOR_IMAGE=ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector:0.53.1
docker run -d \
--name otel-collector \
-p 4318:4318 \
-v $(pwd)/otel-collector-config.yaml:/etc/otel/config.yaml:ro \
-e DT_ENDPOINT=$DT_ENDPOINT \
-e DT_API_TOKEN=$DT_API_TOKEN \
$COLLECTOR_IMAGE \
--config=/etc/otel/config.yaml

Windows CMD:

set DT_ENDPOINT=https://abc12345.live.dynatrace.com
set DT_API_TOKEN=dt0XXX.<your-token-value>
set COLLECTOR_IMAGE=ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector:0.53.1
docker run -d ^
--name otel-collector ^
-p 4318:4318 ^
-v %cd%/otel-collector-config.yaml:/etc/otel/config.yaml:ro ^
-e DT_ENDPOINT=%DT_ENDPOINT% ^
-e DT_API_TOKEN=%DT_API_TOKEN% ^
%COLLECTOR_IMAGE% ^
--config=/etc/otel/config.yaml
2. Run the app

Point the app to the local Collector.

source .env
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_EXPORTER_OTLP_HEADERS=""
python3 app.py

Useful commands:

# Tail Collector logs
docker logs -f otel-collector
# Stop the Collector
docker stop otel-collector && docker rm otel-collector

Dynatrace OpenPipeline

OpenPipeline is a server-side processing pipeline in Dynatrace that applies the same attribute mappings before Dynatrace stores them. The app sends spans directly to Dynatrace, no Collector needed.

The following steps describe how to normalize attributes with OpenPipeline.

1. Deploy the OpenPipeline configuration

This is a one-time setup per environment.

  1. In Dynatrace, select Ctrl+K and search for OpenPipeline.

  2. Select Spans.

  3. Select Add pipeline and name it openinference-ai-spans.

  4. Add processors that match the definitions in the sample repository's openpipeline-openinference.yaml file.

  5. Go to the Routing tab and add an entry.

    • Matcher: isNotNull(openinference.span.kind)
    • Pipeline: openinference-ai-spans

    Use isNotNull(openinference.span.kind) as the routing matcher. OpenPipeline routing evaluates span attributes only, not OTLP scope-level fields, so otel.scope.name is not accessible at routing time. The openinference.span.kind attribute is set on every span by all OpenInference instrumentors.

2. Run the app

Point the app directly to the Dynatrace OTLP endpoint. OpenPipeline intercepts and transforms spans before Dynatrace stores them.

source .env
OTEL_EXPORTER_OTLP_ENDPOINT=$DT_ENDPOINT/api/v2/otlp \
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $DT_API_TOKEN" \
python3 app.py

The required scope is openpipeline:traces:ingest.

source .env
OTEL_EXPORTER_OTLP_ENDPOINT=$DT_ENDPOINT/api/v2/otlp \
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Api-Token $DT_API_TOKEN" \
python3 app.py

Attribute mapping reference

The table below summarizes the mappings in the sample Collector and OpenPipeline configurations. The genainormalizer processor performs the core mappings. The sample transform processors add optional fields, as described in Known limitations. For differences with OpenPipeline, see the Notes column.

Source attributegen_ai.* attributeNotes

openinference.span.kind

  • Collector: gen_ai.operation.name
  • OpenPipeline: gen_ai.operation.kind
  • LLM becomes chat in the Collector and llm in OpenPipeline
  • EMBEDDING becomes embeddings
  • CHAIN and AGENT become invoke_agent
  • RETRIEVER and RERANKER become retrieval
  • TOOL becomes execute_tool
  • PROMPT becomes text_completion
  • The Collector copies other values, including GUARDRAIL, unchanged; the Anthropic OpenPipeline sample maps GUARDRAIL to guardrail

llm.model_name

gen_ai.request.model

LLM spans

embedding.model_name

gen_ai.request.model

Embedding spans

reranker.model_name

gen_ai.request.model

Reranker spans

gen_ai.request.model

gen_ai.response.model

Mirrored; OpenInference has no separate response model field

llm.provider

gen_ai.provider.name

If llm.provider is absent, llm.system is used instead

llm.system

gen_ai.provider.name

llm.system is used only when llm.provider is absent

—

gen_ai.system

Set to azure.ai.openai when provider is azure

—

gen_ai.operation.name

Hardcoded chat for LLM spans, embeddings for embedding spans

llm.token_count.prompt

gen_ai.usage.input_tokens

llm.token_count.completion

gen_ai.usage.output_tokens

llm.token_count.prompt_details.cache_read

gen_ai.prompt_caching

  • Set to read when the value is greater than 0.
  • Set to write when only cache_write is greater than 0.

Guard prevents false positives (Azure emits 0 on every span).

llm.temperature

gen_ai.request.temperature

llm.max_tokens

gen_ai.request.max_tokens

llm.top_p

gen_ai.request.top_p

llm.finish_reason

gen_ai.response.finish_reasons

Renamed directly; source attribute removed

llm.input_messages.0.message.content

gen_ai.system_instructions

Only when llm.input_messages.0.message.role == "system"

agent.name

gen_ai.agent.name

tool.name

gen_ai.tool.name

tool.description

gen_ai.tool.description

validator_name

gen_ai.guardrail.name

The Anthropic OpenPipeline sample renames this unnamespaced attribute for queryability

embedding.vector_length

gen_ai.embeddings.dimension.count

llm.input_messages.N.*

gen_ai.input.messages

Reconstructed into full message history JSON including roles, content, and tool calls when sufficient indexed attributes are available. Source attributes removed. (OpenPipeline uses interim fallback from input.value instead.)

llm.output_messages.N.*

gen_ai.output.messages

Reconstructed into full message history JSON including roles, content, and tool calls when sufficient indexed attributes are available. Source attributes removed. (OpenPipeline uses interim fallback from output.value instead.)

—

ai.observability.source

Hardcoded openinference on all spans

OpenInference supports GUARDRAIL as a span kind but doesn't define a standardized rich guardrail attribute set. The genainormalizer processor copies this value to gen_ai.operation.name unchanged. The Anthropic OpenPipeline sample maps it to guardrail in gen_ai.operation.kind and renames an emitted validator_name source attribute to gen_ai.guardrail.name. Provider- or framework-specific guardrail data can be unavailable, and downstream transforms can't reconstruct data that instrumentation discarded.

Known limitations

Attribute coverage and message reconstruction depend on the instrumentor and the attributes it emits. The genainormalizer processor maps the supported OpenInference attributes it receives, but some optional fields can require custom transforms:

  • Request parameters: gen_ai.request.temperature, gen_ai.request.top_p, gen_ai.request.max_tokens
  • LLM completion finish reason: gen_ai.response.finish_reasons
  • Prompt caching indicators: gen_ai.prompt_caching and gen_ai.cache.type
  • gen_ai.system (however, it does set gen_ai.provider.name)

To add these fields:

  • With the Collector and genainormalizer processor, extend the sample's Collector configuration by adding statements to its transform processor. Use the filename supplied by that sample, such as otel-collector-config.yaml for OpenAI or otelcol-config.yaml for Amazon Bedrock. These mappings are candidates for upstream contribution to the genainormalizer processor.

  • With OpenPipeline, include optional field attributes server-side using Dynatrace Query Language (DQL) transforms. OpenPipeline can't reconstruct the full message history because DQL can't iterate over indexed per-message attributes at transform time. It uses input.value / output.value as an interim fallback when those provider-specific values are available.

Transforms can map data that instrumentation emits, but can't reconstruct source-specific data that an instrumentor doesn't emit or has discarded.

Observe in Dynatrace

Once your spans contain the required gen_ai.* attributes, you can observe them in Dynatrace.

  1. In Dynatrace, select Ctrl+K and search for AI Observability.
  2. Your request appears in the Explorer tab as a span with the model name, token usage, and message content.
  3. Open a span to inspect the full conversation and gen_ai.* attributes.

You can also view spans in Distributed Tracing Distributed Tracing.

Congratulations

Now that you've set up your AI app to send observability data to Dynatrace, you can:

  • Explore Distributed Tracing Distributed Tracing and the AI Observability AI Observability to visualize your AI workloads.
  • Check out the sample applications for more examples.
  • Point the OTLP endpoint to your Collector or ActiveGate endpoint for more advanced setups.
  • For troubleshooting, see the openinference examples README.
  • Follow the README for your selected OpenInference sample, including its instrumentor-specific normalization and message-reconstruction steps.
Related tags
AI Observability