lc_agents/react/
parser.rs1use crate::{AgentAction, AgentError, AgentFinish, AgentOutput, ToolInput};
7use regex::Regex;
8
9pub struct ReActOutputParser {
23 action_regex: Regex,
25 final_answer_marker: &'static str,
27}
28
29impl ReActOutputParser {
30 pub fn new() -> Self {
32 Self {
33 action_regex: Regex::new(r"Action\s*:\s*(.*?)\s*\nAction\s*Input\s*:\s*(.*?)(?:\n|$)")
35 .expect("Invalid regex"),
36 final_answer_marker: "Final Answer:",
37 }
38 }
39
40 pub fn parse(&self, text: &str) -> Result<AgentOutput, AgentError> {
49 let text = text.trim();
50
51 if let Some(action) = self.parse_action(text)? {
56 return Ok(AgentOutput::Action(action));
57 }
58
59 if text.contains(self.final_answer_marker) {
61 return self.parse_final_answer(text);
62 }
63
64 Err(AgentError::OutputParsingError(format!(
66 "failed to parse output. Use one of the following formats:\n\
67 Thought: <your reasoning>\n\
68 Action: <tool name>\n\
69 Action Input: <tool input>\n\n\
70 or\n\n\
71 Thought: <your reasoning>\n\
72 Final Answer: <final answer>\n\n\
73 Actual output: {}",
74 text
75 )))
76 }
77
78 fn parse_final_answer(&self, text: &str) -> Result<AgentOutput, AgentError> {
80 let parts: Vec<&str> = text.split(self.final_answer_marker).collect();
81
82 if parts.len() < 2 {
83 return Err(AgentError::OutputParsingError(
84 "missing content after Final Answer".to_string(),
85 ));
86 }
87
88 let answer = parts.last().unwrap_or(&"").trim().to_string();
92
93 Ok(AgentOutput::Finish(AgentFinish::new(
94 answer,
95 text.to_string(),
96 )))
97 }
98
99 fn parse_action(&self, text: &str) -> Result<Option<AgentAction>, AgentError> {
101 if let Some(caps) = self.action_regex.captures(text) {
102 let tool = caps
103 .get(1)
104 .map(|m| m.as_str().trim().to_string())
105 .ok_or_else(|| AgentError::OutputParsingError("missing Action".to_string()))?;
106
107 let tool_input_str = caps
108 .get(2)
109 .map(|m| m.as_str().trim().to_string())
110 .ok_or_else(|| {
111 AgentError::OutputParsingError("missing Action Input".to_string())
112 })?;
113
114 let tool_input = self.parse_tool_input(&tool_input_str);
116
117 return Ok(Some(AgentAction {
118 tool,
119 tool_input,
120 log: text.to_string(),
121 }));
122 }
123
124 Ok(None)
125 }
126
127 fn parse_tool_input(&self, input: &str) -> ToolInput {
129 let input = input.trim();
130
131 if input.starts_with('{') || input.starts_with('[') {
133 if let Ok(value) = serde_json::from_str(input) {
134 return ToolInput::Object { value };
135 }
136 }
137
138 let cleaned = input.trim_matches('"').trim_matches('\'');
140
141 ToolInput::String {
142 value: cleaned.to_string(),
143 }
144 }
145}
146
147impl Default for ReActOutputParser {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_parse_action() {
159 let parser = ReActOutputParser::new();
160
161 let text = r#"Thought: 我需要计算这个表达式
162Action: calculator
163Action Input: {"expression": "2 + 3"}"#;
164
165 let result = parser.parse(text).unwrap();
166
167 match result {
168 AgentOutput::Action(action) => {
169 assert_eq!(action.tool, "calculator");
170 }
171 _ => panic!("期望 Action"),
172 }
173 }
174
175 #[test]
176 fn test_parse_final_answer() {
177 let parser = ReActOutputParser::new();
178
179 let text = r#"Thought: 我已经知道答案了
180Final Answer: 答案是 42"#;
181
182 let result = parser.parse(text).unwrap();
183
184 match result {
185 AgentOutput::Finish(finish) => {
186 assert_eq!(finish.output(), Some("答案是 42"));
187 }
188 _ => panic!("期望 Finish"),
189 }
190 }
191
192 #[test]
193 fn test_parse_string_input() {
194 let parser = ReActOutputParser::new();
195
196 let text = r#"Thought: 需要查询天气
197Action: weather
198Action Input: 北京"#;
199
200 let result = parser.parse(text).unwrap();
201
202 match result {
203 AgentOutput::Action(action) => {
204 assert_eq!(action.tool, "weather");
205 match action.tool_input {
206 ToolInput::String { value: s } => assert_eq!(s, "北京"),
207 _ => panic!("期望 String 输入"),
208 }
209 }
210 _ => panic!("期望 Action"),
211 }
212 }
213
214 #[test]
215 fn test_parse_error() {
216 let parser = ReActOutputParser::new();
217
218 let text = "这是无效的输出";
219
220 let result = parser.parse(text);
221 assert!(result.is_err());
222 }
223
224 #[test]
225 fn test_action_preferred_when_final_answer_mentioned_in_thought() {
226 let parser = ReActOutputParser::new();
229
230 let text = r#"Thought: 用户要算数,不能用 Final Answer: 直接回答,需要调工具
231Action: calculator
232Action Input: {"expression": "2 + 3"}"#;
233
234 let result = parser.parse(text).unwrap();
235
236 match result {
237 AgentOutput::Action(action) => assert_eq!(action.tool, "calculator"),
238 _ => panic!("期望 Action,而不是被 'Final Answer:' 字样误判收尾"),
239 }
240 }
241
242 #[test]
243 fn test_final_answer_takes_last_occurrence() {
244 let parser = ReActOutputParser::new();
246
247 let text = r#"Thought: 先给个草稿
248Final Answer: 草稿答案
249Final Answer: 正式答案是 42"#;
250
251 let result = parser.parse(text).unwrap();
252
253 match result {
254 AgentOutput::Finish(finish) => {
255 assert_eq!(finish.output(), Some("正式答案是 42"));
256 }
257 _ => panic!("期望 Finish"),
258 }
259 }
260}