OpenInference is an open standard for AI observability developed by Arize AI.
It defines its own semantic conventions (llm.model_name, llm.token_count.*, and others) which differ from the gen_ai.* attributes that Dynatrace AI Observability natively understands.
This guide shows you how to instrument your AI application with OpenInference. Then, you'll normalize the OpenInference attributes to the gen_ai.* format using one of two approaches: an OTel Collector distribution with the genainormalizer processor, or Dynatrace OpenPipeline.
This getting started guide is for:
By following this guide, you'll learn:
gen_ai.* using an OTel Collector distribution with the genainormalizer processor, or Dynatrace OpenPipeline.
AI Observability.To follow this guide, you need:
A running AI application, or use one of the OpenInference sample apps:
Dynatrace SaaS with a Dynatrace Platform Subscription (DPS) license that has Traces powered by Grail enabled.
OTLP ingestion enabled, see OpenTelemetry and Dynatrace.
A Dynatrace API token with the openTelemetryTrace.ingest scope, see Dynatrace API - Tokens and authentication.
An OpenAI-compatible API key and endpoint.
For the Collector option: Docker, and an OTel Collector distribution that bundles the genainormalizer processor. Either of the following works:
It's helpful to have some basic knowledge of:
OpenInference captures and transmits AI model or agent KPIs as OpenTelemetry spans but uses its own semantic conventions rather than the gen_ai.* standard.
Because Dynatrace AI Observability requires gen_ai.* attributes, you need an intermediate normalization step.
There are two different ways to normalize attributes: An OTel Collector with the genainormalizer processor, or OpenPipeline.
For more information, see Normalize your OpenInference attributes.
This section describes how to start ingesting OpenInference attributes into Dynatrace, which can then be normalized into gen_ai.* attributes.
Ctrl+K and search for Access tokens.openTelemetryTrace.ingest scope.For more information, see Tokens and permissions.
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.comDT_API_TOKEN=dt0c01.<token-value>OPENAI_API_KEY=<your-openai-api-key>OPENAI_API_BASE=https://your-endpoint.openai.azure.com/MODEL=gpt-5.5OPENAI_API_VERSION=2024-07-01-preview
DT_ENDPOINT is your base environment URL, not the /api/v2/otlp path.
For more information, see Dynatrace OTLP endpoints.
pip install -r requirements.txt
The key packages are openinference-instrumentation-openai and opentelemetry-sdk.
Use the OpenInference auto-instrumentation for OpenAI. The instrumentation patches the OpenAI client and records spans with OpenInference semantic conventions automatically. The other OpenInference instrumentors, such as those for Amazon Bedrock and LangGraph, follow the same pattern; only the instrumentor package differs.
from openinference.instrumentation.openai import OpenAIInstrumentorfrom opentelemetry.sdk.resources import Resource, SERVICE_NAMEfrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessorfrom opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterimport openaiimport os# Configure the tracer providerservice_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 clientOpenAIInstrumentor().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)
Once you've set up your app to export data to Dynatrace, you can normalize your attributes to the gen_ai.* format in one of two ways, see the sections below.
OTel Collector + genainormalizer | OpenPipeline | |
|---|---|---|
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 | Uses interim fallback from |
Good for | Full control over the pipeline; complete message history without fallbacks | Simpler operations; no Collector to manage |
Collector image | Any distribution bundling | Not applicable |
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:
ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector:0.53.1ghcr.io/observiq/bindplane-agent:1.104.0The Collector:
4318 for incoming OTLP/HTTP spans from the app.
The app only sends spans to http://localhost:4318 and doesn't need Dynatrace credentials.genainormalizer processor (source: openinference, remove_originals: true) to map OpenInference attributes to gen_ai.* and reconstruct message history.
The genainormalizer processor reconstructs the full gen_ai.input.messages / gen_ai.output.messages from indexed per-message attributes.transform/response_model processor to mirror gen_ai.request.model to gen_ai.response.model.DT_ENDPOINT and DT_API_TOKEN. It forwards processed spans to $DT_ENDPOINT/api/v2/otlp authenticated with the API token.The following steps describe how to normalize attributes with the Collector.
Run your chosen Collector image with the otel-collector-config.yaml from the sample repository.
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 .envCOLLECTOR_IMAGE=ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector:0.53.1docker 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.comset DT_API_TOKEN=dt0c01.*****set COLLECTOR_IMAGE=ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector:0.53.1docker 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
Point the app to the local Collector.
source .envOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318OTEL_EXPORTER_OTLP_HEADERS=""python3 app.py
Useful commands:
# Tail Collector logsdocker logs -f otel-collector# Stop the Collectordocker stop otel-collector && docker rm otel-collector
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.
This is a one-time setup per environment.
In Dynatrace, select Ctrl+K and search for OpenPipeline.
Select Spans.
Select Add pipeline and name it openinference-ai-spans.
Add processors that match the definitions in the sample repository's openpipeline-openinference.yaml file.
Go to the Routing tab and add an entry.
isNotNull(openinference.span.kind)openinference-ai-spansUse 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.
Point the app directly to the Dynatrace OTLP endpoint. OpenPipeline intercepts and transforms spans before Dynatrace stores them.
source .envOTEL_EXPORTER_OTLP_ENDPOINT=$DT_ENDPOINT/api/v2/otlp \OTEL_EXPORTER_OTLP_HEADERS="Authorization=Api-Token $DT_API_TOKEN" \python3 app.py
The table below shows how the genainormalizer processor maps OpenInference attributes to the gen_ai.* format. For differences with OpenPipeline, see the Notes column.
| Source attribute | gen_ai.* attribute | Notes |
|---|---|---|
|
|
|
|
| LLM spans |
|
| Embedding spans |
|
| Reranker spans |
|
| Mirrored; OpenInference has no separate response model field |
|
| If |
|
|
|
— |
| Set to |
— |
| Hardcoded |
|
| |
|
| |
|
|
Guard prevents false positives (Azure emits |
|
| |
|
| |
|
| |
|
| Renamed directly; source attribute removed |
|
| Only when |
|
| |
|
| |
|
| |
|
|
|
|
| |
|
| Reconstructed into full message history JSON including roles, content, and tool calls. Source attributes removed. (OpenPipeline uses interim fallback from |
|
| Reconstructed into full message history JSON including roles, content, and tool calls. Source attributes removed. (OpenPipeline uses interim fallback from |
— |
| Hardcoded |
The genainormalizer processor provides full message reconstruction and complete attribute mapping. However, it does not yet map certain optional fields:
gen_ai.request.temperature, gen_ai.request.top_p, gen_ai.request.max_tokensgen_ai.response.finish_reasonsgen_ai.prompt_caching and gen_ai.cache.typegen_ai.system (however, it does set gen_ai.provider.name)To add these fields:
With the Collector and genainormalizer processor: extend the Collector config by adding statements to the transform processor in otel-collector-config.yaml. These mappings are candidates for upstream contribution to the genainormalizer processor.
OpenPipeline includes these optional fields attributes server-side using DQL transforms, but cannot reconstruct the full message history because DQL cannot iterate over indexed per-message attributes at transform time. Instead, it copies the serialized conversation from input.value / output.value as a fallback.
Once you've normalized the OpenInference attributes to the gen_ai.* format, you can observe your spans in Dynatrace.
Ctrl+K and search for AI Observability.gen_ai.* attributes.You can also view spans in Distributed Tracing.
Now that you've set up your AI app to send observability data to Dynatrace, you can:
Explore Distributed Tracing and the
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.
Apply the same normalization to the other OpenInference examples, each of which ships its own Collector configuration and README: