Repository navigation
Conversation
LangchainTool's func fallback only unwrapped StructuredTool.func (the google#707 fix). For a plain Tool it landed on Tool._run(self, *args, config, ...), so FunctionTool derived a declaration with a single required "config" parameter the model is asked for and the invocation always failed with the mandatory-parameters error. Unwrap Tool.func before the generic fallback, mirroring the StructuredTool precedence; custom BaseTool subclasses that override _run keep the current behavior. Fixes google#8035
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
| if isinstance(tool, Tool) and getattr(tool, 'func', None) is not None: | ||
| func = tool.func |
There was a problem hiding this comment.
a plain Tool(func=None, coroutine=afn) still falls through to _run here, so it still declares config and then raises "Tool does not support sync invocation". the StructuredTool branch above already falls back to the coroutine, same thing works here
| if isinstance(tool, Tool) and getattr(tool, 'func', None) is not None: | |
| func = tool.func | |
| if isinstance(tool, Tool) and (tool.func or tool.coroutine) is not None: | |
| func = tool.func or tool.coroutine |
fixed this with tests for both the sync and async plain Tool in this commit, you can just cherry pick it instead of redoing it: its-amann@5642875
There was a problem hiding this comment.
Good catch — the func is not None check still landed on _run for Tool(func=None, coroutine=afn).
Pushed b29f889: the plain Tool branch now mirrors the StructuredTool one (tool.func or tool.coroutine), so an async-only plain Tool no longer advertises config or raises "Tool does not support sync invocation".
I took your test cases as-is (test_plain_tool_with_func / test_plain_tool_with_coroutine); all 12 tests in tests/unittests/integrations/langchain/test_langchain_tool.py pass locally. Thanks also for preparing the cherry-pick commit — much appreciated.
chrikrah
left a comment
There was a problem hiding this comment.
@HhH666-ux the func branch fixes the Tool(func=...) case from #8035, but I'd hold the merge for a test, since the 10 existing ones also pass on the base. blocking: its-amann's 5642875 adds that test plus the coroutine fallback you scoped out. Both tests fail on the base, and the coroutine one fails here too.
non-blocking: they check only the run_async result; asserting the declaration is ['text'] would pin what the model sees.
# git archive 912d7c6 and 5642875 (base 1cb926d), Python 3.12, langchain-core 1.7.6, pytest 9.2.0, langchain_tool.__file__ from the export
$ python repro.py # the issue's script plus a coroutine-only Tool
head Tool: declares ['text'], run_async HI
head Tool(coroutine): declares ['config'], run_async error: mandatory input parameters are not present: config
base Tool: declares ['config'], run_async error: mandatory input parameters are not present: config
$ python -m pytest -q tests/unittests/integrations/langchain/test_langchain_tool.py # tests from 5642875, langchain_tool.py per row
base 1cb926d: 2 failed, 10 passed # test_plain_tool_with_func, test_plain_tool_with_coroutine
head 912d7c6: 1 failed, 11 passed # test_plain_tool_with_coroutine
5642875: 12 passed
Whichever branch ends up carrying 5642875, I'll rerun this there and approve.
A plain Tool built as Tool(func=None, coroutine=afn) only has a coroutine, so the previous check fell through to Tool._run and advertised a required "config" parameter before failing with "Tool does not support sync invocation". Mirror the StructuredTool branch: prefer func, then coroutine. Tests cover both the sync and async plain Tool paths. Addresses the review on this PR (thanks @its-amann).
Fixes #8035.
Problem
LangchainTool.__init__only unwrapsStructuredTool.func(the #707 fix). For a plainlangchain_core.tools.Tool, the fallback lands onTool._run(self, *args, config: RunnableConfig, ...), andFunctionTool.__init__derives the function declaration from that signature — so the model sees a single requiredconfigparameter:while a
StructuredToolbuilt from the same function declares['text']and runs.Changes
Before the generic fallback, unwrap a plain
Tool's real callable:This mirrors the
StructuredToolprecedence (funcfirst): the declaration is built from the user function's signature, and invocation goes through the same callable the StructuredTool path already uses. Scoped strictly toToolinstances — customBaseToolsubclasses that intentionally override_runkeep the current behavior, andTool(coroutine=...)withoutfuncis unchanged.Verification
Offline, no API key (the issue's script,
langchain-core1.7.x):Tool(name="shout", func=shout)declares['config']required['config']['text']required['text'], matching theStructuredTool.from_function(shout)control