import re 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, ) from langchain_core.outputs import ChatGeneration, Generation from langchain.agents.agent import AgentOutputParser from agents.output_parsers.utils import parse_tool_call, check_tool_call import ast class FunctionsAgentOutputParser(AgentOutputParser): """Parses a message into agent action/finish. Is meant to be used with a model with Nous Hermes 2 Pro as the base, as it relies on the specific function_call parameter from Nous Research to convey what tools to use. If a function_call parameter is passed, then that is used to get the tool and tool input. If one is not passed, then the AIMessage is assumed to be the final output. It was add a """ @property def _type(self) -> str: return "functions-agent" @staticmethod def _parse_ai_message(message: BaseMessage): """Parse an AI message.""" if not isinstance(message, AIMessage): raise TypeError(f"Expected an AI message got {type(message)}") actions = [] pattern = re.compile(r"(.*?)", re.DOTALL) try: tool_calls = [parse_tool_call(t.strip()) for t in pattern.findall(message.content)] except: raise OutputParserException( f"Could not parse tool calls from message content: {message.content}. Please ensure that the tool calls are valid JSON." ) if not tool_calls: return AgentFinish( return_values={"output": message.content}, log=str(message.content) ) for tool_call in tool_calls: tool_name, tool_input = check_tool_call(tool_call) content_msg = f"\n{message.content}\n" if message.content else "\n" log = f"\nInvoking: `{tool_name}` with `{tool_input}`\n{content_msg}\n" actions.append(AgentActionMessageLog( tool=tool_name, tool_input=tool_input, log=log, message_log=[message], )) return actions def parse_result( self, result: List[Generation], *, partial: bool = False ) -> Union[AgentAction, AgentFinish]: if not isinstance(result[0], ChatGeneration): raise ValueError("This output parser only works on ChatGeneration output") message = result[0].message return self._parse_ai_message(message) def parse(self, text: str) -> Union[AgentAction, AgentFinish]: raise ValueError("Can only parse messages")