From d07934dd3f9877a901f2e8888775e702bcd2d947 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 4 Sep 2025 16:13:18 +0200 Subject: [PATCH 1/4] Format span attributes in ai integrations so that the UI frontend can format them correctly --- sentry_sdk/ai/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/ai/utils.py b/sentry_sdk/ai/utils.py index cf52cba6e8..6b9fe2bcf3 100644 --- a/sentry_sdk/ai/utils.py +++ b/sentry_sdk/ai/utils.py @@ -1,9 +1,11 @@ +import json + from typing import TYPE_CHECKING if TYPE_CHECKING: from typing import Any + from sentry_sdk.tracing import Span -from sentry_sdk.tracing import Span from sentry_sdk.utils import logger @@ -33,4 +35,4 @@ def set_data_normalized(span, key, value, unpack=True): if isinstance(normalized, (int, float, bool, str)): span.set_data(key, normalized) else: - span.set_data(key, str(normalized)) + span.set_data(key, json.dumps(normalized)) From 5d687449004c1941824166c22da78fb95bb42349 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 4 Sep 2025 16:19:39 +0200 Subject: [PATCH 2/4] updated tests --- tests/integrations/cohere/test_cohere.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integrations/cohere/test_cohere.py b/tests/integrations/cohere/test_cohere.py index b8b6067625..ee876172d1 100644 --- a/tests/integrations/cohere/test_cohere.py +++ b/tests/integrations/cohere/test_cohere.py @@ -58,11 +58,11 @@ def test_nonstreaming_chat( if send_default_pii and include_prompts: assert ( - "{'role': 'system', 'content': 'some context'}" + '{"role": "system", "content": "some context"}' in span["data"][SPANDATA.AI_INPUT_MESSAGES] ) assert ( - "{'role': 'user', 'content': 'hello'}" + '{"role": "user", "content": "hello"}' in span["data"][SPANDATA.AI_INPUT_MESSAGES] ) 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 if send_default_pii and include_prompts: assert ( - "{'role': 'system', 'content': 'some context'}" + '{"role": "system", "content": "some context"}' in span["data"][SPANDATA.AI_INPUT_MESSAGES] ) assert ( - "{'role': 'user', 'content': 'hello'}" + '{"role": "user", "content": "hello"}' in span["data"][SPANDATA.AI_INPUT_MESSAGES] ) assert "the model response" in span["data"][SPANDATA.AI_RESPONSES] From 5b3c754fcde3d04b8240f786c42d56340e7ce86d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 4 Sep 2025 17:01:08 +0200 Subject: [PATCH 3/4] Make _normalize_data deal with model_dump() returning functions or classes --- sentry_sdk/ai/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/ai/utils.py b/sentry_sdk/ai/utils.py index 6b9fe2bcf3..dddbd6c09e 100644 --- a/sentry_sdk/ai/utils.py +++ b/sentry_sdk/ai/utils.py @@ -11,14 +11,13 @@ def _normalize_data(data, unpack=True): # type: (Any, bool) -> Any - # convert pydantic data (e.g. OpenAI v1+) to json compatible format if hasattr(data, "model_dump"): try: - return data.model_dump() + return _normalize_data(data.model_dump(), unpack=unpack) except Exception as e: logger.warning("Could not convert pydantic data to JSON: %s", e) - return data + return data if isinstance(data, (int, float, bool, str)) else str(data) if isinstance(data, list): if unpack and len(data) == 1: return _normalize_data(data[0], unpack=unpack) # remove empty dimensions @@ -26,7 +25,7 @@ def _normalize_data(data, unpack=True): if isinstance(data, dict): return {k: _normalize_data(v, unpack=unpack) for (k, v) in data.items()} - return data + return data if isinstance(data, (int, float, bool, str)) else str(data) def set_data_normalized(span, key, value, unpack=True): From 46cf4cd2a7c834f379efb6576a5b9cc9cbf229a2 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 4 Sep 2025 17:01:43 +0200 Subject: [PATCH 4/4] whitespace --- sentry_sdk/ai/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry_sdk/ai/utils.py b/sentry_sdk/ai/utils.py index dddbd6c09e..2dc0de4ef3 100644 --- a/sentry_sdk/ai/utils.py +++ b/sentry_sdk/ai/utils.py @@ -18,10 +18,12 @@ def _normalize_data(data, unpack=True): except Exception as e: logger.warning("Could not convert pydantic data to JSON: %s", e) return data if isinstance(data, (int, float, bool, str)) else str(data) + if isinstance(data, list): if unpack and len(data) == 1: return _normalize_data(data[0], unpack=unpack) # remove empty dimensions return list(_normalize_data(x, unpack=unpack) for x in data) + if isinstance(data, dict): return {k: _normalize_data(v, unpack=unpack) for (k, v) in data.items()}