You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When @function_tool is applied to a function, the resulting FunctionTool
dataclass captures the original callable only in the closure of _on_invoke_tool_impl (free var the_func, reachable today via function_tool.on_invoke_tool._invoke_tool_impl.__closure__). There is no
public attribute (func, fn, __wrapped__) and no __call__ forward.
PR #2146 was opened in December 2025 to add a .func attribute and was
closed unmerged in February 2026 over an unrelated pytest fixture-collection
regression. The underlying ergonomic gap remains, and from 0.16 onward
the _FailureHandlingFunctionToolInvoker indirection made the
closure-walking workaround more expensive (one more attribute hop).
Why this matters
Anything outside the Agents runtime that wants to introspect, ship, or
call the user's tool body has no stable hook:
Code-shipping SDKs that bundle the user's tool source and re-run
the bottom function in a sandbox. The standard unwrap chain
(__wrapped__ / func / fn) misses every @function_tool-decorated
symbol, so customers get an AttributeError at build time (or a
non-callable wrapper at runtime) on the very first example any
tutorial shows.
with `_create_function_tool` passing `_func=the_func` when constructing
the dataclass. No `functools.update_wrapper`, which dodges the pytest
collection issue that closed #2146.
Option 2 — callable forward (ergonomic ceiling, complementary to 1):
```python
def call(self, *args, **kwargs):
if self._func is None:
raise RuntimeError("FunctionTool has no underlying function")
return self._func(*args, **kwargs)
```
Option 1 unblocks SDKs and tests immediately; Option 2 makes
`my_tool(...)` work in notebooks and unit tests too.
Workaround in the wild
Pending an upstream fix, downstream SDKs walk
`on_invoke_tool._invoke_tool_impl.closure` for the free var named
`the_func`. That's private-implementation-detail spelunking — every
release risks a silent break. A public attribute lets us delete the
workaround entirely.
Summary
When
@function_toolis applied to a function, the resultingFunctionTooldataclass captures the original callable only in the closure of
_on_invoke_tool_impl(free varthe_func, reachable today viafunction_tool.on_invoke_tool._invoke_tool_impl.__closure__). There is nopublic attribute (
func,fn,__wrapped__) and no__call__forward.PR #2146 was opened in December 2025 to add a
.funcattribute and wasclosed unmerged in February 2026 over an unrelated pytest fixture-collection
regression. The underlying ergonomic gap remains, and from 0.16 onward
the
_FailureHandlingFunctionToolInvokerindirection made theclosure-walking workaround more expensive (one more attribute hop).
Why this matters
Anything outside the Agents runtime that wants to introspect, ship, or
call the user's tool body has no stable hook:
the bottom function in a sandbox. The standard unwrap chain
(
__wrapped__/func/fn) misses every@function_tool-decoratedsymbol, so customers get an
AttributeErrorat build time (or anon-callable wrapper at runtime) on the very first example any
tutorial shows.
Agent / ToolContext / JSON encode-decode cycle (Make FuncTool and @function_tool decorated function callable #708).
cleanest portability surface.
Proposed fix
Either of these is sufficient; both are compatible with the existing
dataclass shape.
Option 1 — public attribute (what #2146 was driving at):
```python
@DataClass
class FunctionTool:
...
_func: ToolFunction[...] | None = field(default=None, kw_only=True, repr=False)
```
with `_create_function_tool` passing `_func=the_func` when constructing
the dataclass. No `functools.update_wrapper`, which dodges the pytest
collection issue that closed #2146.
Option 2 — callable forward (ergonomic ceiling, complementary to 1):
```python
def call(self, *args, **kwargs):
if self._func is None:
raise RuntimeError("FunctionTool has no underlying function")
return self._func(*args, **kwargs)
```
Option 1 unblocks SDKs and tests immediately; Option 2 makes
`my_tool(...)` work in notebooks and unit tests too.
Workaround in the wild
Pending an upstream fix, downstream SDKs walk
`on_invoke_tool._invoke_tool_impl.closure` for the free var named
`the_func`. That's private-implementation-detail spelunking — every
release risks a silent break. A public attribute lets us delete the
workaround entirely.
Happy to PR whichever shape a maintainer prefers.