From b48a9b28e1d7d89cbe336588b3cabbcc2f2bf1e1 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:07:16 +0800 Subject: [PATCH] fix: expose original function on FunctionTool --- src/agents/tool.py | 11 +++++++++++ tests/test_function_tool.py | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/agents/tool.py b/src/agents/tool.py index be9e49802c..d281c50c7a 100644 --- a/src/agents/tool.py +++ b/src/agents/tool.py @@ -389,6 +389,14 @@ class FunctionTool: _emit_tool_origin: bool = field(default=True, kw_only=True, repr=False) """Whether runtime item generation should emit tool origin metadata for this tool.""" + _func: ToolFunction[...] | None = field(default=None, kw_only=True, repr=False) + """Original callable when this tool was created from @function_tool.""" + + @property + def func(self) -> ToolFunction[...] | None: + """Return the original callable for tools created with @function_tool.""" + return self._func + @property def qualified_name(self) -> str: """Return the public qualified name used to identify this function tool.""" @@ -514,6 +522,7 @@ def _build_wrapped_function_tool( sync_invoker: bool = False, mcp_title: str | None = None, tool_origin: ToolOrigin | None = None, + original_function: ToolFunction[...] | None = None, ) -> FunctionTool: """Create a FunctionTool with copied-tool-aware failure handling bound in one place.""" on_invoke_tool = _with_context_function_tool_failure_error_handler( @@ -540,6 +549,7 @@ def _build_wrapped_function_tool( defer_loading=defer_loading, _mcp_title=mcp_title, _tool_origin=tool_origin, + _func=original_function, ), failure_error_function, ) @@ -1905,6 +1915,7 @@ async def _on_invoke_tool_impl(ctx: ToolContext[Any], input: str) -> Any: timeout_error_function=timeout_error_function, defer_loading=defer_loading, sync_invoker=is_sync_function_tool, + original_function=the_func, ) return function_tool diff --git a/tests/test_function_tool.py b/tests/test_function_tool.py index 60ae2558cc..3063595e5e 100644 --- a/tests/test_function_tool.py +++ b/tests/test_function_tool.py @@ -155,6 +155,26 @@ async def test_simple_function(): ) +def test_function_tool_exposes_original_function_without_wrapping() -> None: + tool = function_tool(simple_function) + + assert tool.func is simple_function + assert not callable(tool) + assert copy.copy(tool).func is simple_function + assert dataclasses.replace(tool, name="renamed").func is simple_function + + +def test_manual_function_tool_has_no_original_function() -> None: + tool = FunctionTool( + name="manual", + description="manual tool", + params_json_schema={"type": "object", "properties": {}, "additionalProperties": False}, + on_invoke_tool=lambda _ctx, _input: asyncio.sleep(0), + ) + + assert tool.func is None + + @pytest.mark.asyncio async def test_sync_function_runs_via_to_thread(monkeypatch: pytest.MonkeyPatch) -> None: calls = {"to_thread": 0, "func": 0}