Skip to content
Open
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
8 changes: 0 additions & 8 deletions sentry_sdk/integrations/openai_agents/patches/agent_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from sentry_sdk.utils import capture_internal_exceptions, reraise

from ..spans import (
handoff_span,
invoke_agent_span,
update_invoke_agent_span,
)
Expand Down Expand Up @@ -212,16 +211,9 @@ async def _execute_handoffs(
context_wrapper: "Optional[agents.RunContextWrapper]" = kwargs.get(
"context_wrapper"
)
run_handoffs = kwargs.get("run_handoffs")
# openai-agents >= 0.14 renamed `agent` to `public_agent`.
agent: "Optional[agents.Agent]" = kwargs.get("public_agent", kwargs.get("agent"))

# Create Sentry handoff span for the first handoff (agents library only processes the first one)
if run_handoffs:
first_handoff = run_handoffs[0]
handoff_agent_name = first_handoff.handoff.agent_name
handoff_span(context_wrapper, agent, handoff_agent_name)

# Call original method with all parameters
try:
result = await original_execute_handoffs(*args, **kwargs)
Expand Down
1 change: 0 additions & 1 deletion sentry_sdk/integrations/openai_agents/spans/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from .ai_client import ai_client_context, update_ai_client_span # noqa: F401
from .execute_tool import execute_tool_span, update_execute_tool_span # noqa: F401
from .handoff import handoff_span # noqa: F401
from .invoke_agent import (
invoke_agent_span, # noqa: F401
update_invoke_agent_span, # noqa: F401
Expand Down
30 changes: 0 additions & 30 deletions sentry_sdk/integrations/openai_agents/spans/handoff.py

This file was deleted.

270 changes: 1 addition & 269 deletions tests/integrations/openai_agents/test_openai_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
Usage,
)
from agents.computer import Computer
from agents.exceptions import MaxTurnsExceeded, ModelBehaviorError
from agents.exceptions import ModelBehaviorError
from agents.items import (
ResponseFunctionToolCall,
ResponseOutputMessage,
Expand Down Expand Up @@ -1618,274 +1618,6 @@ def test_agent_invocation_span_sync(
)


@pytest.mark.asyncio
async def test_handoff_span(
sentry_init,
capture_items,
get_model_response,
):
"""
Test that handoff spans are created when agents hand off to other agents.
"""
client = AsyncOpenAI(api_key="test-key")
model = OpenAIResponsesModel(model="gpt-4-mini", openai_client=client)

# Create two simple agents with a handoff relationship
secondary_agent = agents.Agent(
name="secondary_agent",
instructions="You are a secondary agent.",
model=model,
)

primary_agent = agents.Agent(
name="primary_agent",
instructions="You are a primary agent that hands off to secondary agent.",
model=model,
handoffs=[secondary_agent],
)

handoff_response = get_model_response(
Response(
id="resp_tool_123",
output=[
ResponseFunctionToolCall(
id="call_handoff_123",
call_id="call_handoff_123",
name="transfer_to_secondary_agent",
type="function_call",
arguments="{}",
)
],
parallel_tool_calls=False,
tool_choice="none",
tools=[],
created_at=10000000,
model="gpt-4",
object="response",
usage=ResponseUsage(
input_tokens=10,
input_tokens_details=InputTokensDetails(
cached_tokens=0,
cache_write_tokens=0,
),
output_tokens=20,
output_tokens_details=OutputTokensDetails(
reasoning_tokens=5,
),
total_tokens=30,
),
),
serialize_pydantic=True,
)

final_response = get_model_response(
Response(
id="resp_final_123",
output=[
ResponseOutputMessage(
id="msg_final",
type="message",
status="completed",
content=[
ResponseOutputText(
text="I'm the specialist and I can help with that!",
type="output_text",
annotations=[],
)
],
role="assistant",
)
],
parallel_tool_calls=False,
tool_choice="none",
tools=[],
created_at=10000000,
model="gpt-4",
object="response",
usage=ResponseUsage(
input_tokens=10,
input_tokens_details=InputTokensDetails(
cached_tokens=0,
cache_write_tokens=0,
),
output_tokens=20,
output_tokens_details=OutputTokensDetails(
reasoning_tokens=5,
),
total_tokens=30,
),
),
serialize_pydantic=True,
)
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,
)

items = capture_items("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]
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["name"] == "handoff from primary_agent to secondary_agent"
assert handoff_span["attributes"]["gen_ai.operation.name"] == "handoff"


@pytest.mark.asyncio
async def test_max_turns_before_handoff_span(
sentry_init,
capture_items,
get_model_response,
):
"""
Example raising agents.exceptions.AgentsException after the agent invocation span is complete.
"""
client = AsyncOpenAI(api_key="test-key")
model = OpenAIResponsesModel(model="gpt-4-mini", openai_client=client)

# Create two simple agents with a handoff relationship
secondary_agent = agents.Agent(
name="secondary_agent",
instructions="You are a secondary agent.",
model=model,
)

primary_agent = agents.Agent(
name="primary_agent",
instructions="You are a primary agent that hands off to secondary agent.",
model=model,
handoffs=[secondary_agent],
)

handoff_response = get_model_response(
Response(
id="resp_tool_123",
output=[
ResponseFunctionToolCall(
id="call_handoff_123",
call_id="call_handoff_123",
name="transfer_to_secondary_agent",
type="function_call",
arguments="{}",
)
],
parallel_tool_calls=False,
tool_choice="none",
tools=[],
created_at=10000000,
model="gpt-4",
object="response",
usage=ResponseUsage(
input_tokens=10,
input_tokens_details=InputTokensDetails(
cached_tokens=0,
cache_write_tokens=0,
),
output_tokens=20,
output_tokens_details=OutputTokensDetails(
reasoning_tokens=5,
),
total_tokens=30,
),
),
serialize_pydantic=True,
)

final_response = get_model_response(
Response(
id="resp_final_123",
output=[
ResponseOutputMessage(
id="msg_final",
type="message",
status="completed",
content=[
ResponseOutputText(
text="I'm the specialist and I can help with that!",
type="output_text",
annotations=[],
)
],
role="assistant",
)
],
parallel_tool_calls=False,
tool_choice="none",
tools=[],
created_at=10000000,
model="gpt-4",
object="response",
usage=ResponseUsage(
input_tokens=10,
input_tokens_details=InputTokensDetails(
cached_tokens=0,
cache_write_tokens=0,
),
output_tokens=20,
output_tokens_details=OutputTokensDetails(
reasoning_tokens=5,
),
total_tokens=30,
),
),
serialize_pydantic=True,
)
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,
)

items = capture_items("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]
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["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.asyncio
async def test_tool_execution_span(
Expand Down
Loading