Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions sentry_sdk/integrations/openai_agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 20 additions & 4 deletions sentry_sdk/integrations/openai_agents/patches/agent_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions sentry_sdk/integrations/openai_agents/patches/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 6 additions & 2 deletions sentry_sdk/integrations/openai_agents/patches/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions sentry_sdk/integrations/openai_agents/patches/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""

Expand Down
Loading