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
4 changes: 4 additions & 0 deletions src/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
ShellResult,
ShellTool,
Tool,
ToolOrigin,
ToolOriginType,
ToolOutputFileContent,
ToolOutputFileContentDict,
ToolOutputImage,
Expand Down Expand Up @@ -359,6 +361,8 @@ def enable_verbose_stdout_logging():
"ApplyPatchResult",
"ApplyPatchTool",
"Tool",
"ToolOrigin",
"ToolOriginType",
"WebSearchTool",
"HostedMCPTool",
"MCPToolApprovalFunction",
Expand Down
7 changes: 7 additions & 0 deletions src/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
FunctionToolResult,
Tool,
ToolErrorFunction,
ToolOrigin,
ToolOriginType,
_extract_tool_argument_json_error,
default_tool_error_function,
)
Expand Down Expand Up @@ -830,6 +832,11 @@ async def _run_agent_tool(context: ToolContext, input_json: str) -> Any:
)
run_agent_tool._is_agent_tool = True
run_agent_tool._agent_instance = self
# Set origin tracking on run_agent (the FunctionTool returned by @function_tool)
run_agent_tool._tool_origin = ToolOrigin(
type=ToolOriginType.AGENT_AS_TOOL,
agent_as_tool=self,
)

return run_agent_tool

