Skip to main content

Genainormalizer Processor

Status Maintainers: @TylerHelmuth Source: opentelemetry-collector-contrib

Supported Telemetry

Traces

Overview

The GenAI Normalizer Processor rewrites attributes on spans emitted by non-OTel GenAI instrumentation libraries into the OTel GenAI Semantic Conventions.

Configuration

Built-in sources:

Any other name is a user-defined source: the entry’s mappings and value_mappings drive the normalization.

Top-level fields

FieldTypeDefaultDescription
sourceslist of sourcerequiredOrdered list of sources to normalize. At least one must be specified. Each span is processed by every source in the order given.

Source

Each entry in sources accepts the following fields:
FieldTypeDefaultDescription
namestringrequiredSource identifier. Built-in names (openinference, openllmetry) use pre-defined mapping tables. Any other name is a user-defined source. Names must be unique across sources.
remove_originalsboolfalseDelete source attributes after mapping.
overwriteboolfalseWhen true, overwrite the target attribute if it already exists. When false, skip the mapping.
mappingsmap[string]stringrequired for user-defined sources, rejected on built-insSource-attribute → target-attribute rename table. See User-defined sources.
value_mappingsmap[string]map[string]stringuser-defined sources onlyPer-target value-fold rules, keyed by post-rename target attribute. See User-defined sources.

Scope

Normalization is applied to:
  • Span attributes
The following are not modified:
  • Resource attributes
  • Scope attributes
  • Span event attributes
  • Span link attributes

Schema URL

