llama-index
112 строк · 3.7 Кб
1"""ReAct output parser."""
2
3import re
4from typing import Tuple
5
6from llama_index.legacy.agent.react.types import (
7ActionReasoningStep,
8BaseReasoningStep,
9ResponseReasoningStep,
10)
11from llama_index.legacy.output_parsers.utils import extract_json_str
12from llama_index.legacy.types import BaseOutputParser
13
14
15def extract_tool_use(input_text: str) -> Tuple[str, str, str]:
16pattern = (
17r"\s*Thought: (.*?)\nAction: ([a-zA-Z0-9_]+).*?\nAction Input: .*?(\{.*\})"
18)
19
20match = re.search(pattern, input_text, re.DOTALL)
21if not match:
22raise ValueError(f"Could not extract tool use from input text: {input_text}")
23
24thought = match.group(1).strip()
25action = match.group(2).strip()
26action_input = match.group(3).strip()
27return thought, action, action_input
28
29
30def action_input_parser(json_str: str) -> dict:
31processed_string = re.sub(r"(?<!\w)\'|\'(?!\w)", '"', json_str)
32pattern = r'"(\w+)":\s*"([^"]*)"'
33matches = re.findall(pattern, processed_string)
34return dict(matches)
35
36
37def extract_final_response(input_text: str) -> Tuple[str, str]:
38pattern = r"\s*Thought:(.*?)Answer:(.*?)(?:$)"
39
40match = re.search(pattern, input_text, re.DOTALL)
41if not match:
42raise ValueError(
43f"Could not extract final answer from input text: {input_text}"
44)
45
46thought = match.group(1).strip()
47answer = match.group(2).strip()
48return thought, answer
49
50
51def parse_action_reasoning_step(output: str) -> ActionReasoningStep:
52"""
53Parse an action reasoning step from the LLM output.
54"""
55# Weaker LLMs may generate ReActAgent steps whose Action Input are horrible JSON strings.
56# `dirtyjson` is more lenient than `json` in parsing JSON strings.
57import dirtyjson as json
58
59thought, action, action_input = extract_tool_use(output)
60json_str = extract_json_str(action_input)
61# First we try json, if this fails we use ast
62try:
63action_input_dict = json.loads(json_str)
64except Exception:
65action_input_dict = action_input_parser(json_str)
66return ActionReasoningStep(
67thought=thought, action=action, action_input=action_input_dict
68)
69
70
71class ReActOutputParser(BaseOutputParser):
72"""ReAct Output parser."""
73
74def parse(self, output: str, is_streaming: bool = False) -> BaseReasoningStep:
75"""Parse output from ReAct agent.
76
77We expect the output to be in one of the following formats:
781. If the agent need to use a tool to answer the question:
79```
80Thought: <thought>
81Action: <action>
82Action Input: <action_input>
83```
842. If the agent can answer the question without any tools:
85```
86Thought: <thought>
87Answer: <answer>
88```
89"""
90if "Thought:" not in output:
91# NOTE: handle the case where the agent directly outputs the answer
92# instead of following the thought-answer format
93return ResponseReasoningStep(
94thought="(Implicit) I can answer without any more tools!",
95response=output,
96is_streaming=is_streaming,
97)
98
99if "Answer:" in output:
100thought, answer = extract_final_response(output)
101return ResponseReasoningStep(
102thought=thought, response=answer, is_streaming=is_streaming
103)
104
105if "Action:" in output:
106return parse_action_reasoning_step(output)
107
108raise ValueError(f"Could not parse output: {output}")
109
110def format(self, output: str) -> str:
111"""Format a query with structured output formatting instructions."""
112raise NotImplementedError
113