Expand Down
19 changes: 19 additions & 0 deletions src/agents/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from .exceptions import AgentsException, ModelBehaviorError
from .logger import logger
from .tool import (
ToolOrigin,
ToolOutputFileContent,
ToolOutputImage,
ToolOutputText,
Expand Down Expand Up @@ -248,6 +249,15 @@ class ToolCallItem(RunItemBase[Any]):
description: str | None = None
"""Optional tool description if known at item creation time."""

tool_origin: ToolOrigin | None = field(default=None, repr=False)
"""Information about the origin/source of the tool call. Only set for FunctionTool calls."""

def release_agent(self) -> None:
"""Release agent references including tool_origin.agent_as_tool."""
super().release_agent()
if self.tool_origin is not None:
self.tool_origin.release_agent()


ToolCallOutputTypes: TypeAlias = Union[
FunctionCallOutput,
Expand All @@ -271,6 +281,15 @@ class ToolCallOutputItem(RunItemBase[Any]):

type: Literal["tool_call_output_item"] = "tool_call_output_item"

tool_origin: ToolOrigin | None = field(default=None, repr=False)
"""Information about the origin/source of the tool call. Only set for FunctionTool calls."""

def release_agent(self) -> None:
"""Release agent references including tool_origin.agent_as_tool."""
super().release_agent()
if self.tool_origin is not None:
self.tool_origin.release_agent()

def to_input_item(self) -> TResponseInputItem:
"""Converts the tool output into an input item for the next model turn.

Expand Down
9 changes: 8 additions & 1 deletion src/agents/mcp/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
FunctionTool,
Tool,
ToolErrorFunction,
ToolOrigin,
ToolOriginType,
ToolOutputImageDict,
ToolOutputTextDict,
default_tool_error_function,
Expand Down Expand Up @@ -301,14 +303,19 @@ async def invoke_func(ctx: ToolContext[Any], input_json: str) -> ToolOutput:
bool | Callable[[RunContextWrapper[Any], dict[str, Any], str], Awaitable[bool]]
) = server._get_needs_approval_for_tool(tool, agent)

return FunctionTool(
function_tool = FunctionTool(
name=tool.name,
description=tool.description or "",
params_json_schema=schema,
on_invoke_tool=invoke_func,
strict_json_schema=is_strict,
needs_approval=needs_approval,
)
function_tool._tool_origin = ToolOrigin(
type=ToolOriginType.MCP,
mcp_server=server,
)
return function_tool

@staticmethod
def _merge_mcp_meta(
Expand Down
4 changes: 3 additions & 1 deletion src/agents/run_internal/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ..agent_tool_state import drop_agent_tool_run_result
from ..items import ItemHelpers, ToolCallOutputItem, TResponseInputItem
from ..models.fake_id import FAKE_RESPONSES_ID
from ..tool import DEFAULT_APPROVAL_REJECTION_MESSAGE
from ..tool import DEFAULT_APPROVAL_REJECTION_MESSAGE, ToolOrigin

REJECTION_MESSAGE = DEFAULT_APPROVAL_REJECTION_MESSAGE
_TOOL_CALL_TO_OUTPUT_TYPE: dict[str, str] = {
Expand Down Expand Up @@ -191,6 +191,7 @@ def function_rejection_item(
tool_call: Any,
*,
rejection_message: str = REJECTION_MESSAGE,
tool_origin: ToolOrigin | None = None,
) -> ToolCallOutputItem:
"""Build a ToolCallOutputItem representing a rejected function tool call."""
if isinstance(tool_call, ResponseFunctionToolCall):
Expand All @@ -199,6 +200,7 @@ def function_rejection_item(
output=rejection_message,
raw_item=ItemHelpers.tool_call_output_item(tool_call, rejection_message),
agent=agent,
tool_origin=tool_origin,
)


Expand Down
26 changes: 23 additions & 3 deletions src/agents/run_internal/run_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
from collections.abc import Awaitable, Callable
from typing import Any, TypeVar, cast

from openai.types.responses import ResponseCompletedEvent, ResponseOutputItemDoneEvent
from openai.types.responses import (
ResponseCompletedEvent,
ResponseFunctionToolCall,
ResponseOutputItemDoneEvent,
)
from openai.types.responses.response_prompt_param import ResponsePromptParam
from openai.types.responses.response_reasoning_item import ResponseReasoningItem

Expand Down Expand Up @@ -49,7 +53,7 @@
RawResponsesStreamEvent,
RunItemStreamEvent,
)
from ..tool import Tool, dispose_resolved_computers
from ..tool import FunctionTool, Tool, _get_tool_origin_info, dispose_resolved_computers
from ..tracing import Span, SpanError, agent_span, get_current_trace
from ..tracing.model_tracing import get_model_tracing_impl
from ..tracing.span_data import AgentSpanData
Expand Down Expand Up @@ -113,6 +117,7 @@
from .streaming import stream_step_items_to_queue, stream_step_result_to_queue
from .tool_actions import ApplyPatchAction, ComputerAction, LocalShellAction, ShellAction
from .tool_execution import (
build_litellm_json_tool_call,
coerce_shell_call,
execute_apply_patch_calls,
execute_computer_actions,
Expand Down Expand Up @@ -1222,13 +1227,28 @@ async def run_single_turn_streamed(
# execution behavior in process_model_response).
tool_name = getattr(output_item, "name", None)
tool_description: str | None = None
tool_origin = None
if isinstance(tool_name, str) and tool_name in tool_map:
tool_description = getattr(tool_map[tool_name], "description", None)
tool = tool_map[tool_name]
tool_description = getattr(tool, "description", None)
if isinstance(tool, FunctionTool):
tool_origin = _get_tool_origin_info(tool)
elif (
isinstance(tool_name, str)
and tool_name == "json_tool_call"
and output_schema is not None
and isinstance(output_item, ResponseFunctionToolCall)
):
# json_tool_call is synthesized dynamically and not in tool_map.
# Synthesize it here to get tool_origin, matching process_model_response.
json_tool = build_litellm_json_tool_call(output_item)
tool_origin = _get_tool_origin_info(json_tool)

tool_item = ToolCallItem(
raw_item=cast(ToolCallItemTypes, output_item),
agent=agent,
description=tool_description,
tool_origin=tool_origin,
)
streamed_result._event_queue.put_nowait(
RunItemStreamEvent(item=tool_item, name="tool_called")
Expand Down
5 changes: 5 additions & 0 deletions src/agents/run_internal/tool_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
ShellCallOutcome,
ShellCommandOutput,
Tool,
_get_tool_origin_info,
resolve_computer,
)
from ..tool_context import ToolContext
Expand Down Expand Up @@ -867,13 +868,15 @@ async def run_single_tool(func_tool: FunctionTool, tool_call: ResponseFunctionTo
)
result = rejection_message
span_fn.span_data.output = result
tool_origin = _get_tool_origin_info(func_tool)
return FunctionToolResult(
tool=func_tool,
output=result,
run_item=function_rejection_item(
agent,
tool_call,
rejection_message=rejection_message,
tool_origin=tool_origin,
),
)

Expand Down Expand Up @@ -973,10 +976,12 @@ async def run_single_tool(func_tool: FunctionTool, tool_call: ResponseFunctionTo

run_item: RunItem | None = None
if not nested_interruptions:
tool_origin = _get_tool_origin_info(tool_run.function_tool)
run_item = ToolCallOutputItem(
output=result,
raw_item=ItemHelpers.tool_call_output_item(tool_run.tool_call, result),
agent=agent,
tool_origin=tool_origin,
)
else:
# Skip tool output until nested interruptions are resolved.
Expand Down
26 changes: 22 additions & 4 deletions src/agents/run_internal/turn_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
LocalShellTool,
ShellTool,
Tool,
_get_tool_origin_info,
)
from ..tool_guardrails import ToolInputGuardrailResult, ToolOutputGuardrailResult
from ..tracing import SpanError, handoff_span
Expand Down Expand Up @@ -691,8 +692,11 @@ async def _record_function_rejection(
tool_name=function_tool.name,
call_id=call_id,
)
tool_origin = _get_tool_origin_info(function_tool)
rejected_function_outputs.append(
function_rejection_item(agent, tool_call, rejection_message=rejection_message)
function_rejection_item(
agent, tool_call, rejection_message=rejection_message, tool_origin=tool_origin
)
)
if isinstance(call_id, str):
rejected_function_call_ids.add(call_id)
Expand Down Expand Up @@ -1455,11 +1459,19 @@ def process_model_response(
else:
if output.name not in function_map:
if output_schema is not None and output.name == "json_tool_call":
items.append(ToolCallItem(raw_item=output, agent=agent))
json_tool = build_litellm_json_tool_call(output)
tool_origin = _get_tool_origin_info(json_tool)
items.append(
ToolCallItem(
raw_item=output,
agent=agent,
tool_origin=tool_origin,
)
)
functions.append(
ToolRunFunction(
tool_call=output,
function_tool=build_litellm_json_tool_call(output),
function_tool=json_tool,
)
)
continue
Expand All @@ -1473,8 +1485,14 @@ def process_model_response(
raise ModelBehaviorError(error)

func_tool = function_map[output.name]
tool_origin = _get_tool_origin_info(func_tool)
items.append(
ToolCallItem(raw_item=output, agent=agent, description=func_tool.description)
ToolCallItem(
raw_item=output,
agent=agent,
description=func_tool.description,
tool_origin=tool_origin,
)
)
functions.append(
ToolRunFunction(
Expand Down
Loading