import re from typing import Union from langchain.agents.output_parsers import ReActSingleInputOutputParser from langchain_core.agents import AgentAction, AgentFinish from .cache import CacheHandler, CacheHit from .exceptions import TaskRepeatedUsageException from .tools_handler import ToolsHandler FINAL_ANSWER_ACTION = "Final Answer:" FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE = ( "Parsing LLM output produced both a final answer and a parse-able action:" ) class CrewAgentOutputParser(ReActSingleInputOutputParser): """Parses ReAct-style LLM calls that have a single tool input. Expects output to be in one of two formats. If the output signals that an action should be taken, should be in the below format. This will result in an AgentAction being returned. ``` Thought: agent thought here Action: search Action Input: what is the temperature in SF? ``` If the output signals that a final answer should be given, should be in the below format. This will result in an AgentFinish being returned. ``` Thought: agent thought here Final Answer: The temperature is 100 degrees ``` It also prevents tools from being reused in a roll. """ class Config: arbitrary_types_allowed = True tools_handler: ToolsHandler cache: CacheHandler def parse(self, text: str) -> Union[AgentAction, AgentFinish, CacheHit]: FINAL_ANSWER_ACTION in text regex = ( r"Action\s*\d*\s*:[\s]*(.*?)[\s]*Action\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" ) if action_match := re.search(regex, text, re.DOTALL): action = action_match.group(1).strip() action_input = action_match.group(2) tool_input = action_input.strip(" ") tool_input = tool_input.strip('"') if last_tool_usage := self.tools_handler.last_used_tool: usage = { "tool": action, "input": tool_input, } if usage == last_tool_usage: raise TaskRepeatedUsageException( tool=action, tool_input=tool_input, text=text ) if result := self.cache.read(action, tool_input): action = AgentAction(action, tool_input, text) return CacheHit(action=action, cache=self.cache) return super().parse(text)