Skip to content

Commit 7bf1d6e

Browse files
authored
fix(openai_agents): Handle starting_agent keyword argument in runner patches (#6428)
`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 5662d86 commit 7bf1d6e

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
@@ -4521,3 +4521,165 @@ async def test_no_conversation_id_when_not_provided(
45214521
)
45224522
assert "gen_ai.conversation.id" not in invoke_agent_span.get("data", {})
45234523
assert "gen_ai.conversation.id" not in ai_client_span.get("data", {})
4524+
4525+
4526+
@pytest.mark.parametrize("stream_gen_ai_spans", [True, False])
4527+
@pytest.mark.asyncio
4528+
async def test_runner_run_with_starting_agent_kwarg(
4529+
sentry_init,
4530+
capture_events,
4531+
test_agent,
4532+
nonstreaming_responses_model_response,
4533+
get_model_response,
4534+
stream_gen_ai_spans,
4535+
):
4536+
"""Runner.run(starting_agent=agent, input=...) must not crash.
4537+
4538+
Regression test for https://github.com/getsentry/sentry-python/issues/6418
4539+
"""
4540+
client = AsyncOpenAI(api_key="test-key")
4541+
model = OpenAIResponsesModel(model="gpt-4", openai_client=client)
4542+
agent = test_agent.clone(model=model)
4543+
4544+
response = get_model_response(
4545+
nonstreaming_responses_model_response, serialize_pydantic=True
4546+
)
4547+
4548+
with patch.object(
4549+
agent.model._client._client,
4550+
"send",
4551+
return_value=response,
4552+
):
4553+
sentry_init(
4554+
integrations=[OpenAIAgentsIntegration()],
4555+
traces_sample_rate=1.0,
4556+
stream_gen_ai_spans=stream_gen_ai_spans,
4557+
)
4558+
4559+
events = capture_events()
4560+
4561+
result = await agents.run.DEFAULT_AGENT_RUNNER.run(
4562+
starting_agent=agent,
4563+
input="Test input",
4564+
run_config=test_run_config,
4565+
)
4566+
4567+
assert result is not None
4568+
assert result.final_output == "Hello, how can I help you?"
4569+
4570+
(transaction,) = events
4571+
assert transaction["transaction"] == "test_agent workflow"
4572+
4573+
4574+
@pytest.mark.parametrize("stream_gen_ai_spans", [True, False])
4575+
@pytest.mark.asyncio
4576+
async def test_runner_run_streamed_with_starting_agent_kwarg(
4577+
sentry_init,
4578+
capture_events,
4579+
test_agent,
4580+
async_iterator,
4581+
server_side_event_chunks,
4582+
get_model_response,
4583+
stream_gen_ai_spans,
4584+
):
4585+
"""Runner.run_streamed(starting_agent=agent, input=...) must not crash.
4586+
4587+
Regression test for https://github.com/getsentry/sentry-python/issues/6418
4588+
"""
4589+
client = AsyncOpenAI(api_key="test-key")
4590+
model = OpenAIResponsesModel(model="gpt-4", openai_client=client)
4591+
agent = test_agent.clone(model=model)
4592+
4593+
request_headers = {}
4594+
if parse_version(OPENAI_AGENTS_VERSION) >= (0, 10, 3) and hasattr(
4595+
agent.model._client.responses, "with_streaming_response"
4596+
):
4597+
request_headers["X-Stainless-Raw-Response"] = "stream"
4598+
4599+
response = get_model_response(
4600+
async_iterator(
4601+
server_side_event_chunks(
4602+
[
4603+
ResponseCreatedEvent(
4604+
response=Response(
4605+
id="chat-id",
4606+
output=[],
4607+
parallel_tool_calls=False,
4608+
tool_choice="none",
4609+
tools=[],
4610+
created_at=10000000,
4611+
model="gpt-4",
4612+
object="response",
4613+
),
4614+
type="response.created",
4615+
sequence_number=0,
4616+
),
4617+
ResponseCompletedEvent(
4618+
response=Response(
4619+
id="chat-id",
4620+
output=[
4621+
ResponseOutputMessage(
4622+
id="message-id",
4623+
content=[
4624+
ResponseOutputText(
4625+
annotations=[],
4626+
text="Hello, how can I help you?",
4627+
type="output_text",
4628+
),
4629+
],
4630+
role="assistant",
4631+
status="completed",
4632+
type="message",
4633+
),
4634+
],
4635+
parallel_tool_calls=False,
4636+
tool_choice="none",
4637+
tools=[],
4638+
created_at=10000000,
4639+
model="gpt-4",
4640+
object="response",
4641+
usage=ResponseUsage(
4642+
input_tokens=10,
4643+
input_tokens_details=InputTokensDetails(
4644+
cached_tokens=0,
4645+
),
4646+
output_tokens=20,
4647+
output_tokens_details=OutputTokensDetails(
4648+
reasoning_tokens=5,
4649+
),
4650+
total_tokens=30,
4651+
),
4652+
),
4653+
type="response.completed",
4654+
sequence_number=1,
4655+
),
4656+
]
4657+
)
4658+
),
4659+
request_headers=request_headers,
4660+
)
4661+
4662+
with patch.object(
4663+
agent.model._client._client,
4664+
"send",
4665+
return_value=response,
4666+
):
4667+
sentry_init(
4668+
integrations=[OpenAIAgentsIntegration()],
4669+
traces_sample_rate=1.0,
4670+
stream_gen_ai_spans=stream_gen_ai_spans,
4671+
)
4672+
4673+
events = capture_events()
4674+
4675+
result = agents.run.DEFAULT_AGENT_RUNNER.run_streamed(
4676+
starting_agent=agent,
4677+
input="Test input",
4678+
run_config=test_run_config,
4679+
)
4680+
4681+
async for _event in result.stream_events():
4682+
pass
4683+
4684+
(transaction,) = events
4685+
assert transaction["transaction"] == "test_agent workflow"

0 commit comments

Comments
 (0)