diff --git a/sentry_sdk/ai/_openai_responses_api.py b/sentry_sdk/ai/_openai_responses_api.py index 11c4962eaf..ffb7c12098 100644 --- a/sentry_sdk/ai/_openai_responses_api.py +++ b/sentry_sdk/ai/_openai_responses_api.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING, Iterable, cast if TYPE_CHECKING: from typing import Iterable, Union @@ -6,10 +6,13 @@ from openai.types.responses import ( ResponseInputItemParam, ResponseInputParam, + ResponseInputTextParam, ToolParam, ) + from openai.types.responses.easy_input_message_param import EasyInputMessageParam + from openai.types.responses.response_input_item_param import Message - from sentry_sdk._types import ToolDefinition + from sentry_sdk._types import TextPart, ToolDefinition def _is_system_instruction(message: "ResponseInputItemParam") -> bool: @@ -28,6 +31,37 @@ def _get_system_instructions( return [message for message in messages if _is_system_instruction(message)] +def _transform_system_instructions( + system_instructions: "list[Union[EasyInputMessageParam, Message]]", +) -> "list[TextPart]": + instruction_text_parts: "list[TextPart]" = [] + + for instruction in system_instructions: + if not isinstance(instruction, dict): + continue + + content = instruction.get("content") + if content is None: + continue + + if isinstance(content, str): + instruction_text_parts.append({"type": "text", "content": content}) + continue + + if not isinstance(content, Iterable): + continue + + for part in content: + if not isinstance(part, dict) or part.get("type") != "input_text": + continue + + text = cast("ResponseInputTextParam", part).get("text", None) + if text is not None: + instruction_text_parts.append({"type": "text", "content": text}) + + return instruction_text_parts + + def _transform_tool_definitions(tools: "Iterable[ToolParam]") -> "list[ToolDefinition]": """ Transform tool definitions to the schema used by the "gen_ai.tool.definitions" attribute. diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 1fbc22021e..d757b16bb6 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -12,11 +12,13 @@ ) from sentry_sdk.ai._openai_completions_api import ( _get_text_items, - _transform_system_instructions, ) from sentry_sdk.ai._openai_completions_api import ( _is_system_instruction as _is_system_instruction_completions, ) +from sentry_sdk.ai._openai_completions_api import ( + _transform_system_instructions as _transform_system_instructions_completions, +) from sentry_sdk.ai._openai_completions_api import ( _transform_tool_definitions as _transform_tool_definitions_completions, ) @@ -26,6 +28,9 @@ from sentry_sdk.ai._openai_responses_api import ( _is_system_instruction as _is_system_instruction_responses, ) +from sentry_sdk.ai._openai_responses_api import ( + _transform_system_instructions as _transform_system_instructions_responses, +) from sentry_sdk.ai._openai_responses_api import ( _transform_tool_definitions as _transform_tool_definitions_responses, ) @@ -428,9 +433,9 @@ def _set_responses_api_input_data( ) system_instructions = _get_system_instructions_responses(messages) - # Deliberate use of function accepting completions API type because - # of shared structure FOR THIS PURPOSE ONLY. - instructions_text_parts += _transform_system_instructions(system_instructions) + instructions_text_parts += _transform_system_instructions_responses( + system_instructions + ) if len(instructions_text_parts) > 0: set_on_span( SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, @@ -570,7 +575,7 @@ def _set_completions_api_input_data( if len(system_instructions) > 0: set_on_span( SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, - json.dumps(_transform_system_instructions(system_instructions)), + json.dumps(_transform_system_instructions_completions(system_instructions)), ) non_system_messages = [ diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index b82518c7b3..add58b6e29 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -2,10 +2,10 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.ai._openai_completions_api import _transform_system_instructions from sentry_sdk.ai._openai_responses_api import ( _get_system_instructions, _is_system_instruction, + _transform_system_instructions, ) from sentry_sdk.ai.utils import ( GEN_AI_ALLOWED_MESSAGE_ROLES, @@ -140,8 +140,6 @@ def _set_input_data( system_instructions = _get_system_instructions(messages) - # Deliberate use of function accepting completions API type because - # of shared structure FOR THIS PURPOSE ONLY. instructions_text_parts += _transform_system_instructions(system_instructions) if len(instructions_text_parts) > 0: diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 02528184cc..cb1c033c5e 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -5466,8 +5466,8 @@ def test_ai_client_span_responses_tool_definitions( { "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -5499,8 +5499,8 @@ def test_ai_client_span_responses_tool_definitions( "type": "message", "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -6586,8 +6586,8 @@ def test_error_in_responses_api( { "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -6619,8 +6619,8 @@ def test_error_in_responses_api( "type": "message", "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -6901,8 +6901,8 @@ async def test_ai_client_span_responses_async_api( { "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -6934,8 +6934,8 @@ async def test_ai_client_span_responses_async_api( "type": "message", "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 455cebbf01..82826b1065 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -1453,8 +1453,8 @@ async def test_data_collection_outputs( { "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -1494,8 +1494,8 @@ async def test_data_collection_outputs( "type": "message", "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -2160,8 +2160,8 @@ def test_agent_invocation_span_sync_no_pii( { "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, { @@ -2201,8 +2201,8 @@ def test_agent_invocation_span_sync_no_pii( "type": "message", "role": "system", "content": [ - {"type": "text", "text": "You are a helpful assistant."}, - {"type": "text", "text": "Be concise and clear."}, + {"type": "input_text", "text": "You are a helpful assistant."}, + {"type": "input_text", "text": "Be concise and clear."}, ], }, {