diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 742b693245..42ecb0ec5e 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -730,7 +730,7 @@ def _set_common_output_data( if record_outputs: for output in response.output: if output.type == "function_call": - output_messages["tool"].append(output.dict()) + output_messages["tool"].append(output.model_dump()) elif output.type == "message": for output_message in output.content: try: @@ -738,7 +738,7 @@ def _set_common_output_data( except AttributeError: # Unknown output message type, just return the json output_messages["response"].append( - output_message.dict() + output_message.model_dump() ) if len(output_messages["tool"]) > 0: @@ -761,14 +761,16 @@ def _set_common_output_data( ): for output in response.output: if output.type == "function_call": - output_messages["tool"].append(output.dict()) + output_messages["tool"].append(output.model_dump()) elif output.type == "message": for output_message in output.content: try: output_messages["response"].append(output_message.text) # type: ignore[union-attr] except AttributeError: # Unknown output message type, just return the json - output_messages["response"].append(output_message.dict()) + output_messages["response"].append( + output_message.model_dump() + ) if len(output_messages["tool"]) > 0: set_data_normalized( diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index 01303e0e58..0e160193ed 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -226,14 +226,14 @@ def _set_output_data( for output in result.output: if output.type == "function_call": - output_messages["tool"].append(output.dict()) + output_messages["tool"].append(output.model_dump()) elif output.type == "message": for output_message in output.content: try: output_messages["response"].append(output_message.text) except AttributeError: # Unknown output message type, just return the json - output_messages["response"].append(output_message.dict()) + output_messages["response"].append(output_message.model_dump()) if record_outputs and len(output_messages["tool"]) > 0: if isinstance(span, StreamedSpan): diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 8bc49a2c9f..13a336441d 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -2,6 +2,8 @@ import json import logging import os +import warnings +from types import SimpleNamespace from unittest import mock from unittest.mock import MagicMock, patch @@ -20,6 +22,7 @@ from agents.items import ( ResponseFunctionToolCall, ResponseOutputMessage, + ResponseOutputRefusal, ResponseOutputText, ) from agents.models.openai_responses import OpenAIResponsesModel @@ -72,7 +75,11 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations.logging import LoggingIntegration from sentry_sdk.integrations.openai_agents import OpenAIAgentsIntegration -from sentry_sdk.integrations.openai_agents.utils import _set_input_data, safe_serialize +from sentry_sdk.integrations.openai_agents.utils import ( + _set_input_data, + _set_output_data, + safe_serialize, +) from sentry_sdk.integrations.stdlib import StdlibIntegration from sentry_sdk.utils import package_version, parse_version @@ -7483,3 +7490,45 @@ async def test_runner_run_streamed_with_starting_agent_kwarg( (transaction,) = events assert transaction["transaction"] == "test_agent workflow" + + +def test_set_output_data_tool_calls_no_pydantic_deprecation(sentry_init): + """Serializing tool-call output must not use the deprecated pydantic v1 API. + + Regression test for GH-7827: _set_output_data called BaseModel.dict(), + which raises PydanticDeprecatedSince20 when DeprecationWarnings are errors. + """ + sentry_init( + integrations=[OpenAIAgentsIntegration()], + send_default_pii=True, + traces_sample_rate=1.0, + ) + + tool_call = ResponseFunctionToolCall( + type="function_call", + id="call_1", + call_id="call_1", + name="get_weather", + arguments='{"city": "Berlin"}', + status="completed", + ) + refusal = ResponseOutputRefusal(type="refusal", refusal="I cannot help.") + message = ResponseOutputMessage( + type="message", + id="msg_1", + role="assistant", + content=[refusal], + status="completed", + ) + result = SimpleNamespace(output=[tool_call, message]) + + with start_span(op="test") as span: + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + _set_output_data(span, result) + + tool_calls = json.loads(span._data[SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS]) + assert tool_calls[0]["name"] == "get_weather" + assert tool_calls[0]["arguments"] == '{"city": "Berlin"}' + + assert "I cannot help." in span._data[SPANDATA.GEN_AI_RESPONSE_TEXT]