Skip to content

Commit b50f7e4

Browse files
authored
Format span attributes in AI integrations (#4762)
The AI Agents integrations render stringified json-like data in a nice way (make the sub nodes of the data structure collapsible) In Javascript it comes down to having double quotes in a string: - Good: `'{"role": "system", "content": "some context"}'` - Bad: `"{'role': 'system', 'content': 'some context'}"` Also pydantics `model_dump()` sometimes returns `function` or `class` objects that can not be json serialized so I updated `_normalize_data()` to make sure everything is converted to a primitive data type, always.
1 parent 9711b3b commit b50f7e4

File tree

2 files changed

+13
-10
lines changed

2 files changed

+13
-10
lines changed

sentry_sdk/ai/utils.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1+
import json
2+
13
from typing import TYPE_CHECKING
24

35
if TYPE_CHECKING:
46
from typing import Any
7+
from sentry_sdk.tracing import Span
58

6-
from sentry_sdk.tracing import Span
79
from sentry_sdk.utils import logger
810

911

1012
def _normalize_data(data, unpack=True):
1113
# type: (Any, bool) -> Any
12-
1314
# convert pydantic data (e.g. OpenAI v1+) to json compatible format
1415
if hasattr(data, "model_dump"):
1516
try:
16-
return data.model_dump()
17+
return _normalize_data(data.model_dump(), unpack=unpack)
1718
except Exception as e:
1819
logger.warning("Could not convert pydantic data to JSON: %s", e)
19-
return data
20+
return data if isinstance(data, (int, float, bool, str)) else str(data)
21+
2022
if isinstance(data, list):
2123
if unpack and len(data) == 1:
2224
return _normalize_data(data[0], unpack=unpack) # remove empty dimensions
2325
return list(_normalize_data(x, unpack=unpack) for x in data)
26+
2427
if isinstance(data, dict):
2528
return {k: _normalize_data(v, unpack=unpack) for (k, v) in data.items()}
2629

27-
return data
30+
return data if isinstance(data, (int, float, bool, str)) else str(data)
2831

2932

3033
def set_data_normalized(span, key, value, unpack=True):
@@ -33,4 +36,4 @@ def set_data_normalized(span, key, value, unpack=True):
3336
if isinstance(normalized, (int, float, bool, str)):
3437
span.set_data(key, normalized)
3538
else:
36-
span.set_data(key, str(normalized))
39+
span.set_data(key, json.dumps(normalized))

tests/integrations/cohere/test_cohere.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ def test_nonstreaming_chat(
5858

5959
if send_default_pii and include_prompts:
6060
assert (
61-
"{'role': 'system', 'content': 'some context'}"
61+
'{"role": "system", "content": "some context"}'
6262
in span["data"][SPANDATA.AI_INPUT_MESSAGES]
6363
)
6464
assert (
65-
"{'role': 'user', 'content': 'hello'}"
65+
'{"role": "user", "content": "hello"}'
6666
in span["data"][SPANDATA.AI_INPUT_MESSAGES]
6767
)
6868
assert "the model response" in span["data"][SPANDATA.AI_RESPONSES]
@@ -135,11 +135,11 @@ def test_streaming_chat(sentry_init, capture_events, send_default_pii, include_p
135135

136136
if send_default_pii and include_prompts:
137137
assert (
138-
"{'role': 'system', 'content': 'some context'}"
138+
'{"role": "system", "content": "some context"}'
139139
in span["data"][SPANDATA.AI_INPUT_MESSAGES]
140140
)
141141
assert (
142-
"{'role': 'user', 'content': 'hello'}"
142+
'{"role": "user", "content": "hello"}'
143143
in span["data"][SPANDATA.AI_INPUT_MESSAGES]
144144
)
145145
assert "the model response" in span["data"][SPANDATA.AI_RESPONSES]

0 commit comments

Comments
 (0)