Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 5 additions & 17 deletions sentry_sdk/integrations/openai_agents/patches/agent_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
14 changes: 3 additions & 11 deletions sentry_sdk/integrations/openai_agents/patches/error_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 6 additions & 10 deletions sentry_sdk/integrations/openai_agents/patches/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
28 changes: 7 additions & 21 deletions sentry_sdk/integrations/openai_agents/patches/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
6 changes: 1 addition & 5 deletions sentry_sdk/integrations/openai_agents/patches/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
51 changes: 16 additions & 35 deletions sentry_sdk/integrations/openai_agents/spans/ai_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -53,7 +52,7 @@
)

if TYPE_CHECKING:
from typing import Any, Optional, Union
from typing import Any, Optional

from agents import Agent, Tool

Expand Down Expand Up @@ -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
Expand All @@ -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)),
)
Expand All @@ -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,
Expand All @@ -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)
41 changes: 13 additions & 28 deletions sentry_sdk/integrations/openai_agents/spans/execute_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading
Loading