From a073290849f8d3bf8972ed68544ee25376ecec77 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 4 May 2026 07:35:48 +0200 Subject: [PATCH 1/6] ref(openai): Separate sync and async Completions patches --- sentry_sdk/integrations/openai.py | 140 ++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 46 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 480db9132d..cb0686d8a9 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -660,7 +660,7 @@ def _set_common_output_data( span.__exit__(None, None, None) -def _new_chat_completion_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": +def _new_sync_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return f(*args, **kwargs) @@ -693,7 +693,97 @@ def _new_chat_completion_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any _set_completions_api_input_data(span, kwargs, integration) start_time = time.perf_counter() - response = yield f, args, kwargs + + try: + response = f(*args, **kwargs) + except Exception as exc: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc) + reraise(*exc_info) + + # 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") + + if messages is not None and isinstance(messages, str): + messages = [messages] + + response._iterator = _wrap_synchronous_completions_chunk_iterator( + span=span, + integration=integration, + start_time=start_time, + messages=messages, + response=response, + old_iterator=response._iterator, + finish_span=True, + ) + + # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. + elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"): + messages = kwargs.get("messages") + + if messages is not None and isinstance(messages, str): + messages = [messages] + + response._iterator = _wrap_asynchronous_completions_chunk_iterator( + span=span, + integration=integration, + start_time=start_time, + messages=messages, + response=response, + old_iterator=response._iterator, + finish_span=True, + ) + else: + _set_completions_api_output_data( + span, response, kwargs, integration, finish_span=True + ) + + return response + + +async def _new_async_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None: + return await f(*args, **kwargs) + + if "messages" not in kwargs: + # invalid call (in all versions of openai), let it return error + return await f(*args, **kwargs) + + try: + iter(kwargs["messages"]) + except TypeError: + # invalid call (in all versions), messages must be iterable + return await f(*args, **kwargs) + + model = kwargs.get("model") + + span = sentry_sdk.start_span( + op=consts.OP.GEN_AI_CHAT, + name=f"chat {model}", + origin=OpenAIIntegration.origin, + ) + span.__enter__() + + span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") + + # Same bool handling as in https://github.com/openai/openai-python/blob/acd0c54d8a68efeedde0e5b4e6c310eef1ce7867/src/openai/resources/completions.py#L585 + is_streaming_response = kwargs.get("stream", False) or False + span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, is_streaming_response) + + _set_completions_api_input_data(span, kwargs, integration) + + start_time = time.perf_counter() + + try: + response = await f(*args, **kwargs) + except Exception as exc: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc) + reraise(*exc_info) # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. if isinstance(response, Stream) and hasattr(response, "_iterator"): @@ -1050,27 +1140,6 @@ def _set_embeddings_output_data( def _wrap_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]": - def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": - gen = _new_chat_completion_common(f, *args, **kwargs) - - try: - f, args, kwargs = next(gen) - except StopIteration as e: - return e.value - - try: - try: - result = f(*args, **kwargs) - except Exception as e: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _capture_exception(e) - reraise(*exc_info) - - return gen.send(result) - except StopIteration as e: - return e.value - @wraps(f) def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) @@ -1078,33 +1147,12 @@ def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any": # no "messages" means invalid call (in all versions of openai), let it return error return f(*args, **kwargs) - return _execute_sync(f, *args, **kwargs) + return _new_sync_chat_completion(f, *args, **kwargs) return _sentry_patched_create_sync def _wrap_async_chat_completion_create(f: "Callable[..., Any]") -> "Callable[..., Any]": - async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": - gen = _new_chat_completion_common(f, *args, **kwargs) - - try: - f, args, kwargs = next(gen) - except StopIteration as e: - return await e.value - - try: - try: - result = await f(*args, **kwargs) - except Exception as e: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _capture_exception(e) - reraise(*exc_info) - - return gen.send(result) - except StopIteration as e: - return e.value - @wraps(f) async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) @@ -1112,7 +1160,7 @@ async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any": # no "messages" means invalid call (in all versions of openai), let it return error return await f(*args, **kwargs) - return await _execute_async(f, *args, **kwargs) + return await _new_async_chat_completion(f, *args, **kwargs) return _sentry_patched_create_async From 5114533cf435b0d3bb0343888bbad2db508c2d7c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 4 May 2026 07:38:06 +0200 Subject: [PATCH 2/6] ref(openai): Separate sync and async Responses patches --- sentry_sdk/integrations/openai.py | 124 +++++++++++++++++++----------- 1 file changed, 81 insertions(+), 43 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index cb0686d8a9..2c337f61c9 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -1255,7 +1255,7 @@ async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any": return _sentry_patched_create_async -def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": +def _new_sync_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return f(*args, **kwargs) @@ -1278,7 +1278,14 @@ def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "An _set_responses_api_input_data(span, kwargs, integration) start_time = time.perf_counter() - response = yield f, args, kwargs + + try: + response = f(*args, **kwargs) + except Exception as exc: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc) + reraise(*exc_info) # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. if isinstance(response, Stream) and hasattr(response, "_iterator"): @@ -1321,68 +1328,99 @@ def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "An return response -def _wrap_responses_create(f: "Any") -> "Any": - def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": - gen = _new_responses_create_common(f, *args, **kwargs) +async def _new_async_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None: + return f(*args, **kwargs) - try: - f, args, kwargs = next(gen) - except StopIteration as e: - return e.value + model = kwargs.get("model") - try: - try: - result = f(*args, **kwargs) - except Exception as e: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _capture_exception(e) - reraise(*exc_info) + span = sentry_sdk.start_span( + op=consts.OP.GEN_AI_RESPONSES, + name=f"responses {model}", + origin=OpenAIIntegration.origin, + ) + span.__enter__() - return gen.send(result) - except StopIteration as e: - return e.value + span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") + + # Same bool handling as in https://github.com/openai/openai-python/blob/acd0c54d8a68efeedde0e5b4e6c310eef1ce7867/src/openai/resources/responses/responses.py#L940 + is_streaming_response = kwargs.get("stream", False) or False + span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, is_streaming_response) + + _set_responses_api_input_data(span, kwargs, integration) + + start_time = time.perf_counter() + + try: + response = await f(*args, **kwargs) + except Exception as exc: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc) + reraise(*exc_info) + + # 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") + + if input is not None and isinstance(input, str): + input = [input] + + response._iterator = _wrap_synchronous_responses_event_iterator( + span=span, + integration=integration, + start_time=start_time, + input=input, + response=response, + old_iterator=response._iterator, + finish_span=True, + ) + + # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. + elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"): + input = kwargs.get("input") + if input is not None and isinstance(input, str): + input = [input] + + response._iterator = _wrap_asynchronous_responses_event_iterator( + span=span, + integration=integration, + start_time=start_time, + input=input, + response=response, + old_iterator=response._iterator, + finish_span=True, + ) + else: + _set_responses_api_output_data( + span, response, kwargs, integration, finish_span=True + ) + + return response + + +def _wrap_responses_create(f: "Any") -> "Any": @wraps(f) def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return f(*args, **kwargs) - return _execute_sync(f, *args, **kwargs) + return _new_sync_responses_create(f, *args, **kwargs) return _sentry_patched_create_sync def _wrap_async_responses_create(f: "Any") -> "Any": - async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": - gen = _new_responses_create_common(f, *args, **kwargs) - - try: - f, args, kwargs = next(gen) - except StopIteration as e: - return await e.value - - try: - try: - result = await f(*args, **kwargs) - except Exception as e: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _capture_exception(e) - reraise(*exc_info) - - return gen.send(result) - except StopIteration as e: - return e.value - @wraps(f) async def _sentry_patched_responses_async(*args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return await f(*args, **kwargs) - return await _execute_async(f, *args, **kwargs) + return await _new_async_responses_create(f, *args, **kwargs) return _sentry_patched_responses_async From d49cfac4930627e2a0e6a9bcb3859886a40244d4 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 4 May 2026 07:41:44 +0200 Subject: [PATCH 3/6] ref(openai): Separate sync and async embeddings patches --- sentry_sdk/integrations/openai.py | 82 +++++++++++++++---------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 2c337f61c9..c08c350971 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -1165,7 +1165,7 @@ async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any": return _sentry_patched_create_async -def _new_embeddings_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": +def _new_sync_embeddings_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return f(*args, **kwargs) @@ -1180,7 +1180,13 @@ def _new_embeddings_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") _set_embeddings_input_data(span, kwargs, integration) - response = yield f, args, kwargs + try: + response = f(*args, **kwargs) + except Exception as exc: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc, manual_span_cleanup=False) + reraise(*exc_info) _set_embeddings_output_data( span, response, kwargs, integration, finish_span=False @@ -1189,68 +1195,58 @@ def _new_embeddings_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A return response -def _wrap_embeddings_create(f: "Any") -> "Any": - def _execute_sync(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": - gen = _new_embeddings_create_common(f, *args, **kwargs) +async def _new_async_embeddings_create( + f: "Any", *args: "Any", **kwargs: "Any" +) -> "Any": + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None: + return f(*args, **kwargs) - try: - f, args, kwargs = next(gen) - except StopIteration as e: - return e.value + model = kwargs.get("model") + + with sentry_sdk.start_span( + op=consts.OP.GEN_AI_EMBEDDINGS, + name=f"embeddings {model}", + origin=OpenAIIntegration.origin, + ) as span: + span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") + _set_embeddings_input_data(span, kwargs, integration) try: - try: - result = f(*args, **kwargs) - except Exception as e: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _capture_exception(e, manual_span_cleanup=False) - reraise(*exc_info) - - return gen.send(result) - except StopIteration as e: - return e.value + response = await f(*args, **kwargs) + except Exception as exc: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc, manual_span_cleanup=False) + reraise(*exc_info) + + _set_embeddings_output_data( + span, response, kwargs, integration, finish_span=False + ) + + return response + +def _wrap_embeddings_create(f: "Any") -> "Any": @wraps(f) def _sentry_patched_create_sync(*args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return f(*args, **kwargs) - return _execute_sync(f, *args, **kwargs) + return _new_sync_embeddings_create(f, *args, **kwargs) return _sentry_patched_create_sync def _wrap_async_embeddings_create(f: "Any") -> "Any": - async def _execute_async(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": - gen = _new_embeddings_create_common(f, *args, **kwargs) - - try: - f, args, kwargs = next(gen) - except StopIteration as e: - return await e.value - - try: - try: - result = await f(*args, **kwargs) - except Exception as e: - exc_info = sys.exc_info() - with capture_internal_exceptions(): - _capture_exception(e, manual_span_cleanup=False) - reraise(*exc_info) - - return gen.send(result) - except StopIteration as e: - return e.value - @wraps(f) async def _sentry_patched_create_async(*args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: return await f(*args, **kwargs) - return await _execute_async(f, *args, **kwargs) + return await _new_async_embeddings_create(f, *args, **kwargs) return _sentry_patched_create_async From d4eb421540ef1b4b6b24f2ec62b5c6bb7ea515d8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 4 May 2026 09:10:20 +0200 Subject: [PATCH 4/6] remove dead branches --- sentry_sdk/integrations/openai.py | 35 +------------------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index cb0686d8a9..7fbe3c977b 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -719,22 +719,6 @@ def _new_sync_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": finish_span=True, ) - # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. - elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"): - messages = kwargs.get("messages") - - if messages is not None and isinstance(messages, str): - messages = [messages] - - response._iterator = _wrap_asynchronous_completions_chunk_iterator( - span=span, - integration=integration, - start_time=start_time, - messages=messages, - response=response, - old_iterator=response._iterator, - finish_span=True, - ) else: _set_completions_api_output_data( span, response, kwargs, integration, finish_span=True @@ -786,24 +770,7 @@ async def _new_async_chat_completion(f: "Any", *args: "Any", **kwargs: "Any") -> reraise(*exc_info) # 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") - - if messages is not None and isinstance(messages, str): - messages = [messages] - - response._iterator = _wrap_synchronous_completions_chunk_iterator( - span=span, - integration=integration, - start_time=start_time, - messages=messages, - response=response, - old_iterator=response._iterator, - finish_span=True, - ) - - # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. - elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"): + if isinstance(response, AsyncStream) and hasattr(response, "_iterator"): messages = kwargs.get("messages") if messages is not None and isinstance(messages, str): From 07d2a49358fd8e860bab70ce0e15a4c2f23d89f7 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 4 May 2026 09:12:21 +0200 Subject: [PATCH 5/6] remove dead code and add await in early return --- sentry_sdk/integrations/openai.py | 37 ++----------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 2c337f61c9..9c242bd98d 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -1304,22 +1304,6 @@ def _new_sync_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any" finish_span=True, ) - # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. - elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"): - input = kwargs.get("input") - - if input is not None and isinstance(input, str): - input = [input] - - response._iterator = _wrap_asynchronous_responses_event_iterator( - span=span, - integration=integration, - start_time=start_time, - input=input, - response=response, - old_iterator=response._iterator, - finish_span=True, - ) else: _set_responses_api_output_data( span, response, kwargs, integration, finish_span=True @@ -1331,7 +1315,7 @@ def _new_sync_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any" async def _new_async_responses_create(f: "Any", *args: "Any", **kwargs: "Any") -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: - return f(*args, **kwargs) + return await f(*args, **kwargs) model = kwargs.get("model") @@ -1361,24 +1345,7 @@ async def _new_async_responses_create(f: "Any", *args: "Any", **kwargs: "Any") - reraise(*exc_info) # 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") - - if input is not None and isinstance(input, str): - input = [input] - - response._iterator = _wrap_synchronous_responses_event_iterator( - span=span, - integration=integration, - start_time=start_time, - input=input, - response=response, - old_iterator=response._iterator, - finish_span=True, - ) - - # Attribute check to fail gracefully if the attribute is not present in future `openai` versions. - elif isinstance(response, AsyncStream) and hasattr(response, "_iterator"): + if isinstance(response, AsyncStream) and hasattr(response, "_iterator"): input = kwargs.get("input") if input is not None and isinstance(input, str): From 4004c0b0143666975bbd8ae677350be9c812748f Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Mon, 4 May 2026 09:14:21 +0200 Subject: [PATCH 6/6] add await in early return --- 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 c08c350971..78212dea3e 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -1200,7 +1200,7 @@ async def _new_async_embeddings_create( ) -> "Any": integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) if integration is None: - return f(*args, **kwargs) + return await f(*args, **kwargs) model = kwargs.get("model")