|
|
import re |
|
|
from typing import Union |
|
|
|
|
|
from langchain_core.agents import AgentAction, AgentFinish |
|
|
from langchain_core.exceptions import OutputParserException |
|
|
|
|
|
from langchain.agents.agent import AgentOutputParser |
|
|
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS |
|
|
|
|
|
FINAL_ANSWER_ACTION = "Final Answer:" |
|
|
MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE = ( |
|
|
"Invalid Format: Missing 'Action:' after 'Thought:" |
|
|
) |
|
|
MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE = ( |
|
|
"Invalid Format: Missing 'Action Input:' after 'Action:'" |
|
|
) |
|
|
FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE = ( |
|
|
"Parsing LLM output produced both a final answer and a parse-able action:" |
|
|
) |
|
|
|
|
|
|
|
|
class MRKLOutputParser(AgentOutputParser): |
|
|
"""MRKL Output parser for the chat agent.""" |
|
|
|
|
|
format_instructions: str = FORMAT_INSTRUCTIONS |
|
|
"""Default formatting instructions""" |
|
|
|
|
|
def get_format_instructions(self) -> str: |
|
|
"""Returns formatting instructions for the given output parser.""" |
|
|
return self.format_instructions |
|
|
|
|
|
def parse(self, text: str) -> Union[AgentAction, AgentFinish]: |
|
|
"""Parse the output from the agent into |
|
|
an AgentAction or AgentFinish object. |
|
|
|
|
|
Args: |
|
|
text: The text to parse. |
|
|
|
|
|
Returns: |
|
|
An AgentAction or AgentFinish object. |
|
|
|
|
|
Raises: |
|
|
OutputParserException: If the output could not be parsed. |
|
|
""" |
|
|
includes_answer = FINAL_ANSWER_ACTION in text |
|
|
regex = ( |
|
|
r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" |
|
|
) |
|
|
action_match = re.search(regex, text, re.DOTALL) |
|
|
if action_match and includes_answer: |
|
|
if text.find(FINAL_ANSWER_ACTION) < text.find(action_match.group(0)): |
|
|
|
|
|
start_index = text.find(FINAL_ANSWER_ACTION) + len(FINAL_ANSWER_ACTION) |
|
|
end_index = text.find("\n\n", start_index) |
|
|
return AgentFinish( |
|
|
{"output": text[start_index:end_index].strip()}, text[:end_index] |
|
|
) |
|
|
else: |
|
|
raise OutputParserException( |
|
|
f"{FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE}: {text}" |
|
|
) |
|
|
|
|
|
if action_match: |
|
|
action = action_match.group(1).strip() |
|
|
action_input = action_match.group(2) |
|
|
tool_input = action_input.strip(" ") |
|
|
|
|
|
if tool_input.startswith("SELECT ") is False: |
|
|
tool_input = tool_input.strip('"') |
|
|
|
|
|
return AgentAction(action, tool_input, text) |
|
|
|
|
|
elif includes_answer: |
|
|
return AgentFinish( |
|
|
{"output": text.split(FINAL_ANSWER_ACTION)[-1].strip()}, text |
|
|
) |
|
|
|
|
|
if not re.search(r"Action\s*\d*\s*:[\s]*(.*?)", text, re.DOTALL): |
|
|
raise OutputParserException( |
|
|
f"Could not parse LLM output: `{text}`", |
|
|
observation=MISSING_ACTION_AFTER_THOUGHT_ERROR_MESSAGE, |
|
|
llm_output=text, |
|
|
send_to_llm=True, |
|
|
) |
|
|
elif not re.search( |
|
|
r"[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)", text, re.DOTALL |
|
|
): |
|
|
raise OutputParserException( |
|
|
f"Could not parse LLM output: `{text}`", |
|
|
observation=MISSING_ACTION_INPUT_AFTER_ACTION_ERROR_MESSAGE, |
|
|
llm_output=text, |
|
|
send_to_llm=True, |
|
|
) |
|
|
else: |
|
|
raise OutputParserException(f"Could not parse LLM output: `{text}`") |
|
|
|
|
|
@property |
|
|
def _type(self) -> str: |
|
|
return "mrkl" |
|
|
|