| | import json |
| | from json import JSONDecodeError |
| | from typing import List, Union |
| |
|
| | from langchain_core.agents import AgentAction, AgentActionMessageLog, AgentFinish |
| | from langchain_core.exceptions import OutputParserException |
| | from langchain_core.messages import ( |
| | AIMessage, |
| | BaseMessage, |
| | ToolCall, |
| | ) |
| | from langchain_core.outputs import ChatGeneration, Generation |
| |
|
| | from langchain.agents.agent import MultiActionAgentOutputParser |
| |
|
| |
|
| | class ToolAgentAction(AgentActionMessageLog): |
| | tool_call_id: str |
| | """Tool call that this message is responding to.""" |
| |
|
| |
|
| | def parse_ai_message_to_tool_action( |
| | message: BaseMessage, |
| | ) -> Union[List[AgentAction], AgentFinish]: |
| | """Parse an AI message potentially containing tool_calls.""" |
| | if not isinstance(message, AIMessage): |
| | raise TypeError(f"Expected an AI message got {type(message)}") |
| |
|
| | actions: List = [] |
| | if message.tool_calls: |
| | tool_calls = message.tool_calls |
| | else: |
| | if not message.additional_kwargs.get("tool_calls"): |
| | return AgentFinish( |
| | return_values={"output": message.content}, log=str(message.content) |
| | ) |
| | |
| | tool_calls = [] |
| | for tool_call in message.additional_kwargs["tool_calls"]: |
| | function = tool_call["function"] |
| | function_name = function["name"] |
| | try: |
| | args = json.loads(function["arguments"] or "{}") |
| | tool_calls.append( |
| | ToolCall(name=function_name, args=args, id=tool_call["id"]) |
| | ) |
| | except JSONDecodeError: |
| | raise OutputParserException( |
| | f"Could not parse tool input: {function} because " |
| | f"the `arguments` is not valid JSON." |
| | ) |
| | for tool_call in tool_calls: |
| | |
| | |
| | |
| | |
| | |
| | |
| | function_name = tool_call["name"] |
| | _tool_input = tool_call["args"] |
| | if "__arg1" in _tool_input: |
| | tool_input = _tool_input["__arg1"] |
| | else: |
| | tool_input = _tool_input |
| |
|
| | content_msg = f"responded: {message.content}\n" if message.content else "\n" |
| | log = f"\nInvoking: `{function_name}` with `{tool_input}`\n{content_msg}\n" |
| | actions.append( |
| | ToolAgentAction( |
| | tool=function_name, |
| | tool_input=tool_input, |
| | log=log, |
| | message_log=[message], |
| | tool_call_id=tool_call["id"], |
| | ) |
| | ) |
| | return actions |
| |
|
| |
|
| | class ToolsAgentOutputParser(MultiActionAgentOutputParser): |
| | """Parses a message into agent actions/finish. |
| | |
| | If a tool_calls parameter is passed, then that is used to get |
| | the tool names and tool inputs. |
| | |
| | If one is not passed, then the AIMessage is assumed to be the final output. |
| | """ |
| |
|
| | @property |
| | def _type(self) -> str: |
| | return "tools-agent-output-parser" |
| |
|
| | def parse_result( |
| | self, result: List[Generation], *, partial: bool = False |
| | ) -> Union[List[AgentAction], AgentFinish]: |
| | if not isinstance(result[0], ChatGeneration): |
| | raise ValueError("This output parser only works on ChatGeneration output") |
| | message = result[0].message |
| | return parse_ai_message_to_tool_action(message) |
| |
|
| | def parse(self, text: str) -> Union[List[AgentAction], AgentFinish]: |
| | raise ValueError("Can only parse messages") |
| |
|