Genainormalizer Processor
Supported Telemetry
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:
openinference— OpenInference instrumentationopenllmetry— OpenLLMetry (Traceloop) instrumentation
name is a user-defined source: the entry’s mappings and value_mappings drive the normalization.
Top-level fields
| Field | Type | Default | Description |
|---|---|---|---|
sources | list of source | required | Ordered 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 insources accepts the following fields:
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Source 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_originals | bool | false | Delete source attributes after mapping. |
overwrite | bool | false | When true, overwrite the target attribute if it already exists. When false, skip the mapping. |
mappings | map[string]string | required for user-defined sources, rejected on built-ins | Source-attribute → target-attribute rename table. See User-defined sources. |
value_mappings | map[string]map[string]string | user-defined sources only | Per-target value-fold rules, keyed by post-rename target attribute. See User-defined sources. |
Scope
Normalization is applied to:- Span attributes
- Resource attributes
- Scope attributes
- Span event attributes
- Span link attributes
Schema URL
When a mapping fires on a span, the enclosingScopeSpans.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 ingo.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).
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:User-defined sources
Anyname 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.
| Field | Type | Description |
|---|---|---|
mappings | map[string]string | Required. Source-attribute → target-attribute rename table. Must be non-empty. |
value_mappings | map[string]map[string]string | Optional. 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. |
mappingsmust be non-empty on any user-defined source.mappingsandvalue_mappingsare rejected on built-in sources.- Each
value_mappingsouter key must appear as a target inmappings(catches unreachable rules at config time). namemust be unique acrosssources.
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 themappings 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 attribute | Target attribute |
|---|---|
llm.token_count.prompt | gen_ai.usage.input_tokens |
llm.token_count.completion | gen_ai.usage.output_tokens |
llm.model_name | gen_ai.request.model |
llm.provider | gen_ai.provider.name |
llm.input_messages | gen_ai.input.messages |
llm.output_messages | gen_ai.output.messages |
embedding.model_name | gen_ai.request.model |
tool.name | gen_ai.tool.name |
tool.description | gen_ai.tool.description |
tool_call.function.arguments | gen_ai.tool.call.arguments |
tool_call.id | gen_ai.tool.call.id |
reranker.model_name | gen_ai.request.model |
agent.name | gen_ai.agent.name |
session.id | gen_ai.conversation.id |
openinference.span.kind | gen_ai.operation.name (with value mapping, see below) |
internal/openinference/mappings.go for the canonical map. Source reference: OpenInference semantic conventions.
openllmetry
Attribute renames:
| Source attribute | Target attribute | Notes |
|---|---|---|
llm.usage.prompt_tokens | gen_ai.usage.input_tokens | |
llm.usage.completion_tokens | gen_ai.usage.output_tokens | |
llm.request.model | gen_ai.request.model | |
llm.response.model | gen_ai.response.model | |
llm.request.max_tokens | gen_ai.request.max_tokens | |
llm.request.temperature | gen_ai.request.temperature | |
llm.request.top_p | gen_ai.request.top_p | |
llm.top_k | gen_ai.request.top_k | |
llm.frequency_penalty | gen_ai.request.frequency_penalty | |
llm.presence_penalty | gen_ai.request.presence_penalty | |
llm.chat.stop_sequences | gen_ai.request.stop_sequences | |
llm.request.functions | gen_ai.tool.definitions | source-shape preserved (Type handling) |
llm.response.finish_reason | gen_ai.response.finish_reasons | string wrapped into a single-element string[] |
llm.response.stop_reason | gen_ai.response.finish_reasons | string wrapped into a single-element string[] |
llm.request.type | gen_ai.operation.name | with value mapping, see below |
traceloop.span.kind | gen_ai.operation.name | with value mapping, see below |
traceloop.entity.name | gen_ai.agent.name | |
traceloop.entity.input | gen_ai.input.messages | source-shape preserved (Type handling) |
traceloop.entity.output | gen_ai.output.messages | source-shape preserved (Type handling) |
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 ongen_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.
| Source | Source attribute | Source value | Target value |
|---|---|---|---|
openinference | openinference.span.kind | LLM | chat |
openinference | openinference.span.kind | EMBEDDING | embeddings |
openinference | openinference.span.kind | CHAIN | invoke_agent |
openinference | openinference.span.kind | RETRIEVER | retrieval |
openinference | openinference.span.kind | RERANKER | retrieval |
openinference | openinference.span.kind | TOOL | execute_tool |
openinference | openinference.span.kind | AGENT | invoke_agent |
openinference | openinference.span.kind | PROMPT | text_completion |
openllmetry | traceloop.span.kind | workflow | invoke_workflow |
openllmetry | traceloop.span.kind | task | invoke_agent |
openllmetry | traceloop.span.kind | agent | invoke_agent |
openllmetry | traceloop.span.kind | tool | execute_tool |
openllmetry | llm.request.type | completion | text_completion |
openllmetry | llm.request.type | chat | chat |
openllmetry | llm.request.type | rerank | retrieval |
openllmetry | llm.request.type | embedding | embeddings |
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
Theschemaprocessor 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
Last generated: 2026-06-01