Skip to content

Commit eee4c4b

Browse files
authored
fix(openai-agents): Isolate agent run (#4720)
It looks like we're running into #4718 (and probably #4690) because the different agent runs are not properly isolated. This only seems to be a problem when multiple agent runs are awaited at once (e.g. via `asyncio.gather`) -- it seems that leads to some scope bleed. ```python import asyncio import sentry_sdk from agents import Agent, Runner from sentry_sdk.integrations.asyncio import AsyncioIntegration from sentry_sdk.integrations.openai_agents import OpenAIAgentsIntegration sentry_sdk.init(...) main_agent = Agent( name="main_agent", model="gpt-5", ) async def run_agent() -> None: runner = await Runner.run( starting_agent=main_agent, input="How are you?", ) print(runner.final_output) async def main() -> None: await asyncio.gather(*[run_agent() for _ in range(2)]) # throws an error # await asyncio.gather(run_agent()) # works ```
1 parent 28d0ddd commit eee4c4b

File tree

3 files changed

+61
-18
lines changed

3 files changed

+61
-18
lines changed

sentry_sdk/integrations/openai_agents/patches/agent_run.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from functools import wraps
22

33
from sentry_sdk.integrations import DidNotEnable
4-
54
from ..spans import invoke_agent_span, update_invoke_agent_span, handoff_span
65

76
from typing import TYPE_CHECKING
87

98
if TYPE_CHECKING:
109
from typing import Any, Optional
1110

12-
1311
try:
1412
import agents
1513
except ImportError:
@@ -62,7 +60,6 @@ def _get_current_agent(context_wrapper):
6260
async def patched_run_single_turn(cls, *args, **kwargs):
6361
# type: (agents.Runner, *Any, **Any) -> Any
6462
"""Patched _run_single_turn that creates agent invocation spans"""
65-
6663
agent = kwargs.get("agent")
6764
context_wrapper = kwargs.get("context_wrapper")
6865
should_run_agent_start_hooks = kwargs.get("should_run_agent_start_hooks")

sentry_sdk/integrations/openai_agents/patches/runner.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,23 @@ def _create_run_wrapper(original_func):
2323
@wraps(original_func)
2424
async def wrapper(*args, **kwargs):
2525
# type: (*Any, **Any) -> Any
26-
agent = args[0]
27-
with agent_workflow_span(agent):
28-
result = None
29-
try:
30-
result = await original_func(*args, **kwargs)
31-
return result
32-
except Exception as exc:
33-
_capture_exception(exc)
34-
35-
# It could be that there is a "invoke agent" span still open
36-
current_span = sentry_sdk.get_current_span()
37-
if current_span is not None and current_span.timestamp is None:
38-
current_span.__exit__(None, None, None)
39-
40-
raise exc from None
26+
# Isolate each workflow so that when agents are run in asyncio tasks they
27+
# don't touch each other's scopes
28+
with sentry_sdk.isolation_scope():
29+
agent = args[0]
30+
with agent_workflow_span(agent):
31+
result = None
32+
try:
33+
result = await original_func(*args, **kwargs)
34+
return result
35+
except Exception as exc:
36+
_capture_exception(exc)
37+
38+
# It could be that there is a "invoke agent" span still open
39+
current_span = sentry_sdk.get_current_span()
40+
if current_span is not None and current_span.timestamp is None:
41+
current_span.__exit__(None, None, None)
42+
43+
raise exc from None
4144

4245
return wrapper

tests/integrations/openai_agents/test_openai_agents.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import re
23
import pytest
34
from unittest.mock import MagicMock, patch
@@ -637,3 +638,45 @@ async def test_error_handling(sentry_init, capture_events, test_agent):
637638
assert ai_client_span["description"] == "chat gpt-4"
638639
assert ai_client_span["origin"] == "auto.ai.openai_agents"
639640
assert ai_client_span["tags"]["status"] == "internal_error"
641+
642+
643+
@pytest.mark.asyncio
644+
async def test_multiple_agents_asyncio(
645+
sentry_init, capture_events, test_agent, mock_model_response
646+
):
647+
"""
648+
Test that multiple agents can be run at the same time in asyncio tasks
649+
without interfering with each other.
650+
"""
651+
652+
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}):
653+
with patch(
654+
"agents.models.openai_responses.OpenAIResponsesModel.get_response"
655+
) as mock_get_response:
656+
mock_get_response.return_value = mock_model_response
657+
658+
sentry_init(
659+
integrations=[OpenAIAgentsIntegration()],
660+
traces_sample_rate=1.0,
661+
)
662+
663+
events = capture_events()
664+
665+
async def run():
666+
await agents.Runner.run(
667+
starting_agent=test_agent,
668+
input="Test input",
669+
run_config=test_run_config,
670+
)
671+
672+
await asyncio.gather(*[run() for _ in range(3)])
673+
674+
assert len(events) == 3
675+
txn1, txn2, txn3 = events
676+
677+
assert txn1["type"] == "transaction"
678+
assert txn1["transaction"] == "test_agent workflow"
679+
assert txn2["type"] == "transaction"
680+
assert txn2["transaction"] == "test_agent workflow"
681+
assert txn3["type"] == "transaction"
682+
assert txn3["transaction"] == "test_agent workflow"

0 commit comments

Comments
 (0)