-
Notifications
You must be signed in to change notification settings - Fork 23
fix(otel): share one AnyValue decoder across all OTLP paths #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
krisztianfekete
merged 5 commits into
agentevals-dev:main
from
Leroyyyyyyyyy:fix/shared-anyvalue-decoder
Aug 31, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c65f82d
fix(otel): share one AnyValue decoder across all OTLP paths
Leroyyyyyyyyy 9efc842
fix(otel): decode AnyValue when promoting span event attributes
Leroyyyyyyyyy 3029ef7
fix(otel): read session_name and eval_set_id as strings only
Leroyyyyyyyyy 450fe72
fix(otel): narrow spec-string attributes once at the decode boundary
Leroyyyyyyyyy 471301c
fix(otel): decode containers on an opt-in allowlist instead of a deny…
Leroyyyyyyyyy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| """Shared decoder for the OTLP ``AnyValue`` union. | ||
|
|
||
| OTLP encodes every attribute value, log body and nested element as an | ||
| ``AnyValue``: a one-of wrapper such as ``{"stringValue": "chat"}`` or | ||
| ``{"arrayValue": {"values": [...]}}``. The protobuf receiver (via | ||
| ``MessageToDict``) and OTLP/JSON payloads deliver that same dict shape, so | ||
| every consumer needs identical decoding rules. | ||
|
|
||
| This module depends only on the standard library and ``trace_attrs`` (a leaf | ||
| constants module), so ``extraction``, ``loader.otlp`` and ``api.otlp_processing`` | ||
| can all use it without creating an import cycle. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| from typing import Any | ||
|
|
||
| from .trace_attrs import SPEC_CONTAINER_ATTRS | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| ANY_VALUE_FIELDS = ( | ||
| "stringValue", | ||
| "intValue", | ||
| "doubleValue", | ||
| "boolValue", | ||
| "kvlistValue", | ||
| "arrayValue", | ||
| "bytesValue", | ||
| ) | ||
|
|
||
|
|
||
| def decode_any_value(value_obj: dict) -> Any: | ||
| """Recursively decode an OTLP ``AnyValue`` to a native Python value. | ||
|
|
||
| Handles the full union: stringValue, intValue (OTLP sends it as a | ||
| string), doubleValue, boolValue, kvlistValue (→ dict), arrayValue | ||
| (→ list), bytesValue. | ||
|
|
||
| ``bytesValue`` is returned unchanged. ``MessageToDict`` base64-encodes | ||
| protobuf bytes fields and OTLP/JSON does the same, so callers already | ||
| receive a str; decoding it here would change the value they see today. | ||
|
|
||
| A value carrying none of the union fields is returned as-is. | ||
| """ | ||
| if "stringValue" in value_obj: | ||
| return value_obj["stringValue"] | ||
| if "intValue" in value_obj: | ||
| return int(value_obj["intValue"]) | ||
| if "doubleValue" in value_obj: | ||
| return float(value_obj["doubleValue"]) | ||
| if "boolValue" in value_obj: | ||
| return value_obj["boolValue"] | ||
| if "kvlistValue" in value_obj: | ||
| kv = value_obj["kvlistValue"] | ||
| return {item.get("key", ""): decode_any_value(item.get("value", {})) for item in kv.get("values", [])} | ||
| if "arrayValue" in value_obj: | ||
| arr = value_obj["arrayValue"] | ||
| return [decode_any_value(v) for v in arr.get("values", [])] | ||
| if "bytesValue" in value_obj: | ||
| return value_obj["bytesValue"] | ||
| return value_obj | ||
|
|
||
|
|
||
| def is_any_value(value_obj: dict) -> bool: | ||
| """Return True when *value_obj* carries one of the ``AnyValue`` fields.""" | ||
| for field in ANY_VALUE_FIELDS: | ||
| if field in value_obj: | ||
| return True | ||
| return False | ||
|
|
||
|
|
||
| def decode_attribute(key: str, value_obj: dict) -> tuple[bool, Any]: | ||
| """Decode one attribute, applying the container allowlist. | ||
|
|
||
| Returns ``(keep, value)``. Scalars are always kept. A list or dict is kept | ||
| only when *key* is in :data:`SPEC_CONTAINER_ATTRS`; otherwise it is dropped, | ||
| which is what ``extraction.py`` did with containers before this decoder was | ||
| shared. | ||
|
|
||
| Dropping rather than serialising is deliberate: JSON-dumping the value would | ||
| put a blob back into user-visible output, which is the symptom #173 is | ||
| about. What the default *should* be is tracked in #208. | ||
| """ | ||
| value = decode_any_value(value_obj) | ||
| if isinstance(value, (list, dict)) and key not in SPEC_CONTAINER_ATTRS: | ||
| logger.warning( | ||
| "Dropping container value for %s (got %s); only spec container attributes are kept", | ||
| key, | ||
| type(value).__name__, | ||
| ) | ||
| return False, None | ||
| return True, value | ||
|
|
||
|
|
||
| def decode_attributes(attrs_list: list[dict]) -> dict[str, Any]: | ||
| """Decode an OTLP attributes array to a flat ``{key: value}`` dict. | ||
|
|
||
| Entries whose value carries no ``AnyValue`` field are skipped, matching the | ||
| behaviour every call site had before they shared this decoder. | ||
|
|
||
| Container values survive only for the keys in | ||
| :data:`~agentevals.trace_attrs.SPEC_CONTAINER_ATTRS`. Keeping the allowlist | ||
| here rather than narrowing per consumer means an attribute nobody has | ||
| thought about cannot become an unhashable dict key downstream - there is | ||
| nothing to remember, because it was never widened in the first place. | ||
| """ | ||
| result: dict[str, Any] = {} | ||
| for attr in attrs_list: | ||
| value_obj = attr.get("value", {}) | ||
| if not is_any_value(value_obj): | ||
| continue | ||
| key = attr.get("key", "") | ||
| keep, value = decode_attribute(key, value_obj) | ||
| if keep: | ||
| result[key] = value | ||
| return result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the widening.
span.tagsnow carries lists into every consumer, and the same unhashable crash assession_namesurvives at other places as well.