diff --git a/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py b/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py index 758f06db8d..5355722fe2 100644 --- a/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +++ b/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py @@ -18,9 +18,12 @@ def agent_workflow_span( # 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: - span = sentry_sdk.traces.start_span( - name=f"{agent.name} workflow", attributes={"sentry.origin": SPAN_ORIGIN} - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"{agent.name} workflow", + attributes={"sentry.origin": SPAN_ORIGIN}, + ) return span diff --git a/sentry_sdk/integrations/openai_agents/spans/ai_client.py b/sentry_sdk/integrations/openai_agents/spans/ai_client.py index f4f02cb674..0de8384aff 100644 --- a/sentry_sdk/integrations/openai_agents/spans/ai_client.py +++ b/sentry_sdk/integrations/openai_agents/spans/ai_client.py @@ -32,14 +32,16 @@ def ai_client_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_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", - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + 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", + }, + ) else: span = sentry_sdk.start_span( op=OP.GEN_AI_CHAT, @@ -49,8 +51,9 @@ def ai_client_span( # 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_agent_data(span, agent) - _set_input_data(span, get_response_kwargs) + if span is not None: + _set_agent_data(span, agent) + _set_input_data(span, get_response_kwargs) return span diff --git a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py index fd3a430951..93f5ae71a5 100644 --- a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +++ b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py @@ -20,18 +20,20 @@ def execute_tool_span( ) -> "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, - }, - ) - - set_on_span = span.set_attribute + span = None + if sentry_sdk.traces.get_current_span() is not None: + 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, + }, + ) + + set_on_span = span.set_attribute if span is not None else None else: span = sentry_sdk.start_span( op=OP.GEN_AI_EXECUTE_TOOL, @@ -46,7 +48,7 @@ def execute_tool_span( set_on_span = span.set_data - if should_send_default_pii(): + if span is not None and should_send_default_pii(): input = args[1] set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, input) diff --git a/sentry_sdk/integrations/openai_agents/spans/handoff.py b/sentry_sdk/integrations/openai_agents/spans/handoff.py index ea91464afb..4d1c987de5 100644 --- a/sentry_sdk/integrations/openai_agents/spans/handoff.py +++ b/sentry_sdk/integrations/openai_agents/spans/handoff.py @@ -15,6 +15,9 @@ def handoff_span( ) -> None: span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: + if sentry_sdk.traces.get_current_span() is None: + return + with sentry_sdk.traces.start_span( name=f"handoff from {from_agent.name} to {to_agent_name}", attributes={ diff --git a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py index c21145ac4a..2d1c65d38b 100644 --- a/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +++ b/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py @@ -30,14 +30,16 @@ def invoke_agent_span( ) -> "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"invoke_agent {agent.name}", - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + 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( @@ -49,53 +51,54 @@ def invoke_agent_span( span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") - if should_send_default_pii(): - messages = [] - if agent.instructions: - message = ( - agent.instructions - if isinstance(agent.instructions, str) - else safe_serialize(agent.instructions) - ) - messages.append( - { - "content": [{"text": message, "type": "text"}], - "role": "system", - } - ) - - original_input = kwargs.get("original_input") - if original_input is not None: - message = ( - original_input - if isinstance(original_input, str) - else safe_serialize(original_input) - ) - messages.append( - { - "content": [{"text": message, "type": "text"}], - "role": "user", - } - ) + if span is not None: + if should_send_default_pii(): + messages = [] + if agent.instructions: + message = ( + agent.instructions + if isinstance(agent.instructions, str) + else safe_serialize(agent.instructions) + ) + messages.append( + { + "content": [{"text": message, "type": "text"}], + "role": "system", + } + ) - if len(messages) > 0: - normalized_messages = normalize_message_roles(messages) - client = sentry_sdk.get_client() - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if should_truncate_gen_ai_input(client.options) - else normalized_messages - ) - if messages_data is not None: - set_data_normalized( - span, - SPANDATA.GEN_AI_REQUEST_MESSAGES, - messages_data, - unpack=False, + original_input = kwargs.get("original_input") + if original_input is not None: + message = ( + original_input + if isinstance(original_input, str) + else safe_serialize(original_input) + ) + messages.append( + { + "content": [{"text": message, "type": "text"}], + "role": "user", + } ) - _set_agent_data(span, agent) + if len(messages) > 0: + normalized_messages = normalize_message_roles(messages) + client = sentry_sdk.get_client() + scope = sentry_sdk.get_current_scope() + messages_data = ( + truncate_and_annotate_messages(normalized_messages, span, scope) + if should_truncate_gen_ai_input(client.options) + else normalized_messages + ) + if messages_data is not None: + set_data_normalized( + span, + SPANDATA.GEN_AI_REQUEST_MESSAGES, + messages_data, + unpack=False, + ) + + _set_agent_data(span, agent) return span diff --git a/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py b/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py index 27deb0c55c..21aff52575 100644 --- a/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +++ b/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py @@ -282,15 +282,17 @@ def ai_client_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_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", - SPANDATA.GEN_AI_RESPONSE_STREAMING: get_is_streaming(), - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + 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", + SPANDATA.GEN_AI_RESPONSE_STREAMING: get_is_streaming(), + }, + ) else: span = sentry_sdk.start_span( op=OP.GEN_AI_CHAT, @@ -302,16 +304,17 @@ def ai_client_span( # Set streaming flag from contextvar span.set_data(SPANDATA.GEN_AI_RESPONSE_STREAMING, get_is_streaming()) - _set_agent_data(span, agent) - _set_model_data(span, model, model_settings) + if span is not None: + _set_agent_data(span, agent) + _set_model_data(span, model, model_settings) - # Add available tools if agent is available - agent_obj = agent or get_current_agent() - _set_available_tools(span, agent_obj) + # Add available tools if agent is available + agent_obj = agent or get_current_agent() + _set_available_tools(span, agent_obj) - # Set input messages (full conversation history) - if messages: - _set_input_messages(span, messages) + # Set input messages (full conversation history) + if messages: + _set_input_messages(span, messages) return span diff --git a/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py b/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py index 7648c1418a..edc00906f2 100644 --- a/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +++ b/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py @@ -31,17 +31,19 @@ def execute_tool_span( """ 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, - }, - ) - - set_on_span = span.set_attribute + span = None + if sentry_sdk.traces.get_current_span() is not None: + 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, + }, + ) + + set_on_span = span.set_attribute if span is not None else None else: span = sentry_sdk.start_span( op=OP.GEN_AI_EXECUTE_TOOL, @@ -54,16 +56,17 @@ def execute_tool_span( set_on_span = span.set_data - if tool_definition is not None and hasattr(tool_definition, "description"): - set_on_span( - SPANDATA.GEN_AI_TOOL_DESCRIPTION, - tool_definition.description, - ) + if span is not None: + if tool_definition is not None and hasattr(tool_definition, "description"): + set_on_span( + SPANDATA.GEN_AI_TOOL_DESCRIPTION, + tool_definition.description, + ) - _set_agent_data(span, agent) + _set_agent_data(span, agent) - if _should_send_prompts() and tool_args is not None: - set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_args)) + if _should_send_prompts() and tool_args is not None: + set_on_span(SPANDATA.GEN_AI_TOOL_INPUT, safe_serialize(tool_args)) return span diff --git a/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py b/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py index f0c68e85ba..73bba0c86a 100644 --- a/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +++ b/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py @@ -51,14 +51,16 @@ def invoke_agent_span( span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options) if span_streaming: - span = sentry_sdk.traces.start_span( - name=f"invoke_agent {name}", - attributes={ - "sentry.op": OP.GEN_AI_INVOKE_AGENT, - "sentry.origin": SPAN_ORIGIN, - SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", - }, - ) + span = None + if sentry_sdk.traces.get_current_span() is not None: + span = sentry_sdk.traces.start_span( + name=f"invoke_agent {name}", + attributes={ + "sentry.op": OP.GEN_AI_INVOKE_AGENT, + "sentry.origin": SPAN_ORIGIN, + SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent", + }, + ) else: span = get_start_span_function()( op=OP.GEN_AI_INVOKE_AGENT, @@ -68,85 +70,86 @@ def invoke_agent_span( span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "invoke_agent") - _set_agent_data(span, agent) - _set_model_data(span, model, model_settings) - _set_available_tools(span, agent) - - # Add user prompt and system prompts if available and prompts are enabled - if _should_send_prompts(): - messages = [] - - # Add system prompts (both instructions and system_prompt) - system_texts = [] - - if agent: - # Check for system_prompt - system_prompts = getattr(agent, "_system_prompts", None) or [] - for prompt in system_prompts: - if isinstance(prompt, str): - system_texts.append(prompt) - - # Check for instructions (stored in _instructions) - instructions = getattr(agent, "_instructions", None) - if instructions: - if isinstance(instructions, str): - system_texts.append(instructions) - elif isinstance(instructions, (list, tuple)): - for instr in instructions: - if isinstance(instr, str): - system_texts.append(instr) - elif callable(instr): - # Skip dynamic/callable instructions - pass - - # Add all system texts as system messages - for system_text in system_texts: - messages.append( - { - "content": [{"text": system_text, "type": "text"}], - "role": "system", - } - ) - - # Add user prompt - if user_prompt: - if isinstance(user_prompt, str): + if span is not None: + _set_agent_data(span, agent) + _set_model_data(span, model, model_settings) + _set_available_tools(span, agent) + + # Add user prompt and system prompts if available and prompts are enabled + if _should_send_prompts(): + messages = [] + + # Add system prompts (both instructions and system_prompt) + system_texts = [] + + if agent: + # Check for system_prompt + system_prompts = getattr(agent, "_system_prompts", None) or [] + for prompt in system_prompts: + if isinstance(prompt, str): + system_texts.append(prompt) + + # Check for instructions (stored in _instructions) + instructions = getattr(agent, "_instructions", None) + if instructions: + if isinstance(instructions, str): + system_texts.append(instructions) + elif isinstance(instructions, (list, tuple)): + for instr in instructions: + if isinstance(instr, str): + system_texts.append(instr) + elif callable(instr): + # Skip dynamic/callable instructions + pass + + # Add all system texts as system messages + for system_text in system_texts: messages.append( { - "content": [{"text": user_prompt, "type": "text"}], - "role": "user", + "content": [{"text": system_text, "type": "text"}], + "role": "system", } ) - elif isinstance(user_prompt, list): - # Handle list of user content - content = [] - for item in user_prompt: - if isinstance(item, str): - content.append({"text": item, "type": "text"}) - elif ImageUrl and isinstance(item, ImageUrl): - content.append(_serialize_image_url_item(item)) - elif BinaryContent and isinstance(item, BinaryContent): - content.append(_serialize_binary_content_item(item)) - if content: + + # Add user prompt + if user_prompt: + if isinstance(user_prompt, str): messages.append( { - "content": content, + "content": [{"text": user_prompt, "type": "text"}], "role": "user", } ) - - if messages: - normalized_messages = normalize_message_roles(messages) - client = sentry_sdk.get_client() - scope = sentry_sdk.get_current_scope() - messages_data = ( - truncate_and_annotate_messages(normalized_messages, span, scope) - if should_truncate_gen_ai_input(client.options) - else normalized_messages - ) - set_data_normalized( - span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False - ) + elif isinstance(user_prompt, list): + # Handle list of user content + content = [] + for item in user_prompt: + if isinstance(item, str): + content.append({"text": item, "type": "text"}) + elif ImageUrl and isinstance(item, ImageUrl): + content.append(_serialize_image_url_item(item)) + elif BinaryContent and isinstance(item, BinaryContent): + content.append(_serialize_binary_content_item(item)) + if content: + messages.append( + { + "content": content, + "role": "user", + } + ) + + if messages: + normalized_messages = normalize_message_roles(messages) + client = sentry_sdk.get_client() + scope = sentry_sdk.get_current_scope() + messages_data = ( + truncate_and_annotate_messages(normalized_messages, span, scope) + if should_truncate_gen_ai_input(client.options) + else normalized_messages + ) + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages_data, unpack=False + ) return span