From 033ac417609297369eb0525794d8b48a425b8b33 Mon Sep 17 00:00:00 2001 From: Qun <51054082+QunBB@users.noreply.github.com> Date: Fri, 20 Dec 2024 02:00:46 +0800 Subject: [PATCH] =?UTF-8?q?fix=20crash=20when=20using=20create=5Fxml=5Fage?= =?UTF-8?q?nt=20with=20parameterless=20function=20as=20=E2=80=A6=20(#26002?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using `create_xml_agent` or `create_json_chat_agent` to create a agent, and the function corresponding to the tool is a parameterless function, the `XMLAgentOutputParser` or `JSONAgentOutputParser` will parse the tool input into an empty string, `BaseTool` will parse it into a positional argument. So, the program will crash finally because we invoke a parameterless function but with a positional argument.Specially, below code will raise StopIteration in [_parse_input](https://github.com/langchain-ai/langchain/blob/master/libs/core/langchain_core/tools/base.py#L419) ```python from langchain import hub from langchain.agents import AgentExecutor, create_json_chat_agent, create_xml_agent from langchain_openai import ChatOpenAI prompt = hub.pull("hwchase17/react-chat-json") llm = ChatOpenAI() # agent = create_xml_agent(llm, tools, prompt) agent = create_json_chat_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) agent_executor.invoke(......) ``` --------- Co-authored-by: Erick Friis Co-authored-by: Bagatur <22008038+baskaryan@users.noreply.github.com> Co-authored-by: Chester Curme --- libs/core/langchain_core/tools/base.py | 3 +++ libs/core/tests/unit_tests/test_tools.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/libs/core/langchain_core/tools/base.py b/libs/core/langchain_core/tools/base.py index ff264edac32..cf755284614 100644 --- a/libs/core/langchain_core/tools/base.py +++ b/libs/core/langchain_core/tools/base.py @@ -605,6 +605,9 @@ class ChildTool(BaseTool): def _to_args_and_kwargs( self, tool_input: Union[str, dict], tool_call_id: Optional[str] ) -> tuple[tuple, dict]: + if self.args_schema is not None and not get_fields(self.args_schema): + # StructuredTool with no args + return (), {} tool_input = self._parse_input(tool_input, tool_call_id) # For backwards compatibility, if run_input is a string, # pass as a positional argument. diff --git a/libs/core/tests/unit_tests/test_tools.py b/libs/core/tests/unit_tests/test_tools.py index b331abea7da..b21fc139a21 100644 --- a/libs/core/tests/unit_tests/test_tools.py +++ b/libs/core/tests/unit_tests/test_tools.py @@ -575,6 +575,19 @@ def test_structured_tool_from_function_with_run_manager() -> None: ) +def test_structured_tool_from_parameterless_function() -> None: + """Test parameterless function of structured tool.""" + + def foo() -> str: + """Docstring.""" + return "invoke foo" + + structured_tool = StructuredTool.from_function(foo) + + assert structured_tool.run({}) == "invoke foo" + assert structured_tool.run("") == "invoke foo" + + def test_named_tool_decorator() -> None: """Test functionality when arguments are provided as input to decorator."""