diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 71883b2eef..9d4199d678 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -13,11 +13,10 @@ ) if TYPE_CHECKING: - from typing import Any, Awaitable, Callable, Optional, Union + from typing import Any, Awaitable, Callable, Optional from agents.run_internal.run_steps import SingleStepResult - from sentry_sdk.tracing import Span try: import agents @@ -51,7 +50,7 @@ def _maybe_start_agent_span( should_run_agent_start_hooks: bool, span_kwargs: "dict[str, Any]", is_streaming: bool = False, -) -> "Optional[Union[Span, StreamedSpan]]": +) -> "Optional[StreamedSpan]": """ Start an agent invocation span if conditions are met. Handles ending any existing span for a different agent. @@ -82,10 +81,7 @@ def _maybe_start_agent_span( if not is_streaming: return span - if isinstance(span, StreamedSpan): - span.set_attribute(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) - else: - span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) + span.set_attribute(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) return span @@ -114,11 +110,7 @@ async def _run_single_turn( context_wrapper, agent, should_run_agent_start_hooks, kwargs ) - if ( - span is None - or (isinstance(span, StreamedSpan) and span.end_timestamp is not None) - or (not isinstance(span, StreamedSpan) and span.timestamp is not None) - ): + if span is None or span.end_timestamp is not None: return await original_run_single_turn(*args, **kwargs) try: @@ -198,11 +190,7 @@ async def _run_single_turn_streamed( is_streaming=True, ) - if ( - span is None - or (isinstance(span, StreamedSpan) and span.end_timestamp is not None) - or (not isinstance(span, StreamedSpan) and span.timestamp is not None) - ): + if span is None or span.end_timestamp is not None: return await original_run_single_turn_streamed(*args, **kwargs) try: diff --git a/sentry_sdk/integrations/openai_agents/patches/error_tracing.py b/sentry_sdk/integrations/openai_agents/patches/error_tracing.py index 68dadb3101..ef7e05924f 100644 --- a/sentry_sdk/integrations/openai_agents/patches/error_tracing.py +++ b/sentry_sdk/integrations/openai_agents/patches/error_tracing.py @@ -2,9 +2,7 @@ from typing import TYPE_CHECKING import sentry_sdk -from sentry_sdk.consts import SPANSTATUS from sentry_sdk.traces import SpanStatus -from sentry_sdk.tracing_utils import has_span_streaming_enabled if TYPE_CHECKING: from typing import Any @@ -56,15 +54,9 @@ def sentry_attach_error_to_current_span( the agents library swallows exceptions. """ # Set the current Sentry span to errored - span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) - if span_streaming: - current_span = sentry_sdk.get_current_scope().streamed_span - if current_span is not None: - current_span.status = SpanStatus.ERROR - else: - current_span = sentry_sdk.get_current_span() - if current_span is not None: - current_span.set_status(SPANSTATUS.INTERNAL_ERROR) + current_span = sentry_sdk.get_current_scope().streamed_span + if current_span is not None: + current_span.status = SpanStatus.ERROR # Call the original function return original_attach_error(error, *args, **kwargs) diff --git a/sentry_sdk/integrations/openai_agents/patches/models.py b/sentry_sdk/integrations/openai_agents/patches/models.py index 8f708dafce..800bcc40c6 100644 --- a/sentry_sdk/integrations/openai_agents/patches/models.py +++ b/sentry_sdk/integrations/openai_agents/patches/models.py @@ -17,9 +17,8 @@ from ..spans import ai_client_span, update_ai_client_span if TYPE_CHECKING: - from typing import Any, Callable, Union + from typing import Any, Callable - from sentry_sdk.tracing import Span try: import agents @@ -29,7 +28,7 @@ def _inject_trace_propagation_headers( - hosted_tool: "HostedMCPTool", span: "Union[Span, StreamedSpan]" + hosted_tool: "HostedMCPTool", span: "StreamedSpan" ) -> None: headers = hosted_tool.tool_config.get("headers") if headers is None: @@ -141,12 +140,7 @@ async def wrapped_stream_response(*args: "Any", **kwargs: "Any") -> "Any": for hosted_tool in hosted_tools: _inject_trace_propagation_headers(hosted_tool, span=span) - set_on_span = ( - span.set_attribute - if isinstance(span, StreamedSpan) - else span.set_data - ) - set_on_span(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) + span.set_attribute(SPANDATA.GEN_AI_RESPONSE_STREAMING, True) streaming_response = None ttft_recorded = False @@ -157,7 +151,9 @@ async def wrapped_stream_response(*args: "Any", **kwargs: "Any") -> "Any": # Detect first content token (text delta event) if not ttft_recorded and hasattr(event, "delta"): ttft = time.perf_counter() - start_time - set_on_span(SPANDATA.GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN, ttft) + span.set_attribute( + SPANDATA.GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN, ttft + ) ttft_recorded = True # Capture the full response from ResponseCompletedEvent diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 870dc91325..56799ad069 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -64,10 +64,7 @@ async def on_tool_start( elif not should_send_default_pii(): return - if isinstance(span, StreamedSpan): - span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) - else: - span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) + span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, context.tool_arguments) async def on_tool_end( self, @@ -161,14 +158,9 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": if conversation_id: agent._sentry_conversation_id = conversation_id - if isinstance(workflow_span, StreamedSpan): - workflow_span.set_attribute( - SPANDATA.GEN_AI_CONVERSATION_ID, conversation_id - ) - else: - workflow_span.set_data( - SPANDATA.GEN_AI_CONVERSATION_ID, conversation_id - ) + workflow_span.set_attribute( + SPANDATA.GEN_AI_CONVERSATION_ID, conversation_id + ) if "starting_agent" in kwargs: kwargs["starting_agent"] = agent @@ -188,15 +180,9 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any": context_wrapper, "_sentry_agent_span", None ) - if invoke_agent_span is not None and ( - ( - isinstance(invoke_agent_span, StreamedSpan) - and invoke_agent_span.end_timestamp is None - ) - or ( - not isinstance(invoke_agent_span, StreamedSpan) - and invoke_agent_span.timestamp is None - ) + if ( + invoke_agent_span is not None + and invoke_agent_span.end_timestamp is None ): update_invoke_agent_span( span=invoke_agent_span, diff --git a/sentry_sdk/integrations/openai_agents/patches/tools.py b/sentry_sdk/integrations/openai_agents/patches/tools.py index 8ba398a27e..37cb63adf7 100644 --- a/sentry_sdk/integrations/openai_agents/patches/tools.py +++ b/sentry_sdk/integrations/openai_agents/patches/tools.py @@ -5,7 +5,6 @@ from sentry_sdk.consts import SPANDATA from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii -from sentry_sdk.traces import StreamedSpan from sentry_sdk.utils import has_data_collection_enabled from ..spans import execute_tool_span, update_execute_tool_span @@ -65,10 +64,7 @@ async def sentry_wrapped_on_invoke_tool( elif not should_send_default_pii(): return result - if isinstance(span, StreamedSpan): - span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, args[1]) - else: - span.set_data(SPANDATA.GEN_AI_TOOL_INPUT, args[1]) + span.set_attribute(SPANDATA.GEN_AI_TOOL_INPUT, args[1]) return result diff --git a/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py b/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py index 758f06db8d..9cc91f2317 100644 --- a/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +++ b/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py @@ -7,14 +7,12 @@ from ..consts import SPAN_ORIGIN if TYPE_CHECKING: - from typing import Union - import agents def agent_workflow_span( agent: "agents.Agent", -) -> "Union[sentry_sdk.tracing.Span, sentry_sdk.traces.StreamedSpan]": +) -> "sentry_sdk.traces.StreamedSpan": # Create a transaction or a span if an transaction is already active span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: diff --git a/sentry_sdk/integrations/openai_agents/spans/ai_client.py b/sentry_sdk/integrations/openai_agents/spans/ai_client.py index d075d3c9a8..28bf6f2676 100644 --- a/sentry_sdk/integrations/openai_agents/spans/ai_client.py +++ b/sentry_sdk/integrations/openai_agents/spans/ai_client.py @@ -5,7 +5,6 @@ from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.integrations import DidNotEnable from sentry_sdk.traces import StreamedSpan -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import has_data_collection_enabled try: @@ -53,7 +52,7 @@ ) if TYPE_CHECKING: - from typing import Any, Optional, Union + from typing import Any, Optional from agents import Agent, Tool @@ -199,7 +198,7 @@ def _transform_tool_definitions(tools: "list[Tool]") -> "list[ToolDefinition]": def ai_client_span( agent: "Agent", get_response_kwargs: "dict[str, Any]" -) -> "Union[sentry_sdk.tracing.Span, StreamedSpan]": +) -> "StreamedSpan": # TODO-anton: implement other types of operations. Now "chat" is hardcoded. # Get model name from agent.model or fall back to request model (for when agent.model is None/default) model_name = None @@ -210,41 +209,27 @@ def ai_client_span( client_options = sentry_sdk.get_client().options - span_streaming = has_span_streaming_enabled(client_options) - if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"chat {model_name}", - attributes={ - "sentry.op": OP.GEN_AI_CHAT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "chat", - }, - ) - - set_on_span = span.set_attribute - else: - span = sentry_sdk.start_span( - op=OP.GEN_AI_CHAT, - name=f"chat {model_name}", - origin=SPAN_ORIGIN, - ) - # TODO-anton: remove hardcoded stuff and replace something that also works for embedding and so on - span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "chat") - - set_on_span = span.set_data + span = sentry_sdk.traces.start_span( + name=f"chat {model_name}", + attributes={ + "sentry.op": OP.GEN_AI_CHAT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "chat", + }, + ) _set_agent_data(span, agent) if has_data_collection_enabled(client_options): if client_options["data_collection"]["gen_ai"]["inputs"]: - set_on_span( + span.set_attribute( SPANDATA.GEN_AI_TOOL_DEFINITIONS, json.dumps(_transform_tool_definitions(agent.tools)), ) else: # This is set unconditionally prior to data collection being introduced. # Remove this block once data collection is fully rolled out - set_on_span( + span.set_attribute( SPANDATA.GEN_AI_TOOL_DEFINITIONS, json.dumps(_transform_tool_definitions(agent.tools)), ) @@ -255,7 +240,7 @@ def ai_client_span( def update_ai_client_span( - span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", + span: "StreamedSpan", response: "Any", response_model: "Optional[str]" = None, agent: "Optional[Agent]" = None, @@ -267,17 +252,13 @@ def update_ai_client_span( if hasattr(response, "output") and response.output: _set_output_data(span, response) - set_on_span = ( - span.set_attribute if isinstance(span, StreamedSpan) else span.set_data - ) - if response_model is not None: - set_on_span(SPANDATA.GEN_AI_RESPONSE_MODEL, response_model) + span.set_attribute(SPANDATA.GEN_AI_RESPONSE_MODEL, response_model) elif hasattr(response, "model") and response.model: - set_on_span(SPANDATA.GEN_AI_RESPONSE_MODEL, str(response.model)) + span.set_attribute(SPANDATA.GEN_AI_RESPONSE_MODEL, str(response.model)) # Set conversation ID from agent if available if agent: conv_id = getattr(agent, "_sentry_conversation_id", None) if conv_id: - set_on_span(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) + span.set_attribute(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) diff --git a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py index 734789b030..a970a72974 100644 --- a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +++ b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py @@ -4,51 +4,36 @@ from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS from sentry_sdk.scope import should_send_default_pii from sentry_sdk.traces import SpanStatus, StreamedSpan -from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import has_data_collection_enabled from ..consts import SPAN_ORIGIN from ..utils import _set_agent_data if TYPE_CHECKING: - from typing import Any, Union + from typing import Any import agents def execute_tool_span( tool: "agents.Tool", *args: "Any", **kwargs: "Any" -) -> "Union[sentry_sdk.tracing.Span, StreamedSpan]": - span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) - - if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"execute_tool {tool.name}", - attributes={ - "sentry.op": OP.GEN_AI_EXECUTE_TOOL, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "execute_tool", - SPANDATA.GEN_AI_TOOL_NAME: tool.name, - SPANDATA.GEN_AI_TOOL_DESCRIPTION: tool.description, - }, - ) - else: - span = sentry_sdk.start_span( - op=OP.GEN_AI_EXECUTE_TOOL, - name=f"execute_tool {tool.name}", - origin=SPAN_ORIGIN, - ) - - span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "execute_tool") - - span.set_data(SPANDATA.GEN_AI_TOOL_NAME, tool.name) - span.set_data(SPANDATA.GEN_AI_TOOL_DESCRIPTION, tool.description) +) -> "StreamedSpan": + span = sentry_sdk.traces.start_span( + name=f"execute_tool {tool.name}", + attributes={ + "sentry.op": OP.GEN_AI_EXECUTE_TOOL, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "execute_tool", + SPANDATA.GEN_AI_TOOL_NAME: tool.name, + SPANDATA.GEN_AI_TOOL_DESCRIPTION: tool.description, + }, + ) return span def update_execute_tool_span( - span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", + span: "StreamedSpan", agent: "agents.Agent", tool: "agents.Tool", result: "Any", diff --git a/sentry_sdk/integrations/openai_agents/spans/handoff.py b/sentry_sdk/integrations/openai_agents/spans/handoff.py index ea91464afb..9fa48b431d 100644 --- a/sentry_sdk/integrations/openai_agents/spans/handoff.py +++ b/sentry_sdk/integrations/openai_agents/spans/handoff.py @@ -2,7 +2,6 @@ import sentry_sdk from sentry_sdk.consts import OP, SPANDATA -from sentry_sdk.tracing_utils import has_span_streaming_enabled from ..consts import SPAN_ORIGIN @@ -13,29 +12,15 @@ def handoff_span( context: "agents.RunContextWrapper", from_agent: "agents.Agent", to_agent_name: str ) -> None: - span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) - if span_streaming: - with sentry_sdk.traces.start_span( - name=f"handoff from {from_agent.name} to {to_agent_name}", - attributes={ - "sentry.op": OP.GEN_AI_HANDOFF, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "handoff", - }, - ) as span: - # Add conversation ID from agent - conv_id = getattr(from_agent, "_sentry_conversation_id", None) - if conv_id: - span.set_attribute(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) - else: - with sentry_sdk.start_span( - op=OP.GEN_AI_HANDOFF, - name=f"handoff from {from_agent.name} to {to_agent_name}", - origin=SPAN_ORIGIN, - ) as span: - span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "handoff") - - # Add conversation ID from agent - conv_id = getattr(from_agent, "_sentry_conversation_id", None) - if conv_id: - span.set_data(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) + with sentry_sdk.traces.start_span( + name=f"handoff from {from_agent.name} to {to_agent_name}", + attributes={ + "sentry.op": OP.GEN_AI_HANDOFF, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "handoff", + }, + ) as span: + # Add conversation ID from agent + conv_id = getattr(from_agent, "_sentry_conversation_id", None) + if conv_id: + span.set_attribute(SPANDATA.GEN_AI_CONVERSATION_ID, conv_id) diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index d8cc0c14a8..fe63a884b0 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -2,7 +2,6 @@ import sentry_sdk from sentry_sdk.ai.utils import ( - get_start_span_function, normalize_message_roles, set_data_normalized, truncate_and_annotate_messages, @@ -19,35 +18,23 @@ from ..utils import _set_agent_data, _set_usage_data if TYPE_CHECKING: - from typing import Any, Union + from typing import Any import agents def invoke_agent_span( context: "agents.RunContextWrapper", agent: "agents.Agent", kwargs: "dict[str, Any]" -) -> "Union[sentry_sdk.tracing.Span, StreamedSpan]": +) -> "StreamedSpan": client_options = sentry_sdk.get_client().options - span_streaming = has_span_streaming_enabled(client_options) - if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"invoke_agent {agent.name}", - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) - else: - start_span_function = get_start_span_function() - span = start_span_function( - op=OP.GEN_AI_INVOKE_AGENT, - name=f"invoke_agent {agent.name}", - origin=SPAN_ORIGIN, - ) - span.__enter__() - - span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") + span = sentry_sdk.traces.start_span( + name=f"invoke_agent {agent.name}", + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) record_inputs = False if has_data_collection_enabled(client_options): @@ -108,7 +95,7 @@ def invoke_agent_span( def update_invoke_agent_span( - span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", + span: "StreamedSpan", context: "agents.RunContextWrapper", agent: "agents.Agent", output: "Any" = None, diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index e819b5e555..6a45777d0c 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -26,7 +26,7 @@ ) if TYPE_CHECKING: - from typing import Any, Union + from typing import Any from agents import TResponseInputItem, Usage @@ -48,21 +48,17 @@ def _capture_exception(exc: "Any") -> None: sentry_sdk.capture_event(event, hint=hint) -def _set_agent_data( - span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", agent: "agents.Agent" -) -> None: - set_on_span = ( - span.set_attribute if isinstance(span, StreamedSpan) else span.set_data - ) - - set_on_span( +def _set_agent_data(span: "StreamedSpan", agent: "agents.Agent") -> None: + span.set_attribute( SPANDATA.GEN_AI_SYSTEM, "openai" ) # See footnote for https://opentelemetry.io/docs/specs/semconv/registry/attributes/gen-ai/#gen-ai-system for explanation why. - set_on_span(SPANDATA.GEN_AI_AGENT_NAME, agent.name) + span.set_attribute(SPANDATA.GEN_AI_AGENT_NAME, agent.name) if agent.model_settings.max_tokens: - set_on_span(SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, agent.model_settings.max_tokens) + span.set_attribute( + SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, agent.model_settings.max_tokens + ) # Get model name from agent.model or fall back to request model (for when agent.model is None/default) model_name = None @@ -72,50 +68,45 @@ def _set_agent_data( model_name = agent._sentry_request_model if model_name: - set_on_span(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) + span.set_attribute(SPANDATA.GEN_AI_REQUEST_MODEL, model_name) if agent.model_settings.presence_penalty: - set_on_span( + span.set_attribute( SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, agent.model_settings.presence_penalty, ) if agent.model_settings.temperature: - set_on_span( + span.set_attribute( SPANDATA.GEN_AI_REQUEST_TEMPERATURE, agent.model_settings.temperature ) if agent.model_settings.top_p: - set_on_span(SPANDATA.GEN_AI_REQUEST_TOP_P, agent.model_settings.top_p) + span.set_attribute(SPANDATA.GEN_AI_REQUEST_TOP_P, agent.model_settings.top_p) if agent.model_settings.frequency_penalty: - set_on_span( + span.set_attribute( SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, agent.model_settings.frequency_penalty, ) -def _set_usage_data( - span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", usage: "Usage" -) -> None: - set_on_span = ( - span.set_attribute if isinstance(span, StreamedSpan) else span.set_data - ) - set_on_span(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, usage.input_tokens) - set_on_span( +def _set_usage_data(span: "StreamedSpan", usage: "Usage") -> None: + span.set_attribute(SPANDATA.GEN_AI_USAGE_INPUT_TOKENS, usage.input_tokens) + span.set_attribute( SPANDATA.GEN_AI_USAGE_INPUT_TOKENS_CACHED, usage.input_tokens_details.cached_tokens, ) - set_on_span(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, usage.output_tokens) - set_on_span( + span.set_attribute(SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS, usage.output_tokens) + span.set_attribute( SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS_REASONING, usage.output_tokens_details.reasoning_tokens, ) - set_on_span(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, usage.total_tokens) + span.set_attribute(SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS, usage.total_tokens) def _set_input_data( - span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", + span: "StreamedSpan", get_response_kwargs: "dict[str, Any]", ) -> None: client = sentry_sdk.get_client() @@ -205,9 +196,7 @@ def _set_input_data( ) -def _set_output_data( - span: "Union[sentry_sdk.tracing.Span, StreamedSpan]", result: "Any" -) -> None: +def _set_output_data(span: "StreamedSpan", result: "Any") -> None: client = sentry_sdk.get_client() record_outputs = False if has_data_collection_enabled(client.options): diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index fbaff23ea0..8cdbd73874 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -267,16 +267,13 @@ def drag(self, _path): self.calls.append("drag") -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_tool_definitions( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, - span_streaming, ): """ Verifies that the `gen_ai.tool.definitions` attribute is set. @@ -390,86 +387,49 @@ def some_function(a: str, b: list[int]) -> str: response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=False, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent, - "Test input", - run_config=test_run_config, - ) - - assert result is not None - assert result.final_output == "Hello, how can I help you?" - - sentry_sdk.flush() - spans = [item.payload for item in items] - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=False, + trace_lifecycle="stream", ) - assert ( - json.loads(ai_client_span["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS]) - == expected_available_tools - ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=False, - stream_gen_ai_spans=False, - ) - events = capture_events() + items = capture_items("span") - result = await agents.Runner.run( - agent, - "Test input", - run_config=test_run_config, - ) + result = await agents.Runner.run( + agent, + "Test input", + run_config=test_run_config, + ) - assert result is not None - assert result.final_output == "Hello, how can I help you?" + assert result is not None + assert result.final_output == "Hello, how can I help you?" - (transaction,) = events - spans = transaction["spans"] - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + sentry_sdk.flush() + spans = [item.payload for item in items] + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - assert ( - json.loads(ai_client_span["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS]) - == expected_available_tools - ) + assert ( + json.loads(ai_client_span["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS]) + == expected_available_tools + ) -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_agent_invocation_span_no_pii( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, - span_streaming, ): client = AsyncOpenAI(api_key="test-key") model = OpenAIResponsesModel(model="gpt-4", openai_client=client) @@ -478,127 +438,66 @@ async def test_agent_invocation_span_no_pii( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=False, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - assert result.final_output == "Hello, how can I help you?" - - sentry_sdk.flush() - spans = [item.payload for item in items] - invoke_agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=False, + trace_lifecycle="stream", ) - assert spans[2]["name"] == "test_agent workflow" - assert spans[2]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - - assert invoke_agent_span["name"] == "invoke_agent test_agent" - - assert ( - SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_agent_span["attributes"] - ) - assert "gen_ai.request.messages" not in invoke_agent_span["attributes"] - assert "gen_ai.response.text" not in invoke_agent_span["attributes"] + items = capture_items("span") - assert ( - invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config ) - assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" - assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - - assert ai_client_span["name"] == "chat gpt-4" - assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=False, - stream_gen_ai_spans=False, - ) - events = capture_events() - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - assert result.final_output == "Hello, how can I help you?" + assert result is not None + assert result.final_output == "Hello, how can I help you?" - (transaction,) = events - spans = transaction["spans"] - invoke_agent_span = next( - span for span in spans if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + sentry_sdk.flush() + spans = [item.payload for item in items] + invoke_agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + assert spans[2]["name"] == "test_agent workflow" + assert spans[2]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert invoke_agent_span["description"] == "invoke_agent test_agent" + assert invoke_agent_span["name"] == "invoke_agent test_agent" - assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_agent_span["data"] - assert "gen_ai.request.messages" not in invoke_agent_span["data"] - assert "gen_ai.response.text" not in invoke_agent_span["data"] + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_agent_span["attributes"] + assert "gen_ai.request.messages" not in invoke_agent_span["attributes"] + assert "gen_ai.response.text" not in invoke_agent_span["attributes"] - assert invoke_agent_span["data"]["gen_ai.operation.name"] == "invoke_agent" - assert invoke_agent_span["data"]["gen_ai.system"] == "openai" - assert invoke_agent_span["data"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["data"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["data"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["data"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["data"]["gen_ai.request.top_p"] == 1.0 + assert invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" + assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" + assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ai_client_span["description"] == "chat gpt-4" - assert ai_client_span["data"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["data"]["gen_ai.system"] == "openai" - assert ai_client_span["data"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["data"]["gen_ai.request.top_p"] == 1.0 + assert ai_client_span["name"] == "chat gpt-4" + assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" + assert ai_client_span["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "init_kwargs,expect_messages", [ @@ -648,14 +547,12 @@ async def test_agent_invocation_span_no_pii( @pytest.mark.asyncio async def test_invoke_agent_span_data_collection_inputs( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, init_kwargs, expect_messages, - span_streaming, ): client = AsyncOpenAI(api_key="test-key") model = OpenAIResponsesModel(model="gpt-4", openai_client=client) @@ -673,68 +570,36 @@ async def test_invoke_agent_span_data_collection_inputs( "role": "user", "content": [{"text": "Test input", "type": "text"}], } - expected_messages = ( - [user_message] if not span_streaming else [system_message, user_message] - ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - **init_kwargs, - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) + expected_messages = [system_message, user_message] + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", + **init_kwargs, + ) - assert result is not None + items = capture_items("span") - sentry_sdk.flush() - spans = [item.payload for item in items] - invoke_agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config ) - span_data = invoke_agent_span["attributes"] - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - **init_kwargs, - stream_gen_ai_spans=False, - ) - events = capture_events() - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - assert result is not None + assert result is not None - (transaction,) = events - invoke_agent_span = next( - span - for span in transaction["spans"] - if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - span_data = invoke_agent_span["data"] + sentry_sdk.flush() + spans = [item.payload for item in items] + invoke_agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + span_data = invoke_agent_span["attributes"] if expect_messages: assert ( @@ -744,7 +609,6 @@ async def test_invoke_agent_span_data_collection_inputs( assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span_data -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "init_kwargs,expect_response_text", [ @@ -794,14 +658,12 @@ async def test_invoke_agent_span_data_collection_inputs( @pytest.mark.asyncio async def test_invoke_agent_span_data_collection_outputs( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, init_kwargs, expect_response_text, - span_streaming, ): client = AsyncOpenAI(api_key="test-key") model = OpenAIResponsesModel(model="gpt-4", openai_client=client) @@ -810,65 +672,35 @@ async def test_invoke_agent_span_data_collection_outputs( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", + **init_kwargs, + ) - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - **init_kwargs, - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None + items = capture_items("span") - sentry_sdk.flush() - spans = [item.payload for item in items] - invoke_agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config ) - span_data = invoke_agent_span["attributes"] - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - **init_kwargs, - stream_gen_ai_spans=False, - ) - events = capture_events() - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - assert result is not None + assert result is not None - (transaction,) = events - invoke_agent_span = next( - span - for span in transaction["spans"] - if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - span_data = invoke_agent_span["data"] + sentry_sdk.flush() + spans = [item.payload for item in items] + invoke_agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + span_data = invoke_agent_span["attributes"] if expect_response_text: assert span_data[SPANDATA.GEN_AI_RESPONSE_TEXT] == "Hello, how can I help you?" @@ -876,7 +708,6 @@ async def test_invoke_agent_span_data_collection_outputs( assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span_data -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "data_collection,send_default_pii,expect_input", [ @@ -927,7 +758,6 @@ async def test_invoke_agent_span_data_collection_outputs( @pytest.mark.asyncio async def test_data_collection_inputs( sentry_init, - capture_events, capture_items, test_agent, simple_test_tool, @@ -936,7 +766,6 @@ async def test_data_collection_inputs( data_collection, send_default_pii, expect_input, - span_streaming, ): client = AsyncOpenAI(api_key="test-key") model = OpenAIResponsesModel(model="gpt-4", openai_client=client) @@ -950,11 +779,9 @@ async def test_data_collection_inputs( "integrations": [OpenAIAgentsIntegration()], "traces_sample_rate": 1.0, "send_default_pii": send_default_pii, - "stream_gen_ai_spans": False, } - if span_streaming: - init_kwargs["disabled_integrations"] = [StdlibIntegration] - init_kwargs["trace_lifecycle"] = "stream" + init_kwargs["disabled_integrations"] = [StdlibIntegration] + init_kwargs["trace_lifecycle"] = "stream" if data_collection is not None: init_kwargs["_experiments"] = {"data_collection": data_collection} @@ -964,31 +791,19 @@ async def test_data_collection_inputs( return_value=response, ) as _: sentry_init(**init_kwargs) - - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") result = await agents.Runner.run( agent, "Test input", run_config=test_run_config ) assert result is not None - - if span_streaming: - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT - ) - span_data = ai_client_span["attributes"] - else: - (transaction,) = events - ai_client_span = next( - span for span in transaction["spans"] if span["op"] == OP.GEN_AI_CHAT - ) - span_data = ai_client_span["data"] + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) + span_data = ai_client_span["attributes"] if expect_input: assert "Test input" in span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES] @@ -1011,7 +826,6 @@ async def test_data_collection_inputs( assert span_data[SPANDATA.GEN_AI_USAGE_INPUT_TOKENS] == 10 -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "data_collection,send_default_pii,expect_output", [ @@ -1062,7 +876,6 @@ async def test_data_collection_inputs( @pytest.mark.asyncio async def test_data_collection_outputs( sentry_init, - capture_events, capture_items, test_agent, simple_test_tool, @@ -1071,7 +884,6 @@ async def test_data_collection_outputs( data_collection, send_default_pii, expect_output, - span_streaming, ): client = AsyncOpenAI(api_key="test-key") model = OpenAIResponsesModel(model="gpt-4", openai_client=client) @@ -1104,11 +916,9 @@ async def test_data_collection_outputs( "integrations": [OpenAIAgentsIntegration()], "traces_sample_rate": 1.0, "send_default_pii": send_default_pii, - "stream_gen_ai_spans": False, } - if span_streaming: - init_kwargs["disabled_integrations"] = [StdlibIntegration] - init_kwargs["trace_lifecycle"] = "stream" + init_kwargs["disabled_integrations"] = [StdlibIntegration] + init_kwargs["trace_lifecycle"] = "stream" if data_collection is not None: init_kwargs["_experiments"] = {"data_collection": data_collection} @@ -1118,33 +928,20 @@ async def test_data_collection_outputs( side_effect=[tool_response, final_response], ) as _: sentry_init(**init_kwargs) - - if span_streaming: - items = capture_items("span") - else: - events = capture_events() + items = capture_items("span") await agents.Runner.run( agent_with_tool, "Please use the simple test tool", run_config=test_run_config, ) - - if span_streaming: - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - chat_span_data = [ - span["attributes"] - for span in spans - if span["attributes"].get("sentry.op") == OP.GEN_AI_CHAT - ] - else: - (transaction,) = events - chat_span_data = [ - span["data"] - for span in transaction["spans"] - if span["op"] == OP.GEN_AI_CHAT - ] + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + chat_span_data = [ + span["attributes"] + for span in spans + if span["attributes"].get("sentry.op") == OP.GEN_AI_CHAT + ] assert len(chat_span_data) == 2 @@ -1169,7 +966,6 @@ async def test_data_collection_outputs( assert data[SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS] == 5 -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio @pytest.mark.parametrize( "instructions,input,expected_system_instructions,expected_request_messages", @@ -1363,7 +1159,6 @@ async def test_data_collection_outputs( ) async def test_agent_invocation_span( sentry_init, - capture_events, capture_items, test_agent_with_instructions, nonstreaming_responses_model_response, @@ -1372,7 +1167,6 @@ async def test_agent_invocation_span( expected_system_instructions, expected_request_messages, get_model_response, - span_streaming, ): """ Test that the integration creates spans for agent invocations. @@ -1384,155 +1178,81 @@ async def test_agent_invocation_span( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", + ) - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) + items = capture_items("span") - items = capture_items("span") + result = await agents.Runner.run( + agent, + input, + run_config=test_run_config, + ) - result = await agents.Runner.run( - agent, - input, - run_config=test_run_config, - ) + assert result is not None + assert result.final_output == "Hello, how can I help you?" - assert result is not None - assert result.final_output == "Hello, how can I help you?" + sentry_sdk.flush() + spans = [item.payload for item in items] + ai_client_span, invoke_agent_span, workflow_span = spans - sentry_sdk.flush() - spans = [item.payload for item in items] - ai_client_span, invoke_agent_span, workflow_span = spans + assert workflow_span["name"] == "test_agent workflow" + assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert workflow_span["name"] == "test_agent workflow" - assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + assert invoke_agent_span["name"] == "invoke_agent test_agent" - assert invoke_agent_span["name"] == "invoke_agent test_agent" + if expected_system_instructions is None: + assert "gen_ai.system_instructions" not in ai_client_span["attributes"] + else: + assert ai_client_span["attributes"][ + "gen_ai.system_instructions" + ] == safe_serialize(expected_system_instructions) - if expected_system_instructions is None: - assert "gen_ai.system_instructions" not in ai_client_span["attributes"] - else: - assert ai_client_span["attributes"][ - "gen_ai.system_instructions" - ] == safe_serialize(expected_system_instructions) + assert ( + json.loads(ai_client_span["attributes"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) + == expected_request_messages + ) - assert ( - json.loads(ai_client_span["attributes"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - == expected_request_messages - ) + assert ( + invoke_agent_span["attributes"]["gen_ai.response.text"] + == "Hello, how can I help you?" + ) - assert ( - invoke_agent_span["attributes"]["gen_ai.response.text"] - == "Hello, how can I help you?" - ) + assert invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" + assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" + assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ( - invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" - ) - assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" - assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - - assert ai_client_span["name"] == "chat gpt-4" - assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 + assert ai_client_span["name"] == "chat gpt-4" + assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" + assert ai_client_span["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() - - result = await agents.Runner.run( - agent, - input, - run_config=test_run_config, - ) - - assert result is not None - assert result.final_output == "Hello, how can I help you?" - - (transaction,) = events - spans = transaction["spans"] - invoke_agent_span, ai_client_span = spans - - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" - - assert invoke_agent_span["description"] == "invoke_agent test_agent" - - if expected_system_instructions is None: - assert "gen_ai.system_instructions" not in ai_client_span["data"] - else: - assert ai_client_span["data"][ - "gen_ai.system_instructions" - ] == safe_serialize(expected_system_instructions) - - assert ( - json.loads(ai_client_span["data"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - == expected_request_messages[-1:] - ) - - assert ( - invoke_agent_span["data"]["gen_ai.response.text"] - == "Hello, how can I help you?" - ) - assert invoke_agent_span["data"]["gen_ai.operation.name"] == "invoke_agent" - assert invoke_agent_span["data"]["gen_ai.system"] == "openai" - assert invoke_agent_span["data"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["data"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["data"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["data"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["data"]["gen_ai.request.top_p"] == 1.0 - - assert ai_client_span["description"] == "chat gpt-4" - assert ai_client_span["data"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["data"]["gen_ai.system"] == "openai" - assert ai_client_span["data"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["data"]["gen_ai.request.top_p"] == 1.0 - - -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_client_span_custom_model( sentry_init, - capture_events, capture_items, test_agent_custom_model, nonstreaming_responses_model_response, get_model_response, - span_streaming, ): """ Test that the integration uses the correct model name if a custom model is used. @@ -1545,75 +1265,43 @@ async def test_client_span_custom_model( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - assert result.final_output == "Hello, how can I help you?" - - sentry_sdk.flush() - spans = [item.payload for item in items] - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", ) - assert ai_client_span["name"] == "chat my-custom-model" - assert ai_client_span["attributes"]["gen_ai.request.model"] == "my-custom-model" - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, - ) - events = capture_events() + items = capture_items("span") - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config + ) - assert result is not None - assert result.final_output == "Hello, how can I help you?" + assert result is not None + assert result.final_output == "Hello, how can I help you?" - (transaction,) = events - spans = transaction["spans"] - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + sentry_sdk.flush() + spans = [item.payload for item in items] + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - assert ai_client_span["description"] == "chat my-custom-model" - assert ai_client_span["data"]["gen_ai.request.model"] == "my-custom-model" + assert ai_client_span["name"] == "chat my-custom-model" + assert ai_client_span["attributes"]["gen_ai.request.model"] == "my-custom-model" -@pytest.mark.parametrize("span_streaming", [True, False]) def test_agent_invocation_span_sync_no_pii( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, - span_streaming, sync_event_loop, ): """ @@ -1626,122 +1314,62 @@ def test_agent_invocation_span_sync_no_pii( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=False, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = agents.Runner.run_sync( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - assert result.final_output == "Hello, how can I help you?" - - sentry_sdk.flush() - spans = [item.payload for item in items] - - assert spans[2]["name"] == "test_agent workflow" - assert spans[2]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - - invoke_agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=False, + trace_lifecycle="stream", ) - assert invoke_agent_span["name"] == "invoke_agent test_agent" - assert ( - invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" - ) - assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" - assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - - assert ai_client_span["name"] == "chat gpt-4" - assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 + items = capture_items("span") - assert ( - SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_agent_span["attributes"] - ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=False, - stream_gen_ai_spans=False, - ) - events = capture_events() + result = agents.Runner.run_sync(agent, "Test input", run_config=test_run_config) - result = agents.Runner.run_sync( - agent, "Test input", run_config=test_run_config - ) + assert result is not None + assert result.final_output == "Hello, how can I help you?" - assert result is not None - assert result.final_output == "Hello, how can I help you?" + sentry_sdk.flush() + spans = [item.payload for item in items] - (transaction,) = events - spans = transaction["spans"] - invoke_agent_span = next( - span for span in spans if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + assert spans[2]["name"] == "test_agent workflow" + assert spans[2]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + invoke_agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - assert invoke_agent_span["description"] == "invoke_agent test_agent" - assert invoke_agent_span["data"]["gen_ai.operation.name"] == "invoke_agent" - assert invoke_agent_span["data"]["gen_ai.system"] == "openai" - assert invoke_agent_span["data"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["data"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["data"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["data"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["data"]["gen_ai.request.top_p"] == 1.0 + assert invoke_agent_span["name"] == "invoke_agent test_agent" + assert invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" + assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" + assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ai_client_span["description"] == "chat gpt-4" - assert ai_client_span["data"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["data"]["gen_ai.system"] == "openai" - assert ai_client_span["data"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["data"]["gen_ai.request.top_p"] == 1.0 + assert ai_client_span["name"] == "chat gpt-4" + assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" + assert ai_client_span["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_agent_span["data"] + assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in invoke_agent_span["attributes"] -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "instructions,input,expected_system_instructions,expected_request_messages", [ @@ -1934,7 +1562,6 @@ def test_agent_invocation_span_sync_no_pii( ) def test_agent_invocation_span_sync( sentry_init, - capture_events, capture_items, test_agent_with_instructions, nonstreaming_responses_model_response, @@ -1943,7 +1570,6 @@ def test_agent_invocation_span_sync( expected_system_instructions, expected_request_messages, get_model_response, - span_streaming, sync_event_loop, ): """ @@ -1956,136 +1582,73 @@ def test_agent_invocation_span_sync( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", + ) - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = agents.Runner.run_sync( - agent, - input, - run_config=test_run_config, - ) - - assert result is not None - assert result.final_output == "Hello, how can I help you?" - - sentry_sdk.flush() - spans = [item.payload for item in items] - ai_client_span, invoke_agent_span, workflow_span = spans - - assert workflow_span["name"] == "test_agent workflow" - assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + items = capture_items("span") - assert invoke_agent_span["name"] == "invoke_agent test_agent" - assert ( - invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" + result = agents.Runner.run_sync( + agent, + input, + run_config=test_run_config, ) - assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" - assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - - assert ai_client_span["name"] == "chat gpt-4" - assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 - - if expected_system_instructions is None: - assert "gen_ai.system_instructions" not in ai_client_span["attributes"] - else: - assert ai_client_span["attributes"][ - "gen_ai.system_instructions" - ] == safe_serialize(expected_system_instructions) - assert ( - json.loads(ai_client_span["attributes"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) - == expected_request_messages - ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) + assert result is not None + assert result.final_output == "Hello, how can I help you?" - events = capture_events() + sentry_sdk.flush() + spans = [item.payload for item in items] + ai_client_span, invoke_agent_span, workflow_span = spans + + assert workflow_span["name"] == "test_agent workflow" + assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + + assert invoke_agent_span["name"] == "invoke_agent test_agent" + assert invoke_agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" + assert invoke_agent_span["attributes"]["gen_ai.system"] == "openai" + assert invoke_agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert invoke_agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert invoke_agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert invoke_agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert invoke_agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 + + assert ai_client_span["name"] == "chat gpt-4" + assert ai_client_span["attributes"]["gen_ai.operation.name"] == "chat" + assert ai_client_span["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span["attributes"]["gen_ai.request.top_p"] == 1.0 + + if expected_system_instructions is None: + assert "gen_ai.system_instructions" not in ai_client_span["attributes"] + else: + assert ai_client_span["attributes"][ + "gen_ai.system_instructions" + ] == safe_serialize(expected_system_instructions) - result = agents.Runner.run_sync( - agent, - input, - run_config=test_run_config, - ) + assert ( + json.loads(ai_client_span["attributes"][SPANDATA.GEN_AI_REQUEST_MESSAGES]) + == expected_request_messages + ) - assert result is not None - assert result.final_output == "Hello, how can I help you?" - (transaction,) = events - spans = transaction["spans"] - invoke_agent_span, ai_client_span = spans - - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" - - assert invoke_agent_span["description"] == "invoke_agent test_agent" - assert invoke_agent_span["data"]["gen_ai.operation.name"] == "invoke_agent" - assert invoke_agent_span["data"]["gen_ai.system"] == "openai" - assert invoke_agent_span["data"]["gen_ai.agent.name"] == "test_agent" - assert invoke_agent_span["data"]["gen_ai.request.max_tokens"] == 100 - assert invoke_agent_span["data"]["gen_ai.request.model"] == "gpt-4" - assert invoke_agent_span["data"]["gen_ai.request.temperature"] == 0.7 - assert invoke_agent_span["data"]["gen_ai.request.top_p"] == 1.0 - - assert ai_client_span["description"] == "chat gpt-4" - assert ai_client_span["data"]["gen_ai.operation.name"] == "chat" - assert ai_client_span["data"]["gen_ai.system"] == "openai" - assert ai_client_span["data"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span["data"]["gen_ai.request.top_p"] == 1.0 - - if expected_system_instructions is None: - assert "gen_ai.system_instructions" not in ai_client_span["data"] - else: - assert ai_client_span["data"][ - "gen_ai.system_instructions" - ] == safe_serialize(expected_system_instructions) - - -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_handoff_span( sentry_init, - capture_events, capture_items, get_model_response, - span_streaming, ): """ Test that handoff spans are created when agents hand off to other agents. @@ -2180,87 +1743,47 @@ async def test_handoff_span( ), serialize_pydantic=True, ) - - if span_streaming: - with patch.object( - primary_agent.model._client._client, - "send", - side_effect=[handoff_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - - items = capture_items("transaction", "span") - - result = await agents.Runner.run( - primary_agent, - "Please hand off to secondary agent", - run_config=test_run_config, - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - handoff_span = next( - span - for span in spans - if span["attributes"].get("sentry.op") == OP.GEN_AI_HANDOFF + with patch.object( + primary_agent.model._client._client, + "send", + side_effect=[handoff_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", ) - # Verify handoff span was created - assert handoff_span is not None - assert handoff_span["name"] == "handoff from primary_agent to secondary_agent" - assert handoff_span["attributes"]["gen_ai.operation.name"] == "handoff" - else: - with patch.object( - primary_agent.model._client._client, - "send", - side_effect=[handoff_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, - ) - events = capture_events() + items = capture_items("transaction", "span") - result = await agents.Runner.run( - primary_agent, - "Please hand off to secondary agent", - run_config=test_run_config, - ) + result = await agents.Runner.run( + primary_agent, + "Please hand off to secondary agent", + run_config=test_run_config, + ) - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] - handoff_span = next( - span for span in spans if span.get("op") == OP.GEN_AI_HANDOFF - ) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + handoff_span = next( + span + for span in spans + if span["attributes"].get("sentry.op") == OP.GEN_AI_HANDOFF + ) - # Verify handoff span was created - assert handoff_span is not None - assert ( - handoff_span["description"] - == "handoff from primary_agent to secondary_agent" - ) - assert handoff_span["data"]["gen_ai.operation.name"] == "handoff" + # Verify handoff span was created + assert handoff_span is not None + assert handoff_span["name"] == "handoff from primary_agent to secondary_agent" + assert handoff_span["attributes"]["gen_ai.operation.name"] == "handoff" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_max_turns_before_handoff_span( sentry_init, - capture_events, capture_items, get_model_response, - span_streaming, ): """ Example raising agents.exceptions.AgentsException after the agent invocation span is complete. @@ -2355,91 +1878,51 @@ async def test_max_turns_before_handoff_span( ), serialize_pydantic=True, ) - - if span_streaming: - with patch.object( - primary_agent.model._client._client, - "send", - side_effect=[handoff_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - - items = capture_items("transaction", "span") - - with pytest.raises(MaxTurnsExceeded): - await agents.Runner.run( - primary_agent, - "Please hand off to secondary agent", - run_config=test_run_config, - max_turns=1, - ) - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - handoff_span = next( - span - for span in spans - if span["attributes"].get("sentry.op") == OP.GEN_AI_HANDOFF + with patch.object( + primary_agent.model._client._client, + "send", + side_effect=[handoff_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", ) - # Verify handoff span was created - assert handoff_span is not None - assert handoff_span["name"] == "handoff from primary_agent to secondary_agent" - assert handoff_span["attributes"]["gen_ai.operation.name"] == "handoff" - else: - with patch.object( - primary_agent.model._client._client, - "send", - side_effect=[handoff_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, + items = capture_items("transaction", "span") + + with pytest.raises(MaxTurnsExceeded): + await agents.Runner.run( + primary_agent, + "Please hand off to secondary agent", + run_config=test_run_config, + max_turns=1, ) - events = capture_events() - - with pytest.raises(MaxTurnsExceeded): - await agents.Runner.run( - primary_agent, - "Please hand off to secondary agent", - run_config=test_run_config, - max_turns=1, - ) - (error, transaction) = events - spans = transaction["spans"] - handoff_span = next( - span for span in spans if span.get("op") == OP.GEN_AI_HANDOFF - ) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + handoff_span = next( + span + for span in spans + if span["attributes"].get("sentry.op") == OP.GEN_AI_HANDOFF + ) - # Verify handoff span was created - assert handoff_span is not None - assert ( - handoff_span["description"] - == "handoff from primary_agent to secondary_agent" - ) - assert handoff_span["data"]["gen_ai.operation.name"] == "handoff" + # Verify handoff span was created + assert handoff_span is not None + assert handoff_span["name"] == "handoff from primary_agent to secondary_agent" + assert handoff_span["attributes"]["gen_ai.operation.name"] == "handoff" @pytest.mark.parametrize("user_hooks", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_tool_execution_span( sentry_init, - capture_events, capture_items, test_agent, simple_test_tool, get_model_response, nonstreaming_responses_tool_call_model_responses, - span_streaming, user_hooks, ): """ @@ -2494,374 +1977,199 @@ async def test_tool_execution_span( next(responses), serialize_pydantic=True, ) + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", + ) - if span_streaming: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) + items = capture_items("span") - items = capture_items("span") + await agents.Runner.run( + agent_with_tool, + "Please use the simple test tool", + run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, + ) - await agents.Runner.run( - agent_with_tool, - "Please use the simple test tool", - run_config=test_run_config, - hooks=RunHooks() if user_hooks else None, - ) + sentry_sdk.flush() + spans = [item.payload for item in items] - sentry_sdk.flush() - spans = [item.payload for item in items] + assert spans[4]["name"] == "test_agent workflow" + assert spans[4]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert spans[4]["name"] == "test_agent workflow" - assert spans[4]["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + ai_client_span1, ai_client_span2 = ( + span for span in spans if span["attributes"].get("sentry.op") == OP.GEN_AI_CHAT + ) + tool_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_EXECUTE_TOOL + ) - agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span1, ai_client_span2 = ( - span - for span in spans - if span["attributes"].get("sentry.op") == OP.GEN_AI_CHAT - ) - tool_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_EXECUTE_TOOL - ) + available_tool = { + "name": "simple_test_tool", + "description": "A simple tool", + "parameters": { + "properties": {"message": {"title": "Message", "type": "string"}}, + "required": ["message"], + "title": "simple_test_tool_args", + "type": "object", + "additionalProperties": False, + }, + } - available_tool = { - "name": "simple_test_tool", - "description": "A simple tool", - "parameters": { - "properties": {"message": {"title": "Message", "type": "string"}}, - "required": ["message"], - "title": "simple_test_tool_args", - "type": "object", - "additionalProperties": False, - }, - } - - assert agent_span["name"] == "invoke_agent test_agent" - assert agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" - - assert agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert agent_span["attributes"]["gen_ai.system"] == "openai" - - assert ai_client_span1["name"] == "chat gpt-4" - assert ai_client_span1["attributes"]["gen_ai.operation.name"] == "chat" - assert ai_client_span1["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span1["attributes"]["gen_ai.agent.name"] == "test_agent" - - ai_client_span1_available_tool = json.loads( - ai_client_span1["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - - assert all( - ai_client_span1_available_tool[k] == v for k, v in available_tool.items() - ) + assert agent_span["name"] == "invoke_agent test_agent" + assert agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + assert agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" - assert ai_client_span1["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span1["attributes"][ - "gen_ai.request.messages" - ] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - ] - ) - assert ai_client_span1["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span1["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span1["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens"] == 10 - assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span1["attributes"]["gen_ai.usage.output_tokens"] == 5 - assert ( - ai_client_span1["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 - ) - assert ai_client_span1["attributes"]["gen_ai.usage.total_tokens"] == 15 - - tool_call = { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - "status": None, - } - - if OPENAI_VERSION >= (2, 25, 0): - tool_call["namespace"] = None - - parsed_tool_calls = json.loads( - ai_client_span1["attributes"]["gen_ai.response.tool_calls"] - ) - assert len(parsed_tool_calls) == 1 - assert tool_call.items() <= parsed_tool_calls[0].items() - - assert tool_span["name"] == "execute_tool simple_test_tool" - assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" - - assert tool_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert tool_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert tool_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert tool_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert tool_span["attributes"]["gen_ai.system"] == "openai" - assert tool_span["attributes"]["gen_ai.tool.description"] == "A simple tool" - assert tool_span["attributes"]["gen_ai.tool.input"] == '{"message": "hello"}' - assert tool_span["attributes"]["gen_ai.tool.name"] == "simple_test_tool" - assert ( - tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" - ) - assert ai_client_span2["name"] == "chat gpt-4" - assert ai_client_span2["attributes"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span2["attributes"]["gen_ai.operation.name"] == "chat" + assert agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 + assert agent_span["attributes"]["gen_ai.system"] == "openai" - ai_client_span2_available_tool = json.loads( - ai_client_span2["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] + assert ai_client_span1["name"] == "chat gpt-4" + assert ai_client_span1["attributes"]["gen_ai.operation.name"] == "chat" + assert ai_client_span1["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span1["attributes"]["gen_ai.agent.name"] == "test_agent" - assert all( - ai_client_span2_available_tool[k] == v for k, v in available_tool.items() - ) + ai_client_span1_available_tool = json.loads( + ai_client_span1["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] - assert ai_client_span2["attributes"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span2["attributes"][ - "gen_ai.request.messages" - ] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - { - "role": "assistant", - "content": [ - { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - } - ], - }, - { - "role": "tool", - "content": [ - { - "call_id": "call_123", - "output": "Tool executed with: hello", - "type": "function_call_output", - } - ], - }, - ] - ) - assert ai_client_span2["attributes"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span2["attributes"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span2["attributes"]["gen_ai.request.top_p"] == 1.0 - assert ( - ai_client_span2["attributes"]["gen_ai.response.text"] - == "Task completed using the tool" - ) - assert ai_client_span2["attributes"]["gen_ai.system"] == "openai" - assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens"] == 15 - assert ( - ai_client_span2["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 - ) - assert ai_client_span2["attributes"]["gen_ai.usage.output_tokens"] == 10 - assert ai_client_span2["attributes"]["gen_ai.usage.total_tokens"] == 25 + assert all( + ai_client_span1_available_tool[k] == v for k, v in available_tool.items() + ) - else: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) + assert ai_client_span1["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span1["attributes"]["gen_ai.request.messages"] == safe_serialize( + [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please use the simple test tool"} + ], + }, + ] + ) + assert ai_client_span1["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span1["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span1["attributes"]["gen_ai.request.top_p"] == 1.0 + assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens"] == 10 + assert ai_client_span1["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span1["attributes"]["gen_ai.usage.output_tokens"] == 5 + assert ai_client_span1["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 + assert ai_client_span1["attributes"]["gen_ai.usage.total_tokens"] == 15 + + tool_call = { + "arguments": '{"message": "hello"}', + "call_id": "call_123", + "name": "simple_test_tool", + "type": "function_call", + "id": "call_123", + "status": None, + } - events = capture_events() + if OPENAI_VERSION >= (2, 25, 0): + tool_call["namespace"] = None - await agents.Runner.run( - agent_with_tool, - "Please use the simple test tool", - run_config=test_run_config, - hooks=RunHooks() if user_hooks else None, - ) + parsed_tool_calls = json.loads( + ai_client_span1["attributes"]["gen_ai.response.tool_calls"] + ) + assert len(parsed_tool_calls) == 1 + assert tool_call.items() <= parsed_tool_calls[0].items() + + assert tool_span["name"] == "execute_tool simple_test_tool" + assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" + + assert tool_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert tool_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert tool_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert tool_span["attributes"]["gen_ai.request.top_p"] == 1.0 + assert tool_span["attributes"]["gen_ai.system"] == "openai" + assert tool_span["attributes"]["gen_ai.tool.description"] == "A simple tool" + assert tool_span["attributes"]["gen_ai.tool.input"] == '{"message": "hello"}' + assert tool_span["attributes"]["gen_ai.tool.name"] == "simple_test_tool" + assert tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" + assert ai_client_span2["name"] == "chat gpt-4" + assert ai_client_span2["attributes"]["gen_ai.agent.name"] == "test_agent" + assert ai_client_span2["attributes"]["gen_ai.operation.name"] == "chat" + + ai_client_span2_available_tool = json.loads( + ai_client_span2["attributes"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] + )[0] + + assert all( + ai_client_span2_available_tool[k] == v for k, v in available_tool.items() + ) - (transaction,) = events - spans = transaction["spans"] - agent_span = next( - span for span in spans if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span1, ai_client_span2 = ( - span for span in spans if span["op"] == OP.GEN_AI_CHAT - ) - tool_span = next(span for span in spans if span["op"] == OP.GEN_AI_EXECUTE_TOOL) - - available_tool = { - "name": "simple_test_tool", - "description": "A simple tool", - "parameters": { - "properties": {"message": {"title": "Message", "type": "string"}}, - "required": ["message"], - "title": "simple_test_tool_args", - "type": "object", - "additionalProperties": False, + assert ai_client_span2["attributes"]["gen_ai.request.max_tokens"] == 100 + assert ai_client_span2["attributes"]["gen_ai.request.messages"] == safe_serialize( + [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Please use the simple test tool"} + ], }, - } - - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" - - assert agent_span["description"] == "invoke_agent test_agent" - assert agent_span["origin"] == "auto.ai.openai_agents" - assert agent_span["data"]["gen_ai.agent.name"] == "test_agent" - assert agent_span["data"]["gen_ai.operation.name"] == "invoke_agent" - - assert agent_span["data"]["gen_ai.request.max_tokens"] == 100 - assert agent_span["data"]["gen_ai.request.model"] == "gpt-4" - assert agent_span["data"]["gen_ai.request.temperature"] == 0.7 - assert agent_span["data"]["gen_ai.request.top_p"] == 1.0 - assert agent_span["data"]["gen_ai.system"] == "openai" - - assert ai_client_span1["description"] == "chat gpt-4" - assert ai_client_span1["data"]["gen_ai.operation.name"] == "chat" - assert ai_client_span1["data"]["gen_ai.system"] == "openai" - assert ai_client_span1["data"]["gen_ai.agent.name"] == "test_agent" - - ai_client_span1_available_tool = json.loads( - ai_client_span1["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - assert all( - ai_client_span1_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span1["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span1["data"]["gen_ai.request.messages"] == safe_serialize( - [ - { - "role": "user", - "content": [ - {"type": "text", "text": "Please use the simple test tool"} - ], - }, - ] - ) - assert ai_client_span1["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span1["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span1["data"]["gen_ai.request.top_p"] == 1.0 - assert ai_client_span1["data"]["gen_ai.usage.input_tokens"] == 10 - assert ai_client_span1["data"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span1["data"]["gen_ai.usage.output_tokens"] == 5 - assert ai_client_span1["data"]["gen_ai.usage.output_tokens.reasoning"] == 0 - assert ai_client_span1["data"]["gen_ai.usage.total_tokens"] == 15 - - tool_call = { - "arguments": '{"message": "hello"}', - "call_id": "call_123", - "name": "simple_test_tool", - "type": "function_call", - "id": "call_123", - "status": None, - } - - parsed_tool_calls = json.loads( - ai_client_span1["data"]["gen_ai.response.tool_calls"] - ) - assert len(parsed_tool_calls) == 1 - assert tool_call.items() <= parsed_tool_calls[0].items() - - assert tool_span["description"] == "execute_tool simple_test_tool" - assert tool_span["data"]["gen_ai.agent.name"] == "test_agent" - assert tool_span["data"]["gen_ai.operation.name"] == "execute_tool" - - assert tool_span["data"]["gen_ai.request.max_tokens"] == 100 - assert tool_span["data"]["gen_ai.request.model"] == "gpt-4" - assert tool_span["data"]["gen_ai.request.temperature"] == 0.7 - assert tool_span["data"]["gen_ai.request.top_p"] == 1.0 - assert tool_span["data"]["gen_ai.system"] == "openai" - assert tool_span["data"]["gen_ai.tool.description"] == "A simple tool" - assert tool_span["data"]["gen_ai.tool.input"] == '{"message": "hello"}' - assert tool_span["data"]["gen_ai.tool.name"] == "simple_test_tool" - assert tool_span["data"]["gen_ai.tool.output"] == "Tool executed with: hello" - assert ai_client_span2["description"] == "chat gpt-4" - assert ai_client_span2["data"]["gen_ai.agent.name"] == "test_agent" - assert ai_client_span2["data"]["gen_ai.operation.name"] == "chat" - - ai_client_span2_available_tool = json.loads( - ai_client_span2["data"][SPANDATA.GEN_AI_TOOL_DEFINITIONS] - )[0] - assert all( - ai_client_span2_available_tool[k] == v for k, v in available_tool.items() - ) - - assert ai_client_span2["data"]["gen_ai.request.max_tokens"] == 100 - assert ai_client_span2["data"]["gen_ai.request.messages"] == safe_serialize( - [ - { - "role": "tool", - "content": [ - { - "call_id": "call_123", - "output": "Tool executed with: hello", - "type": "function_call_output", - } - ], - }, - ] - ) - assert ai_client_span2["data"]["gen_ai.request.model"] == "gpt-4" - assert ai_client_span2["data"]["gen_ai.request.temperature"] == 0.7 - assert ai_client_span2["data"]["gen_ai.request.top_p"] == 1.0 - assert ( - ai_client_span2["data"]["gen_ai.response.text"] - == "Task completed using the tool" - ) - assert ai_client_span2["data"]["gen_ai.system"] == "openai" - assert ai_client_span2["data"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ai_client_span2["data"]["gen_ai.usage.input_tokens"] == 15 - assert ai_client_span2["data"]["gen_ai.usage.output_tokens.reasoning"] == 0 - assert ai_client_span2["data"]["gen_ai.usage.output_tokens"] == 10 - assert ai_client_span2["data"]["gen_ai.usage.total_tokens"] == 25 + { + "role": "assistant", + "content": [ + { + "arguments": '{"message": "hello"}', + "call_id": "call_123", + "name": "simple_test_tool", + "type": "function_call", + "id": "call_123", + } + ], + }, + { + "role": "tool", + "content": [ + { + "call_id": "call_123", + "output": "Tool executed with: hello", + "type": "function_call_output", + } + ], + }, + ] + ) + assert ai_client_span2["attributes"]["gen_ai.request.model"] == "gpt-4" + assert ai_client_span2["attributes"]["gen_ai.request.temperature"] == 0.7 + assert ai_client_span2["attributes"]["gen_ai.request.top_p"] == 1.0 + assert ( + ai_client_span2["attributes"]["gen_ai.response.text"] + == "Task completed using the tool" + ) + assert ai_client_span2["attributes"]["gen_ai.system"] == "openai" + assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 + assert ai_client_span2["attributes"]["gen_ai.usage.input_tokens"] == 15 + assert ai_client_span2["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 0 + assert ai_client_span2["attributes"]["gen_ai.usage.output_tokens"] == 10 + assert ai_client_span2["attributes"]["gen_ai.usage.total_tokens"] == 25 @pytest.mark.parametrize("user_hooks", [True, False]) -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_run_streamed_tool_execution_span( sentry_init, - capture_events, capture_items, test_agent, simple_test_tool, @@ -2869,7 +2177,6 @@ async def test_run_streamed_tool_execution_span( async_iterator, server_side_event_chunks, streaming_responses_tool_call_model_responses, - span_streaming, user_hooks, ): """ @@ -2935,145 +2242,80 @@ async def test_run_streamed_tool_execution_span( async_iterator(server_side_event_chunks(next(responses))), request_headers=request_headers, ) - - if span_streaming: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = agents.Runner.run_streamed( - agent_with_tool, - "Please use the simple test tool", - run_config=test_run_config, - hooks=RunHooks() if user_hooks else None, - ) - - async for event in result.stream_events(): - pass - - sentry_sdk.flush() - spans = [item.payload for item in items] - - agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT - ) - tool_span = next( - span - for span in spans - if span["attributes"].get("sentry.op") == OP.GEN_AI_EXECUTE_TOOL - ) - - assert agent_span["name"] == "invoke_agent test_agent" - assert agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" - - assert agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert agent_span["attributes"]["gen_ai.system"] == "openai" - - assert tool_span["name"] == "execute_tool simple_test_tool" - assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" - assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" - - assert tool_span["attributes"]["gen_ai.request.max_tokens"] == 100 - assert tool_span["attributes"]["gen_ai.request.model"] == "gpt-4" - assert tool_span["attributes"]["gen_ai.request.temperature"] == 0.7 - assert tool_span["attributes"]["gen_ai.request.top_p"] == 1.0 - assert tool_span["attributes"]["gen_ai.system"] == "openai" - assert tool_span["attributes"]["gen_ai.tool.description"] == "A simple tool" - assert tool_span["attributes"]["gen_ai.tool.input"] == '{"message": "hello"}' - assert tool_span["attributes"]["gen_ai.tool.name"] == "simple_test_tool" - assert ( - tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", ) - else: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() + items = capture_items("span") - result = agents.Runner.run_streamed( - agent_with_tool, - "Please use the simple test tool", - run_config=test_run_config, - hooks=RunHooks() if user_hooks else None, - ) + result = agents.Runner.run_streamed( + agent_with_tool, + "Please use the simple test tool", + run_config=test_run_config, + hooks=RunHooks() if user_hooks else None, + ) - async for event in result.stream_events(): - pass + async for event in result.stream_events(): + pass - (transaction,) = events - spans = transaction["spans"] - agent_span = next( - span for span in spans if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - tool_span = next(span for span in spans if span["op"] == OP.GEN_AI_EXECUTE_TOOL) + sentry_sdk.flush() + spans = [item.payload for item in items] - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + tool_span = next( + span + for span in spans + if span["attributes"].get("sentry.op") == OP.GEN_AI_EXECUTE_TOOL + ) - assert agent_span["description"] == "invoke_agent test_agent" - assert agent_span["origin"] == "auto.ai.openai_agents" - assert agent_span["data"]["gen_ai.agent.name"] == "test_agent" - assert agent_span["data"]["gen_ai.operation.name"] == "invoke_agent" + assert agent_span["name"] == "invoke_agent test_agent" + assert agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + assert agent_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert agent_span["attributes"]["gen_ai.operation.name"] == "invoke_agent" - assert agent_span["data"]["gen_ai.request.max_tokens"] == 100 - assert agent_span["data"]["gen_ai.request.model"] == "gpt-4" - assert agent_span["data"]["gen_ai.request.temperature"] == 0.7 - assert agent_span["data"]["gen_ai.request.top_p"] == 1.0 - assert agent_span["data"]["gen_ai.system"] == "openai" + assert agent_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert agent_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert agent_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert agent_span["attributes"]["gen_ai.request.top_p"] == 1.0 + assert agent_span["attributes"]["gen_ai.system"] == "openai" - assert tool_span["description"] == "execute_tool simple_test_tool" - assert tool_span["data"]["gen_ai.agent.name"] == "test_agent" - assert tool_span["data"]["gen_ai.operation.name"] == "execute_tool" + assert tool_span["name"] == "execute_tool simple_test_tool" + assert tool_span["attributes"]["gen_ai.agent.name"] == "test_agent" + assert tool_span["attributes"]["gen_ai.operation.name"] == "execute_tool" - assert tool_span["data"]["gen_ai.request.max_tokens"] == 100 - assert tool_span["data"]["gen_ai.request.model"] == "gpt-4" - assert tool_span["data"]["gen_ai.request.temperature"] == 0.7 - assert tool_span["data"]["gen_ai.request.top_p"] == 1.0 - assert tool_span["data"]["gen_ai.system"] == "openai" - assert tool_span["data"]["gen_ai.tool.description"] == "A simple tool" - assert tool_span["data"]["gen_ai.tool.input"] == '{"message": "hello"}' - assert tool_span["data"]["gen_ai.tool.name"] == "simple_test_tool" - assert tool_span["data"]["gen_ai.tool.output"] == "Tool executed with: hello" + assert tool_span["attributes"]["gen_ai.request.max_tokens"] == 100 + assert tool_span["attributes"]["gen_ai.request.model"] == "gpt-4" + assert tool_span["attributes"]["gen_ai.request.temperature"] == 0.7 + assert tool_span["attributes"]["gen_ai.request.top_p"] == 1.0 + assert tool_span["attributes"]["gen_ai.system"] == "openai" + assert tool_span["attributes"]["gen_ai.tool.description"] == "A simple tool" + assert tool_span["attributes"]["gen_ai.tool.input"] == '{"message": "hello"}' + assert tool_span["attributes"]["gen_ai.tool.name"] == "simple_test_tool" + assert tool_span["attributes"]["gen_ai.tool.output"] == "Tool executed with: hello" @pytest.fixture def run_tool_agent( sentry_init, - capture_events, capture_items, test_agent, get_model_response, nonstreaming_responses_tool_call_model_responses, ): - async def inner(tool, span_streaming, run_kwargs=None, **init_kwargs): + async def inner(tool, run_kwargs=None, **init_kwargs): client = AsyncOpenAI(api_key="test-key") model = OpenAIResponsesModel(model="gpt-4", openai_client=client) agent_with_tool = test_agent.clone(tools=[tool], model=model) @@ -3131,13 +2373,12 @@ async def inner(tool, span_streaming, run_kwargs=None, **init_kwargs): integrations=[OpenAIAgentsIntegration()], disabled_integrations=[StdlibIntegration], traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, + trace_lifecycle="stream", **init_kwargs, ) - items = capture_items("span") if span_streaming else None - events = None if span_streaming else capture_events() + items = capture_items("span") + events = None await agents.Runner.run( agent_with_tool, @@ -3145,15 +2386,13 @@ async def inner(tool, span_streaming, run_kwargs=None, **init_kwargs): run_config=test_run_config, **(run_kwargs or {}), ) - - if span_streaming: - sentry_sdk.flush() - tool_span = next( - item.payload - for item in items - if item.payload["attributes"].get("sentry.op") == OP.GEN_AI_EXECUTE_TOOL - ) - return tool_span, tool_span["attributes"] + sentry_sdk.flush() + tool_span = next( + item.payload + for item in items + if item.payload["attributes"].get("sentry.op") == OP.GEN_AI_EXECUTE_TOOL + ) + return tool_span, tool_span["attributes"] (transaction,) = events tool_span = next( @@ -3176,7 +2415,6 @@ def simple_test_tool(message: str) -> str: return simple_test_tool -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.parametrize( "data_collection,send_default_pii,expect_input,expect_output", [ @@ -3246,7 +2484,6 @@ async def test_tool_execution_span_data_collection( send_default_pii, expect_input, expect_output, - span_streaming, ): init_kwargs = { "send_default_pii": send_default_pii, @@ -3256,7 +2493,6 @@ async def test_tool_execution_span_data_collection( _, tool_span_data = await run_tool_agent( simple_test_tool, - span_streaming, **init_kwargs, ) @@ -3273,11 +2509,9 @@ async def test_tool_execution_span_data_collection( assert SPANDATA.GEN_AI_TOOL_OUTPUT not in tool_span_data -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_tool_execution_error_data_collection( run_tool_agent, - span_streaming, ): @agents.function_tool def failing_tool(message: str) -> str: @@ -3286,16 +2520,14 @@ def failing_tool(message: str) -> str: tool_span, tool_span_data = await run_tool_agent( failing_tool, - span_streaming, _experiments={"data_collection": {"gen_ai": {"outputs": False}}}, ) assert tool_span_data[SPANDATA.GEN_AI_TOOL_NAME] == "failing_tool" - assert tool_span["status"] == ("error" if span_streaming else "internal_error") + assert tool_span["status"] == "error" assert SPANDATA.GEN_AI_TOOL_OUTPUT not in tool_span_data -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.skipif( parse_version(OPENAI_AGENTS_VERSION) < (0, 4, 0), reason="conversation_id support requires openai-agents >= 0.4.0", @@ -3304,11 +2536,9 @@ def failing_tool(message: str) -> str: async def test_tool_execution_span_non_pii_data_always_set( run_tool_agent, simple_test_tool, - span_streaming, ): _, tool_span_data = await run_tool_agent( simple_test_tool, - span_streaming, run_kwargs={"conversation_id": "conv_tool_test_456"}, _experiments={ "data_collection": {"gen_ai": {"inputs": False, "outputs": False}} @@ -3356,7 +2586,6 @@ async def test_hosted_mcp_tool_propagation_header_streamed( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, release="d08ebdb9309e1b004c6f52202de58a09c2268e42", - stream_gen_ai_spans=False, ) request_headers = {} @@ -3520,7 +2749,6 @@ async def test_hosted_mcp_tool_propagation_headers( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, release="d08ebdb9309e1b004c6f52202de58a09c2268e42", - stream_gen_ai_spans=False, ) response = get_model_response(EXAMPLE_RESPONSE, serialize_pydantic=True) @@ -3577,15 +2805,12 @@ async def test_hosted_mcp_tool_propagation_headers( assert hosted_mcp_tool["headers"]["baggage"] == expected_outgoing_baggage -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_model_behavior_error( sentry_init, - capture_events, capture_items, test_agent, simple_test_tool, - span_streaming, ): """ Example raising agents.exceptions.AgentsException before the agent invocation span is complete. @@ -3594,333 +2819,167 @@ async def test_model_behavior_error( # Create agent with the tool agent_with_tool = test_agent.clone(tools=[simple_test_tool]) + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( + "agents.models.openai_responses.OpenAIResponsesModel.get_response" + ) as mock_get_response: + # Create a mock response that includes tool calls + tool_call = ResponseFunctionToolCall( + id="call_123", + call_id="call_123", + name="wrong_tool", + type="function_call", + arguments='{"message": "hello"}', + ) - if span_streaming: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.get_response" - ) as mock_get_response: - # Create a mock response that includes tool calls - tool_call = ResponseFunctionToolCall( - id="call_123", - call_id="call_123", - name="wrong_tool", - type="function_call", - arguments='{"message": "hello"}', - ) - - tool_response = ModelResponse( - output=[tool_call], - usage=Usage( - requests=1, input_tokens=10, output_tokens=5, total_tokens=15 - ), - response_id="resp_tool_123", - ) - - mock_get_response.side_effect = [tool_response] - - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - with pytest.raises(ModelBehaviorError): - await agents.Runner.run( - agent_with_tool, - "Please use the simple test tool", - run_config=test_run_config, - ) - - sentry_sdk.flush() - spans = [item.payload for item in items] + tool_response = ModelResponse( + output=[tool_call], + usage=Usage(requests=1, input_tokens=10, output_tokens=5, total_tokens=15), + response_id="resp_tool_123", + ) - ( - ai_client_span1, - agent_span, - workflow_span, - ) = spans - assert workflow_span["name"] == "test_agent workflow" - assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - - assert agent_span["name"] == "invoke_agent test_agent" - assert agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - - # Error due to unrecognized tool in model response. - assert agent_span["status"] == "error" - else: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.get_response" - ) as mock_get_response: - # Create a mock response that includes tool calls - tool_call = ResponseFunctionToolCall( - id="call_123", - call_id="call_123", - name="wrong_tool", - type="function_call", - arguments='{"message": "hello"}', - ) + mock_get_response.side_effect = [tool_response] - tool_response = ModelResponse( - output=[tool_call], - usage=Usage( - requests=1, input_tokens=10, output_tokens=5, total_tokens=15 - ), - response_id="resp_tool_123", - ) + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", + ) - mock_get_response.side_effect = [tool_response] + items = capture_items("span") - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, + with pytest.raises(ModelBehaviorError): + await agents.Runner.run( + agent_with_tool, + "Please use the simple test tool", + run_config=test_run_config, ) - events = capture_events() - - with pytest.raises(ModelBehaviorError): - await agents.Runner.run( - agent_with_tool, - "Please use the simple test tool", - run_config=test_run_config, - ) - (error, transaction) = events - spans = transaction["spans"] - ( - agent_span, - ai_client_span1, - ) = spans + sentry_sdk.flush() + spans = [item.payload for item in items] - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + ( + ai_client_span1, + agent_span, + workflow_span, + ) = spans + assert workflow_span["name"] == "test_agent workflow" + assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert agent_span["description"] == "invoke_agent test_agent" - assert agent_span["origin"] == "auto.ai.openai_agents" + assert agent_span["name"] == "invoke_agent test_agent" + assert agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - # Error due to unrecognized tool in model response. - assert agent_span["status"] == "internal_error" - assert agent_span["tags"]["status"] == "internal_error" + # Error due to unrecognized tool in model response. + assert agent_span["status"] == "error" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_run_error_handling( sentry_init, - capture_events, capture_items, test_agent, - span_streaming, ): """ Test error handling in agent execution. """ + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( + "agents.models.openai_responses.OpenAIResponsesModel.get_response" + ) as mock_get_response: + mock_get_response.side_effect = Exception("Model Error") - if span_streaming: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.get_response" - ) as mock_get_response: - mock_get_response.side_effect = Exception("Model Error") - - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("event", "span") - - with pytest.raises(Exception, match="Model Error"): - await agents.Runner.run( - test_agent, "Test input", run_config=test_run_config - ) - - (error_event,) = (item.payload for item in items if item.type == "event") - - assert error_event["exception"]["values"][0]["type"] == "Exception" - assert error_event["exception"]["values"][0]["value"] == "Model Error" - assert ( - error_event["exception"]["values"][0]["mechanism"]["type"] - == "openai_agents" - ) - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - (ai_client_span, invoke_agent_span, workflow_span) = spans - - assert workflow_span["name"] == "test_agent workflow" - assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - - assert invoke_agent_span["name"] == "invoke_agent test_agent" - assert ( - invoke_agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + sentry_init( + integrations=[ + OpenAIAgentsIntegration(), + LoggingIntegration(event_level=logging.CRITICAL), + ], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", ) - assert ai_client_span["name"] == "chat gpt-4" - assert ai_client_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert ai_client_span["status"] == "error" - else: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.get_response" - ) as mock_get_response: - mock_get_response.side_effect = Exception("Model Error") + items = capture_items("event", "span") - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, + with pytest.raises(Exception, match="Model Error"): + await agents.Runner.run( + test_agent, "Test input", run_config=test_run_config ) - events = capture_events() - with pytest.raises(Exception, match="Model Error"): - await agents.Runner.run( - test_agent, "Test input", run_config=test_run_config - ) - - ( - error_event, - transaction, - ) = events + (error_event,) = (item.payload for item in items if item.type == "event") - assert error_event["exception"]["values"][0]["type"] == "Exception" - assert error_event["exception"]["values"][0]["value"] == "Model Error" - assert ( - error_event["exception"]["values"][0]["mechanism"]["type"] - == "openai_agents" - ) + assert error_event["exception"]["values"][0]["type"] == "Exception" + assert error_event["exception"]["values"][0]["value"] == "Model Error" + assert error_event["exception"]["values"][0]["mechanism"]["type"] == "openai_agents" - spans = transaction["spans"] - (invoke_agent_span, ai_client_span) = spans + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + (ai_client_span, invoke_agent_span, workflow_span) = spans - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + assert workflow_span["name"] == "test_agent workflow" + assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert invoke_agent_span["description"] == "invoke_agent test_agent" - assert invoke_agent_span["origin"] == "auto.ai.openai_agents" + assert invoke_agent_span["name"] == "invoke_agent test_agent" + assert invoke_agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert ai_client_span["description"] == "chat gpt-4" - assert ai_client_span["origin"] == "auto.ai.openai_agents" - assert ai_client_span["status"] == "internal_error" - assert ai_client_span["tags"]["status"] == "internal_error" + assert ai_client_span["name"] == "chat gpt-4" + assert ai_client_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + assert ai_client_span["status"] == "error" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_run_streamed_error_handling( sentry_init, - capture_events, capture_items, test_agent, - span_streaming, ): """ Test error handling in agent execution. """ + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( + "agents.models.openai_responses.OpenAIResponsesModel.stream_response" + ) as mock_get_response: + mock_get_response.side_effect = Exception("Model Error") - if span_streaming: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.stream_response" - ) as mock_get_response: - mock_get_response.side_effect = Exception("Model Error") - - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("event", "span") - - with pytest.raises(Exception, match="Model Error"): - result = agents.Runner.run_streamed( - test_agent, "Test input", run_config=test_run_config - ) - - async for event in result.stream_events(): - pass - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - (ai_client_span, invoke_agent_span, workflow_span) = spans - - assert workflow_span["name"] == "test_agent workflow" - assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - - assert invoke_agent_span["name"] == "invoke_agent test_agent" - assert ( - invoke_agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + sentry_init( + integrations=[ + OpenAIAgentsIntegration(), + LoggingIntegration(event_level=logging.CRITICAL), + ], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", ) - assert ai_client_span["name"] == "chat gpt-4" - assert ai_client_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert ai_client_span["status"] == "error" - else: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.stream_response" - ) as mock_get_response: - mock_get_response.side_effect = Exception("Model Error") + items = capture_items("event", "span") - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, + with pytest.raises(Exception, match="Model Error"): + result = agents.Runner.run_streamed( + test_agent, "Test input", run_config=test_run_config ) - events = capture_events() - - with pytest.raises(Exception, match="Model Error"): - result = agents.Runner.run_streamed( - test_agent, "Test input", run_config=test_run_config - ) - - async for event in result.stream_events(): - pass - (transaction,) = events + async for event in result.stream_events(): + pass - spans = transaction["spans"] - (invoke_agent_span, ai_client_span) = spans + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + (ai_client_span, invoke_agent_span, workflow_span) = spans - assert transaction["transaction"] == "test_agent workflow" - assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" + assert workflow_span["name"] == "test_agent workflow" + assert workflow_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert invoke_agent_span["description"] == "invoke_agent test_agent" - assert invoke_agent_span["origin"] == "auto.ai.openai_agents" + assert invoke_agent_span["name"] == "invoke_agent test_agent" + assert invoke_agent_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" - assert ai_client_span["description"] == "chat gpt-4" - assert ai_client_span["origin"] == "auto.ai.openai_agents" - assert ai_client_span["status"] == "internal_error" - assert ai_client_span["tags"]["status"] == "internal_error" + assert ai_client_span["name"] == "chat gpt-4" + assert ai_client_span["attributes"]["sentry.origin"] == "auto.ai.openai_agents" + assert ai_client_span["status"] == "error" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_error_captures_input_data( sentry_init, - capture_events, capture_items, test_agent, - span_streaming, ): """ Test that input data is captured even when the API call raises an exception. @@ -3939,179 +2998,96 @@ async def test_error_captures_input_data( 500, request=model_request, ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - - items = capture_items("event", "span") - - with pytest.raises(InternalServerError, match="Error code: 500"): - await agents.Runner.run(agent, "Test input", run_config=test_run_config) - - (error_event,) = (item.payload for item in items if item.type == "event") - - assert error_event["exception"]["values"][0]["type"] == "InternalServerError" - assert error_event["exception"]["values"][0]["value"] == "Error code: 500" - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - ai_client_span = [ - s for s in spans if s["attributes"].get("sentry.op", "") == "gen_ai.chat" - ][0] - - assert ai_client_span["name"] == "chat gpt-4" - assert ai_client_span["status"] == "error" - - assert "gen_ai.request.messages" in ai_client_span["attributes"] - request_messages = safe_serialize( - [ - {"role": "user", "content": [{"type": "text", "text": "Test input"}]}, - ] - ) - assert ( - ai_client_span["attributes"]["gen_ai.request.messages"] == request_messages + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[ + OpenAIAgentsIntegration(), + LoggingIntegration(event_level=logging.CRITICAL), + ], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() + items = capture_items("event", "span") - with pytest.raises(InternalServerError, match="Error code: 500"): - await agents.Runner.run(agent, "Test input", run_config=test_run_config) + with pytest.raises(InternalServerError, match="Error code: 500"): + await agents.Runner.run(agent, "Test input", run_config=test_run_config) - ( - error_event, - transaction, - ) = events + (error_event,) = (item.payload for item in items if item.type == "event") - assert error_event["exception"]["values"][0]["type"] == "InternalServerError" - assert error_event["exception"]["values"][0]["value"] == "Error code: 500" + assert error_event["exception"]["values"][0]["type"] == "InternalServerError" + assert error_event["exception"]["values"][0]["value"] == "Error code: 500" - spans = transaction["spans"] - ai_client_span = [s for s in spans if s["op"] == "gen_ai.chat"][0] + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + ai_client_span = [ + s for s in spans if s["attributes"].get("sentry.op", "") == "gen_ai.chat" + ][0] - assert ai_client_span["description"] == "chat gpt-4" - assert ai_client_span["status"] == "internal_error" - assert ai_client_span["tags"]["status"] == "internal_error" + assert ai_client_span["name"] == "chat gpt-4" + assert ai_client_span["status"] == "error" - assert "gen_ai.request.messages" in ai_client_span["data"] - request_messages = safe_serialize( - [ - {"role": "user", "content": [{"type": "text", "text": "Test input"}]}, - ] - ) - assert ai_client_span["data"]["gen_ai.request.messages"] == request_messages + assert "gen_ai.request.messages" in ai_client_span["attributes"] + request_messages = safe_serialize( + [ + {"role": "user", "content": [{"type": "text", "text": "Test input"}]}, + ] + ) + assert ai_client_span["attributes"]["gen_ai.request.messages"] == request_messages -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_span_status_error( sentry_init, - capture_events, capture_items, test_agent, - span_streaming, ): - if span_streaming: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.get_response" - ) as mock_get_response: - mock_get_response.side_effect = ValueError("Model Error") - - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("event", "span") + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( + "agents.models.openai_responses.OpenAIResponsesModel.get_response" + ) as mock_get_response: + mock_get_response.side_effect = ValueError("Model Error") - with pytest.raises(ValueError, match="Model Error"): - await agents.Runner.run( - test_agent, "Test input", run_config=test_run_config - ) - - (error,) = (item.payload for item in items if item.type == "event") - assert error["level"] == "error" - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - assert spans[0]["status"] == "error" + sentry_init( + integrations=[ + OpenAIAgentsIntegration(), + LoggingIntegration(event_level=logging.CRITICAL), + ], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) - assert spans[2]["is_segment"] is True - assert spans[2]["status"] == "error" - else: - with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}), patch( - "agents.models.openai_responses.OpenAIResponsesModel.get_response" - ) as mock_get_response: - mock_get_response.side_effect = ValueError("Model Error") + items = capture_items("event", "span") - sentry_init( - integrations=[ - OpenAIAgentsIntegration(), - LoggingIntegration(event_level=logging.CRITICAL), - ], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, + with pytest.raises(ValueError, match="Model Error"): + await agents.Runner.run( + test_agent, "Test input", run_config=test_run_config ) - events = capture_events() + (error,) = (item.payload for item in items if item.type == "event") + assert error["level"] == "error" - with pytest.raises(ValueError, match="Model Error"): - await agents.Runner.run( - test_agent, "Test input", run_config=test_run_config - ) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + assert spans[0]["status"] == "error" - (error, transaction) = events - assert error["level"] == "error" - assert transaction["spans"][0]["status"] == "internal_error" - assert transaction["spans"][0]["tags"]["status"] == "internal_error" - assert transaction["contexts"]["trace"]["status"] == "internal_error" + assert spans[2]["is_segment"] is True + assert spans[2]["status"] == "error" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_multiple_agents_asyncio( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, - span_streaming, ): """ Test that multiple agents can be run at the same time in asyncio tasks @@ -4124,69 +3100,35 @@ async def test_multiple_agents_asyncio( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - async def run(): - await agents.Runner.run( - starting_agent=agent, - input="Test input", - run_config=test_run_config, - ) - - await asyncio.gather(*[run() for _ in range(3)]) - - sentry_sdk.flush() - spans = [item.payload for item in items] + items = capture_items("span") - assert spans[2]["name"] == "test_agent workflow" - assert spans[5]["name"] == "test_agent workflow" - assert spans[8]["name"] == "test_agent workflow" - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, + async def run(): + await agents.Runner.run( + starting_agent=agent, + input="Test input", + run_config=test_run_config, ) - events = capture_events() - async def run(): - await agents.Runner.run( - starting_agent=agent, - input="Test input", - run_config=test_run_config, - ) + await asyncio.gather(*[run() for _ in range(3)]) - await asyncio.gather(*[run() for _ in range(3)]) + sentry_sdk.flush() + spans = [item.payload for item in items] - assert len(events) == 3 - txn1, txn2, txn3 = events - - assert txn1["type"] == "transaction" - assert txn1["transaction"] == "test_agent workflow" - assert txn2["type"] == "transaction" - assert txn2["transaction"] == "test_agent workflow" - assert txn3["type"] == "transaction" - assert txn3["transaction"] == "test_agent workflow" + assert spans[2]["name"] == "test_agent workflow" + assert spans[5]["name"] == "test_agent workflow" + assert spans[8]["name"] == "test_agent workflow" # Test input messages with mixed roles including "ai" @@ -4210,7 +3152,6 @@ def test_openai_agents_message_role_mapping(sentry_init, test_message, expected_ integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, send_default_pii=True, - stream_gen_ai_spans=False, ) get_response_kwargs = {"input": [test_message]} @@ -4230,16 +3171,13 @@ def test_openai_agents_message_role_mapping(sentry_init, test_message, expected_ assert stored_messages[0]["role"] == expected_role -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_tool_execution_error_tracing( sentry_init, - capture_events, capture_items, test_agent, get_model_response, nonstreaming_responses_tool_call_model_responses, - span_streaming, ): """ Test that tool execution errors are properly tracked via error tracing patch. @@ -4305,107 +3243,57 @@ def failing_tool(message: str) -> str: next(responses), serialize_pydantic=True, ) + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", + ) + items = capture_items("span", "transaction") - if span_streaming: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - items = capture_items("span", "transaction") + # Note: The agents library catches tool exceptions internally, + # so we don't expect this to raise + await agents.Runner.run( + agent_with_tool, + "Please use the failing tool", + run_config=test_run_config, + ) - # Note: The agents library catches tool exceptions internally, - # so we don't expect this to raise - await agents.Runner.run( - agent_with_tool, - "Please use the failing tool", - run_config=test_run_config, - ) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - - # Find the execute_tool span - execute_tool_span = None - for span in spans: - description = span.get("name", "") - if description is not None and description.startswith( - "execute_tool failing_tool" - ): - execute_tool_span = span - break - - # Verify the execute_tool span was created - assert execute_tool_span is not None, "execute_tool span was not created" - assert execute_tool_span["name"] == "execute_tool failing_tool" - assert execute_tool_span["attributes"]["gen_ai.tool.name"] == "failing_tool" - - # Verify error status was set (this is the key test for our patch) - # The span should be marked as error because the tool execution failed - assert execute_tool_span["status"] == "error" - else: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() + # Find the execute_tool span + execute_tool_span = None + for span in spans: + description = span.get("name", "") + if description is not None and description.startswith( + "execute_tool failing_tool" + ): + execute_tool_span = span + break + + # Verify the execute_tool span was created + assert execute_tool_span is not None, "execute_tool span was not created" + assert execute_tool_span["name"] == "execute_tool failing_tool" + assert execute_tool_span["attributes"]["gen_ai.tool.name"] == "failing_tool" + + # Verify error status was set (this is the key test for our patch) + # The span should be marked as error because the tool execution failed + assert execute_tool_span["status"] == "error" - # Note: The agents library catches tool exceptions internally, - # so we don't expect this to raise - await agents.Runner.run( - agent_with_tool, - "Please use the failing tool", - run_config=test_run_config, - ) - (transaction,) = events - spans = transaction["spans"] - - # Find the execute_tool span - execute_tool_span = None - for span in spans: - description = span.get("description", "") - if description is not None and description.startswith( - "execute_tool failing_tool" - ): - execute_tool_span = span - break - - # Verify the execute_tool span was created - assert execute_tool_span is not None, "execute_tool span was not created" - assert execute_tool_span["description"] == "execute_tool failing_tool" - assert execute_tool_span["data"]["gen_ai.tool.name"] == "failing_tool" - - # Verify error status was set (this is the key test for our patch) - # The span should be marked as error because the tool execution failed - assert execute_tool_span["status"] == "internal_error" - assert execute_tool_span["tags"]["status"] == "internal_error" - - -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_invoke_agent_span_includes_usage_data( sentry_init, - capture_events, capture_items, test_agent, get_model_response, - span_streaming, ): """ Test that invoke_agent spans include aggregated usage data from context_wrapper. @@ -4454,98 +3342,53 @@ async def test_invoke_agent_span_includes_usage_data( ), serialize_pydantic=True, ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - items = capture_items("span", "transaction") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - invoke_agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", ) + items = capture_items("span", "transaction") - # Verify invoke_agent span has usage data from context_wrapper - assert invoke_agent_span["name"] == "invoke_agent test_agent" - assert "gen_ai.usage.input_tokens" in invoke_agent_span["attributes"] - assert "gen_ai.usage.output_tokens" in invoke_agent_span["attributes"] - assert "gen_ai.usage.total_tokens" in invoke_agent_span["attributes"] - - assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens"] == 10 - assert invoke_agent_span["attributes"]["gen_ai.usage.output_tokens"] == 20 - assert invoke_agent_span["attributes"]["gen_ai.usage.total_tokens"] == 30 - assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 - assert ( - invoke_agent_span["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 5 + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] - invoke_agent_span = next( - span for span in spans if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + invoke_agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) - # Verify invoke_agent span has usage data from context_wrapper - assert invoke_agent_span["description"] == "invoke_agent test_agent" - assert "gen_ai.usage.input_tokens" in invoke_agent_span["data"] - assert "gen_ai.usage.output_tokens" in invoke_agent_span["data"] - assert "gen_ai.usage.total_tokens" in invoke_agent_span["data"] + # Verify invoke_agent span has usage data from context_wrapper + assert invoke_agent_span["name"] == "invoke_agent test_agent" + assert "gen_ai.usage.input_tokens" in invoke_agent_span["attributes"] + assert "gen_ai.usage.output_tokens" in invoke_agent_span["attributes"] + assert "gen_ai.usage.total_tokens" in invoke_agent_span["attributes"] - assert invoke_agent_span["data"]["gen_ai.usage.input_tokens"] == 10 - assert invoke_agent_span["data"]["gen_ai.usage.output_tokens"] == 20 - assert invoke_agent_span["data"]["gen_ai.usage.total_tokens"] == 30 - assert invoke_agent_span["data"]["gen_ai.usage.input_tokens.cached"] == 0 - assert invoke_agent_span["data"]["gen_ai.usage.output_tokens.reasoning"] == 5 + assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens"] == 10 + assert invoke_agent_span["attributes"]["gen_ai.usage.output_tokens"] == 20 + assert invoke_agent_span["attributes"]["gen_ai.usage.total_tokens"] == 30 + assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens.cached"] == 0 + assert invoke_agent_span["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 5 -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_ai_client_span_includes_response_model( sentry_init, - capture_events, capture_items, test_agent, get_model_response, - span_streaming, ): """ Test that ai_client spans (gen_ai.chat) include the response model from the actual API response. @@ -4594,80 +3437,43 @@ async def test_ai_client_span_includes_response_model( ), serialize_pydantic=True, ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - items = capture_items("span", "transaction") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", ) + items = capture_items("span", "transaction") - # Verify ai_client span has response model from API response - assert ai_client_span["name"] == "chat gpt-4" - assert "gen_ai.response.model" in ai_client_span["attributes"] - assert ( - ai_client_span["attributes"]["gen_ai.response.model"] - == "gpt-4.1-2025-04-14" + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - # Verify ai_client span has response model from API response - assert ai_client_span["description"] == "chat gpt-4" - assert "gen_ai.response.model" in ai_client_span["data"] - assert ai_client_span["data"]["gen_ai.response.model"] == "gpt-4.1-2025-04-14" + # Verify ai_client span has response model from API response + assert ai_client_span["name"] == "chat gpt-4" + assert "gen_ai.response.model" in ai_client_span["attributes"] + assert ai_client_span["attributes"]["gen_ai.response.model"] == "gpt-4.1-2025-04-14" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_ai_client_span_response_model_with_chat_completions( sentry_init, - capture_events, capture_items, get_model_response, - span_streaming, ): """ Test that response model is captured when using ChatCompletions API (not Responses API). @@ -4722,80 +3528,46 @@ async def test_ai_client_span_response_model_with_chat_completions( ), serialize_pydantic=True, ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - - items = capture_items("span", "transaction") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", ) - # Verify response model from API response is captured - assert "gen_ai.response.model" in ai_client_span["attributes"] - assert ( - ai_client_span["attributes"]["gen_ai.response.model"] - == "gpt-4o-mini-2024-07-18" - ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, - ) - events = capture_events() + items = capture_items("span", "transaction") - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config + ) - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - # Verify response model from API response is captured - assert "gen_ai.response.model" in ai_client_span["data"] - assert ( - ai_client_span["data"]["gen_ai.response.model"] == "gpt-4o-mini-2024-07-18" - ) + # Verify response model from API response is captured + assert "gen_ai.response.model" in ai_client_span["attributes"] + assert ( + ai_client_span["attributes"]["gen_ai.response.model"] + == "gpt-4o-mini-2024-07-18" + ) -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_multiple_llm_calls_aggregate_usage( sentry_init, - capture_events, capture_items, test_agent, get_model_response, - span_streaming, ): """ Test that invoke_agent spans show aggregated usage across multiple LLM calls @@ -4884,95 +3656,51 @@ def calculator(a: int, b: int) -> int: ), serialize_pydantic=True, ) - - if span_streaming: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_call_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent_with_tool, - "What is 5 + 3?", - run_config=test_run_config, - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items] - - invoke_agent_span = spans[3] - - # Verify invoke_agent span has aggregated usage from both API calls - # Total: 10 + 20 = 30 input tokens, 5 + 15 = 20 output tokens, 15 + 35 = 50 total - assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens"] == 30 - assert invoke_agent_span["attributes"]["gen_ai.usage.output_tokens"] == 20 - assert invoke_agent_span["attributes"]["gen_ai.usage.total_tokens"] == 50 - # Cached tokens should be aggregated: 0 + 5 = 5 - assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens.cached"] == 5 - # Reasoning tokens should be aggregated: 0 + 3 = 3 - assert ( - invoke_agent_span["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 3 + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_call_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", ) - else: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_call_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() - result = await agents.Runner.run( - agent_with_tool, - "What is 5 + 3?", - run_config=test_run_config, - ) + items = capture_items("span") + + result = await agents.Runner.run( + agent_with_tool, + "What is 5 + 3?", + run_config=test_run_config, + ) - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] + sentry_sdk.flush() + spans = [item.payload for item in items] - invoke_agent_span = spans[0] + invoke_agent_span = spans[3] - # Verify invoke_agent span has aggregated usage from both API calls - # Total: 10 + 20 = 30 input tokens, 5 + 15 = 20 output tokens, 15 + 35 = 50 total - assert invoke_agent_span["data"]["gen_ai.usage.input_tokens"] == 30 - assert invoke_agent_span["data"]["gen_ai.usage.output_tokens"] == 20 - assert invoke_agent_span["data"]["gen_ai.usage.total_tokens"] == 50 - # Cached tokens should be aggregated: 0 + 5 = 5 - assert invoke_agent_span["data"]["gen_ai.usage.input_tokens.cached"] == 5 - # Reasoning tokens should be aggregated: 0 + 3 = 3 - assert invoke_agent_span["data"]["gen_ai.usage.output_tokens.reasoning"] == 3 + # Verify invoke_agent span has aggregated usage from both API calls + # Total: 10 + 20 = 30 input tokens, 5 + 15 = 20 output tokens, 15 + 35 = 50 total + assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens"] == 30 + assert invoke_agent_span["attributes"]["gen_ai.usage.output_tokens"] == 20 + assert invoke_agent_span["attributes"]["gen_ai.usage.total_tokens"] == 50 + # Cached tokens should be aggregated: 0 + 5 = 5 + assert invoke_agent_span["attributes"]["gen_ai.usage.input_tokens.cached"] == 5 + # Reasoning tokens should be aggregated: 0 + 3 = 3 + assert invoke_agent_span["attributes"]["gen_ai.usage.output_tokens.reasoning"] == 3 -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_invoke_agent_span_includes_response_model( sentry_init, - capture_events, capture_items, test_agent, get_model_response, - span_streaming, ): """ Test that invoke_agent spans include the response model from the API response. @@ -5020,80 +3748,44 @@ async def test_invoke_agent_span_includes_response_model( ), serialize_pydantic=True, ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream" if span_streaming else "static", - stream_gen_ai_spans=False, - ) - - items = capture_items("span", "transaction") - - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items if item.type == "span"] - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", ) - # Also verify ai_client span has it - assert "gen_ai.response.model" in ai_client_span["attributes"] - assert ( - ai_client_span["attributes"]["gen_ai.response.model"] - == "gpt-4.1-2025-04-14" - ) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() + items = capture_items("span", "transaction") - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config + ) - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + sentry_sdk.flush() + spans = [item.payload for item in items if item.type == "span"] + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - # Also verify ai_client span has it - assert "gen_ai.response.model" in ai_client_span["data"] - assert ai_client_span["data"]["gen_ai.response.model"] == "gpt-4.1-2025-04-14" + # Also verify ai_client span has it + assert "gen_ai.response.model" in ai_client_span["attributes"] + assert ai_client_span["attributes"]["gen_ai.response.model"] == "gpt-4.1-2025-04-14" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.asyncio async def test_invoke_agent_span_uses_last_response_model( sentry_init, - capture_events, capture_items, test_agent, get_model_response, - span_streaming, ): """ Test that when an agent makes multiple LLM calls (e.g., with tools), @@ -5182,80 +3874,41 @@ def calculator(a: int, b: int) -> int: ), serialize_pydantic=True, ) + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[first_response, second_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + send_default_pii=True, + trace_lifecycle="stream", + ) - if span_streaming: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[first_response, second_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - send_default_pii=True, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent_with_tool, - "What is 5 + 3?", - run_config=test_run_config, - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items] - - first_ai_client_span = spans[0] - second_ai_client_span = spans[2] # After tool span + items = capture_items("span") - # Each ai_client span has its own response model from the API - assert ( - first_ai_client_span["attributes"]["gen_ai.response.model"] == "gpt-4-0613" - ) - assert ( - second_ai_client_span["attributes"]["gen_ai.response.model"] - == "gpt-4.1-2025-04-14" + result = await agents.Runner.run( + agent_with_tool, + "What is 5 + 3?", + run_config=test_run_config, ) - else: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[first_response, second_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - send_default_pii=True, - stream_gen_ai_spans=False, - ) - events = capture_events() - - result = await agents.Runner.run( - agent_with_tool, - "What is 5 + 3?", - run_config=test_run_config, - ) - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] + sentry_sdk.flush() + spans = [item.payload for item in items] - first_ai_client_span = spans[1] - second_ai_client_span = spans[3] # After tool span + first_ai_client_span = spans[0] + second_ai_client_span = spans[2] # After tool span - # Each ai_client span has its own response model from the API - assert first_ai_client_span["data"]["gen_ai.response.model"] == "gpt-4-0613" - assert ( - second_ai_client_span["data"]["gen_ai.response.model"] - == "gpt-4.1-2025-04-14" - ) + # Each ai_client span has its own response model from the API + assert first_ai_client_span["attributes"]["gen_ai.response.model"] == "gpt-4-0613" + assert ( + second_ai_client_span["attributes"]["gen_ai.response.model"] + == "gpt-4.1-2025-04-14" + ) def test_openai_agents_message_truncation( @@ -5271,7 +3924,6 @@ def test_openai_agents_message_truncation( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, send_default_pii=True, - stream_gen_ai_spans=False, ) test_messages = [ @@ -5318,7 +3970,7 @@ async def test_streaming_span_update_captures_response_data( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, send_default_pii=True, - stream_gen_ai_spans=False, + trace_lifecycle="stream", ) # Create a mock streaming response object (similar to what we'd get from ResponseCompletedEvent) @@ -5342,14 +3994,16 @@ async def test_streaming_span_update_captures_response_data( ] # Test the unified update function (works for both streaming and non-streaming) - with start_span(op="gen_ai.chat", description="test chat") as span: + with sentry_sdk.traces.start_span( + attributes={"sentry.op": "gen_ai.chat"}, name="test chat" + ) as span: update_ai_client_span(span, mock_streaming_response) # Verify the span data was set correctly - assert span._data["gen_ai.response.text"] == "Hello from streaming!" - assert span._data["gen_ai.usage.input_tokens"] == 10 - assert span._data["gen_ai.usage.output_tokens"] == 20 - assert span._data["gen_ai.response.model"] == "gpt-4-streaming" + assert span._attributes["gen_ai.response.text"] == "Hello from streaming!" + assert span._attributes["gen_ai.usage.input_tokens"] == 10 + assert span._attributes["gen_ai.usage.output_tokens"] == 20 + assert span._attributes["gen_ai.response.model"] == "gpt-4-streaming" @pytest.mark.asyncio @@ -5384,7 +4038,6 @@ async def test_streaming_ttft_on_chat_span( sentry_init( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, - stream_gen_ai_spans=False, ) request_headers = {} @@ -5505,7 +4158,6 @@ async def test_streaming_ttft_on_chat_span( assert chat_span._data.get(SPANDATA.GEN_AI_RESPONSE_STREAMING) is True -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.skipif( parse_version(OPENAI_AGENTS_VERSION) < (0, 4, 0), reason="conversation_id support requires openai-agents >= 0.4.0", @@ -5513,12 +4165,10 @@ async def test_streaming_ttft_on_chat_span( @pytest.mark.asyncio async def test_conversation_id_on_all_spans( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, - span_streaming, ): """ Test that gen_ai.conversation.id is set on all AI-related spans when passed to Runner.run(). @@ -5531,95 +4181,49 @@ async def test_conversation_id_on_all_spans( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) - - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - result = await agents.Runner.run( - agent, - "Test input", - run_config=test_run_config, - conversation_id="conv_test_123", - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items] - invoke_agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", ) - assert spans[2]["attributes"]["gen_ai.conversation.id"] == "conv_test_123" + items = capture_items("span") - # Verify invoke_agent span has conversation_id - assert ( - invoke_agent_span["attributes"]["gen_ai.conversation.id"] == "conv_test_123" + result = await agents.Runner.run( + agent, + "Test input", + run_config=test_run_config, + conversation_id="conv_test_123", ) - # Verify ai_client span has conversation_id - assert ai_client_span["attributes"]["gen_ai.conversation.id"] == "conv_test_123" - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, - ) - events = capture_events() - - result = await agents.Runner.run( - agent, - "Test input", - run_config=test_run_config, - conversation_id="conv_test_123", - ) - - assert result is not None + assert result is not None - (transaction,) = events - spans = transaction["spans"] - invoke_agent_span = next( - span for span in spans if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + sentry_sdk.flush() + spans = [item.payload for item in items] + invoke_agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - # Verify workflow span (transaction) has conversation_id - assert ( - transaction["contexts"]["trace"]["data"]["gen_ai.conversation.id"] - == "conv_test_123" - ) + assert spans[2]["attributes"]["gen_ai.conversation.id"] == "conv_test_123" - # Verify invoke_agent span has conversation_id - assert invoke_agent_span["data"]["gen_ai.conversation.id"] == "conv_test_123" + # Verify invoke_agent span has conversation_id + assert invoke_agent_span["attributes"]["gen_ai.conversation.id"] == "conv_test_123" - # Verify ai_client span has conversation_id - assert ai_client_span["data"]["gen_ai.conversation.id"] == "conv_test_123" + # Verify ai_client span has conversation_id + assert ai_client_span["attributes"]["gen_ai.conversation.id"] == "conv_test_123" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.skipif( parse_version(OPENAI_AGENTS_VERSION) < (0, 4, 0), reason="conversation_id support requires openai-agents >= 0.4.0", @@ -5627,11 +4231,9 @@ async def test_conversation_id_on_all_spans( @pytest.mark.asyncio async def test_conversation_id_on_tool_span( sentry_init, - capture_events, capture_items, test_agent, get_model_response, - span_streaming, ): """ Test that gen_ai.conversation.id is set on tool execution spans when passed to Runner.run(). @@ -5719,95 +4321,48 @@ def simple_tool(message: str) -> str: ), serialize_pydantic=True, ) + with patch.object( + agent_with_tool.model._client._client, + "send", + side_effect=[tool_response, final_response], + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) - if span_streaming: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - await agents.Runner.run( - agent_with_tool, - "Use the tool", - run_config=test_run_config, - conversation_id="conv_tool_test_456", - ) - - sentry_sdk.flush() - spans = [item.payload for item in items] - - # Find the tool span - tool_span = None - for span in spans: - if span.get("name", "").startswith("execute_tool"): - tool_span = span - break - - assert tool_span is not None - # Tool span should have the conversation_id passed to Runner.run() - assert tool_span["attributes"]["gen_ai.conversation.id"] == "conv_tool_test_456" - - # Workflow span should have the same conversation_id - workflow_span = spans[4] - assert workflow_span["is_segment"] is True + items = capture_items("span") - assert ( - workflow_span["attributes"]["gen_ai.conversation.id"] - == "conv_tool_test_456" + await agents.Runner.run( + agent_with_tool, + "Use the tool", + run_config=test_run_config, + conversation_id="conv_tool_test_456", ) - else: - with patch.object( - agent_with_tool.model._client._client, - "send", - side_effect=[tool_response, final_response], - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, - ) - - events = capture_events() - await agents.Runner.run( - agent_with_tool, - "Use the tool", - run_config=test_run_config, - conversation_id="conv_tool_test_456", - ) + sentry_sdk.flush() + spans = [item.payload for item in items] - (transaction,) = events - spans = transaction["spans"] + # Find the tool span + tool_span = None + for span in spans: + if span.get("name", "").startswith("execute_tool"): + tool_span = span + break - # Find the tool span - tool_span = None - for span in spans: - if span.get("description", "").startswith("execute_tool"): - tool_span = span - break + assert tool_span is not None + # Tool span should have the conversation_id passed to Runner.run() + assert tool_span["attributes"]["gen_ai.conversation.id"] == "conv_tool_test_456" - assert tool_span is not None - # Tool span should have the conversation_id passed to Runner.run() - assert tool_span["data"]["gen_ai.conversation.id"] == "conv_tool_test_456" + # Workflow span should have the same conversation_id + workflow_span = spans[4] + assert workflow_span["is_segment"] is True - # Workflow span (transaction) should have the same conversation_id - assert ( - transaction["contexts"]["trace"]["data"]["gen_ai.conversation.id"] - == "conv_tool_test_456" - ) + assert workflow_span["attributes"]["gen_ai.conversation.id"] == "conv_tool_test_456" -@pytest.mark.parametrize("span_streaming", [True, False]) @pytest.mark.skipif( parse_version(OPENAI_AGENTS_VERSION) < (0, 4, 0), reason="conversation_id support requires openai-agents >= 0.4.0", @@ -5815,12 +4370,10 @@ def simple_tool(message: str) -> str: @pytest.mark.asyncio async def test_no_conversation_id_when_not_provided( sentry_init, - capture_events, capture_items, test_agent, nonstreaming_responses_model_response, get_model_response, - span_streaming, ): """ Test that gen_ai.conversation.id is not set when not passed to Runner.run(). @@ -5833,83 +4386,46 @@ async def test_no_conversation_id_when_not_provided( response = get_model_response( nonstreaming_responses_model_response, serialize_pydantic=True ) + with patch.object( + agent.model._client._client, + "send", + return_value=response, + ) as _: + sentry_init( + integrations=[OpenAIAgentsIntegration()], + disabled_integrations=[StdlibIntegration], + traces_sample_rate=1.0, + trace_lifecycle="stream", + ) - if span_streaming: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - disabled_integrations=[StdlibIntegration], - traces_sample_rate=1.0, - trace_lifecycle="stream", - stream_gen_ai_spans=False, - ) - - items = capture_items("span") - - # Don't pass conversation_id - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) - - assert result is not None - - sentry_sdk.flush() - spans = [item.payload for item in items] - - workflow_span = spans[2] - assert workflow_span["is_segment"] is True + items = capture_items("span") - invoke_agent_span = next( - span - for span in spans - if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next( - span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + # Don't pass conversation_id + result = await agents.Runner.run( + agent, "Test input", run_config=test_run_config ) - # Verify conversation_id is NOT set on any spans - assert "gen_ai.conversation.id" not in workflow_span.get("attributes", {}) - assert "gen_ai.conversation.id" not in invoke_agent_span.get("attributes", {}) - assert "gen_ai.conversation.id" not in ai_client_span.get("attributes", {}) - else: - with patch.object( - agent.model._client._client, - "send", - return_value=response, - ) as _: - sentry_init( - integrations=[OpenAIAgentsIntegration()], - traces_sample_rate=1.0, - stream_gen_ai_spans=False, - ) - - events = capture_events() + assert result is not None - # Don't pass conversation_id - result = await agents.Runner.run( - agent, "Test input", run_config=test_run_config - ) + sentry_sdk.flush() + spans = [item.payload for item in items] - assert result is not None + workflow_span = spans[2] + assert workflow_span["is_segment"] is True - (transaction,) = events - spans = transaction["spans"] - invoke_agent_span = next( - span for span in spans if span["op"] == OP.GEN_AI_INVOKE_AGENT - ) - ai_client_span = next(span for span in spans if span["op"] == OP.GEN_AI_CHAT) + invoke_agent_span = next( + span + for span in spans + if span["attributes"]["sentry.op"] == OP.GEN_AI_INVOKE_AGENT + ) + ai_client_span = next( + span for span in spans if span["attributes"]["sentry.op"] == OP.GEN_AI_CHAT + ) - # Verify conversation_id is NOT set on any spans - assert "gen_ai.conversation.id" not in transaction["contexts"]["trace"].get( - "data", {} - ) - assert "gen_ai.conversation.id" not in invoke_agent_span.get("data", {}) - assert "gen_ai.conversation.id" not in ai_client_span.get("data", {}) + # Verify conversation_id is NOT set on any spans + assert "gen_ai.conversation.id" not in workflow_span.get("attributes", {}) + assert "gen_ai.conversation.id" not in invoke_agent_span.get("attributes", {}) + assert "gen_ai.conversation.id" not in ai_client_span.get("attributes", {}) @pytest.mark.asyncio @@ -5940,7 +4456,6 @@ async def test_runner_run_with_starting_agent_kwarg( sentry_init( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, - stream_gen_ai_spans=False, ) events = capture_events() @@ -6053,7 +4568,6 @@ async def test_runner_run_streamed_with_starting_agent_kwarg( sentry_init( integrations=[OpenAIAgentsIntegration()], traces_sample_rate=1.0, - stream_gen_ai_spans=False, ) events = capture_events()