From 59fead19e34e0cd925db8aceb1877b17fe5c39ff Mon Sep 17 00:00:00 2001 From: Simon Hellmayr Date: Thu, 30 Oct 2025 16:12:05 +0100 Subject: [PATCH 1/3] fix(ai): truncate messages in openai agents --- .../openai_agents/spans/invoke_agent.py | 16 +++-- .../integrations/openai_agents/utils.py | 41 +++++++---- .../openai_agents/test_openai_agents.py | 70 +++++++++++++++---- 3 files changed, 95 insertions(+), 32 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index 2a9c5ebe66..18217daa6f 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -3,6 +3,7 @@ get_start_span_function, set_data_normalized, normalize_message_roles, + truncate_and_annotate_messages, ) from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.scope import should_send_default_pii @@ -61,12 +62,17 @@ def invoke_agent_span(context, agent, kwargs): if len(messages) > 0: normalized_messages = normalize_message_roles(messages) - set_data_normalized( - span, - SPANDATA.GEN_AI_REQUEST_MESSAGES, - normalized_messages, - unpack=False, + scope = sentry_sdk.get_current_scope() + messages_data = truncate_and_annotate_messages( + normalized_messages, span, scope ) + if messages_data is not None: + set_data_normalized( + span, + SPANDATA.GEN_AI_REQUEST_MESSAGES, + messages_data, + unpack=False, + ) _set_agent_data(span, agent) diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index 125ff1175b..792f8df841 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -4,6 +4,7 @@ normalize_message_roles, set_data_normalized, normalize_message_role, + truncate_and_annotate_messages, ) from sentry_sdk.consts import SPANDATA, SPANSTATUS, OP from sentry_sdk.integrations import DidNotEnable @@ -111,36 +112,50 @@ def _set_input_data(span, get_response_kwargs): ) for message in get_response_kwargs.get("input", []): - if "role" in message: - normalized_role = normalize_message_role(message.get("role")) + import json + + serialized_str = safe_serialize(message) + try: + serialized_message = json.loads(serialized_str) + except (json.JSONDecodeError, TypeError): + continue + + if "role" in serialized_message: + normalized_role = normalize_message_role(serialized_message.get("role")) request_messages.append( { "role": normalized_role, - "content": [{"type": "text", "text": message.get("content")}], + "content": [ + {"type": "text", "text": serialized_message.get("content")} + ], } ) else: - if message.get("type") == "function_call": + if serialized_message.get("type") == "function_call": request_messages.append( { "role": GEN_AI_ALLOWED_MESSAGE_ROLES.ASSISTANT, - "content": [message], + "content": [serialized_message], } ) - elif message.get("type") == "function_call_output": + elif serialized_message.get("type") == "function_call_output": request_messages.append( { "role": GEN_AI_ALLOWED_MESSAGE_ROLES.TOOL, - "content": [message], + "content": [serialized_message], } ) - set_data_normalized( - span, - SPANDATA.GEN_AI_REQUEST_MESSAGES, - normalize_message_roles(request_messages), - unpack=False, - ) + normalized_messages = normalize_message_roles(request_messages) + scope = sentry_sdk.get_current_scope() + messages_data = truncate_and_annotate_messages(normalized_messages, span, scope) + if messages_data is not None: + set_data_normalized( + span, + SPANDATA.GEN_AI_REQUEST_MESSAGES, + messages_data, + unpack=False, + ) def _set_output_data(span, result): diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index bc1de4e95b..8fd6870199 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -1,33 +1,35 @@ import asyncio +import json +import os import re -import pytest from unittest.mock import MagicMock, patch -import os - -from sentry_sdk.integrations.openai_agents import OpenAIAgentsIntegration -from sentry_sdk.integrations.openai_agents.utils import safe_serialize -from sentry_sdk.utils import parse_version import agents +import pytest from agents import ( Agent, ModelResponse, - Usage, ModelSettings, + Usage, ) from agents.items import ( McpCall, + ResponseFunctionToolCall, ResponseOutputMessage, ResponseOutputText, - ResponseFunctionToolCall, ) from agents.version import __version__ as OPENAI_AGENTS_VERSION - from openai.types.responses.response_usage import ( InputTokensDetails, OutputTokensDetails, ) +from sentry_sdk import start_span +from sentry_sdk.consts import SPANDATA +from sentry_sdk.integrations.openai_agents import OpenAIAgentsIntegration +from sentry_sdk.integrations.openai_agents.utils import _set_input_data, safe_serialize +from sentry_sdk.utils import parse_version + test_run_config = agents.RunConfig(tracing_disabled=True) @@ -1051,9 +1053,6 @@ def test_openai_agents_message_role_mapping(sentry_init, capture_events): get_response_kwargs = {"input": test_input} - from sentry_sdk.integrations.openai_agents.utils import _set_input_data - from sentry_sdk import start_span - with start_span(op="test") as span: _set_input_data(span, get_response_kwargs) @@ -1061,8 +1060,6 @@ def test_openai_agents_message_role_mapping(sentry_init, capture_events): from sentry_sdk.consts import SPANDATA if SPANDATA.GEN_AI_REQUEST_MESSAGES in span._data: - import json - stored_messages = json.loads(span._data[SPANDATA.GEN_AI_REQUEST_MESSAGES]) # Verify roles were properly mapped @@ -1184,3 +1181,48 @@ def failing_tool(message: str) -> str: # Verify error status was set (this is the key test for our patch) # The span should be marked as error because the tool execution failed assert execute_tool_span["tags"]["status"] == "error" + + +def test_openai_agents_message_truncation(sentry_init, capture_events): + """Test that large messages are truncated properly in OpenAI Agents integration.""" + + large_content = ( + "This is a very long message that will exceed our size limits. " * 1000 + ) + + sentry_init( + integrations=[OpenAIAgentsIntegration()], + traces_sample_rate=1.0, + send_default_pii=True, + ) + + test_messages = [ + {"role": "system", "content": "small message 1"}, + {"role": "user", "content": large_content}, + {"role": "assistant", "content": large_content}, + {"role": "user", "content": "small message 4"}, + {"role": "assistant", "content": "small message 5"}, + ] + + get_response_kwargs = {"input": test_messages} + + with start_span(op="gen_ai.chat") as span: + import sentry_sdk + + scope = sentry_sdk.get_current_scope() + _set_input_data(span, get_response_kwargs) + if hasattr(scope, "_gen_ai_original_message_count"): + truncated_count = scope._gen_ai_original_message_count.get(span.span_id) + assert truncated_count == 5, ( + f"Expected 5 original messages, got {truncated_count}" + ) + + assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span._data + messages_data = span._data[SPANDATA.GEN_AI_REQUEST_MESSAGES] + assert isinstance(messages_data, str) + + parsed_messages = json.loads(messages_data) + assert isinstance(parsed_messages, list) + assert len(parsed_messages) == 2 + assert "small message 4" in str(parsed_messages[0]) + assert "small message 5" in str(parsed_messages[1]) From bb81f7b43bc4881f81a6475d70f4fc7317ea6732 Mon Sep 17 00:00:00 2001 From: Simon Hellmayr Date: Fri, 31 Oct 2025 10:50:19 +0100 Subject: [PATCH 2/3] fix imports --- sentry_sdk/integrations/openai_agents/utils.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index 792f8df841..aac282718e 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -1,19 +1,20 @@ +import json +from typing import TYPE_CHECKING + import sentry_sdk from sentry_sdk.ai.utils import ( GEN_AI_ALLOWED_MESSAGE_ROLES, + normalize_message_role, normalize_message_roles, set_data_normalized, - normalize_message_role, truncate_and_annotate_messages, ) -from sentry_sdk.consts import SPANDATA, SPANSTATUS, OP +from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing_utils import set_span_errored from sentry_sdk.utils import event_from_exception, safe_serialize -from typing import TYPE_CHECKING - if TYPE_CHECKING: from typing import Any from agents import Usage @@ -112,8 +113,6 @@ def _set_input_data(span, get_response_kwargs): ) for message in get_response_kwargs.get("input", []): - import json - serialized_str = safe_serialize(message) try: serialized_message = json.loads(serialized_str) From 767fd622185b1ee5c99515d39bf6798db56d3c6a Mon Sep 17 00:00:00 2001 From: Simon Hellmayr Date: Fri, 31 Oct 2025 10:52:33 +0100 Subject: [PATCH 3/3] fix imports --- tests/integrations/openai_agents/test_openai_agents.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 8fd6870199..84a17549fe 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -24,6 +24,7 @@ OutputTokensDetails, ) +import sentry_sdk from sentry_sdk import start_span from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations.openai_agents import OpenAIAgentsIntegration @@ -1056,9 +1057,6 @@ def test_openai_agents_message_role_mapping(sentry_init, capture_events): with start_span(op="test") as span: _set_input_data(span, get_response_kwargs) - # Verify that messages were processed and roles were mapped - from sentry_sdk.consts import SPANDATA - if SPANDATA.GEN_AI_REQUEST_MESSAGES in span._data: stored_messages = json.loads(span._data[SPANDATA.GEN_AI_REQUEST_MESSAGES]) @@ -1207,8 +1205,6 @@ def test_openai_agents_message_truncation(sentry_init, capture_events): get_response_kwargs = {"input": test_messages} with start_span(op="gen_ai.chat") as span: - import sentry_sdk - scope = sentry_sdk.get_current_scope() _set_input_data(span, get_response_kwargs) if hasattr(scope, "_gen_ai_original_message_count"):