When a mapping fires on a span, the enclosing ScopeSpans.schema_url is set to the OTel semantic-conventions version this processor targets (https://opentelemetry.io/schemas/1.40.0). An existing schema_url is overwritten. ResourceSpans.schema_url is never modified.

Type handling

After renaming, the processor enforces target attribute types against the OTel GenAI semantic conventions, derived from the typed constructor functions in go.opentelemetry.io/otel/semconv. For target keys with a typed primitive constructor in semconv (gen_ai.usage.input_tokens int, gen_ai.request.temperature float64, gen_ai.request.model string, gen_ai.response.finish_reasons []string, etc.), the processor coerces between compatible scalar types and drops the rename when coercion is unsafe:
  • string -> int: parsed via strconv.ParseInt; non-numeric strings drop.
  • string -> float64: parsed via strconv.ParseFloat; non-numeric strings drop.
  • string -> []string: wrapped into a single-element slice.
  • int / double / bool -> string: converted to canonical string form.
  • structured source (map / slice) -> primitive target: dropped (would lose information).
For target keys defined as any in the spec (gen_ai.input.messages, gen_ai.output.messages, gen_ai.tool.definitions, gen_ai.operation.name enum, etc.), the processor preserves whatever shape the source emitted. Backends that require a uniform type for these targets should pair this processor with the transformprocessor for OTTL-based shape normalization.

Examples

Default configuration:
processors:
  genainormalizer:
    sources:
      - name: openinference
Delete source attributes after mapping:
processors:
  genainormalizer:
    sources:
      - name: openinference
        remove_originals: true
Overwrite existing target attributes:
processors:
  genainormalizer:
    sources:
      - name: openinference
        remove_originals: true
        overwrite: true
Normalize both OpenInference and OpenLLMetry:
processors:
  genainormalizer:
    sources:
      - name: openinference
        remove_originals: true
      - name: openllmetry
        remove_originals: true
User-defined renames and value foldings (see User-defined sources):
processors:
  genainormalizer:
    sources:
      - name: my_vendor
        remove_originals: true
        mappings:
          my_vendor.model: gen_ai.request.model
          my_vendor.tokens.in: gen_ai.usage.input_tokens
        value_mappings:
          gen_ai.operation.name:
            chat_completion: chat
            tool_invoke: execute_tool

User-defined sources

Any name that is not a built-in (openinference, openllmetry) is a user-defined source. The entry’s mappings and value_mappings drive the normalization. User-defined sources reuse the same remove_originals, overwrite, and type-coercion semantics as the built-in sources.
FieldTypeDescription
mappingsmap[string]stringRequired. Source-attribute → target-attribute rename table. Must be non-empty.
value_mappingsmap[string]map[string]stringOptional. Outer key is the post-rename target attribute name; inner map folds source string values onto preferred target string values. Source-value lookups are exact-match. Non-string sources, missing rules, and unmatched source values pass through verbatim.
Validation rules:
  • mappings must be non-empty on any user-defined source.
  • mappings and value_mappings are rejected on built-in sources.
  • Each value_mappings outer key must appear as a target in mappings (catches unreachable rules at config time).
  • name must be unique across sources.
User-defined mappings landing on typed gen_ai.* targets get the same int/float/string/bool/[]string coercion as built-in mappings (see Type handling). User-defined mappings landing on non-gen_ai.* targets pass through verbatim. Future built-in sources. New built-in source names may be added in future releases. This is not treated as a breaking change. To avoid collisions, namespace user-defined names with a vendor or company prefix (e.g. custom.anthropic, acme.internal).

Performance

For user-defined sources, cost grows with the number of attributes on each span, not with the size of the mappings table. The processor walks every attribute on every span, and looking up a single attribute in mappings is constant time. Real-world spans carry tens to a few hundred attributes and process in microseconds. Spans with thousands of attributes still work, but know that per-span cost grows proportionally to the number of attributes in each span. See processor_benchmark_test.go for the benchmark suite. Run with go test -bench=. -benchmem.

Built-in mappings

openinference

Attribute renames:
Source attributeTarget attribute
llm.token_count.promptgen_ai.usage.input_tokens
llm.token_count.completiongen_ai.usage.output_tokens
llm.model_namegen_ai.request.model
llm.providergen_ai.provider.name
llm.input_messagesgen_ai.input.messages
llm.output_messagesgen_ai.output.messages
embedding.model_namegen_ai.request.model
tool.namegen_ai.tool.name
tool.descriptiongen_ai.tool.description
tool_call.function.argumentsgen_ai.tool.call.arguments
tool_call.idgen_ai.tool.call.id
reranker.model_namegen_ai.request.model
agent.namegen_ai.agent.name
session.idgen_ai.conversation.id
openinference.span.kindgen_ai.operation.name (with value mapping, see below)
See internal/openinference/mappings.go for the canonical map. Source reference: OpenInference semantic conventions.

openllmetry

Attribute renames:
Source attributeTarget attributeNotes
llm.usage.prompt_tokensgen_ai.usage.input_tokens
llm.usage.completion_tokensgen_ai.usage.output_tokens
llm.request.modelgen_ai.request.model
llm.response.modelgen_ai.response.model
llm.request.max_tokensgen_ai.request.max_tokens
llm.request.temperaturegen_ai.request.temperature
llm.request.top_pgen_ai.request.top_p
llm.top_kgen_ai.request.top_k
llm.frequency_penaltygen_ai.request.frequency_penalty
llm.presence_penaltygen_ai.request.presence_penalty
llm.chat.stop_sequencesgen_ai.request.stop_sequences
llm.request.functionsgen_ai.tool.definitionssource-shape preserved (Type handling)
llm.response.finish_reasongen_ai.response.finish_reasonsstring wrapped into a single-element string[]
llm.response.stop_reasongen_ai.response.finish_reasonsstring wrapped into a single-element string[]
llm.request.typegen_ai.operation.namewith value mapping, see below
traceloop.span.kindgen_ai.operation.namewith value mapping, see below
traceloop.entity.namegen_ai.agent.name
traceloop.entity.inputgen_ai.input.messagessource-shape preserved (Type handling)
traceloop.entity.outputgen_ai.output.messagessource-shape preserved (Type handling)
Coverage: this table covers the most common OpenLLMetry attributes. OpenLLMetry attributes not listed pass through unchanged. Open an issue if a missing attribute is blocking your migration. OpenLLMetry instrumentation typically emits one of each collision pair (llm.response.finish_reason xor llm.response.stop_reason; llm.request.type xor traceloop.span.kind). When both attributes in a pair are present on a span, the resolved value at the target key is undefined. See internal/openllmetry/mappings.go for the canonical map. Source reference: OpenLLMetry semantic conventions.

Value transformations

When a built-in mapping lands on gen_ai.operation.name, the string value is normalized to the OTel GenAI enum. Built-in lookups are case-insensitive; user-defined value_mappings are exact-match.
SourceSource attributeSource valueTarget value
openinferenceopeninference.span.kindLLMchat
openinferenceopeninference.span.kindEMBEDDINGembeddings
openinferenceopeninference.span.kindCHAINinvoke_agent
openinferenceopeninference.span.kindRETRIEVERretrieval
openinferenceopeninference.span.kindRERANKERretrieval
openinferenceopeninference.span.kindTOOLexecute_tool
openinferenceopeninference.span.kindAGENTinvoke_agent
openinferenceopeninference.span.kindPROMPTtext_completion
openllmetrytraceloop.span.kindworkflowinvoke_workflow
openllmetrytraceloop.span.kindtaskinvoke_agent
openllmetrytraceloop.span.kindagentinvoke_agent
openllmetrytraceloop.span.kindtoolexecute_tool
openllmetryllm.request.typecompletiontext_completion
openllmetryllm.request.typechatchat
openllmetryllm.request.typererankretrieval
openllmetryllm.request.typeembeddingembeddings
When a mapped attribute lands on gen_ai.response.finish_reasons with a string source value, the value is wrapped into a single-element string[] to match the OTel GenAI spec type. Target reference: OTel GenAI operation names.

Relationship to other processors

The schemaprocessor translates between OTel semantic convention versions using schema_url and the OTel schema file format. Source conventions normalized by this processor do not set schema_url and do not publish OTel schema files, so schemaprocessor cannot be used for this translation today. The transformprocessor can rewrite attributes via OTTL but requires users to author and maintain the full mapping set themselves. This processor ships the mappings built-in. For pure value-mutation without renames, prefer transformprocessor.

Configuration

Example Configuration

# Full config exercising the supported source and its per-source fields.
gen_ai_normalizer:
  sources:
    - name: openinference
      remove_originals: true
      overwrite: false

# Sources omitted entirely: validation error (at least one source required).
gen_ai_normalizer/empty:

# Sources with only openinference defaults.
gen_ai_normalizer/openinference_only:
  sources:
    - name: openinference

# Sources with only openllmetry defaults.
gen_ai_normalizer/openllmetry_only:
  sources:
    - name: openllmetry

# Both sources, applied in listed order.
gen_ai_normalizer/openinference_and_openllmetry:
  sources:
    - name: openinference
      remove_originals: true
    - name: openllmetry
      remove_originals: true

# Explicit empty sources list: validation error.
gen_ai_normalizer/empty_sources:
  sources: []

# Same source listed twice: validation error.
gen_ai_normalizer/duplicate_source:
  sources:
    - name: openinference
    - name: openinference

# Single user-defined source with two mappings.
gen_ai_normalizer/user_defined_only:
  sources:
    - name: my_vendor
      remove_originals: true
      mappings:
        my_vendor.model: gen_ai.request.model
        my_vendor.tokens.in: gen_ai.usage.input_tokens

# Built-in source followed by a user-defined source.
gen_ai_normalizer/user_defined_with_builtin:
  sources:
    - name: openinference
      remove_originals: true
    - name: my_vendor
      remove_originals: true
      mappings:
        my_vendor.model: gen_ai.request.model

# Two distinct user-defined sources in one config; built-in sources still
# reject duplicates.
gen_ai_normalizer/multiple_user_defined:
  sources:
    - name: vendor_a
      remove_originals: true
      mappings:
        vendor_a.model: gen_ai.request.model
    - name: vendor_b
      remove_originals: true
      overwrite: true
      mappings:
        vendor_b.model: gen_ai.request.model

# User-defined source with empty mappings: validation error.
gen_ai_normalizer/user_defined_empty_mappings:
  sources:
    - name: my_vendor

# Built-in source with mappings set: validation error
# (mappings only valid on user-defined sources).
gen_ai_normalizer/openinference_with_mappings:
  sources:
    - name: openinference
      mappings:
        foo: gen_ai.request.model

# User-defined source with a value_mappings key that is not a target
# in mappings: validation error (the rule would never fire).
gen_ai_normalizer/user_defined_unreachable_value_mapping:
  sources:
    - name: my_vendor
      mappings:
        my_vendor.model: gen_ai.request.model
      value_mappings:
        gen_ai.operation.name:
          chat_completion: chat

# User-defined source with mappings + value_mappings folding onto an OTel
# GenAI enum target.
gen_ai_normalizer/user_defined_with_value_mappings:
  sources:
    - name: my_vendor
      remove_originals: true
      mappings:
        my_vendor.op: gen_ai.operation.name
      value_mappings:
        gen_ai.operation.name:
          chat_completion: chat
          tool_invoke: execute_tool

Last generated: 2026-06-01