faiss_rag_enterprise/llama_index/agent/react/output_parser.py

114 lines
3.6 KiB
Python

"""ReAct output parser."""
import re
from typing import Tuple
from llama_index.agent.react.types import (
ActionReasoningStep,
BaseReasoningStep,
ResponseReasoningStep,
)
from llama_index.output_parsers.utils import extract_json_str
from llama_index.types import BaseOutputParser
def extract_tool_use(input_text: str) -> Tuple[str, str, str]:
pattern = (
r"\s*Thought: (.*?)\nAction: ([a-zA-Z0-9_]+).*?\nAction Input: .*?(\{.*\})"
)
match = re.search(pattern, input_text, re.DOTALL)
if not match:
raise ValueError(f"Could not extract tool use from input text: {input_text}")
thought = match.group(1).strip()
action = match.group(2).strip()
action_input = match.group(3).strip()
return thought, action, action_input
def action_input_parser(json_str: str) -> dict:
processed_string = re.sub(r"(?<!\w)\'|\'(?!\w)", '"', json_str)
pattern = r'"(\w+)":\s*"([^"]*)"'
matches = re.findall(pattern, processed_string)
return dict(matches)
def extract_final_response(input_text: str) -> Tuple[str, str]:
pattern = r"\s*Thought:(.*?)Answer:(.*?)(?:$)"
match = re.search(pattern, input_text, re.DOTALL)
if not match:
raise ValueError(
f"Could not extract final answer from input text: {input_text}"
)
thought = match.group(1).strip()
answer = match.group(2).strip()
return thought, answer
def parse_action_reasoning_step(output: str) -> ActionReasoningStep:
"""
Parse an action reasoning step from the LLM output.
"""
# Weaker LLMs may generate ReActAgent steps whose Action Input are horrible JSON strings.
# `dirtyjson` is more lenient than `json` in parsing JSON strings.
import dirtyjson as json
thought, action, action_input = extract_tool_use(output)
json_str = extract_json_str(action_input)
# First we try json, if this fails we use ast
try:
action_input_dict = json.loads(json_str)
except Exception:
action_input_dict = action_input_parser(json_str)
return ActionReasoningStep(
thought=thought, action=action, action_input=action_input_dict
)
class ReActOutputParser(BaseOutputParser):
"""ReAct Output parser."""
def parse(self, output: str, is_streaming: bool = False) -> BaseReasoningStep:
"""Parse output from ReAct agent.
We expect the output to be in one of the following formats:
1. If the agent need to use a tool to answer the question:
```
Thought: <thought>
Action: <action>
Action Input: <action_input>
```
2. If the agent can answer the question without any tools:
```
Thought: <thought>
Answer: <answer>
```
"""
if "Thought:" not in output:
# NOTE: handle the case where the agent directly outputs the answer
# instead of following the thought-answer format
return ResponseReasoningStep(
thought="(Implicit) I can answer without any more tools!",
response=output,
is_streaming=is_streaming,
)
if "Answer:" in output:
thought, answer = extract_final_response(output)
return ResponseReasoningStep(
thought=thought, response=answer, is_streaming=is_streaming
)
if "Action:" in output:
return parse_action_reasoning_step(output)
raise ValueError(f"Could not parse output: {output}")
def format(self, output: str) -> str:
"""Format a query with structured output formatting instructions."""
raise NotImplementedError