From d4c4cd4513c54e725f5917be32080cdc820dabf5 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 25 Mar 2026 14:03:18 +0100 Subject: [PATCH 01/24] fix(langchain): Set agent name as gen_ai.agent.name --- sentry_sdk/integrations/langchain.py | 154 +++++++----------- .../integrations/langchain/test_langchain.py | 6 + 2 files changed, 66 insertions(+), 94 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index f4ec75310d..11b44b7096 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -1,4 +1,3 @@ -import contextvars import itertools import sys import json @@ -153,44 +152,6 @@ def _transform_langchain_message_content(content: "Any") -> "Any": return content -# Contextvar to track agent names in a stack for re-entrant agent support -_agent_stack: "contextvars.ContextVar[Optional[List[Optional[str]]]]" = ( - contextvars.ContextVar("langchain_agent_stack", default=None) -) - - -def _push_agent(agent_name: "Optional[str]") -> None: - """Push an agent name onto the stack.""" - stack = _agent_stack.get() - if stack is None: - stack = [] - else: - # Copy the list to maintain contextvar isolation across async contexts - stack = stack.copy() - stack.append(agent_name) - _agent_stack.set(stack) - - -def _pop_agent() -> "Optional[str]": - """Pop an agent name from the stack and return it.""" - stack = _agent_stack.get() - if stack: - # Copy the list to maintain contextvar isolation across async contexts - stack = stack.copy() - agent_name = stack.pop() - _agent_stack.set(stack) - return agent_name - return None - - -def _get_current_agent() -> "Optional[str]": - """Get the current agent name (top of stack) without removing it.""" - stack = _agent_stack.get() - if stack: - return stack[-1] - return None - - def _get_system_instructions(messages: "List[List[BaseMessage]]") -> "List[str]": system_instructions = [] @@ -454,8 +415,8 @@ def on_chat_model_start( elif "openai" in ai_type: span.set_data(SPANDATA.GEN_AI_SYSTEM, "openai") - agent_name = _get_current_agent() - if agent_name: + agent_name = kwargs.get("metadata", {}).get("lc_agent_name") + if agent_name is not None: span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name) for key, attribute in DATA_FIELDS.items(): @@ -654,8 +615,8 @@ def on_tool_start( if tool_description is not None: span.set_data(SPANDATA.GEN_AI_TOOL_DESCRIPTION, tool_description) - agent_name = _get_current_agent() - if agent_name: + agent_name = kwargs.get("metadata", {}).get("lc_agent_name") + if agent_name is not None: span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name) if should_send_default_pii() and self.include_prompts: @@ -782,9 +743,7 @@ def _record_token_usage(span: "Span", response: "Any") -> None: span.set_data(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens) -def _get_request_data( - obj: "Any", args: "Any", kwargs: "Any" -) -> "tuple[Optional[str], Optional[List[Any]]]": +def _get_available_tools(obj: "Any") -> "tuple[Optional[str], Optional[List[Any]]]": """ Get the agent name and available tools for the agent. """ @@ -799,6 +758,13 @@ def _get_request_data( ) tools = tools if tools and len(tools) > 0 else None + return tools + + +def _get_run_name(obj: "Any", args: "Any"): + agent = getattr(obj, "agent", None) + runnable = getattr(agent, "runnable", None) + runnable_config = getattr(runnable, "config", {}) try: agent_name = None if len(args) > 1: @@ -808,7 +774,7 @@ def _get_request_data( except Exception: pass - return (agent_name, tools) + return agent_name def _simplify_langchain_tools(tools: "Any") -> "Optional[List[Any]]": @@ -976,58 +942,53 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if integration is None: return f(self, *args, **kwargs) - agent_name, tools = _get_request_data(self, args, kwargs) start_span_function = get_start_span_function() - + run_name = _get_run_name(self, args) with start_span_function( op=OP.GEN_AI_INVOKE_AGENT, - name=f"invoke_agent {agent_name}" if agent_name else "invoke_agent", + name=run_name, origin=LangchainIntegration.origin, ) as span: - _push_agent(agent_name) - try: - if agent_name: - span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name) + if run_name: + span.set_data(SPANDATA.GEN_AI_AGENT_NAME, run_name) - span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") - span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, False) + span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") + span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, False) - _set_tools_on_span(span, tools) + tools = _get_available_tools(self) + _set_tools_on_span(span, tools) - # Run the agent - result = f(self, *args, **kwargs) + # Run the agent + result = f(self, *args, **kwargs) - input = result.get("input") - if ( - input is not None - and should_send_default_pii() - and integration.include_prompts - ): - normalized_messages = normalize_message_roles([input]) - scope = sentry_sdk.get_current_scope() - messages_data = truncate_and_annotate_messages( - normalized_messages, span, scope + input = result.get("input") + if ( + input is not None + and should_send_default_pii() + and integration.include_prompts + ): + normalized_messages = normalize_message_roles([input]) + scope = sentry_sdk.get_current_scope() + messages_data = truncate_and_annotate_messages( + normalized_messages, span, scope + ) + if messages_data is not None: + set_data_normalized( + span, + SPANDATA.GEN_AI_REQUEST_MESSAGES, + messages_data, + unpack=False, ) - if messages_data is not None: - set_data_normalized( - span, - SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, - unpack=False, - ) - output = result.get("output") - if ( - output is not None - and should_send_default_pii() - and integration.include_prompts - ): - set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output) + output = result.get("output") + if ( + output is not None + and should_send_default_pii() + and integration.include_prompts + ): + set_data_normalized(span, SPANDATA.GEN_AI_RESPONSE_TEXT, output) - return result - finally: - # Ensure agent is popped even if an exception occurs - _pop_agent() + return result return new_invoke @@ -1039,24 +1000,31 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if integration is None: return f(self, *args, **kwargs) - agent_name, tools = _get_request_data(self, args, kwargs) start_span_function = get_start_span_function() + agent_name = kwargs.get("metadata", {}).get("lc_agent_name") + run_name = _get_run_name(self, args) + + span_name = "invoke_agent" + if agent_name is not None: + span_name = f"invoke_agent {agent_name}" + elif run_name: + span_name = f"invoke_agent {run_name}" + span = start_span_function( op=OP.GEN_AI_INVOKE_AGENT, - name=f"invoke_agent {agent_name}" if agent_name else "invoke_agent", + name=span_name, origin=LangchainIntegration.origin, ) span.__enter__() - _push_agent(agent_name) - - if agent_name: + if agent_name is not None: span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name) span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) + tools = _get_available_tools(self) _set_tools_on_span(span, tools) input = args[0].get("input") if len(args) >= 1 else None @@ -1106,7 +1074,6 @@ def new_iterator() -> "Iterator[Any]": raise finally: # Ensure cleanup happens even if iterator is abandoned or fails - _pop_agent() span.__exit__(*exc_info) async def new_iterator_async() -> "AsyncIterator[Any]": @@ -1132,7 +1099,6 @@ async def new_iterator_async() -> "AsyncIterator[Any]": raise finally: # Ensure cleanup happens even if iterator is abandoned or fails - _pop_agent() span.__exit__(*exc_info) if str(type(result)) == "": diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 46d831a3ea..7e5041c4be 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -254,6 +254,8 @@ def test_langchain_create_agent( assert len(chat_spans) == 1 assert chat_spans[0]["origin"] == "auto.ai.langchain" + assert chat_spans[0]["data"]["gen_ai.agent.name"] == "word_length_agent" + assert chat_spans[0]["data"]["gen_ai.usage.input_tokens"] == 10 assert chat_spans[0]["data"]["gen_ai.usage.output_tokens"] == 20 assert chat_spans[0]["data"]["gen_ai.usage.total_tokens"] == 30 @@ -410,6 +412,10 @@ def test_tool_execution_span( assert chat_spans[1]["origin"] == "auto.ai.langchain" assert tool_exec_span["origin"] == "auto.ai.langchain" + assert chat_spans[0]["data"]["gen_ai.agent.name"] == "word_length_agent" + assert chat_spans[1]["data"]["gen_ai.agent.name"] == "word_length_agent" + assert tool_exec_span["data"]["gen_ai.agent.name"] == "word_length_agent" + assert chat_spans[0]["data"]["gen_ai.usage.input_tokens"] == 142 assert chat_spans[0]["data"]["gen_ai.usage.output_tokens"] == 50 assert chat_spans[0]["data"]["gen_ai.usage.total_tokens"] == 192 From 8cf3f81a59a1458ff5695418e03cc66582a8a612 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 09:14:30 +0200 Subject: [PATCH 02/24] typing --- sentry_sdk/integrations/langchain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 6430477e5a..b685a838aa 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -754,7 +754,7 @@ def _record_token_usage(span: "Span", response: "Any") -> None: span.set_data(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens) -def _get_available_tools(obj: "Any") -> "tuple[Optional[str], Optional[List[Any]]]": +def _get_available_tools(obj: "Any") -> "Optional[List[Any]]": """ Get the agent name and available tools for the agent. """ @@ -772,7 +772,7 @@ def _get_available_tools(obj: "Any") -> "tuple[Optional[str], Optional[List[Any] return tools -def _get_run_name(obj: "Any", args: "Any"): +def _get_run_name(obj: "Any", args: "Any") -> "Optional[str]": agent = getattr(obj, "agent", None) runnable = getattr(agent, "runnable", None) runnable_config = getattr(runnable, "config", {}) From 56ec48f644714f9a1f2645257985efe5880a2826 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 09:19:51 +0200 Subject: [PATCH 03/24] fix span description --- sentry_sdk/integrations/langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index b685a838aa..c6e7346d7b 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -957,7 +957,7 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": run_name = _get_run_name(self, args) with start_span_function( op=OP.GEN_AI_INVOKE_AGENT, - name=run_name, + name=f"invoke_agent {run_name}" if run_name else "invoke_agent", origin=LangchainIntegration.origin, ) as span: if run_name: From d8c06f8cce3fffe728ccc38966c4901777e8f3a8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 09:29:08 +0200 Subject: [PATCH 04/24] defensive check --- sentry_sdk/integrations/langchain.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index c6e7346d7b..6279a29d6b 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -426,9 +426,11 @@ def on_chat_model_start( if ai_system: span.set_data(SPANDATA.GEN_AI_SYSTEM, ai_system) - agent_name = kwargs.get("metadata", {}).get("lc_agent_name") - if agent_name is not None: - span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name) + agent_metadata = kwargs.get("metadata") + if isinstance(agent_metadata, dict) and "lc_agent_name" in agent_metadata: + span.set_data( + SPANDATA.GEN_AI_AGENT_NAME, agent_metadata["lc_agent_name"] + ) for key, attribute in DATA_FIELDS.items(): if key in all_params and all_params[key] is not None: @@ -626,9 +628,11 @@ def on_tool_start( if tool_description is not None: span.set_data(SPANDATA.GEN_AI_TOOL_DESCRIPTION, tool_description) - agent_name = kwargs.get("metadata", {}).get("lc_agent_name") - if agent_name is not None: - span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name) + agent_metadata = kwargs.get("metadata") + if isinstance(agent_metadata, dict) and "lc_agent_name" in agent_metadata: + span.set_data( + SPANDATA.GEN_AI_AGENT_NAME, agent_metadata["lc_agent_name"] + ) if should_send_default_pii() and self.include_prompts: set_data_normalized( From 36ca817269ff88a1f144402732e2be43cbde7fbd Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 09:34:06 +0200 Subject: [PATCH 05/24] no agent name in stream --- sentry_sdk/integrations/langchain.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 6279a29d6b..c786b22562 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -1017,25 +1017,15 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": start_span_function = get_start_span_function() - agent_name = kwargs.get("metadata", {}).get("lc_agent_name") run_name = _get_run_name(self, args) - span_name = "invoke_agent" - if agent_name is not None: - span_name = f"invoke_agent {agent_name}" - elif run_name: - span_name = f"invoke_agent {run_name}" - span = start_span_function( op=OP.GEN_AI_INVOKE_AGENT, - name=span_name, + name=f"invoke_agent {run_name}" if run_name else "invoke_agent", origin=LangchainIntegration.origin, ) span.__enter__() - if agent_name is not None: - span.set_data(SPANDATA.GEN_AI_AGENT_NAME, agent_name) - span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) From 87ed0608417b7da872471a611996a04a327f5322 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 14:14:46 +0200 Subject: [PATCH 06/24] feat(langchain): Record run_name in on_chat_model_start --- sentry_sdk/integrations/langchain.py | 7 +++ .../integrations/langchain/test_langchain.py | 50 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index c786b22562..2d64c22325 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -432,6 +432,13 @@ def on_chat_model_start( SPANDATA.GEN_AI_AGENT_NAME, agent_metadata["lc_agent_name"] ) + run_name = kwargs.get("name") + if run_name is not None: + span.set_data( + SPANDATA.GEN_AI_PIPELINE_NAME, + run_name, + ) + for key, attribute in DATA_FIELDS.items(): if key in all_params and all_params[key] is not None: set_data_normalized(span, attribute, all_params[key], unpack=False) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 9243fcda53..0f8b5bed51 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -170,6 +170,56 @@ def test_langchain_text_completion( assert llm_span["data"]["gen_ai.usage.output_tokens"] == 15 +def test_langchain_chat( + sentry_init, + capture_events, + get_model_response, + nonstreaming_responses_model_response, +): + sentry_init( + integrations=[ + LangchainIntegration( + include_prompts=True, + ) + ], + traces_sample_rate=1.0, + send_default_pii=True, + ) + events = capture_events() + + model_response = get_model_response( + nonstreaming_responses_model_response, + serialize_pydantic=True, + request_headers={ + "X-Stainless-Raw-Response": "True", + }, + ) + + llm = ChatOpenAI( + model_name="gpt-3.5-turbo", + temperature=0, + openai_api_key="badkey", + use_responses_api=True, + ) + + with patch.object( + llm.client._client._client, + "send", + return_value=model_response, + ) as _: + with start_transaction(): + llm.invoke( + "How many letters in the word eudca", + config={"run_name": "my-snazzy-pipeline"}, + ) + + tx = events[0] + + chat_spans = list(x for x in tx["spans"] if x["op"] == "gen_ai.chat") + assert len(chat_spans) == 1 + assert chat_spans[0]["data"]["gen_ai.pipeline.name"] == "my-snazzy-pipeline" + + @pytest.mark.skipif( LANGCHAIN_VERSION < (1,), reason="LangChain 1.0+ required (ONE AGENT refactor)", From ee0b7d94228331c23d507a76d3aa5475a58a71b1 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 14:25:10 +0200 Subject: [PATCH 07/24] feat(langchain): Record run_name in on_tool_start --- sentry_sdk/integrations/langchain.py | 7 +++++ .../integrations/langchain/test_langchain.py | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 2d64c22325..3dec48c7f1 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -641,6 +641,13 @@ def on_tool_start( SPANDATA.GEN_AI_AGENT_NAME, agent_metadata["lc_agent_name"] ) + run_name = kwargs.get("name") + if run_name is not None: + span.set_data( + SPANDATA.GEN_AI_PIPELINE_NAME, + run_name, + ) + if should_send_default_pii() and self.include_prompts: set_data_normalized( span, diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 0f8b5bed51..029bfa7986 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -220,6 +220,33 @@ def test_langchain_chat( assert chat_spans[0]["data"]["gen_ai.pipeline.name"] == "my-snazzy-pipeline" +def test_langchain_tool( + sentry_init, + capture_events, +): + sentry_init( + integrations=[ + LangchainIntegration( + include_prompts=True, + ) + ], + traces_sample_rate=1.0, + send_default_pii=True, + ) + events = capture_events() + + with start_transaction(): + get_word_length.invoke( + {"word": "eudca"}, + config={"run_name": "my-snazzy-pipeline"}, + ) + + tx = events[0] + tool_spans = list(x for x in tx["spans"] if x["op"] == "gen_ai.execute_tool") + assert len(tool_spans) == 1 + assert tool_spans[0]["data"]["gen_ai.pipeline.name"] == "my-snazzy-pipeline" + + @pytest.mark.skipif( LANGCHAIN_VERSION < (1,), reason="LangChain 1.0+ required (ONE AGENT refactor)", From ea94bfcb1241ee3110fb657f182c4f194466c424 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 14:37:34 +0200 Subject: [PATCH 08/24] . --- tests/conftest.py | 24 ++++ .../integrations/langchain/test_langchain.py | 5 +- tests/integrations/openai/test_openai.py | 109 +++++++++++------- 3 files changed, 94 insertions(+), 44 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 71f2431aac..6a15d3668f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1102,6 +1102,30 @@ def nonstreaming_responses_model_response(): ) +@pytest.fixture +def nonstreaming_chat_completions_model_response(): + return openai.types.chat.ChatCompletion( + id="chat-id", + choices=[ + openai.types.chat.chat_completion.Choice( + index=0, + finish_reason="stop", + message=openai.types.chat.ChatCompletionMessage( + role="assistant", content="the model response" + ), + ) + ], + created=10000000, + model="response-model-id", + object="chat.completion", + usage=openai.types.CompletionUsage( + completion_tokens=10, + prompt_tokens=20, + total_tokens=30, + ), + ) + + @pytest.fixture def responses_tool_call_model_responses(): def inner( diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 0f8b5bed51..269b4052e2 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -174,7 +174,7 @@ def test_langchain_chat( sentry_init, capture_events, get_model_response, - nonstreaming_responses_model_response, + nonstreaming_chat_completions_model_response, ): sentry_init( integrations=[ @@ -188,7 +188,7 @@ def test_langchain_chat( events = capture_events() model_response = get_model_response( - nonstreaming_responses_model_response, + nonstreaming_chat_completions_model_response, serialize_pydantic=True, request_headers={ "X-Stainless-Raw-Response": "True", @@ -199,7 +199,6 @@ def test_langchain_chat( model_name="gpt-3.5-turbo", temperature=0, openai_api_key="badkey", - use_responses_api=True, ) with patch.object( diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 0fd049e742..cd60afe551 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -15,9 +15,8 @@ Omit = None from openai import AsyncOpenAI, OpenAI, AsyncStream, Stream, OpenAIError -from openai.types import CompletionUsage, CreateEmbeddingResponse, Embedding -from openai.types.chat import ChatCompletion, ChatCompletionMessage, ChatCompletionChunk -from openai.types.chat.chat_completion import Choice +from openai.types import CreateEmbeddingResponse, Embedding +from openai.types.chat import ChatCompletionChunk from openai.types.chat.chat_completion_chunk import ChoiceDelta, Choice as DeltaChoice from openai.types.create_embedding_response import Usage as EmbeddingTokenUsage @@ -60,26 +59,6 @@ async def __call__(self, *args, **kwargs): OPENAI_VERSION = package_version("openai") -EXAMPLE_CHAT_COMPLETION = ChatCompletion( - id="chat-id", - choices=[ - Choice( - index=0, - finish_reason="stop", - message=ChatCompletionMessage( - role="assistant", content="the model response" - ), - ) - ], - created=10000000, - model="response-model-id", - object="chat.completion", - usage=CompletionUsage( - completion_tokens=10, - prompt_tokens=20, - total_tokens=30, - ), -) if SKIP_RESPONSES_TESTS: @@ -131,7 +110,11 @@ async def __call__(self, *args, **kwargs): ], ) def test_nonstreaming_chat_completion_no_prompts( - sentry_init, capture_events, send_default_pii, include_prompts + sentry_init, + capture_events, + send_default_pii, + include_prompts, + nonstreaming_chat_completions_model_response, ): sentry_init( integrations=[OpenAIIntegration(include_prompts=include_prompts)], @@ -141,7 +124,9 @@ def test_nonstreaming_chat_completion_no_prompts( events = capture_events() client = OpenAI(api_key="z") - client.chat.completions._post = mock.Mock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = mock.Mock( + return_value=nonstreaming_chat_completions_model_response + ) with start_transaction(name="openai tx"): response = ( @@ -228,7 +213,13 @@ def test_nonstreaming_chat_completion_no_prompts( ), ], ) -def test_nonstreaming_chat_completion(sentry_init, capture_events, messages, request): +def test_nonstreaming_chat_completion( + sentry_init, + capture_events, + messages, + request, + nonstreaming_chat_completions_model_response, +): sentry_init( integrations=[OpenAIIntegration(include_prompts=True)], traces_sample_rate=1.0, @@ -237,7 +228,9 @@ def test_nonstreaming_chat_completion(sentry_init, capture_events, messages, req events = capture_events() client = OpenAI(api_key="z") - client.chat.completions._post = mock.Mock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = mock.Mock( + return_value=nonstreaming_chat_completions_model_response + ) with start_transaction(name="openai tx"): response = ( @@ -307,7 +300,11 @@ def test_nonstreaming_chat_completion(sentry_init, capture_events, messages, req ], ) async def test_nonstreaming_chat_completion_async_no_prompts( - sentry_init, capture_events, send_default_pii, include_prompts + sentry_init, + capture_events, + send_default_pii, + include_prompts, + nonstreaming_chat_completions_model_response, ): sentry_init( integrations=[OpenAIIntegration(include_prompts=include_prompts)], @@ -317,7 +314,9 @@ async def test_nonstreaming_chat_completion_async_no_prompts( events = capture_events() client = AsyncOpenAI(api_key="z") - client.chat.completions._post = mock.AsyncMock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = mock.AsyncMock( + return_value=nonstreaming_chat_completions_model_response + ) with start_transaction(name="openai tx"): response = await client.chat.completions.create( @@ -403,7 +402,11 @@ async def test_nonstreaming_chat_completion_async_no_prompts( ], ) async def test_nonstreaming_chat_completion_async( - sentry_init, capture_events, messages, request + sentry_init, + capture_events, + messages, + request, + nonstreaming_chat_completions_model_response, ): sentry_init( integrations=[OpenAIIntegration(include_prompts=True)], @@ -413,7 +416,9 @@ async def test_nonstreaming_chat_completion_async( events = capture_events() client = AsyncOpenAI(api_key="z") - client.chat.completions._post = AsyncMock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = AsyncMock( + return_value=nonstreaming_chat_completions_model_response + ) with start_transaction(name="openai tx"): response = await client.chat.completions.create( @@ -1551,7 +1556,9 @@ async def test_embeddings_create_raises_error_async( assert event["level"] == "error" -def test_span_origin_nonstreaming_chat(sentry_init, capture_events): +def test_span_origin_nonstreaming_chat( + sentry_init, capture_events, nonstreaming_chat_completions_model_response +): sentry_init( integrations=[OpenAIIntegration()], traces_sample_rate=1.0, @@ -1559,7 +1566,9 @@ def test_span_origin_nonstreaming_chat(sentry_init, capture_events): events = capture_events() client = OpenAI(api_key="z") - client.chat.completions._post = mock.Mock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = mock.Mock( + return_value=nonstreaming_chat_completions_model_response + ) with start_transaction(name="openai tx"): client.chat.completions.create( @@ -1573,7 +1582,9 @@ def test_span_origin_nonstreaming_chat(sentry_init, capture_events): @pytest.mark.asyncio -async def test_span_origin_nonstreaming_chat_async(sentry_init, capture_events): +async def test_span_origin_nonstreaming_chat_async( + sentry_init, capture_events, nonstreaming_chat_completions_model_response +): sentry_init( integrations=[OpenAIIntegration()], traces_sample_rate=1.0, @@ -1581,7 +1592,9 @@ async def test_span_origin_nonstreaming_chat_async(sentry_init, capture_events): events = capture_events() client = AsyncOpenAI(api_key="z") - client.chat.completions._post = AsyncMock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = AsyncMock( + return_value=nonstreaming_chat_completions_model_response + ) with start_transaction(name="openai tx"): await client.chat.completions.create( @@ -3125,7 +3138,9 @@ async def test_streaming_responses_api_async( "tools", [[], None, NOT_GIVEN, omit], ) -def test_empty_tools_in_chat_completion(sentry_init, capture_events, tools): +def test_empty_tools_in_chat_completion( + sentry_init, capture_events, tools, nonstreaming_chat_completions_model_response +): sentry_init( integrations=[OpenAIIntegration()], traces_sample_rate=1.0, @@ -3133,7 +3148,9 @@ def test_empty_tools_in_chat_completion(sentry_init, capture_events, tools): events = capture_events() client = OpenAI(api_key="z") - client.chat.completions._post = mock.Mock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = mock.Mock( + return_value=nonstreaming_chat_completions_model_response + ) with start_transaction(name="openai tx"): client.chat.completions.create( @@ -3164,7 +3181,11 @@ def test_empty_tools_in_chat_completion(sentry_init, capture_events, tools): ], ) def test_openai_message_role_mapping( - sentry_init, capture_events, test_message, expected_role + sentry_init, + capture_events, + test_message, + expected_role, + nonstreaming_chat_completions_model_response, ): """Test that OpenAI integration properly maps message roles like 'ai' to 'assistant'""" @@ -3176,7 +3197,9 @@ def test_openai_message_role_mapping( events = capture_events() client = OpenAI(api_key="z") - client.chat.completions._post = mock.Mock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = mock.Mock( + return_value=nonstreaming_chat_completions_model_response + ) test_messages = [test_message] @@ -3197,7 +3220,9 @@ def test_openai_message_role_mapping( assert stored_messages[0]["role"] == expected_role -def test_openai_message_truncation(sentry_init, capture_events): +def test_openai_message_truncation( + sentry_init, capture_events, nonstreaming_chat_completions_model_response +): """Test that large messages are truncated properly in OpenAI integration.""" sentry_init( integrations=[OpenAIIntegration(include_prompts=True)], @@ -3207,7 +3232,9 @@ def test_openai_message_truncation(sentry_init, capture_events): events = capture_events() client = OpenAI(api_key="z") - client.chat.completions._post = mock.Mock(return_value=EXAMPLE_CHAT_COMPLETION) + client.chat.completions._post = mock.Mock( + return_value=nonstreaming_chat_completions_model_response + ) large_content = ( "This is a very long message that will exceed our size limits. " * 1000 From cd08d96d0cfd7c9c900808ec55649a8eeca13225 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 15:06:21 +0200 Subject: [PATCH 09/24] . --- tests/integrations/langchain/test_langchain.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 269b4052e2..e514b30958 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -67,6 +67,7 @@ ) LANGCHAIN_VERSION = package_version("langchain") +LANGCHAIN_OPENAI_VERSION = package_version("langchain-openai") @tool @@ -187,12 +188,15 @@ def test_langchain_chat( ) events = capture_events() + request_headers = {} + # Changed in https://github.com/langchain-ai/langchain/pull/32655 + if LANGCHAIN_OPENAI_VERSION >= (0, 3, 32): + request_headers["X-Stainless-Raw-Response"] = "True" + model_response = get_model_response( nonstreaming_chat_completions_model_response, serialize_pydantic=True, - request_headers={ - "X-Stainless-Raw-Response": "True", - }, + request_headers=request_headers, ) llm = ChatOpenAI( From 0d43616ca8e4ff4888d52cbc8d2fddbe199d41c9 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 15:18:12 +0200 Subject: [PATCH 10/24] simplify --- sentry_sdk/integrations/langchain.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index c786b22562..87c01ff326 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -758,7 +758,9 @@ def _record_token_usage(span: "Span", response: "Any") -> None: span.set_data(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens) -def _get_available_tools(obj: "Any") -> "Optional[List[Any]]": +def _get_request_data( + obj: "Any", args: "Any", kwargs: "Any" +) -> "tuple[Optional[str], Optional[List[Any]]]": """ Get the agent name and available tools for the agent. """ @@ -773,13 +775,6 @@ def _get_available_tools(obj: "Any") -> "Optional[List[Any]]": ) tools = tools if tools and len(tools) > 0 else None - return tools - - -def _get_run_name(obj: "Any", args: "Any") -> "Optional[str]": - agent = getattr(obj, "agent", None) - runnable = getattr(agent, "runnable", None) - runnable_config = getattr(runnable, "config", {}) try: agent_name = None if len(args) > 1: @@ -789,7 +784,7 @@ def _get_run_name(obj: "Any", args: "Any") -> "Optional[str]": except Exception: pass - return agent_name + return (agent_name, tools) def _simplify_langchain_tools(tools: "Any") -> "Optional[List[Any]]": @@ -957,8 +952,9 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if integration is None: return f(self, *args, **kwargs) + run_name, tools = _get_request_data(self, args, kwargs) start_span_function = get_start_span_function() - run_name = _get_run_name(self, args) + with start_span_function( op=OP.GEN_AI_INVOKE_AGENT, name=f"invoke_agent {run_name}" if run_name else "invoke_agent", @@ -970,7 +966,6 @@ def new_invoke(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, False) - tools = _get_available_tools(self) _set_tools_on_span(span, tools) # Run the agent @@ -1015,10 +1010,9 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": if integration is None: return f(self, *args, **kwargs) + run_name, tools = _get_request_data(self, args, kwargs) start_span_function = get_start_span_function() - run_name = _get_run_name(self, args) - span = start_span_function( op=OP.GEN_AI_INVOKE_AGENT, name=f"invoke_agent {run_name}" if run_name else "invoke_agent", @@ -1029,7 +1023,6 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) - tools = _get_available_tools(self) _set_tools_on_span(span, tools) input = args[0].get("input") if len(args) >= 1 else None From 568e6f7eb4858423793369536b53ec8310ee4a69 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 15:24:59 +0200 Subject: [PATCH 11/24] truthy check --- sentry_sdk/integrations/langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 2d64c22325..8115b660b1 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -433,7 +433,7 @@ def on_chat_model_start( ) run_name = kwargs.get("name") - if run_name is not None: + if run_name: span.set_data( SPANDATA.GEN_AI_PIPELINE_NAME, run_name, From 471260ede3f805c1c66b3d38ba42c890bbfb91de Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 31 Mar 2026 15:26:00 +0200 Subject: [PATCH 12/24] truthy check --- sentry_sdk/integrations/langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 3dec48c7f1..54c90172fb 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -642,7 +642,7 @@ def on_tool_start( ) run_name = kwargs.get("name") - if run_name is not None: + if run_name: span.set_data( SPANDATA.GEN_AI_PIPELINE_NAME, run_name, From b9387b8d4aae379cbf665cdacf329fdad9bb1e88 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Apr 2026 08:52:22 +0200 Subject: [PATCH 13/24] set run name --- sentry_sdk/integrations/langchain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 87c01ff326..7a9d02f521 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -1020,6 +1020,9 @@ def new_stream(self: "Any", *args: "Any", **kwargs: "Any") -> "Any": ) span.__enter__() + if run_name: + span.set_data(SPANDATA.GEN_AI_AGENT_NAME, run_name) + span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) From 848797a18da1d52b4359d7f2d5694c5348d221b5 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Apr 2026 11:21:53 +0200 Subject: [PATCH 14/24] update test name --- tests/integrations/langchain/test_langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index e7edd645f2..a711ef0c7b 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -223,7 +223,7 @@ def test_langchain_chat( assert chat_spans[0]["data"]["gen_ai.pipeline.name"] == "my-snazzy-pipeline" -def test_langchain_tool( +def test_langchain_tool_call_with_run_name( sentry_init, capture_events, ): From 52eb5c358d8e318920d3ccdd60ceea69e8881505 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 1 Apr 2026 11:28:44 +0200 Subject: [PATCH 15/24] more descriptive test name --- tests/integrations/langchain/test_langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index e514b30958..bd9ab894c4 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -171,7 +171,7 @@ def test_langchain_text_completion( assert llm_span["data"]["gen_ai.usage.output_tokens"] == 15 -def test_langchain_chat( +def test_langchain_chat_with_run_name( sentry_init, capture_events, get_model_response, From 5bddf72f048dfe3f124e70e0e07ce386aab26be8 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 10:51:44 +0200 Subject: [PATCH 16/24] import order --- tests/integrations/openai/test_openai.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 7fb9708e9f..4a5215b7ae 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -15,8 +15,9 @@ Omit = None from openai import AsyncOpenAI, OpenAI, AsyncStream, Stream, OpenAIError -from openai.types import CreateEmbeddingResponse, Embedding -from openai.types.chat import ChatCompletionChunk +from openai.types import CompletionUsage, CreateEmbeddingResponse, Embedding +from openai.types.chat import ChatCompletionMessage, ChatCompletionChunk +from openai.types.chat.chat_completion import Choice from openai.types.chat.chat_completion_chunk import ChoiceDelta, Choice as DeltaChoice from openai.types.create_embedding_response import Usage as EmbeddingTokenUsage From 1efa74834bfae1fcf13e39e2cd00f89fc64c94b5 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 10:52:23 +0200 Subject: [PATCH 17/24] remove duplicate imports --- tests/integrations/openai/test_openai.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index 4a5215b7ae..f2bb8912a4 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -24,9 +24,6 @@ SKIP_RESPONSES_TESTS = False try: - from openai.types.chat.chat_completion import Choice - from openai.types.chat import ChatCompletionMessage - from openai.types.completion_usage import CompletionUsage from openai.types.responses.response_completed_event import ResponseCompletedEvent from openai.types.responses.response_created_event import ResponseCreatedEvent from openai.types.responses.response_text_delta_event import ResponseTextDeltaEvent From df5a72f4bce420c3ff9028a83698692968f4fc56 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 10:55:20 +0200 Subject: [PATCH 18/24] merge and function_id --- tests/integrations/langchain/test_langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index d885883ac2..431cba023f 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -252,7 +252,7 @@ def test_langchain_tool_call_with_run_name( tx = events[0] tool_spans = list(x for x in tx["spans"] if x["op"] == "gen_ai.execute_tool") assert len(tool_spans) == 1 - assert tool_spans[0]["data"]["gen_ai.pipeline.name"] == "my-snazzy-pipeline" + assert tool_spans[0]["data"][SPANDATA.GEN_AI_FUNCTION_ID] == "my-snazzy-pipeline" @pytest.mark.skipif( From ec4ee52493e45b3ad1ee5b8025c58a847e432f7e Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 10:55:42 +0200 Subject: [PATCH 19/24] set function id --- sentry_sdk/integrations/langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 973be3e520..bffa174f22 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -644,7 +644,7 @@ def on_tool_start( run_name = kwargs.get("name") if run_name: span.set_data( - SPANDATA.GEN_AI_PIPELINE_NAME, + SPANDATA.GEN_AI_FUNCTION_ID, run_name, ) From a566ced637428af15ebe4c30816313e2e0cd2c10 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 10:56:49 +0200 Subject: [PATCH 20/24] update assertion --- tests/integrations/langchain/test_langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 92a2607a2f..aed451e791 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -225,7 +225,7 @@ def test_langchain_chat_with_run_name( chat_spans = list(x for x in tx["spans"] if x["op"] == "gen_ai.chat") assert len(chat_spans) == 1 - assert chat_spans[0]["data"]["gen_ai.pipeline.name"] == "my-snazzy-pipeline" + assert chat_spans[0]["data"][SPANDATA.GEN_AI_FUNCTION_ID] == "my-snazzy-pipeline" @pytest.mark.skipif( From fb388a968b3509c048e9dffd5e7cf726cbd24509 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 11:07:17 +0200 Subject: [PATCH 21/24] update kwarg --- tests/integrations/langchain/test_langchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index aed451e791..0a7a920624 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -197,7 +197,7 @@ def test_langchain_chat_with_run_name( nonstreaming_chat_completions_model_response( response_id="chat-id", response_model="response-model-id", - response_content="the model response", + message_content="the model response", created=10000000, ), serialize_pydantic=True, From efc9460077728974c1a0c3c48eed57746942431f Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 11:12:44 +0200 Subject: [PATCH 22/24] make openai test values consistent with previous values --- tests/integrations/openai/test_openai.py | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index f2bb8912a4..c1f07e83eb 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -128,10 +128,10 @@ def test_nonstreaming_chat_completion_no_prompts( client = OpenAI(api_key="z") client.chat.completions._post = mock.Mock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -237,10 +237,10 @@ def test_nonstreaming_chat_completion( client = OpenAI(api_key="z") client.chat.completions._post = mock.Mock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -328,10 +328,10 @@ async def test_nonstreaming_chat_completion_async_no_prompts( client = AsyncOpenAI(api_key="z") client.chat.completions._post = mock.AsyncMock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -435,10 +435,10 @@ async def test_nonstreaming_chat_completion_async( client = AsyncOpenAI(api_key="z") client.chat.completions._post = AsyncMock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -1888,10 +1888,10 @@ def test_span_origin_nonstreaming_chat( client = OpenAI(api_key="z") client.chat.completions._post = mock.Mock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -1919,10 +1919,10 @@ async def test_span_origin_nonstreaming_chat_async( client = AsyncOpenAI(api_key="z") client.chat.completions._post = AsyncMock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -3686,10 +3686,10 @@ def test_empty_tools_in_chat_completion( client = OpenAI(api_key="z") client.chat.completions._post = mock.Mock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -3740,10 +3740,10 @@ def test_openai_message_role_mapping( client = OpenAI(api_key="z") client.chat.completions._post = mock.Mock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) @@ -3780,10 +3780,10 @@ def test_openai_message_truncation( client = OpenAI(api_key="z") client.chat.completions._post = mock.Mock( return_value=nonstreaming_chat_completions_model_response( - response_id="chatcmpl-test", + response_id="chat-id", response_model="gpt-3.5-turbo", - message_content="Test response", - created=1234567890, + message_content="the model response", + created=10000000, ) ) From 1de30a1bf6f2314fcd4d16e398a2647adae5129a Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 11:25:06 +0200 Subject: [PATCH 23/24] update fixture arguments --- .../integrations/langchain/test_langchain.py | 5 +++ tests/integrations/openai/test_openai.py | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 0a7a920624..437bcb17a3 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -199,6 +199,11 @@ def test_langchain_chat_with_run_name( response_model="response-model-id", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers=request_headers, diff --git a/tests/integrations/openai/test_openai.py b/tests/integrations/openai/test_openai.py index c1f07e83eb..20bbf2adf5 100644 --- a/tests/integrations/openai/test_openai.py +++ b/tests/integrations/openai/test_openai.py @@ -132,6 +132,11 @@ def test_nonstreaming_chat_completion_no_prompts( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -241,6 +246,11 @@ def test_nonstreaming_chat_completion( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -332,6 +342,11 @@ async def test_nonstreaming_chat_completion_async_no_prompts( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -439,6 +454,11 @@ async def test_nonstreaming_chat_completion_async( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -1892,6 +1912,11 @@ def test_span_origin_nonstreaming_chat( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -1923,6 +1948,11 @@ async def test_span_origin_nonstreaming_chat_async( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -3690,6 +3720,11 @@ def test_empty_tools_in_chat_completion( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -3744,6 +3779,11 @@ def test_openai_message_role_mapping( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) @@ -3784,6 +3824,11 @@ def test_openai_message_truncation( response_model="gpt-3.5-turbo", message_content="the model response", created=10000000, + usage=CompletionUsage( + prompt_tokens=20, + completion_tokens=10, + total_tokens=30, + ), ) ) From 42dbc3a147305ac9bc0b392d2cd1fea85cecd071 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Tue, 14 Apr 2026 11:25:39 +0200 Subject: [PATCH 24/24] . --- tests/conftest.py | 12 ++-- tests/integrations/litellm/test_litellm.py | 76 ++++++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5d014d2411..ba28e4991c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1266,7 +1266,11 @@ def streaming_chat_completions_model_response(): @pytest.fixture def nonstreaming_chat_completions_model_response(): def inner( - response_id: str, response_model: str, message_content: str, created: int + response_id: str, + response_model: str, + message_content: str, + created: int, + usage: openai.types.CompletionUsage, ): return openai.types.chat.ChatCompletion( id=response_id, @@ -1282,11 +1286,7 @@ def inner( created=created, model=response_model, object="chat.completion", - usage=openai.types.CompletionUsage( - prompt_tokens=10, - completion_tokens=20, - total_tokens=30, - ), + usage=usage, ) return inner diff --git a/tests/integrations/litellm/test_litellm.py b/tests/integrations/litellm/test_litellm.py index b1a3b6af89..18f8cfaf6e 100644 --- a/tests/integrations/litellm/test_litellm.py +++ b/tests/integrations/litellm/test_litellm.py @@ -33,6 +33,7 @@ async def __call__(self, *args, **kwargs): from sentry_sdk.utils import package_version from openai import OpenAI, AsyncOpenAI +from openai.types import CompletionUsage from concurrent.futures import ThreadPoolExecutor @@ -165,6 +166,11 @@ def test_nonstreaming_chat_completion( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -252,6 +258,11 @@ async def test_async_nonstreaming_chat_completion( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -919,6 +930,11 @@ def test_span_origin( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -969,6 +985,11 @@ def test_multiple_providers( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1068,6 +1089,11 @@ async def test_async_multiple_providers( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1168,6 +1194,11 @@ def test_additional_parameters( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1231,6 +1262,11 @@ async def test_async_additional_parameters( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1294,6 +1330,11 @@ def test_no_integration( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1346,6 +1387,11 @@ async def test_async_no_integration( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1528,6 +1574,11 @@ def test_binary_content_encoding_image_url( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1611,6 +1662,11 @@ async def test_async_binary_content_encoding_image_url( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1696,6 +1752,11 @@ def test_binary_content_encoding_mixed_content( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1768,6 +1829,11 @@ async def test_async_binary_content_encoding_mixed_content( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1839,6 +1905,11 @@ def test_binary_content_encoding_uri_type( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"}, @@ -1916,6 +1987,11 @@ async def test_async_binary_content_encoding_uri_type( response_model="gpt-3.5-turbo", message_content="Test response", created=1234567890, + usage=CompletionUsage( + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ), ), serialize_pydantic=True, request_headers={"X-Stainless-Raw-Response": "true"},