Skip to content

Commit db240f2

Browse files
committed
fix(openai_agents): Handle starting_agent keyword argument in runner patches
Runner.run() and Runner.run_streamed() accept starting_agent as either a positional or keyword argument. The patches were always reading args[0], which crashes when the caller uses the keyword form (e.g. Runner.run(starting_agent=agent, input=...)). Check for the keyword argument first; fall back to the positional arg. Fixes GH-6418 Fixes PY-2498
1 parent b1986bb commit db240f2

2 files changed

Lines changed: 179 additions & 4 deletions

File tree

‎sentry_sdk/integrations/openai_agents/patches/runner.py‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
3636
# don't touch each other's scopes
3737
with sentry_sdk.isolation_scope():
3838
# Clone agent because agent invocation spans are attached per run.
39-
agent = args[0].clone()
39+
if "starting_agent" in kwargs:
40+
agent = kwargs["starting_agent"].clone()
41+
else:
42+
agent = args[0].clone()
4043

4144
with agent_workflow_span(agent) as workflow_span:
4245
# Set conversation ID on workflow span early so it's captured even on errors
@@ -47,7 +50,11 @@ async def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
4750
SPANDATA.GEN_AI_CONVERSATION_ID, conversation_id
4851
)
4952

50-
args = (agent, *args[1:])
53+
if "starting_agent" in kwargs:
54+
kwargs["starting_agent"] = agent
55+
else:
56+
args = (agent, *args[1:])
57+
5158
try:
5259
run_result = await original_func(*args, **kwargs)
5360
except AgentsException as exc:
@@ -122,7 +129,10 @@ def _create_run_streamed_wrapper(
122129
@wraps(original_func)
123130
def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
124131
# Clone agent because agent invocation spans are attached per run.
125-
agent = args[0].clone()
132+
if "starting_agent" in kwargs:
133+
agent = kwargs["starting_agent"].clone()
134+
else:
135+
agent = args[0].clone()
126136

127137
# Capture conversation_id from kwargs if provided
128138
conversation_id = kwargs.get("conversation_id")
@@ -140,7 +150,10 @@ def wrapper(*args: "Any", **kwargs: "Any") -> "Any":
140150
# Store span on agent for cleanup
141151
agent._sentry_workflow_span = workflow_span
142152

143-
args = (agent, *args[1:])
153+
if "starting_agent" in kwargs:
154+
kwargs["starting_agent"] = agent
155+
else:
156+
args = (agent, *args[1:])
144157

145158
try:
146159
# Call original function to get RunResultStreaming

‎tests/integrations/openai_agents/test_openai_agents.py‎

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4943,3 +4943,165 @@ async def test_no_conversation_id_when_not_provided(
49434943
)
49444944
assert "gen_ai.conversation.id" not in invoke_agent_span.get("data", {})
49454945
assert "gen_ai.conversation.id" not in ai_client_span.get("data", {})
4946+
4947+
4948+
@pytest.mark.parametrize("stream_gen_ai_spans", [True, False])
4949+
@pytest.mark.asyncio
4950+
async def test_runner_run_with_starting_agent_kwarg(
4951+
sentry_init,
4952+
capture_events,
4953+
test_agent,
4954+
nonstreaming_responses_model_response,
4955+
get_model_response,
4956+
stream_gen_ai_spans,
4957+
):
4958+
"""Runner.run(starting_agent=agent, input=...) must not crash.
4959+
4960+
Regression test for https://github.com/getsentry/sentry-python/issues/6418
4961+
"""
4962+
client = AsyncOpenAI(api_key="test-key")
4963+
model = OpenAIResponsesModel(model="gpt-4", openai_client=client)
4964+
agent = test_agent.clone(model=model)
4965+
4966+
response = get_model_response(
4967+
nonstreaming_responses_model_response, serialize_pydantic=True
4968+
)
4969+
4970+
with patch.object(
4971+
agent.model._client._client,
4972+
"send",
4973+
return_value=response,
4974+
):
4975+
sentry_init(
4976+
integrations=[OpenAIAgentsIntegration()],
4977+
traces_sample_rate=1.0,
4978+
stream_gen_ai_spans=stream_gen_ai_spans,
4979+
)
4980+
4981+
events = capture_events()
4982+
4983+
result = await agents.run.DEFAULT_AGENT_RUNNER.run(
4984+
starting_agent=agent,
4985+
input="Test input",
4986+
run_config=test_run_config,
4987+
)
4988+
4989+
assert result is not None
4990+
assert result.final_output == "Hello, how can I help you?"
4991+
4992+
(transaction,) = events
4993+
assert transaction["transaction"] == "test_agent workflow"
4994+
4995+
4996+
@pytest.mark.parametrize("stream_gen_ai_spans", [True, False])
4997+
@pytest.mark.asyncio
4998+
async def test_runner_run_streamed_with_starting_agent_kwarg(
4999+
sentry_init,
5000+
capture_events,
5001+
test_agent,
5002+
async_iterator,
5003+
server_side_event_chunks,
5004+
get_model_response,
5005+
stream_gen_ai_spans,
5006+
):
5007+
"""Runner.run_streamed(starting_agent=agent, input=...) must not crash.
5008+
5009+
Regression test for https://github.com/getsentry/sentry-python/issues/6418
5010+
"""
5011+
client = AsyncOpenAI(api_key="test-key")
5012+
model = OpenAIResponsesModel(model="gpt-4", openai_client=client)
5013+
agent = test_agent.clone(model=model)
5014+
5015+
request_headers = {}
5016+
if parse_version(OPENAI_AGENTS_VERSION) >= (0, 10, 3) and hasattr(
5017+
agent.model._client.responses, "with_streaming_response"
5018+
):
5019+
request_headers["X-Stainless-Raw-Response"] = "stream"
5020+
5021+
response = get_model_response(
5022+
async_iterator(
5023+
server_side_event_chunks(
5024+
[
5025+
ResponseCreatedEvent(
5026+
response=Response(
5027+
id="chat-id",
5028+
output=[],
5029+
parallel_tool_calls=False,
5030+
tool_choice="none",
5031+
tools=[],
5032+
created_at=10000000,
5033+
model="gpt-4",
5034+
object="response",
5035+
),
5036+
type="response.created",
5037+
sequence_number=0,
5038+
),
5039+
ResponseCompletedEvent(
5040+
response=Response(
5041+
id="chat-id",
5042+
output=[
5043+
ResponseOutputMessage(
5044+
id="message-id",
5045+
content=[
5046+
ResponseOutputText(
5047+
annotations=[],
5048+
text="Hello, how can I help you?",
5049+
type="output_text",
5050+
),
5051+
],
5052+
role="assistant",
5053+
status="completed",
5054+
type="message",
5055+
),
5056+
],
5057+
parallel_tool_calls=False,
5058+
tool_choice="none",
5059+
tools=[],
5060+
created_at=10000000,
5061+
model="gpt-4",
5062+
object="response",
5063+
usage=ResponseUsage(
5064+
input_tokens=10,
5065+
input_tokens_details=InputTokensDetails(
5066+
cached_tokens=0,
5067+
),
5068+
output_tokens=20,
5069+
output_tokens_details=OutputTokensDetails(
5070+
reasoning_tokens=5,
5071+
),
5072+
total_tokens=30,
5073+
),
5074+
),
5075+
type="response.completed",
5076+
sequence_number=1,
5077+
),
5078+
]
5079+
)
5080+
),
5081+
request_headers=request_headers,
5082+
)
5083+
5084+
with patch.object(
5085+
agent.model._client._client,
5086+
"send",
5087+
return_value=response,
5088+
):
5089+
sentry_init(
5090+
integrations=[OpenAIAgentsIntegration()],
5091+
traces_sample_rate=1.0,
5092+
stream_gen_ai_spans=stream_gen_ai_spans,
5093+
)
5094+
5095+
events = capture_events()
5096+
5097+
result = agents.run.DEFAULT_AGENT_RUNNER.run_streamed(
5098+
starting_agent=agent,
5099+
input="Test input",
5100+
run_config=test_run_config,
5101+
)
5102+
5103+
async for _event in result.stream_events():
5104+
pass
5105+
5106+
(transaction,) = events
5107+
assert transaction["transaction"] == "test_agent workflow"

0 commit comments

Comments
 (0)