From 4917b6a6570908837461344f27348a86b955bfb3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 10:19:58 +0100 Subject: [PATCH 01/10] ref(openai): Move input handling code into API-specific functions --- sentry_sdk/integrations/openai.py | 135 ++++++++++++++++------- tests/integrations/openai/test_openai.py | 14 +++ 2 files changed, 110 insertions(+), 39 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index a5556b8776..dfa3225d74 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -219,28 +219,13 @@ def _calculate_token_usage( ) -def _commmon_set_input_data( +def _set_responses_api_input_data( span: "Span", kwargs: "dict[str, Any]", + integration: "OpenAIIntegration", ) -> None: - # Input attributes: Common - set_data_normalized(span, SPANDATA.GEN_AI_SYSTEM, "openai") - - # Input attributes: Optional - kwargs_keys_to_attributes = { - "model": SPANDATA.GEN_AI_REQUEST_MODEL, - "stream": SPANDATA.GEN_AI_RESPONSE_STREAMING, - "max_tokens": SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, - "presence_penalty": SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, - "frequency_penalty": SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, - "temperature": SPANDATA.GEN_AI_REQUEST_TEMPERATURE, - "top_p": SPANDATA.GEN_AI_REQUEST_TOP_P, - } - for key, attribute in kwargs_keys_to_attributes.items(): - value = kwargs.get(key) - - if value is not None and _is_given(value): - set_data_normalized(span, attribute, value) + explicit_instructions: "Union[Optional[str], Omit]" = kwargs.get("instructions") + messages: "Optional[Union[str, ResponseInputParam]]" = kwargs.get("input") # Input attributes: Tools tools = kwargs.get("tools") @@ -249,18 +234,36 @@ def _commmon_set_input_data( span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools) ) + model = kwargs.get("model") + if model is not None and _is_given(model): + span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) -def _set_responses_api_input_data( - span: "Span", - kwargs: "dict[str, Any]", - integration: "OpenAIIntegration", -) -> None: - explicit_instructions: "Union[Optional[str], Omit]" = kwargs.get("instructions") - messages: "Optional[Union[str, ResponseInputParam]]" = kwargs.get("input") + stream = kwargs.get("stream") + if stream is not None and _is_given(stream): + span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + + max_tokens = kwargs.get("max_tokens") + if max_tokens is not None and _is_given(max_tokens): + span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + + presence_penalty = kwargs.get("presence_penalty") + if presence_penalty is not None and _is_given(presence_penalty): + span.set_data(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + + frequency_penalty = kwargs.get("frequency_penalty") + if frequency_penalty is not None and _is_given(frequency_penalty): + span.set_data(SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty) + + temperature = kwargs.get("temperature") + if temperature is not None and _is_given(temperature): + span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + + top_p = kwargs.get("top_p") + if top_p is not None and _is_given(top_p): + span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) if not should_send_default_pii() or not integration.include_prompts: set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") - _commmon_set_input_data(span, kwargs) return if ( @@ -281,12 +284,10 @@ def _set_responses_api_input_data( ) set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") - _commmon_set_input_data(span, kwargs) return if messages is None: set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") - _commmon_set_input_data(span, kwargs) return instructions_text_parts: "list[TextPart]" = [] @@ -319,7 +320,6 @@ def _set_responses_api_input_data( ) set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") - _commmon_set_input_data(span, kwargs) return non_system_messages = [ @@ -335,7 +335,6 @@ def _set_responses_api_input_data( ) set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") - _commmon_set_input_data(span, kwargs) def _set_completions_api_input_data( @@ -347,13 +346,47 @@ def _set_completions_api_input_data( "messages" ) + # Input attributes: Tools + tools = kwargs.get("tools") + if tools is not None and _is_given(tools) and len(tools) > 0: + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools) + ) + + model = kwargs.get("model") + if model is not None and _is_given(model): + span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) + + stream = kwargs.get("stream") + if stream is not None and _is_given(stream): + span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + + max_tokens = kwargs.get("max_tokens") + if max_tokens is not None and _is_given(max_tokens): + span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + + presence_penalty = kwargs.get("presence_penalty") + if presence_penalty is not None and _is_given(presence_penalty): + span.set_data(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + + frequency_penalty = kwargs.get("frequency_penalty") + if frequency_penalty is not None and _is_given(frequency_penalty): + span.set_data(SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty) + + temperature = kwargs.get("temperature") + if temperature is not None and _is_given(temperature): + span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + + top_p = kwargs.get("top_p") + if top_p is not None and _is_given(top_p): + span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + if ( not should_send_default_pii() or not integration.include_prompts or messages is None ): set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat") - _commmon_set_input_data(span, kwargs) return if isinstance(messages, str): @@ -365,13 +398,11 @@ def _set_completions_api_input_data( span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False ) set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat") - _commmon_set_input_data(span, kwargs) return # dict special case following https://github.com/openai/openai-python/blob/3e0c05b84a2056870abf3bd6a5e7849020209cc3/src/openai/_utils/_transform.py#L194-L197 if not isinstance(messages, Iterable) or isinstance(messages, dict): set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat") - _commmon_set_input_data(span, kwargs) return messages = list(messages) @@ -399,7 +430,6 @@ def _set_completions_api_input_data( ) set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "chat") - _commmon_set_input_data(span, kwargs) def _set_embeddings_input_data( @@ -411,19 +441,45 @@ def _set_embeddings_input_data( "input" ) + model = kwargs.get("model") + if model is not None and _is_given(model): + span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) + + stream = kwargs.get("stream") + if stream is not None and _is_given(stream): + span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + + max_tokens = kwargs.get("max_tokens") + if max_tokens is not None and _is_given(max_tokens): + span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + + presence_penalty = kwargs.get("presence_penalty") + if presence_penalty is not None and _is_given(presence_penalty): + span.set_data(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + + frequency_penalty = kwargs.get("frequency_penalty") + if frequency_penalty is not None and _is_given(frequency_penalty): + span.set_data(SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty) + + temperature = kwargs.get("temperature") + if temperature is not None and _is_given(temperature): + span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + + top_p = kwargs.get("top_p") + if top_p is not None and _is_given(top_p): + span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + if ( not should_send_default_pii() or not integration.include_prompts or messages is None ): set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings") - _commmon_set_input_data(span, kwargs) return if isinstance(messages, str): set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings") - _commmon_set_input_data(span, kwargs) normalized_messages = normalize_message_roles([messages]) # type: ignore scope = sentry_sdk.get_current_scope() @@ -440,7 +496,6 @@ def _set_embeddings_input_data( # dict special case following https://github.com/openai/openai-python/blob/3e0c05b84a2056870abf3bd6a5e7849020209cc3/src/openai/_utils/_transform.py#L194-L197 if not isinstance(messages, Iterable) or isinstance(messages, dict): set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings") - _commmon_set_input_data(span, kwargs) return messages = list(messages) @@ -458,7 +513,6 @@ def _set_embeddings_input_data( ) set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "embeddings") - _commmon_set_input_data(span, kwargs) def _set_common_output_data( @@ -551,6 +605,7 @@ def _new_chat_completion_common(f: "Any", *args: "Any", **kwargs: "Any") -> "Any ) span.__enter__() + span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") _set_completions_api_input_data(span, kwargs, integration) start_time = time.perf_counter() @@ -945,6 +1000,7 @@ def _new_embeddings_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "A name=f"embeddings {model}", origin=OpenAIIntegration.origin, ) as span: + span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") _set_embeddings_input_data(span, kwargs, integration) response = yield f, args, kwargs @@ -1036,6 +1092,7 @@ def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "An ) span.__enter__() + span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") _set_responses_api_input_data(span, kwargs, integration) start_time = time.perf_counter() diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 23fb0d9ad7..249a32b768 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -161,6 +161,7 @@ def test_nonstreaming_chat_completion_no_prompts( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in span["data"] assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"] @@ -240,6 +241,7 @@ def test_nonstreaming_chat_completion(sentry_init, capture_events, messages, req assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" param_id = request.node.callspec.id if "blocks" in param_id: @@ -306,6 +308,7 @@ async def test_nonstreaming_chat_completion_async_no_prompts( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in span["data"] assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"] @@ -385,6 +388,7 @@ async def test_nonstreaming_chat_completion_async( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" param_id = request.node.callspec.id if "blocks" in param_id: @@ -503,6 +507,7 @@ def test_streaming_chat_completion_no_prompts( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "model-id" @@ -630,6 +635,7 @@ def test_streaming_chat_completion(sentry_init, capture_events, messages, reques assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" param_id = request.node.callspec.id if "blocks" in param_id: @@ -758,6 +764,7 @@ async def test_streaming_chat_completion_async_no_prompts( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "model-id" @@ -895,6 +902,7 @@ async def test_streaming_chat_completion_async( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.chat" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "model-id" @@ -1036,6 +1044,7 @@ def test_embeddings_create_no_pii( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.embeddings" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert SPANDATA.GEN_AI_EMBEDDINGS_INPUT not in span["data"] @@ -1116,6 +1125,7 @@ def test_embeddings_create(sentry_init, capture_events, input, request): assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.embeddings" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" param_id = request.node.callspec.id if param_id == "string": @@ -1187,6 +1197,7 @@ async def test_embeddings_create_async_no_pii( assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.embeddings" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert SPANDATA.GEN_AI_EMBEDDINGS_INPUT not in span["data"] @@ -1270,6 +1281,7 @@ async def test_embeddings_create_async(sentry_init, capture_events, input, reque assert tx["type"] == "transaction" span = tx["spans"][0] assert span["op"] == "gen_ai.embeddings" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" param_id = request.node.callspec.id if param_id == "string": @@ -2772,6 +2784,7 @@ def test_streaming_responses_api( (transaction,) = events (span,) = transaction["spans"] assert span["op"] == "gen_ai.responses" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "response-model-id" @@ -2829,6 +2842,7 @@ async def test_streaming_responses_api_async( (transaction,) = events (span,) = transaction["spans"] assert span["op"] == "gen_ai.responses" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "response-model-id" From 426154603849122896f939a82a90d3c6022a7dca Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 10:38:23 +0100 Subject: [PATCH 02/10] keep set_data_normalized --- sentry_sdk/integrations/openai.py | 48 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index dfa3225d74..c79d5d5a43 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -236,31 +236,33 @@ def _set_responses_api_input_data( model = kwargs.get("model") if model is not None and _is_given(model): - span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_MODEL, model) stream = kwargs.get("stream") if stream is not None and _is_given(stream): - span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + set_data_normalized(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) max_tokens = kwargs.get("max_tokens") if max_tokens is not None and _is_given(max_tokens): - span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): - span.set_data(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) frequency_penalty = kwargs.get("frequency_penalty") if frequency_penalty is not None and _is_given(frequency_penalty): - span.set_data(SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty) + set_data_normalized( + SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty + ) temperature = kwargs.get("temperature") if temperature is not None and _is_given(temperature): - span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) top_p = kwargs.get("top_p") if top_p is not None and _is_given(top_p): - span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) if not should_send_default_pii() or not integration.include_prompts: set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") @@ -355,31 +357,33 @@ def _set_completions_api_input_data( model = kwargs.get("model") if model is not None and _is_given(model): - span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_MODEL, model) stream = kwargs.get("stream") if stream is not None and _is_given(stream): - span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + set_data_normalized(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) max_tokens = kwargs.get("max_tokens") if max_tokens is not None and _is_given(max_tokens): - span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): - span.set_data(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) frequency_penalty = kwargs.get("frequency_penalty") if frequency_penalty is not None and _is_given(frequency_penalty): - span.set_data(SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty) + set_data_normalized( + SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty + ) temperature = kwargs.get("temperature") if temperature is not None and _is_given(temperature): - span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) top_p = kwargs.get("top_p") if top_p is not None and _is_given(top_p): - span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) if ( not should_send_default_pii() @@ -443,31 +447,33 @@ def _set_embeddings_input_data( model = kwargs.get("model") if model is not None and _is_given(model): - span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_MODEL, model) stream = kwargs.get("stream") if stream is not None and _is_given(stream): - span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + set_data_normalized(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) max_tokens = kwargs.get("max_tokens") if max_tokens is not None and _is_given(max_tokens): - span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): - span.set_data(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) frequency_penalty = kwargs.get("frequency_penalty") if frequency_penalty is not None and _is_given(frequency_penalty): - span.set_data(SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty) + set_data_normalized( + SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty + ) temperature = kwargs.get("temperature") if temperature is not None and _is_given(temperature): - span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) top_p = kwargs.get("top_p") if top_p is not None and _is_given(top_p): - span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + set_data_normalized(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) if ( not should_send_default_pii() From 1f6149e0fa2e878fac06c2edb23305c022557935 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 10:59:09 +0100 Subject: [PATCH 03/10] . --- sentry_sdk/integrations/openai.py | 56 +++++++++++++++++++------------ 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index c79d5d5a43..59590abcce 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -236,33 +236,35 @@ def _set_responses_api_input_data( model = kwargs.get("model") if model is not None and _is_given(model): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_MODEL, model) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MODEL, model) stream = kwargs.get("stream") if stream is not None and _is_given(stream): - set_data_normalized(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) max_tokens = kwargs.get("max_tokens") if max_tokens is not None and _is_given(max_tokens): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty + ) frequency_penalty = kwargs.get("frequency_penalty") if frequency_penalty is not None and _is_given(frequency_penalty): set_data_normalized( - SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty + span, SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty ) temperature = kwargs.get("temperature") if temperature is not None and _is_given(temperature): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) top_p = kwargs.get("top_p") if top_p is not None and _is_given(top_p): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) if not should_send_default_pii() or not integration.include_prompts: set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") @@ -357,33 +359,39 @@ def _set_completions_api_input_data( model = kwargs.get("model") if model is not None and _is_given(model): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_MODEL, model) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MODEL, model) stream = kwargs.get("stream") if stream is not None and _is_given(stream): - set_data_normalized(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) max_tokens = kwargs.get("max_tokens") if max_tokens is not None and _is_given(max_tokens): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty + ) frequency_penalty = kwargs.get("frequency_penalty") if frequency_penalty is not None and _is_given(frequency_penalty): set_data_normalized( - SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty + span, SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty ) temperature = kwargs.get("temperature") if temperature is not None and _is_given(temperature): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) top_p = kwargs.get("top_p") if top_p is not None and _is_given(top_p): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + + if not should_send_default_pii() or not integration.include_prompts: + set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") + return if ( not should_send_default_pii() @@ -447,33 +455,39 @@ def _set_embeddings_input_data( model = kwargs.get("model") if model is not None and _is_given(model): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_MODEL, model) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MODEL, model) stream = kwargs.get("stream") if stream is not None and _is_given(stream): - set_data_normalized(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) max_tokens = kwargs.get("max_tokens") if max_tokens is not None and _is_given(max_tokens): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty) + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty + ) frequency_penalty = kwargs.get("frequency_penalty") if frequency_penalty is not None and _is_given(frequency_penalty): set_data_normalized( - SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty + span, SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty ) temperature = kwargs.get("temperature") if temperature is not None and _is_given(temperature): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) top_p = kwargs.get("top_p") if top_p is not None and _is_given(top_p): - set_data_normalized(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + + if not should_send_default_pii() or not integration.include_prompts: + set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") + return if ( not should_send_default_pii() From 36842e05c818b0ae526b66a6c3e2d63f5eaf344f Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 11:03:57 +0100 Subject: [PATCH 04/10] . --- sentry_sdk/integrations/openai.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 59590abcce..a82c406ad7 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -389,10 +389,6 @@ def _set_completions_api_input_data( if top_p is not None and _is_given(top_p): set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) - if not should_send_default_pii() or not integration.include_prompts: - set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") - return - if ( not should_send_default_pii() or not integration.include_prompts @@ -485,10 +481,6 @@ def _set_embeddings_input_data( if top_p is not None and _is_given(top_p): set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) - if not should_send_default_pii() or not integration.include_prompts: - set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") - return - if ( not should_send_default_pii() or not integration.include_prompts From 532fa654306efe6272a454e73b8aa4c7b29a6350 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 11:25:30 +0100 Subject: [PATCH 05/10] . --- sentry_sdk/integrations/openai.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index a82c406ad7..f47cd46384 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -227,7 +227,6 @@ def _set_responses_api_input_data( explicit_instructions: "Union[Optional[str], Omit]" = kwargs.get("instructions") messages: "Optional[Union[str, ResponseInputParam]]" = kwargs.get("input") - # Input attributes: Tools tools = kwargs.get("tools") if tools is not None and _is_given(tools) and len(tools) > 0: set_data_normalized( @@ -350,7 +349,6 @@ def _set_completions_api_input_data( "messages" ) - # Input attributes: Tools tools = kwargs.get("tools") if tools is not None and _is_given(tools) and len(tools) > 0: set_data_normalized( @@ -449,6 +447,12 @@ def _set_embeddings_input_data( "input" ) + tools = kwargs.get("tools") + if tools is not None and _is_given(tools) and len(tools) > 0: + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS, safe_serialize(tools) + ) + model = kwargs.get("model") if model is not None and _is_given(model): set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MODEL, model) From 9fb4bff88c06be8b482646dbc9504abfd8583b83 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 13:15:49 +0100 Subject: [PATCH 06/10] fix(openai): Use max_output_tokens for Responses API --- sentry_sdk/integrations/openai.py | 4 ++-- tests/integrations/openai/test_openai.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index f47cd46384..97a69eda77 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -241,9 +241,9 @@ def _set_responses_api_input_data( if stream is not None and _is_given(stream): set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) - max_tokens = kwargs.get("max_tokens") + max_tokens = kwargs.get("max_output_tokens") if max_tokens is not None and _is_given(max_tokens): - set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + span.set_data(span, SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 249a32b768..aac6ded13d 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -1766,6 +1766,7 @@ def test_ai_client_span_responses_api_no_pii(sentry_init, capture_events): model="gpt-4o", instructions="You are a coding assistant that talks like a pirate.", input="How do I check if a Python object is an instance of a class?", + max_output_tokens=100, ) (transaction,) = events @@ -1776,6 +1777,7 @@ def test_ai_client_span_responses_api_no_pii(sentry_init, capture_events): assert spans[0]["origin"] == "auto.ai.openai" assert spans[0]["data"] == { "gen_ai.operation.name": "responses", + "gen_ai.request.max_tokens": 100, "gen_ai.request.model": "gpt-4o", "gen_ai.response.model": "response-model-id", "gen_ai.system": "openai", @@ -1876,6 +1878,7 @@ def test_ai_client_span_responses_api( model="gpt-4o", instructions=instructions, input=input, + max_output_tokens=100, ) (transaction,) = events @@ -1887,6 +1890,7 @@ def test_ai_client_span_responses_api( expected_data = { "gen_ai.operation.name": "responses", + "gen_ai.request.max_tokens": 100, "gen_ai.system": "openai", "gen_ai.response.model": "response-model-id", "gen_ai.usage.input_tokens": 20, @@ -2178,6 +2182,7 @@ async def test_ai_client_span_responses_async_api( model="gpt-4o", instructions=instructions, input=input, + max_output_tokens=100, ) (transaction,) = events @@ -2189,6 +2194,7 @@ async def test_ai_client_span_responses_async_api( expected_data = { "gen_ai.operation.name": "responses", + "gen_ai.request.max_tokens": 100, "gen_ai.request.messages": '["How do I check if a Python object is an instance of a class?"]', "gen_ai.request.model": "gpt-4o", "gen_ai.response.model": "response-model-id", @@ -2447,6 +2453,7 @@ async def test_ai_client_span_streaming_responses_async_api( instructions=instructions, input=input, stream=True, + max_output_tokens=100, ) async for _ in result: pass @@ -2460,6 +2467,7 @@ async def test_ai_client_span_streaming_responses_async_api( expected_data = { "gen_ai.operation.name": "responses", + "gen_ai.request.max_tokens": 100, "gen_ai.response.model": "response-model-id", "gen_ai.response.streaming": True, "gen_ai.system": "openai", @@ -2772,6 +2780,7 @@ def test_streaming_responses_api( model="some-model", input="hello", stream=True, + max_output_tokens=100, ) response_string = "" @@ -2785,6 +2794,7 @@ def test_streaming_responses_api( (span,) = transaction["spans"] assert span["op"] == "gen_ai.responses" assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" + assert span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "response-model-id" @@ -2830,6 +2840,7 @@ async def test_streaming_responses_api_async( model="some-model", input="hello", stream=True, + max_output_tokens=100, ) response_string = "" @@ -2843,6 +2854,7 @@ async def test_streaming_responses_api_async( (span,) = transaction["spans"] assert span["op"] == "gen_ai.responses" assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" + assert span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "response-model-id" From 9935178bf44b090fd029053a06ff964ce52d94b0 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 13:18:36 +0100 Subject: [PATCH 07/10] . --- 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 97a69eda77..a637e4fc47 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -243,7 +243,7 @@ def _set_responses_api_input_data( max_tokens = kwargs.get("max_output_tokens") if max_tokens is not None and _is_given(max_tokens): - span.set_data(span, SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) + span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) presence_penalty = kwargs.get("presence_penalty") if presence_penalty is not None and _is_given(presence_penalty): From 645570743e08c1fc6d3b6e492589b2d3a031866b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 13:35:08 +0100 Subject: [PATCH 08/10] fix(openai): Simplify Responses input handling --- sentry_sdk/integrations/openai.py | 22 +++++----------------- tests/integrations/openai/test_openai.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index a637e4fc47..eeeede575f 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -234,36 +234,24 @@ def _set_responses_api_input_data( ) model = kwargs.get("model") - if model is not None and _is_given(model): - set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_MODEL, model) + if model is not None: + span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) stream = kwargs.get("stream") if stream is not None and _is_given(stream): - set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) + span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) max_tokens = kwargs.get("max_output_tokens") if max_tokens is not None and _is_given(max_tokens): span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) - presence_penalty = kwargs.get("presence_penalty") - if presence_penalty is not None and _is_given(presence_penalty): - set_data_normalized( - span, SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, presence_penalty - ) - - frequency_penalty = kwargs.get("frequency_penalty") - if frequency_penalty is not None and _is_given(frequency_penalty): - set_data_normalized( - span, SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, frequency_penalty - ) - temperature = kwargs.get("temperature") if temperature is not None and _is_given(temperature): - set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) + span.set_data(SPANDATA.GEN_AI_REQUEST_TEMPERATURE, temperature) top_p = kwargs.get("top_p") if top_p is not None and _is_given(top_p): - set_data_normalized(span, SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) + span.set_data(SPANDATA.GEN_AI_REQUEST_TOP_P, top_p) if not should_send_default_pii() or not integration.include_prompts: set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, "responses") diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index aac6ded13d..390b26481b 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -1767,6 +1767,8 @@ def test_ai_client_span_responses_api_no_pii(sentry_init, capture_events): instructions="You are a coding assistant that talks like a pirate.", input="How do I check if a Python object is an instance of a class?", max_output_tokens=100, + temperature=0.7, + top_p=0.9, ) (transaction,) = events @@ -1778,6 +1780,8 @@ def test_ai_client_span_responses_api_no_pii(sentry_init, capture_events): assert spans[0]["data"] == { "gen_ai.operation.name": "responses", "gen_ai.request.max_tokens": 100, + "gen_ai.request.temperature": 0.7, + "gen_ai.request.top_p": 0.9, "gen_ai.request.model": "gpt-4o", "gen_ai.response.model": "response-model-id", "gen_ai.system": "openai", @@ -1879,6 +1883,8 @@ def test_ai_client_span_responses_api( instructions=instructions, input=input, max_output_tokens=100, + temperature=0.7, + top_p=0.9, ) (transaction,) = events @@ -1891,6 +1897,8 @@ def test_ai_client_span_responses_api( expected_data = { "gen_ai.operation.name": "responses", "gen_ai.request.max_tokens": 100, + "gen_ai.request.temperature": 0.7, + "gen_ai.request.top_p": 0.9, "gen_ai.system": "openai", "gen_ai.response.model": "response-model-id", "gen_ai.usage.input_tokens": 20, @@ -2183,6 +2191,8 @@ async def test_ai_client_span_responses_async_api( instructions=instructions, input=input, max_output_tokens=100, + temperature=0.7, + top_p=0.9, ) (transaction,) = events @@ -2195,6 +2205,8 @@ async def test_ai_client_span_responses_async_api( expected_data = { "gen_ai.operation.name": "responses", "gen_ai.request.max_tokens": 100, + "gen_ai.request.temperature": 0.7, + "gen_ai.request.top_p": 0.9, "gen_ai.request.messages": '["How do I check if a Python object is an instance of a class?"]', "gen_ai.request.model": "gpt-4o", "gen_ai.response.model": "response-model-id", @@ -2454,6 +2466,8 @@ async def test_ai_client_span_streaming_responses_async_api( input=input, stream=True, max_output_tokens=100, + temperature=0.7, + top_p=0.9, ) async for _ in result: pass @@ -2468,6 +2482,8 @@ async def test_ai_client_span_streaming_responses_async_api( expected_data = { "gen_ai.operation.name": "responses", "gen_ai.request.max_tokens": 100, + "gen_ai.request.temperature": 0.7, + "gen_ai.request.top_p": 0.9, "gen_ai.response.model": "response-model-id", "gen_ai.response.streaming": True, "gen_ai.system": "openai", @@ -2781,6 +2797,8 @@ def test_streaming_responses_api( input="hello", stream=True, max_output_tokens=100, + temperature=0.7, + top_p=0.9, ) response_string = "" @@ -2795,6 +2813,8 @@ def test_streaming_responses_api( assert span["op"] == "gen_ai.responses" assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 + assert span["data"][SPANDATA.GEN_AI_REQUEST_TEMPERATURE] == 0.7 + assert span["data"][SPANDATA.GEN_AI_REQUEST_TOP_P] == 0.9 assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "response-model-id" @@ -2841,6 +2861,8 @@ async def test_streaming_responses_api_async( input="hello", stream=True, max_output_tokens=100, + temperature=0.7, + top_p=0.9, ) response_string = "" @@ -2855,6 +2877,8 @@ async def test_streaming_responses_api_async( assert span["op"] == "gen_ai.responses" assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" assert span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 + assert span["data"][SPANDATA.GEN_AI_REQUEST_TEMPERATURE] == 0.7 + assert span["data"][SPANDATA.GEN_AI_REQUEST_TOP_P] == 0.9 assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "response-model-id" From fb93ba5c26c622f109bd8d6ff53a41c1daae97d1 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 18 Mar 2026 13:53:50 +0100 Subject: [PATCH 09/10] fix(openai): Always set gen_ai.response.streaming for Responses --- sentry_sdk/integrations/openai.py | 8 +++----- tests/integrations/openai/test_openai.py | 3 +++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index eeeede575f..54574ba752 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -237,10 +237,6 @@ def _set_responses_api_input_data( if model is not None: span.set_data(SPANDATA.GEN_AI_REQUEST_MODEL, model) - stream = kwargs.get("stream") - if stream is not None and _is_given(stream): - span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, stream) - max_tokens = kwargs.get("max_output_tokens") if max_tokens is not None and _is_given(max_tokens): span.set_data(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, max_tokens) @@ -1097,12 +1093,14 @@ def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "An span.__enter__() span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") + is_streaming_response = kwargs.get("stream", 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() response = yield f, args, kwargs - is_streaming_response = kwargs.get("stream", False) if is_streaming_response: _set_streaming_responses_api_output_data( span, response, kwargs, integration, start_time, finish_span=True diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 390b26481b..237fe7d71e 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -1784,6 +1784,7 @@ def test_ai_client_span_responses_api_no_pii(sentry_init, capture_events): "gen_ai.request.top_p": 0.9, "gen_ai.request.model": "gpt-4o", "gen_ai.response.model": "response-model-id", + "gen_ai.response.streaming": False, "gen_ai.system": "openai", "gen_ai.usage.input_tokens": 20, "gen_ai.usage.input_tokens.cached": 5, @@ -1901,6 +1902,7 @@ def test_ai_client_span_responses_api( "gen_ai.request.top_p": 0.9, "gen_ai.system": "openai", "gen_ai.response.model": "response-model-id", + "gen_ai.response.streaming": False, "gen_ai.usage.input_tokens": 20, "gen_ai.usage.input_tokens.cached": 5, "gen_ai.usage.output_tokens": 10, @@ -2210,6 +2212,7 @@ async def test_ai_client_span_responses_async_api( "gen_ai.request.messages": '["How do I check if a Python object is an instance of a class?"]', "gen_ai.request.model": "gpt-4o", "gen_ai.response.model": "response-model-id", + "gen_ai.response.streaming": False, "gen_ai.system": "openai", "gen_ai.usage.input_tokens": 20, "gen_ai.usage.input_tokens.cached": 5, From 2c17f17a116ba156b70b47943c540c889079018c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 25 Mar 2026 14:20:22 +0100 Subject: [PATCH 10/10] vendor bool handling --- sentry_sdk/integrations/openai.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 54574ba752..b991e38eb4 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -1093,7 +1093,9 @@ def _new_responses_create_common(f: "Any", *args: "Any", **kwargs: "Any") -> "An span.__enter__() span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") - is_streaming_response = kwargs.get("stream", False) + + # 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)