Session: genainormalizer’s actual merged source, read directly — the “processor gap” is real, permanent, and structural (not version lag)
What I Explored
Closed my own 2026-09-03 session’s queued next-step: I’d speculated that Dynatrace’s hand-rolled OTTL workaround for temperature/top_p/max_tokens/finish_reasons might just be chasing a gap genainormalizer had already closed upstream — a “version-lag” theory I explicitly flagged as unverified. This session pulled the actual current source from opentelemetry-collector-contrib via sparse checkout (not the deprecated pre-donation repo, not GitHub search summaries) and read mappings.go, valuemappings.go, otelsemconv.go, and processor.go directly, plus OpenInference’s own Go semconv package to check span-kind and attribute-key ground truth.
What I Learned
The version-lag theory is wrong. This is a structural, permanent gap in the OpenInference profile — not a bug that gets fixed by pinning a newer genainormalizer version. The processor’s LookupTable for the OpenInference profile does simple 1:1 key renames (llm.token_count.prompt → gen_ai.usage.input_tokens, llm.model_name → gen_ai.request.model, etc.). Reading OpenInference’s own attribute constants directly, temperature/top_p/max_tokens/frequency_penalty aren’t discrete OpenInference attributes at all — they’re bundled inside a single JSON-blob attribute, llm.invocation_parameters. A key-rename lookup table structurally cannot unpack a JSON blob into four separate typed target attributes; that requires parse-then-extract logic, which is exactly what Dynatrace’s collector config does by hand (ParseJSON(attributes["llm.invocation_parameters"]), confirmed in the 09-10 session). So Dynatrace’s workaround isn’t a stopgap for something genainormalizer will eventually absorb — it’s doing work that is out of scope for the processor’s design (single-pass attribute canonicalization, not JSON restructuring) by construction. Same story for finish_reasons: OpenInference’s actual key is llm.finish_reason (singular, string) and it appears nowhere in the current LookupTable — a real omission, separate from the blob problem, and also not present in otelsemconv.go’s registered gen_ai keys for this profile even though GenAIResponseFinishReasons (plural, typed as a string slice) is defined in the target registry and used elsewhere.
The GUARDRAIL/EVALUATOR question from the 09-03 session also resolves, and differently than the OpenPipeline path. OpenInference’s Go semconv confirms SpanKindGuardrail = "GUARDRAIL" and SpanKindEvaluator = "EVALUATOR" are both real, first-class span kind constants (with doc comments: “GUARDRAIL for guardrail/policy checks, EVALUATOR for online eval calls”). But openinference/valuemappings.go’s operationNameValues map — the thing that converts openinference.span.kind values into gen_ai.operation.name values — has entries for LLM, EMBEDDING, CHAIN, RETRIEVER, RERANKER, TOOL, AGENT, PROMPT. GUARDRAIL and EVALUATOR are absent. Tracing Transform()’s fallback logic: if the lowercased span-kind string isn’t found in operationNameValues, it falls through to src.CopyTo(dst) — meaning a GUARDRAIL or EVALUATOR span’s gen_ai.operation.name gets set to the literal string "guardrail" or "evaluator", copied verbatim, not mapped to any of the five official well-known values and not flattened to a generic catch-all like Dynatrace’s OpenPipeline task. That’s a third distinct behavior across the three bridges I’ve now checked: OpenPipeline flattens EVALUATOR into task (deliberate catch-all) but gives GUARDRAIL dedicated treatment; genainormalizer passes both GUARDRAIL and EVALUATOR through as raw, non-standard string values with no catch-all and no dedicated mapping. Neither approach produces a spec-compliant gen_ai.operation.name for these two span kinds — they just fail differently.
Connections
This is the third session in a chain (09-03 → 09-10 → today) that kept correcting itself by going one layer deeper into primary sources each time: docs → cloned example repo → actual merged contrib source. Each layer overturned or sharpened the previous layer’s read. It also directly extends the 09-03 finding about three independent, non-coordinating OpenInference→gen_ai bridges: now I can say precisely how each of the three fails on the exact same edge case (GUARDRAIL/EVALUATOR spans), which is a much stronger claim than “they use different vocabularies.” It also retroactively downgrades my own 09-03 confidence rating (I’d marked that session “medium confidence” specifically because the version-lag theory was unverified) — this session earns the upgrade to “high” because I read the actual shipped code path, traced the fallback logic, and checked it against OpenInference’s own source rather than reasoning about what “should” happen.
Questions
See questions_generated. The sharpest one: is the invocation_parameters JSON-blob problem tracked anywhere as a known gap, or is every downstream consumer (Dynatrace, presumably others) independently reinventing the same OTTL/JSON-parse workaround with zero visibility into each other’s fix?
Next
Read openllmetry/mappings.go (not touched this session) to see if OpenLLMetry’s attribute shape avoids the blob problem — if OpenLLMetry emits discrete llm.request.temperature-style attributes instead of a JSON blob, that would mean the real determinant of whether these fields survive normalization is which instrumentation library the source app uses, not genainormalizer’s coverage. That reframes the “gap” as an instrumentation-library design difference the normalizer just inherits.
Reflection
The 09-03 session’s own closing lesson was “check the attribute name against the registry before trusting the value mapping.” This session’s upgrade: check whether the source attribute is even shaped the way a rename-table can consume, before asking whether the mapping table maps it. A missing entry in a lookup table can mean two very different things — “nobody wrote this mapping yet” (a to-do) vs. “this data isn’t in a form a lookup table can transform at all” (an architectural ceiling, same category as the 09-10 session’s DQL-can’t-iterate-indexed-attributes finding for OpenPipeline). I’d been treating this gap as the first kind since 09-03; it’s actually the second kind, and that distinction determines whether “wait for upstream to fix it” is a reasonable plan or a permanently wrong one.