From 66c95ecb56f2362272ba8e1a1ca317ec3ae13d4b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 08:57:25 +0200 Subject: [PATCH 1/9] fix(openai): Read input_text parts for the Responses API --- sentry_sdk/ai/_openai_responses_api.py | 38 ++++++++++++++++++++++-- tests/integrations/openai/test_openai.py | 24 +++++++-------- 2 files changed, 48 insertions(+), 14 deletions(-) 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/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index a86843a3ca..f3f2bac53c 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."}, ], }, { @@ -6609,8 +6609,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."}, ], }, { @@ -6642,8 +6642,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."}, ], }, { @@ -6924,8 +6924,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."}, ], }, { @@ -6957,8 +6957,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."}, ], }, { From 0de2174d39d4cc96f58aca7f4d87a7faf476b207 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 08:58:41 +0200 Subject: [PATCH 2/9] ci: Add openai to uv typing group --- pyproject.toml | 1 + sentry_sdk/ai/_openai_completions_api.py | 18 +-- sentry_sdk/ai/_openai_responses_api.py | 8 +- sentry_sdk/integrations/openai.py | 138 +++++++++++++++-------- uv.lock | 54 +++++++++ 5 files changed, 162 insertions(+), 57 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f7a0035dab..7a22efd03e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ typing = [ "pydantic-ai-slim>=2.23.0", "langchain-core>=1.5.3", "huggingface-hub>=1.26.1", + "openai>=3.7.0", ] test = [ "dataclasses ; python_full_version < '3.7'", diff --git a/sentry_sdk/ai/_openai_completions_api.py b/sentry_sdk/ai/_openai_completions_api.py index 3dc9290660..214d3bccc3 100644 --- a/sentry_sdk/ai/_openai_completions_api.py +++ b/sentry_sdk/ai/_openai_completions_api.py @@ -1,11 +1,13 @@ from collections.abc import Iterable -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast if TYPE_CHECKING: - from typing import Union + from typing import TypeGuard, Union from openai.types.chat import ( ChatCompletionContentPartParam, + ChatCompletionContentPartRefusalParam, + ChatCompletionContentPartTextParam, ChatCompletionMessageParam, ChatCompletionSystemMessageParam, ChatCompletionToolUnionParam, @@ -14,13 +16,15 @@ from sentry_sdk._types import TextPart, ToolDefinition -def _is_system_instruction(message: "ChatCompletionMessageParam") -> bool: +def _is_system_instruction( + message: "ChatCompletionMessageParam", +) -> "TypeGuard[ChatCompletionSystemMessageParam]": return isinstance(message, dict) and message.get("role") == "system" def _get_system_instructions( messages: "Iterable[ChatCompletionMessageParam]", -) -> "list[ChatCompletionMessageParam]": +) -> "list[ChatCompletionSystemMessageParam]": if not isinstance(messages, Iterable): return [] @@ -28,7 +32,7 @@ def _get_system_instructions( def _get_text_items( - content: "Union[str, Iterable[ChatCompletionContentPartParam]]", + content: "Union[str, Iterable[Union[ChatCompletionContentPartParam, ChatCompletionContentPartTextParam, ChatCompletionContentPartRefusalParam]]]", ) -> "list[str]": if isinstance(content, str): return [content] @@ -36,10 +40,10 @@ def _get_text_items( if not isinstance(content, Iterable): return [] - text_items = [] + text_items: "list[str]" = [] for part in content: if isinstance(part, dict) and part.get("type") == "text": - text = part.get("text", None) + text = cast("ChatCompletionContentPartTextParam", part).get("text", None) if text is not None: text_items.append(text) diff --git a/sentry_sdk/ai/_openai_responses_api.py b/sentry_sdk/ai/_openai_responses_api.py index ffb7c12098..fddcf0019c 100644 --- a/sentry_sdk/ai/_openai_responses_api.py +++ b/sentry_sdk/ai/_openai_responses_api.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Iterable, cast if TYPE_CHECKING: - from typing import Iterable, Union + from typing import Iterable, TypeGuard, Union from openai.types.responses import ( ResponseInputItemParam, @@ -15,7 +15,9 @@ from sentry_sdk._types import TextPart, ToolDefinition -def _is_system_instruction(message: "ResponseInputItemParam") -> bool: +def _is_system_instruction( + message: "ResponseInputItemParam", +) -> "TypeGuard[Union[EasyInputMessageParam, Message]]": if not isinstance(message, dict) or not message.get("role") == "system": return False @@ -24,7 +26,7 @@ def _is_system_instruction(message: "ResponseInputItemParam") -> bool: def _get_system_instructions( messages: "Union[str, ResponseInputParam]", -) -> "list[ResponseInputItemParam]": +) -> "list[Union[EasyInputMessageParam, Message]]": if not isinstance(messages, list): return [] diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 63653bdee0..01c7a43fe6 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, ) @@ -57,6 +62,7 @@ Any, AsyncIterator, Callable, + Coroutine, Iterable, Iterator, List, @@ -65,11 +71,12 @@ ) from openai import Omit + from openai._types import SequenceNotStr from openai.types import CompletionUsage from openai.types.responses import ( + Response, ResponseInputParam, ResponseStreamEvent, - SequenceNotStr, ) from openai.types.responses.response_usage import ResponseUsage @@ -80,16 +87,18 @@ try: from openai import NotGiven except ImportError: - NotGiven = None + NotGiven = None # type: ignore[misc,assignment] try: from openai import Omit except ImportError: - Omit = None + Omit = None # type: ignore[misc,assignment] from openai import AsyncStream, Stream from openai.resources import AsyncEmbeddings, Embeddings from openai.resources.chat.completions import AsyncCompletions, Completions + from openai.types import CreateEmbeddingResponse + from openai.types.chat import ChatCompletion if TYPE_CHECKING: from openai.types.chat import ( @@ -127,17 +136,17 @@ def __init__( @staticmethod def setup_once() -> None: - Completions.create = _wrap_chat_completion_create(Completions.create) - AsyncCompletions.create = _wrap_async_chat_completion_create( + Completions.create = _wrap_chat_completion_create(Completions.create) # type: ignore[assignment,method-assign] + AsyncCompletions.create = _wrap_async_chat_completion_create( # type: ignore[assignment,method-assign] AsyncCompletions.create ) - Embeddings.create = _wrap_embeddings_create(Embeddings.create) - AsyncEmbeddings.create = _wrap_async_embeddings_create(AsyncEmbeddings.create) + Embeddings.create = _wrap_embeddings_create(Embeddings.create) # type: ignore[method-assign] + AsyncEmbeddings.create = _wrap_async_embeddings_create(AsyncEmbeddings.create) # type: ignore[method-assign] if RESPONSES_API_ENABLED: - Responses.create = _wrap_responses_create(Responses.create) - AsyncResponses.create = _wrap_async_responses_create(AsyncResponses.create) + Responses.create = _wrap_responses_create(Responses.create) # type: ignore[method-assign] + AsyncResponses.create = _wrap_async_responses_create(AsyncResponses.create) # type: ignore[assignment,method-assign] def count_tokens(self: "OpenAIIntegration", s: str) -> int: if self.tiktoken_encoding is None: @@ -166,7 +175,7 @@ def _has_attr_and_is_int( def _calculate_completions_token_usage( - messages: "Optional[Iterable[ChatCompletionMessageParam]]", + messages: "Optional[Union[Iterable[ChatCompletionMessageParam], list[str]]]", response: "Any", span: "Union[Span, StreamedSpan]", streaming_message_responses: "Optional[List[str]]", @@ -428,9 +437,10 @@ 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) + # Deliberate use of function accepting completions API type because of shared structure + instructions_text_parts += _transform_system_instructions_responses( + system_instructions + ) if len(instructions_text_parts) > 0: set_on_span( SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS, @@ -458,7 +468,7 @@ def _set_responses_api_input_data( message for message in messages if not _is_system_instruction_responses(message) ] if len(non_system_messages) > 0: - normalized_messages = normalize_message_roles(non_system_messages) + normalized_messages = normalize_message_roles(non_system_messages) # type: ignore client = sentry_sdk.get_client() scope = sentry_sdk.get_current_scope() messages_data = ( @@ -570,7 +580,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 = [ @@ -579,7 +589,7 @@ def _set_completions_api_input_data( if not _is_system_instruction_completions(message) ] if len(non_system_messages) > 0: - normalized_messages = normalize_message_roles(non_system_messages) + normalized_messages = normalize_message_roles(non_system_messages) # type: ignore client = sentry_sdk.get_client() scope = sentry_sdk.get_current_scope() messages_data = ( @@ -609,7 +619,7 @@ def _set_embeddings_input_data( if model is not None: set_on_span(SPANDATA.GEN_AI_REQUEST_MODEL, model) - messages: "Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]]" = kwargs.get( + messages: "Optional[Union[str, SequenceNotStr[str], Iterable[int], Iterable[Iterable[int]]]]" = kwargs.get( "input" ) @@ -642,11 +652,11 @@ def _set_embeddings_input_data( if not isinstance(messages, Iterable) or isinstance(messages, dict): return - messages = list(messages) + messages_copy = list(messages) kwargs["input"] = messages - if len(messages) > 0: - normalized_messages = normalize_message_roles(messages) + if len(messages_copy) > 0: + normalized_messages = normalize_message_roles(messages_copy) # type: ignore scope = sentry_sdk.get_current_scope() messages_data = ( truncate_and_annotate_embedding_inputs(normalized_messages, span, scope) @@ -661,7 +671,7 @@ def _set_embeddings_input_data( def _set_common_output_data( span: "Union[Span, StreamedSpan]", - response: "Any", + response: "Union[ChatCompletion, Stream[ChatCompletionChunk], AsyncStream[ChatCompletionChunk], Response, Stream[ResponseStreamEvent], AsyncStream[ResponseStreamEvent], CreateEmbeddingResponse]", input: "Any", integration: "OpenAIIntegration", finish_span: bool = True, @@ -705,7 +715,7 @@ def _set_common_output_data( span.__exit__(None, None, None) # Responses API - elif hasattr(response, "output"): + elif isinstance(response, Response): output_messages: "dict[str, list[Any]]" = { "response": [], "tool": [], @@ -791,7 +801,11 @@ def _set_common_output_data( span.__exit__(None, None, None) -def _new_sync_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": +def _new_sync_chat_completion( + f: "Callable[..., Union[ChatCompletion, Stream[ChatCompletionChunk]]]", + *args: "Any", + **kwargs: "Any", +) -> "Union[ChatCompletion, Stream[ChatCompletionChunk]]": client = sentry_sdk.get_client() integration = client.get_integration(OpenAIIntegration) if integration is None: @@ -849,7 +863,9 @@ def _new_sync_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. if isinstance(response, Stream) and hasattr(response, "_iterator"): - messages = kwargs.get("messages") + messages: "Optional[Union[Iterable[ChatCompletionMessageParam], list[str]]]" = ( + kwargs.get("messages") + ) if messages is not None and isinstance(messages, str): messages = [messages] @@ -872,7 +888,11 @@ def _new_sync_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": return response -async def _new_async_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": +async def _new_async_chat_completion( + f: "Callable[..., Coroutine[Any, Any, Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]]]", + *args: "Any", + **kwargs: "Any", +) -> "Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]": client = sentry_sdk.get_client() integration = client.get_integration(OpenAIIntegration) if integration is None: @@ -929,7 +949,7 @@ async def _new_async_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. if isinstance(response, AsyncStream) and hasattr(response, "_iterator"): - messages = kwargs.get("messages") + messages: "Optional[list[str]]" = kwargs.get("messages") if messages is not None and isinstance(messages, str): messages = [messages] @@ -953,7 +973,7 @@ async def _new_async_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> def _set_completions_api_output_data( span: "Union[Span, StreamedSpan]", - response: "Any", + response: "Union[ChatCompletion, Stream[ChatCompletionChunk], AsyncStream[ChatCompletionChunk]]", kwargs: "dict[str, Any]", integration: "OpenAIIntegration", finish_span: bool = True, @@ -976,7 +996,7 @@ def _wrap_synchronous_completions_chunk_iterator( span: "Union[Span, StreamedSpan]", integration: "OpenAIIntegration", start_time: "Optional[float]", - messages: "Optional[Iterable[ChatCompletionMessageParam]]", + messages: "Optional[Union[Iterable[ChatCompletionMessageParam], list[str]]]", response: "Stream[ChatCompletionChunk]", old_iterator: "Iterator[ChatCompletionChunk]", finish_span: "bool", @@ -1047,7 +1067,7 @@ async def _wrap_asynchronous_completions_chunk_iterator( span: "Union[Span, StreamedSpan]", integration: "OpenAIIntegration", start_time: "Optional[float]", - messages: "Optional[Iterable[ChatCompletionMessageParam]]", + messages: "Optional[Union[Iterable[ChatCompletionMessageParam], list[str]]]", response: "AsyncStream[ChatCompletionChunk]", old_iterator: "AsyncIterator[ChatCompletionChunk]", finish_span: "bool", @@ -1118,7 +1138,7 @@ def _wrap_synchronous_responses_event_iterator( span: "Union[Span, StreamedSpan]", integration: "OpenAIIntegration", start_time: "Optional[float]", - input: "Optional[Union[str, ResponseInputParam]]", + input: "Optional[Union[str, list[str], ResponseInputParam]]", response: "Stream[ResponseStreamEvent]", old_iterator: "Iterator[ResponseStreamEvent]", finish_span: "bool", @@ -1140,7 +1160,7 @@ def _wrap_synchronous_responses_event_iterator( ttft = time.perf_counter() - start_time if len(data_buf) == 0: data_buf.append([]) - data_buf[0].append(x.delta or "") + data_buf[0].append(str(x.delta) or "") if isinstance(x, ResponseCompletedEvent): if isinstance(span, StreamedSpan): @@ -1191,7 +1211,7 @@ async def _wrap_asynchronous_responses_event_iterator( span: "Union[Span, StreamedSpan]", integration: "OpenAIIntegration", start_time: "Optional[float]", - input: "Optional[Union[str, ResponseInputParam]]", + input: "Optional[Union[str, list[str], ResponseInputParam]]", response: "AsyncStream[ResponseStreamEvent]", old_iterator: "AsyncIterator[ResponseStreamEvent]", finish_span: "bool", @@ -1213,7 +1233,7 @@ async def _wrap_asynchronous_responses_event_iterator( ttft = time.perf_counter() - start_time if len(data_buf) == 0: data_buf.append([]) - data_buf[0].append(x.delta or "") + data_buf[0].append(str(x.delta) or "") if isinstance(x, ResponseCompletedEvent): if isinstance(span, StreamedSpan): @@ -1262,7 +1282,7 @@ async def _wrap_asynchronous_responses_event_iterator( def _set_responses_api_output_data( span: "Union[Span, StreamedSpan]", - response: "Any", + response: "Union[Response, Stream[ResponseStreamEvent], AsyncStream[ResponseStreamEvent]]", kwargs: "dict[str, Any]", integration: "OpenAIIntegration", finish_span: bool = True, @@ -1283,7 +1303,7 @@ def _set_responses_api_output_data( def _set_embeddings_output_data( span: "Union[Span, StreamedSpan]", - response: "Any", + response: "CreateEmbeddingResponse", kwargs: "dict[str, Any]", integration: "OpenAIIntegration", finish_span: bool = True, @@ -1302,7 +1322,9 @@ def _set_embeddings_output_data( ) -def _wrap_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]": +def _wrap_chat_completion_create( + f: "Callable[..., Union[ChatCompletion, Stream[ChatCompletionChunk]]]", +) -> "Callable[..., Coroutine[Any, Any, Union[ChatCompletion, Stream[ChatCompletionChunk]]]]": @wraps(f) def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) @@ -1315,9 +1337,13 @@ def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any": return _sentry_patched_create_sync -def _wrap_async_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]": +def _wrap_async_chat_completion_create( + f: "Callable[..., Coroutine[Any, Any, Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]]]", +) -> "Callable[..., Coroutine[Any, Any, Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]]]": @wraps(f) - async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any": + async def _sentry_patched_create_async( + *args: "Any", **kwargs: "Any" + ) -> "Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None or "messages" not in kwargs: # no "messages" means invalid call (in all versions of openai), let it return error @@ -1385,8 +1411,10 @@ def _new_sync_embeddings_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any async def _new_async_embeddings_create( - f: "Any", *args: "Any", **kwargs: "Any" -) -> "Any": + f: "Callable[..., Coroutine[Any, Any, CreateEmbeddingResponse]]", + *args: "Any", + **kwargs: "Any", +) -> "CreateEmbeddingResponse": client = sentry_sdk.get_client() integration = client.get_integration(OpenAIIntegration) if integration is None: @@ -1466,7 +1494,11 @@ async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any": return _sentry_patched_create_async -def _new_sync_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": +def _new_sync_responses_create( + f: "Callable[..., Union[Response, Stream[ResponseStreamEvent]]]", + *args: "Any", + **kwargs: "Any", +) -> "Union[Response, Stream[ResponseStreamEvent]]": client = sentry_sdk.get_client() integration = client.get_integration(OpenAIIntegration) if integration is None: @@ -1513,7 +1545,9 @@ def _new_sync_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any" # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. if isinstance(response, Stream) and hasattr(response, "_iterator"): - input = kwargs.get("input") + input: "Optional[Union[str, list[str], ResponseInputParam]]" = kwargs.get( + "input" + ) if input is not None and isinstance(input, str): input = [input] @@ -1536,7 +1570,11 @@ def _new_sync_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any" return response -async def _new_async_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": +async def _new_async_responses_create( + f: "Callable[..., Coroutine[Any, Any, Union[Response, AsyncStream[ResponseStreamEvent]]]]", + *args: "Any", + **kwargs: "Any", +) -> "Union[Response, AsyncStream[ResponseStreamEvent]]": client = sentry_sdk.get_client() integration = client.get_integration(OpenAIIntegration) if integration is None: @@ -1583,7 +1621,9 @@ async def _new_async_responses_create(f: "Any", *args: "Any", **kwargs: "Any") - # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. if isinstance(response, AsyncStream) and hasattr(response, "_iterator"): - input = kwargs.get("input") + input: "Optional[Union[str, list[str], ResponseInputParam]]" = kwargs.get( + "input" + ) if input is not None and isinstance(input, str): input = [input] @@ -1617,9 +1657,13 @@ def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any": return _sentry_patched_create_sync -def _wrap_async_responses_create(f: "Any") -> "Any": +def _wrap_async_responses_create( + f: "Callable[..., Coroutine[Any, Any, Union[Response, AsyncStream[ResponseStreamEvent]]]]", +) -> "Callable[..., Coroutine[Any, Any, Union[Response, AsyncStream[ResponseStreamEvent]]]]": @wraps(f) - async def _sentry_patched_responses_async(*args: "Any", **kwargs: "Any") -> "Any": + async def _sentry_patched_responses_async( + *args: "Any", **kwargs: "Any" + ) -> "Union[Response, AsyncStream[ResponseStreamEvent]]": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return await f(*args, **kwargs) diff --git a/uv.lock b/uv.lock index bf2403cdd7..72cd180b6b 100644 --- a/uv.lock +++ b/uv.lock @@ -51,6 +51,7 @@ typing = [ { name = "loguru" }, { name = "mcp", specifier = ">=2.0.0b1" }, { name = "mypy" }, + { name = "openai", specifier = ">=3.7.0" }, { name = "openfeature-sdk" }, { name = "opentelemetry-distro", extras = ["otlp"] }, { name = "pydantic", specifier = ">=2.13.4" }, @@ -1010,6 +1011,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jiter" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/89/bc4f1b57d5da938fd344a466396541e586d161320d70bffd929aaafcd8f4/jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee", size = 308239, upload-time = "2026-06-29T13:03:57.205Z" }, + { url = "https://files.pythonhosted.org/packages/65/7a/c415453e5213001bf3b411ff65dec3d303b0e76a4a2cfea9768cd4960994/jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2", size = 308928, upload-time = "2026-06-29T13:03:58.643Z" }, + { url = "https://files.pythonhosted.org/packages/11/fc/1f4fb7ebf9a724c7741994f4aae18fba1e2f3133df14521a79194952c34a/jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883", size = 336998, upload-time = "2026-06-29T13:04:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8d/72cadaac05ccfa7cc3a0a2232862e6c72443ca40cf300ba8b57f9f18b69b/jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898", size = 362112, upload-time = "2026-06-29T13:04:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/58/4a/c4b0d5f651fda90a24ffce9f8d56cde462a2e09d31ae3de3c68cef34c04e/jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5", size = 459807, upload-time = "2026-06-29T13:04:03.214Z" }, + { url = "https://files.pythonhosted.org/packages/80/58/ef77879ea9aa56b50824edc5a445e226422c7a8d211f3fd2a56bcb9493cf/jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567", size = 373181, upload-time = "2026-06-29T13:04:04.629Z" }, + { url = "https://files.pythonhosted.org/packages/49/2e/ffbc3f254e4d8a66da3062c624a7df4b7c2b2cf9e1fe43cf394b3e104041/jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69", size = 344927, upload-time = "2026-06-29T13:04:06.067Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f6/0be5dc6d64a89f80aa8fec984f94dedb2973e251edcae55841d60786d578/jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29", size = 352754, upload-time = "2026-06-29T13:04:07.477Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/7d31243b3b91cd261dd19e9d3557fc3251a80883d3d8049c86174e7ab7af/jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93", size = 390553, upload-time = "2026-06-29T13:04:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/25/33/51ae371fde3c88897520f62b4d5f8b27ad7103e2bb10812ff52195609853/jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a", size = 516900, upload-time = "2026-06-29T13:04:10.407Z" }, + { url = "https://files.pythonhosted.org/packages/a0/45/6449b3d123ea439ba79507c657288f461d55049e7bcbdc2cf8eb8210f491/jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00", size = 548754, upload-time = "2026-06-29T13:04:12.046Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/fd2fb11ae3e2649333da3aa170d04d7b3000bbdc3b270f6513382fdf4e04/jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe", size = 122381, upload-time = "2026-06-29T13:04:13.413Z" }, + { url = "https://files.pythonhosted.org/packages/26/80/f0b147a62c315a164ed2168908286ca302310824c218d3aae52b06c0c9a9/jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106", size = 204578, upload-time = "2026-06-29T13:04:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/4758a14304b4523a6f5adb2419340086aa3593bd4327c2b25b5948a90548/jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8", size = 198154, upload-time = "2026-06-29T13:04:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/26/be/41fa54a2e7ea41d6c99f1dc5b1f0fd4cb474680304b5d268dd518e81da3a/jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585", size = 191458, upload-time = "2026-06-29T13:04:17.707Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/59127338b86d9fe4d99418f5a15118bea778103ee0fe9d9dd7e0af174e95/jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207", size = 316739, upload-time = "2026-06-29T13:04:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/2d/95/49461034d5388196d3dabf98748935f017b7785d8f3f5349f834bcc4ed0d/jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818", size = 340911, upload-time = "2026-06-29T13:04:21.257Z" }, + { url = "https://files.pythonhosted.org/packages/cd/97/a4369f2fb82cb3dda13b98622f31249b2e014b223fe64ee534413ad72294/jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3", size = 361747, upload-time = "2026-06-29T13:04:22.677Z" }, + { url = "https://files.pythonhosted.org/packages/28/51/49b6ed456261646e1906016a6760367a28aacd3c24805e4e5fe64116c1db/jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284", size = 460225, upload-time = "2026-06-29T13:04:24.441Z" }, + { url = "https://files.pythonhosted.org/packages/33/b5/5689aff4f66c5b60be63106e591dbfcba2190df97d2c9c7cf052361ddb98/jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929", size = 373169, upload-time = "2026-06-29T13:04:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/a2/96/3ae1b85ee0d6d6cab254fb7f8da018272b932bbf2d69b07e98aa2a96c746/jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620", size = 350332, upload-time = "2026-06-29T13:04:27.302Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/c99d7bafd78986556c95bf60ce84c6cc98786eac56066c12d7f828bb6747/jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af", size = 353377, upload-time = "2026-06-29T13:04:28.731Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/f99a8e571287c3dec766bcc18528bbe8e8fb5365522ab5e6d64c93e87066/jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e", size = 387746, upload-time = "2026-06-29T13:04:30.319Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/c78a5b3f71040e34eb5917df26fb7ae9a2174cad1ccbf277512507c53a6e/jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077", size = 517292, upload-time = "2026-06-29T13:04:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f7/095b38eda4c70d03651c403f29a5590f16d12ddc5d544aac9f9cddf72277/jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734", size = 549259, upload-time = "2026-06-29T13:04:33.721Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" }, +] + [[package]] name = "jsonpatch" version = "1.33" @@ -1419,6 +1456,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] +[[package]] +name = "openai" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx2" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/ba/6d46da4232f80cb5842280e024242e6fed163418ab81bebc1c83f693bb0b/openai-3.7.0.tar.gz", hash = "sha256:e836eb7effee89df802cd0c7d1bad8de8c993976cf238c44d5b5b844f5aefd38", size = 1455615, upload-time = "2026-09-02T01:30:54.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/9d/77e0f43f04ae51a3d21bb08bd372a752856b86ee22648b1458c93a623927/openai-3.7.0-py3-none-any.whl", hash = "sha256:008fa33e0a01dc71039c27355ef8f476eed690e8e48bec18183878dcd32fb841", size = 1699594, upload-time = "2026-09-02T01:30:52.445Z" }, +] + [[package]] name = "openfeature-sdk" version = "0.10.0" From a815206df81d8bff7f9103caeedea51b198e6365 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 09:05:02 +0200 Subject: [PATCH 3/9] . --- sentry_sdk/integrations/openai.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 63653bdee0..3d602fbe6d 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 = [ From 6c16258ce12265389aec5d68a0d664ea49417ff7 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 09:17:57 +0200 Subject: [PATCH 4/9] add two type ignores --- sentry_sdk/integrations/openai.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index f3fdf033f6..9938cab787 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -732,7 +732,7 @@ def _set_common_output_data( elif output.type == "message": for output_message in output.content: try: - output_messages["response"].append(output_message.text) + 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( @@ -763,7 +763,7 @@ def _set_common_output_data( elif output.type == "message": for output_message in output.content: try: - output_messages["response"].append(output_message.text) + 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()) From 558362b221371accdccaa13b3079670fd54fdfe9 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 09:30:28 +0200 Subject: [PATCH 5/9] adapt openai agents as well --- sentry_sdk/integrations/openai_agents/utils.py | 4 +--- .../openai_agents/test_openai_agents.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) 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_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."}, ], }, { From 729eec6b69f47bdce5418f6f5ba6715489598c7c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 09:33:10 +0200 Subject: [PATCH 6/9] . --- sentry_sdk/integrations/openai_agents/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index add58b6e29..fa9237e5e9 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -155,7 +155,9 @@ def _set_input_data( ) non_system_messages = [ - message for message in messages if not _is_system_instruction(message) + message + for message in messages + if not _is_system_instruction(message) # type: ignore[arg-type] ] for message in non_system_messages: if "role" in message: From 14104d1e63796e8e751d9f94f5f1c573fac71772 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 09:41:04 +0200 Subject: [PATCH 7/9] add runtime import for Response --- sentry_sdk/integrations/openai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 9938cab787..5d0ddb2c9a 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -74,7 +74,6 @@ from openai._types import SequenceNotStr from openai.types import CompletionUsage from openai.types.responses import ( - Response, ResponseInputParam, ResponseStreamEvent, ) @@ -99,6 +98,7 @@ from openai.resources.chat.completions import AsyncCompletions, Completions from openai.types import CreateEmbeddingResponse from openai.types.chat import ChatCompletion + from openai.types.responses import Response if TYPE_CHECKING: from openai.types.chat import ( From d7ed0c5c8c2e4b4e0249334604960568d28f78bb Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 09:46:42 +0200 Subject: [PATCH 8/9] use cast instead of isinstance --- sentry_sdk/integrations/openai.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 5d0ddb2c9a..d903871302 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -74,6 +74,7 @@ from openai._types import SequenceNotStr from openai.types import CompletionUsage from openai.types.responses import ( + Response, ResponseInputParam, ResponseStreamEvent, ) @@ -98,7 +99,6 @@ from openai.resources.chat.completions import AsyncCompletions, Completions from openai.types import CreateEmbeddingResponse from openai.types.chat import ChatCompletion - from openai.types.responses import Response if TYPE_CHECKING: from openai.types.chat import ( @@ -714,7 +714,9 @@ def _set_common_output_data( span.__exit__(None, None, None) # Responses API - elif isinstance(response, Response): + elif hasattr(response, "output"): + response = cast("Response", response) + output_messages: "dict[str, list[Any]]" = { "response": [], "tool": [], @@ -1164,7 +1166,7 @@ def _wrap_synchronous_responses_event_iterator( ttft = time.perf_counter() - start_time if len(data_buf) == 0: data_buf.append([]) - data_buf[0].append(str(x.delta) or "") + data_buf[0].append(x.delta or "") if isinstance(x, ResponseCompletedEvent): if isinstance(span, StreamedSpan): From fe3f10a22e5ea70357dab771692c38bc726b207e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 2 Sep 2026 09:50:54 +0200 Subject: [PATCH 9/9] isinstance check for deltas --- sentry_sdk/integrations/openai.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index d903871302..03d93a6559 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -74,7 +74,6 @@ from openai._types import SequenceNotStr from openai.types import CompletionUsage from openai.types.responses import ( - Response, ResponseInputParam, ResponseStreamEvent, ) @@ -112,6 +111,7 @@ try: # responses API support was introduced in v1.66.0 from openai.resources.responses import AsyncResponses, Responses + from openai.types.responses import Response, ResponseTextDeltaEvent from openai.types.responses.response_completed_event import ResponseCompletedEvent except ImportError: RESPONSES_API_ENABLED = False @@ -1161,14 +1161,14 @@ def _wrap_synchronous_responses_event_iterator( count_tokens_manually = True for x in old_iterator: with capture_internal_exceptions(): - if hasattr(x, "delta"): + if isinstance(x, ResponseTextDeltaEvent): if start_time is not None and ttft is None: ttft = time.perf_counter() - start_time if len(data_buf) == 0: data_buf.append([]) data_buf[0].append(x.delta or "") - if isinstance(x, ResponseCompletedEvent): + elif isinstance(x, ResponseCompletedEvent): if isinstance(span, StreamedSpan): span.set_attribute(SPANDATA.GEN_AI_RESPONSE_MODEL, x.response.model) else: @@ -1234,14 +1234,14 @@ async def _wrap_asynchronous_responses_event_iterator( count_tokens_manually = True async for x in old_iterator: with capture_internal_exceptions(): - if hasattr(x, "delta"): + if isinstance(x, ResponseTextDeltaEvent): if start_time is not None and ttft is None: ttft = time.perf_counter() - start_time if len(data_buf) == 0: data_buf.append([]) - data_buf[0].append(str(x.delta) or "") + data_buf[0].append(x.delta or "") - if isinstance(x, ResponseCompletedEvent): + elif isinstance(x, ResponseCompletedEvent): if isinstance(span, StreamedSpan): span.set_attribute(SPANDATA.GEN_AI_RESPONSE_MODEL, x.response.model) else: