Skip to content

Commit 8f41ec9

Browse files
committed
fix: cleaning up context after agent finished
1 parent 0f32768 commit 8f41ec9

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed

sentry_sdk/integrations/pydantic_ai/patches/agent_run.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
8383
)
8484
update_invoke_agent_span(self._span, output)
8585
finally:
86+
sentry_sdk.get_current_scope().remove_context("pydantic_ai_agent")
8687
# Clean up invoke span
8788
if self._span:
8889
self._span.__exit__(exc_type, exc_val, exc_tb)
@@ -132,6 +133,8 @@ async def wrapper(self, *args, **kwargs):
132133
except Exception as exc:
133134
_capture_exception(exc)
134135
raise exc from None
136+
finally:
137+
sentry_sdk.get_current_scope().remove_context("pydantic_ai_agent")
135138

136139
return wrapper
137140

tests/integrations/pydantic_ai/test_pydantic_ai.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,3 +787,152 @@ async def mock_map_tool_result_part(part):
787787
pytest.skip(
788788
"MCP test needs to be rewritten to use agent.run() instead of manually calling toolset methods"
789789
)
790+
791+
792+
@pytest.mark.asyncio
793+
async def test_context_cleanup_after_run(sentry_init, test_agent):
794+
"""
795+
Test that the pydantic_ai_agent context is properly cleaned up after agent execution.
796+
"""
797+
import sentry_sdk
798+
799+
sentry_init(
800+
integrations=[PydanticAIIntegration()],
801+
traces_sample_rate=1.0,
802+
)
803+
804+
# Verify context is not set before run
805+
scope = sentry_sdk.get_current_scope()
806+
assert "pydantic_ai_agent" not in scope._contexts
807+
808+
# Run the agent
809+
await test_agent.run("Test input")
810+
811+
# Verify context is cleaned up after run
812+
assert "pydantic_ai_agent" not in scope._contexts
813+
814+
815+
def test_context_cleanup_after_run_sync(sentry_init, test_agent):
816+
"""
817+
Test that the pydantic_ai_agent context is properly cleaned up after sync agent execution.
818+
"""
819+
import sentry_sdk
820+
821+
sentry_init(
822+
integrations=[PydanticAIIntegration()],
823+
traces_sample_rate=1.0,
824+
)
825+
826+
# Verify context is not set before run
827+
scope = sentry_sdk.get_current_scope()
828+
assert "pydantic_ai_agent" not in scope._contexts
829+
830+
# Run the agent synchronously
831+
test_agent.run_sync("Test input")
832+
833+
# Verify context is cleaned up after run
834+
assert "pydantic_ai_agent" not in scope._contexts
835+
836+
837+
@pytest.mark.asyncio
838+
async def test_context_cleanup_after_streaming(sentry_init, test_agent):
839+
"""
840+
Test that the pydantic_ai_agent context is properly cleaned up after streaming execution.
841+
"""
842+
import sentry_sdk
843+
844+
sentry_init(
845+
integrations=[PydanticAIIntegration()],
846+
traces_sample_rate=1.0,
847+
)
848+
849+
# Verify context is not set before run
850+
scope = sentry_sdk.get_current_scope()
851+
assert "pydantic_ai_agent" not in scope._contexts
852+
853+
# Run the agent with streaming
854+
async with test_agent.run_stream("Test input") as result:
855+
async for _ in result.stream_output():
856+
pass
857+
858+
# Verify context is cleaned up after streaming completes
859+
assert "pydantic_ai_agent" not in scope._contexts
860+
861+
862+
@pytest.mark.asyncio
863+
async def test_context_cleanup_on_error(sentry_init, test_agent):
864+
"""
865+
Test that the pydantic_ai_agent context is cleaned up even when an error occurs.
866+
"""
867+
import sentry_sdk
868+
869+
# Create an agent with a tool that raises an error
870+
@test_agent.tool_plain
871+
def failing_tool() -> str:
872+
"""A tool that always fails."""
873+
raise ValueError("Tool error")
874+
875+
sentry_init(
876+
integrations=[PydanticAIIntegration()],
877+
traces_sample_rate=1.0,
878+
)
879+
880+
# Verify context is not set before run
881+
scope = sentry_sdk.get_current_scope()
882+
assert "pydantic_ai_agent" not in scope._contexts
883+
884+
# Run the agent - this may or may not raise depending on pydantic-ai's error handling
885+
try:
886+
await test_agent.run("Use the failing tool")
887+
except Exception:
888+
pass
889+
890+
# Verify context is cleaned up even if there was an error
891+
assert "pydantic_ai_agent" not in scope._contexts
892+
893+
894+
@pytest.mark.asyncio
895+
async def test_context_isolation_concurrent_agents(sentry_init, test_agent):
896+
"""
897+
Test that concurrent agent executions maintain isolated contexts.
898+
"""
899+
import sentry_sdk
900+
901+
sentry_init(
902+
integrations=[PydanticAIIntegration()],
903+
traces_sample_rate=1.0,
904+
)
905+
906+
# Create a second agent
907+
agent2 = Agent(
908+
"test",
909+
name="test_agent_2",
910+
system_prompt="Second test agent.",
911+
)
912+
913+
async def run_and_check_context(agent, agent_name):
914+
"""Run an agent and verify its context during and after execution."""
915+
# Before execution, context should not exist in the outer scope
916+
outer_scope = sentry_sdk.get_current_scope()
917+
918+
# Run the agent
919+
await agent.run(f"Input for {agent_name}")
920+
921+
# After execution, verify context is cleaned up
922+
# Note: Due to isolation_scope, we can't easily check the inner scope here,
923+
# but we can verify the outer scope remains clean
924+
assert "pydantic_ai_agent" not in outer_scope._contexts
925+
926+
return agent_name
927+
928+
# Run both agents concurrently
929+
results = await asyncio.gather(
930+
run_and_check_context(test_agent, "agent1"),
931+
run_and_check_context(agent2, "agent2"),
932+
)
933+
934+
assert results == ["agent1", "agent2"]
935+
936+
# Final check: outer scope should be clean
937+
final_scope = sentry_sdk.get_current_scope()
938+
assert "pydantic_ai_agent" not in final_scope._contexts

0 commit comments

Comments
 (0)