| 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"<tool_call>(.*?)</tool_call>", 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") |