From ff1de3d347968800614405204a82467ea18ecc2a Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Wed, 13 Dec 2023 16:47:03 -0500 Subject: [PATCH] x --- libs/langchain/langchain/agents/agent.py | 21 +++---- .../langchain/agents/output_parsers/xml.py | 39 ++++++++++-- libs/langchain/langchain/agents/xml/base.py | 61 +++++++++++++------ 3 files changed, 87 insertions(+), 34 deletions(-) diff --git a/libs/langchain/langchain/agents/agent.py b/libs/langchain/langchain/agents/agent.py index a28377625315c..8fc7055b85ed2 100644 --- a/libs/langchain/langchain/agents/agent.py +++ b/libs/langchain/langchain/agents/agent.py @@ -1034,18 +1034,17 @@ def _take_next_step( intermediate_steps: List[Tuple[AgentAction, str]], run_manager: Optional[CallbackManagerForChainRun] = None, ) -> Union[AgentFinish, List[Tuple[AgentAction, str]]]: - return self._consume_next_step( - [ - a - for a in self._iter_next_step( - name_to_tool_map, - color_mapping, - inputs, - intermediate_steps, - run_manager, - ) - ] + """Take a single step.""" + next_steps = list( + self._iter_next_step( + name_to_tool_map, + color_mapping, + inputs, + intermediate_steps, + run_manager, + ) ) + return self._consume_next_step(next_steps) def _iter_next_step( self, diff --git a/libs/langchain/langchain/agents/output_parsers/xml.py b/libs/langchain/langchain/agents/output_parsers/xml.py index 730d069ae6a0f..8ade4838c9d46 100644 --- a/libs/langchain/langchain/agents/output_parsers/xml.py +++ b/libs/langchain/langchain/agents/output_parsers/xml.py @@ -1,6 +1,7 @@ from typing import Union from langchain_core.agents import AgentAction, AgentFinish +from langchain_core.exceptions import OutputParserException from langchain.agents import AgentOutputParser @@ -32,9 +33,22 @@ def parse(self, text: str) -> Union[AgentAction, AgentFinish]: if "" in text: tool, tool_input = text.split("") _tool = tool.split("")[1] - _tool_input = tool_input.split("")[1] - if "" in _tool_input: - _tool_input = _tool_input.split("")[0] + if "" in tool_input: + _tool_input = tool_input.split("")[1] + if "" in _tool_input: + _tool_input = _tool_input.split("")[0] + else: + raise OutputParserException( + error=ValueError("Invalid format for output."), + llm_output=text, + observation=( + "ERROR: For a fool invocation, be sure to include a and" + " tags. A function without parameters could be invoked with a " + "an empty dictionary as the tool input.\n" + "To invoke a tool, use the format " + "`$TOOL_NAME$TOOL_INPUT`.\n " + ), + ) return AgentAction(tool=_tool, tool_input=_tool_input, log=text) elif "" in text: _, answer = text.split("") @@ -42,10 +56,25 @@ def parse(self, text: str) -> Union[AgentAction, AgentFinish]: answer = answer.split("")[0] return AgentFinish(return_values={"output": answer}, log=text) else: - raise ValueError + raise OutputParserException( + error=ValueError("Invalid format for output."), + llm_output=text, + observation=( + "ERROR: Please either invoke a tool or provide a final answer." + "To invoke a tool, use the format " + "`$TOOL_NAME$TOOL_INPUT`. " + "where $TOOL_NAME is one of the provided tools and $TOOL_INPUT " + "is a dictionary of arguments to pass to the tool, " + "matching the schema.\n" + ), + send_to_llm=True, + ) def get_format_instructions(self) -> str: - raise NotImplementedError + """Get the format instructions for this output parser.""" + raise NotImplementedError( + "XMLAgentOutputParser does contain format instructions." + ) @property def _type(self) -> str: diff --git a/libs/langchain/langchain/agents/xml/base.py b/libs/langchain/langchain/agents/xml/base.py index 2fdf8fb7da222..4a82e77e87db3 100644 --- a/libs/langchain/langchain/agents/xml/base.py +++ b/libs/langchain/langchain/agents/xml/base.py @@ -14,6 +14,11 @@ class XMLAgent(BaseSingleActionAgent): """Agent that uses XML tags. + This agent only works with LLMs not chat models! + + Ability of agent to invoke tools varies a lot depending on how good the underlying + LLM is! + Args: tools: list of tools the agent can choose from llm_chain: The LLMChain to call to predict the next action @@ -22,13 +27,25 @@ class XMLAgent(BaseSingleActionAgent): .. code-block:: python - from langchain.agents import XMLAgent - from langchain + from langchain.agents import AgentExecutor, XMLAgent + from langchain.chains import LLMChain - tools = ... - model = + chain = LLMChain( + llm=model, + prompt=XMLAgent.get_default_prompt(), + output_parser=XMLAgent.get_default_output_parser(), + ) + agent = XMLAgent(tools=tools, llm_chain=chain) + agent_executor = AgentExecutor( + agent=agent, + tools=tools, + verbose=True, + handle_parsing_errors=True + ) + + agent_executor.invoke({"input": "what's the weather in New york?"}) """ tools: List[BaseTool] @@ -38,6 +55,7 @@ class XMLAgent(BaseSingleActionAgent): @property def input_keys(self) -> List[str]: + """Get the input keys.""" return ["input"] @staticmethod @@ -48,25 +66,38 @@ def get_default_prompt() -> ChatPromptTemplate: @staticmethod def get_default_output_parser() -> XMLAgentOutputParser: + """Get the default output parser.""" return XMLAgentOutputParser() + def _format_intermediate_steps( + self, intermediate_steps: List[Tuple[AgentAction, str]] + ) -> str: + """Format the steps.""" + log = "" + for action, observation in intermediate_steps: + if action.tool == "_Exception": + # This only works correctly when handle_parsing_errors=True + log += action.log # Will contain the llm output from the exception + log += "\n{observation}\n" + pass + else: + log += ( + f"{action.tool}{action.tool_input}" + f"\n{observation}\n" + ) + return log + def plan( self, intermediate_steps: List[Tuple[AgentAction, str]], callbacks: Callbacks = None, **kwargs: Any, ) -> Union[AgentAction, AgentFinish]: - log = "" - for action, observation in intermediate_steps: - log += ( - f"{action.tool}{action.tool_input}" - f"{observation}" - ) tools = "" for tool in self.tools: tools += f"{tool.name}: {tool.description}\n" inputs = { - "intermediate_steps": log, + "intermediate_steps": self._format_intermediate_steps(intermediate_steps), "tools": tools, "question": kwargs["input"], "stop": ["", ""], @@ -80,17 +111,11 @@ async def aplan( callbacks: Callbacks = None, **kwargs: Any, ) -> Union[AgentAction, AgentFinish]: - log = "" - for action, observation in intermediate_steps: - log += ( - f"{action.tool}{action.tool_input}" - f"{observation}" - ) tools = "" for tool in self.tools: tools += f"{tool.name}: {tool.description}\n" inputs = { - "intermediate_steps": log, + "intermediate_steps": self._format_intermediate_steps(intermediate_steps), "tools": tools, "question": kwargs["input"], "stop": ["", ""],