diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index deb136de01..50cdae222b 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -52,6 +52,32 @@ def _patch_tools() -> None: class OpenAIAgentsIntegration(Integration): + """ + NOTE: With version 0.8.0, the class methods below have been refactored to functions. + - `AgentRunner._get_model()` -> `agents.run_internal.turn_preparation.get_model()` + - `AgentRunner._get_all_tools()` -> `agents.run_internal.turn_preparation.get_all_tools()` + - `AgentRunner._run_single_turn()` -> `agents.run_internal.run_loop.run_single_turn()` + - `RunImpl.execute_handoffs()` -> `agents.run_internal.turn_resolution.execute_handoffs()` + - `RunImpl.execute_final_output()` -> `agents.run_internal.turn_resolution.execute_final_output()` + + Typical interaction with the library: + 1. The user creates an Agent instance with configuration, including system instructions sent to every Responses API call. + 2. The user passes the agent instance to a Runner with `run()` and `run_streamed()` methods. The latter can be used to incrementally receive progress. + - `Runner.run()` and `Runner.run_streamed()` are thin wrappers for `DEFAULT_AGENT_RUNNER.run()` and `DEFAULT_AGENT_RUNNER.run_streamed()`. + - `DEFAULT_AGENT_RUNNER.run()` and `DEFAULT_AGENT_RUNNER.run_streamed()` are patched in `_patch_runner()` with `_create_run_wrapper()` and `_create_run_streamed_wrapper()`, respectively. + 3. In a loop, the agent repeatedly calls the Responses API, maintaining a conversation history that includes previous messages and tool results, which is passed to each call. + - A Model instance is created at the start of the loop by calling the `Runner._get_model()`. We patch the Model instance using `_create_get_model_wrapper()` in `_patch_model()`. + - Available tools are also deteremined at the start of the loop, with `Runner._get_all_tools()`. We patch Tool instances by iterating through the returned tools, in `_create_get_all_tools_wrapper()` called via `_patch_tools()` + - In each loop iteration, `run_single_turn()` or `run_single_turn_streamed()` is responsible for calling the Responses API, patched with `patched_run_single_turn()` and `patched_run_single_turn_streamed()`. + 4. On loop termination, `RunImpl.execute_final_output()` is called. The function is patched with `patched_execute_final_output()`. + + Local tools are run based on the return value from the Responses API as a post-API call step in the above loop. + Hosted MCP Tools are run as part of the Responses API call, and involve OpenAI reaching out to an external MCP server. + An agent can handoff to another agent, also directed by the return value of the Responses API and run post-API call in the loop. + Handoffs are a way to switch agent-wide configuration. + - Handoffs are executed by calling `RunImpl.execute_handoffs()`. The method is patched in `patched_execute_handoffs()` + """ + identifier = "openai_agents" @staticmethod diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index eeb821d42a..138151d930 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -94,7 +94,11 @@ def _maybe_start_agent_span( async def patched_run_single_turn( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched _run_single_turn that creates agent invocation spans""" + """ + Patched _run_single_turn that + - creates agent invocation spans if there is no already active agent invocation span. + - ends the agent invocation span if and only if an exception is raised in `_run_single_turn()`. + """ agent = kwargs.get("agent") context_wrapper = kwargs.get("context_wrapper") should_run_agent_start_hooks = kwargs.get("should_run_agent_start_hooks", False) @@ -121,7 +125,12 @@ async def patched_run_single_turn( async def patched_execute_handoffs( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched execute_handoffs that creates handoff spans and ends agent span for handoffs""" + """ + Patched execute_handoffs that + - creates and manages handoff spans. + - ends the agent invocation span. + - ends the workflow span if the response is streamed and an exception is raised in `execute_handoffs()`. + """ context_wrapper = kwargs.get("context_wrapper") run_handoffs = kwargs.get("run_handoffs") @@ -156,7 +165,11 @@ async def patched_execute_handoffs( async def patched_execute_final_output( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched execute_final_output that ends agent span for final outputs""" + """ + Patched execute_final_output that + - ends the agent invocation span. + - ends the workflow span if the response is streamed. + """ agent = kwargs.get("agent") context_wrapper = kwargs.get("context_wrapper") @@ -185,7 +198,10 @@ async def patched_execute_final_output( async def patched_run_single_turn_streamed( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched _run_single_turn_streamed that creates agent invocation spans for streaming. + """ + Patched _run_single_turn_streamed that + - creates agent invocation spans for streaming if there is no already active agent invocation span. + - ends the agent invocation span if and only if `_run_single_turn_streamed()` raises an exception. Note: Unlike _run_single_turn which uses keyword-only arguments (*,), _run_single_turn_streamed uses positional arguments. The call signature is: diff --git a/sentry_sdk/integrations/openai_agents/patches/models.py b/sentry_sdk/integrations/openai_agents/patches/models.py index b6a69ae9f7..5d4d71185f 100644 --- a/sentry_sdk/integrations/openai_agents/patches/models.py +++ b/sentry_sdk/integrations/openai_agents/patches/models.py @@ -71,6 +71,11 @@ def _create_get_model_wrapper( ) -> "Callable[..., Any]": """ Wraps the agents.Runner._get_model method to wrap the get_response method of the model to create a AI client span. + + Responsible for + - creating and managing AI client spans. + - adding trace propagation headers to tools with type HostedMCPTool. + - setting the response model on agent invocation spans. """ @wraps( diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index b561500495..78c055890e 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -22,7 +22,9 @@ def _create_run_wrapper(original_func: "Callable[..., Any]") -> "Callable[..., Any]": """ - Wraps the agents.Runner.run methods to create a root span for the agent workflow runs. + Wraps the agents.Runner.run methods to + - create and manage a root span for the agent workflow runs. + - end the agent invocation span if an `AgentsException` is raised in `run()`. Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(), so it does not need to be wrapped separately. @@ -85,7 +87,9 @@ def _create_run_streamed_wrapper( original_func: "Callable[..., Any]", ) -> "Callable[..., Any]": """ - Wraps the agents.Runner.run_streamed method to create a root span for streaming agent workflow runs. + Wraps the agents.Runner.run_streamed method to + - create a root span for streaming agent workflow runs. + - end the workflow span if and only if the response stream is consumed or cancelled. Unlike run(), run_streamed() returns immediately with a RunResultStreaming object while execution continues in a background task. The workflow span must stay open diff --git a/sentry_sdk/integrations/openai_agents/patches/tools.py b/sentry_sdk/integrations/openai_agents/patches/tools.py index d14a3019aa..56047e20c5 100644 --- a/sentry_sdk/integrations/openai_agents/patches/tools.py +++ b/sentry_sdk/integrations/openai_agents/patches/tools.py @@ -19,6 +19,8 @@ def _create_get_all_tools_wrapper( original_get_all_tools: "Callable[..., Any]", ) -> "Callable[..., Any]": """ + Responsible for creating and managing `gen_ai.execute_tool` spans. + Wraps the agents.Runner._get_all_tools method of the Runner class to wrap all function tools with Sentry instrumentation. """