Skip to content
Closed
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 @@ -148,6 +148,8 @@
ShellToolLocalSkill,
ShellToolSkillReference,
Tool,
ToolOrigin,
ToolOriginType,
ToolOutputFileContent,
ToolOutputFileContentDict,
ToolOutputImage,
Expand Down Expand Up @@ -411,6 +413,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 @@ -46,6 +46,8 @@
FunctionToolResult,
Tool,
ToolErrorFunction,
ToolOrigin,
ToolOriginType,
_build_handled_function_tool_error_handler,
_build_wrapped_function_tool,
_log_function_tool_invocation,
Expand Down Expand Up @@ -857,6 +859,11 @@ async def dispatch_stream_events() -> None:
)
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
28 changes: 28 additions & 0 deletions src/agents/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
from .exceptions import AgentsException, ModelBehaviorError
from .logger import logger
from .tool import (
ToolOrigin,
ToolOutputFileContent,
ToolOutputImage,
ToolOutputText,
Expand Down Expand Up @@ -358,6 +359,15 @@ class ToolCallItem(RunItemBase[Any]):
title: str | None = None
"""Optional short display label 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 @@ -382,6 +392,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 Expand Up @@ -500,6 +519,15 @@ class ToolApprovalItem(RunItemBase[Any]):
)
"""Canonical function-tool lookup metadata when the approval targets a function tool."""

tool_origin: ToolOrigin | None = field(default=None, repr=False)
"""Information about the origin/source of the tool. 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 __post_init__(self) -> None:
"""Populate tool_name from the raw item if not provided."""
if self.tool_name is None:
Expand Down
6 changes: 6 additions & 0 deletions src/agents/mcp/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
FunctionTool,
Tool,
ToolErrorFunction,
ToolOrigin,
ToolOriginType,
ToolOutputImageDict,
ToolOutputTextDict,
_build_handled_function_tool_error_handler,
Expand Down Expand Up @@ -285,6 +287,10 @@ def to_function_tool(
needs_approval=needs_approval,
mcp_title=resolve_mcp_tool_title(tool),
)
function_tool._tool_origin = ToolOrigin(
type=ToolOriginType.MCP,
mcp_server=server,
)
return function_tool

@staticmethod
Expand Down
9 changes: 8 additions & 1 deletion src/agents/realtime/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,14 @@ def _build_tool_approval_item(
"call_id": tool_call.call_id,
"arguments": tool_call.arguments,
}
return ToolApprovalItem(agent=cast(Any, agent), raw_item=raw_item, tool_name=tool.name)
from ..tool import _get_tool_origin_info

return ToolApprovalItem(
agent=cast(Any, agent),
raw_item=raw_item,
tool_name=tool.name,
tool_origin=_get_tool_origin_info(tool),
)

async def _maybe_request_tool_approval(
self,
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, RunItem, 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 @@ -255,6 +255,7 @@ def function_rejection_item(
tool_call: Any,
*,
rejection_message: str = REJECTION_MESSAGE,
tool_origin: ToolOrigin | None = None,
scope_id: str | None = None,
) -> ToolCallOutputItem:
"""Build a ToolCallOutputItem representing a rejected function tool call."""
Expand All @@ -264,6 +265,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
21 changes: 19 additions & 2 deletions src/agents/run_internal/run_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
from collections.abc import Awaitable, Callable, Mapping
from typing import Any, TypeVar, cast

from openai.types.responses import Response, ResponseCompletedEvent, ResponseOutputItemDoneEvent
from openai.types.responses import (
Response,
ResponseCompletedEvent,
ResponseFunctionToolCall,
ResponseOutputItemDoneEvent,
)
from openai.types.responses.response_output_item import McpCall, McpListTools
from openai.types.responses.response_prompt_param import ResponsePromptParam
from openai.types.responses.response_reasoning_item import ResponseReasoningItem
Expand Down Expand Up @@ -62,7 +67,7 @@
RawResponsesStreamEvent,
RunItemStreamEvent,
)
from ..tool import FunctionTool, 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 @@ -126,6 +131,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 @@ -1328,6 +1334,7 @@ def _tool_search_fingerprint(raw_item: Any) -> str:
)
tool_description: str | None = None
tool_title: str | None = None
tool_origin = None
if isinstance(output_item, McpCall):
metadata = hosted_mcp_tool_metadata.get(
(output_item.server_label, output_item.name)
Expand All @@ -1338,12 +1345,22 @@ def _tool_search_fingerprint(raw_item: Any) -> str:
elif matched_tool is not None:
tool_description = getattr(matched_tool, "description", None)
tool_title = getattr(matched_tool, "_mcp_title", None)
if isinstance(matched_tool, FunctionTool):
tool_origin = _get_tool_origin_info(matched_tool)
elif (
isinstance(output_item, ResponseFunctionToolCall)
and getattr(output_item, "name", None) == "json_tool_call"
and output_schema is not None
):
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,
title=tool_title,
tool_origin=tool_origin,
)
streamed_result._event_queue.put_nowait(
RunItemStreamEvent(item=tool_item, name="tool_called")
Expand Down
7 changes: 7 additions & 0 deletions src/agents/run_internal/tool_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
ShellCallOutcome,
ShellCommandOutput,
Tool,
_get_tool_origin_info,
invoke_function_tool,
maybe_invoke_function_tool_failure_error_function,
resolve_computer,
Expand Down Expand Up @@ -1498,12 +1499,14 @@ async def _maybe_execute_tool_approval(
tool_lookup_key=tool_lookup_key,
)
if approval_status is None:
tool_origin = _get_tool_origin_info(func_tool)
approval_item = ToolApprovalItem(
agent=self.agent,
raw_item=raw_tool_call,
tool_name=func_tool.name,
tool_namespace=tool_namespace,
tool_lookup_key=tool_lookup_key,
tool_origin=tool_origin,
_allow_bare_name_alias=should_allow_bare_name_approval_alias(
func_tool,
self.available_function_tools,
Expand Down Expand Up @@ -1533,13 +1536,15 @@ async def _maybe_execute_tool_approval(
)
)
span_fn.span_data.output = rejection_message
tool_origin = _get_tool_origin_info(func_tool)
return FunctionToolResult(
tool=func_tool,
output=rejection_message,
run_item=function_rejection_item(
self.agent,
tool_call,
rejection_message=rejection_message,
tool_origin=tool_origin,
scope_id=self.tool_state_scope_id,
),
)
Expand Down Expand Up @@ -1731,10 +1736,12 @@ def _build_function_tool_results(self) -> list[FunctionToolResult]:

run_item: RunItem | 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=self.agent,
tool_origin=tool_origin,
)
else:
# Skip tool output until nested interruptions are resolved.
Expand Down
2 changes: 1 addition & 1 deletion src/agents/run_internal/tool_use_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def hydrate_tool_use_tracker(
if not snapshot:
return

agent_map = _build_agent_map(starting_agent)
agent_map, _ = _build_agent_map(starting_agent)
for agent_name, tool_names in snapshot.items():
agent = agent_map.get(agent_name)
if agent is None:
Expand Down
18 changes: 16 additions & 2 deletions src/agents/run_internal/turn_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
LocalShellTool,
ShellTool,
Tool,
_get_tool_origin_info,
)
from ..tool_guardrails import ToolInputGuardrailResult, ToolOutputGuardrailResult
from ..tracing import SpanError, handoff_span
Expand Down Expand Up @@ -712,11 +713,13 @@ async def _record_function_rejection(
tool_name=get_tool_call_trace_name(tool_call) or 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,
tool_origin=tool_origin,
scope_id=tool_state_scope_id,
)
)
Expand Down Expand Up @@ -1028,6 +1031,7 @@ def _add_unmatched_pending(approval: ToolApprovalItem) -> None:
agent=agent,
raw_item=run.tool_call,
tool_name=run.function_tool.name,
tool_origin=_get_tool_origin_info(run.function_tool),
tool_namespace=get_tool_call_namespace(run.tool_call),
tool_lookup_key=get_function_tool_lookup_key_for_call(run.tool_call),
_allow_bare_name_alias=should_allow_bare_name_approval_alias(
Expand Down Expand Up @@ -1629,11 +1633,19 @@ def _dump_output_item(raw_item: Any) -> dict[str, Any]:
func_tool = function_map.get(lookup_key) if lookup_key is not None else None
if func_tool is None:
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 @@ -1648,12 +1660,14 @@ def _dump_output_item(raw_item: Any) -> dict[str, Any]:
)
raise ModelBehaviorError(error)

tool_origin = _get_tool_origin_info(func_tool)
items.append(
ToolCallItem(
raw_item=output,
agent=agent,
description=func_tool.description,
title=func_tool._mcp_title,
tool_origin=tool_origin,
)
)
functions.append(
Expand Down
Loading