Skip to main content

lc_agents/react/
parser.rs

1// src/agents/react/parser.rs
2//! ReAct 输出解析器
3//!
4//! 解析 LLM 的 ReAct 格式输出。
5
6use crate::{AgentAction, AgentError, AgentFinish, AgentOutput, ToolInput};
7use regex::Regex;
8
9/// ReAct 输出解析器
10///
11/// 解析格式:
12/// ```text
13/// Thought: 思考内容
14/// Action: 工具名称
15/// Action Input: 工具输入
16/// ```
17/// 或
18/// ```text
19/// Thought: 思考内容
20/// Final Answer: 最终答案
21/// ```
22pub struct ReActOutputParser {
23    /// Action 正则表达式
24    action_regex: Regex,
25    /// Final Answer 标记
26    final_answer_marker: &'static str,
27}
28
29impl ReActOutputParser {
30    /// 创建新的解析器
31    pub fn new() -> Self {
32        Self {
33            // 匹配: Action: xxx\nAction Input: yyy
34            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    /// 解析 LLM 输出
41    ///
42    /// # 参数
43    /// * `text` - LLM 的输出文本
44    ///
45    /// # 返回
46    /// * `AgentOutput::Action` - 需要执行动作
47    /// * `AgentOutput::Finish` - 最终答案
48    pub fn parse(&self, text: &str) -> Result<AgentOutput, AgentError> {
49        let text = text.trim();
50
51        // 检查是否包含 Final Answer
52        if text.contains(self.final_answer_marker) {
53            return self.parse_final_answer(text);
54        }
55
56        // 尝试解析 Action
57        if let Some(action) = self.parse_action(text)? {
58            return Ok(AgentOutput::Action(action));
59        }
60
61        // 无法解析
62        Err(AgentError::OutputParsingError(format!(
63            "无法解析输出。请使用以下格式:\n\
64             Thought: 你的思考\n\
65             Action: 工具名称\n\
66             Action Input: 工具输入\n\n\
67             或\n\n\
68             Thought: 你的思考\n\
69             Final Answer: 最终答案\n\n\
70             实际输出: {}",
71            text
72        )))
73    }
74
75    /// 解析 Final Answer
76    fn parse_final_answer(&self, text: &str) -> Result<AgentOutput, AgentError> {
77        let parts: Vec<&str> = text.split(self.final_answer_marker).collect();
78
79        if parts.len() < 2 {
80            return Err(AgentError::OutputParsingError(
81                "Final Answer 后缺少内容".to_string(),
82            ));
83        }
84
85        let answer = parts[1].trim().to_string();
86
87        Ok(AgentOutput::Finish(AgentFinish::new(
88            answer,
89            text.to_string(),
90        )))
91    }
92
93    /// 解析 Action
94    fn parse_action(&self, text: &str) -> Result<Option<AgentAction>, AgentError> {
95        if let Some(caps) = self.action_regex.captures(text) {
96            let tool = caps
97                .get(1)
98                .map(|m| m.as_str().trim().to_string())
99                .ok_or_else(|| AgentError::OutputParsingError("缺少 Action".to_string()))?;
100
101            let tool_input_str = caps
102                .get(2)
103                .map(|m| m.as_str().trim().to_string())
104                .ok_or_else(|| AgentError::OutputParsingError("缺少 Action Input".to_string()))?;
105
106            // 解析工具输入
107            let tool_input = self.parse_tool_input(&tool_input_str);
108
109            return Ok(Some(AgentAction {
110                tool,
111                tool_input,
112                log: text.to_string(),
113            }));
114        }
115
116        Ok(None)
117    }
118
119    /// 解析工具输入
120    fn parse_tool_input(&self, input: &str) -> ToolInput {
121        let input = input.trim();
122
123        // 尝试解析为 JSON
124        if input.starts_with('{') || input.starts_with('[') {
125            if let Ok(value) = serde_json::from_str(input) {
126                return ToolInput::Object { value };
127            }
128        }
129
130        // 移除引号
131        let cleaned = input.trim_matches('"').trim_matches('\'');
132
133        ToolInput::String {
134            value: cleaned.to_string(),
135        }
136    }
137}
138
139impl Default for ReActOutputParser {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_parse_action() {
151        let parser = ReActOutputParser::new();
152
153        let text = r#"Thought: 我需要计算这个表达式
154Action: calculator
155Action Input: {"expression": "2 + 3"}"#;
156
157        let result = parser.parse(text).unwrap();
158
159        match result {
160            AgentOutput::Action(action) => {
161                assert_eq!(action.tool, "calculator");
162            }
163            _ => panic!("期望 Action"),
164        }
165    }
166
167    #[test]
168    fn test_parse_final_answer() {
169        let parser = ReActOutputParser::new();
170
171        let text = r#"Thought: 我已经知道答案了
172Final Answer: 答案是 42"#;
173
174        let result = parser.parse(text).unwrap();
175
176        match result {
177            AgentOutput::Finish(finish) => {
178                assert_eq!(finish.output(), Some("答案是 42"));
179            }
180            _ => panic!("期望 Finish"),
181        }
182    }
183
184    #[test]
185    fn test_parse_string_input() {
186        let parser = ReActOutputParser::new();
187
188        let text = r#"Thought: 需要查询天气
189Action: weather
190Action Input: 北京"#;
191
192        let result = parser.parse(text).unwrap();
193
194        match result {
195            AgentOutput::Action(action) => {
196                assert_eq!(action.tool, "weather");
197                match action.tool_input {
198                    ToolInput::String { value: s } => assert_eq!(s, "北京"),
199                    _ => panic!("期望 String 输入"),
200                }
201            }
202            _ => panic!("期望 Action"),
203        }
204    }
205
206    #[test]
207    fn test_parse_error() {
208        let parser = ReActOutputParser::new();
209
210        let text = "这是无效的输出";
211
212        let result = parser.parse(text);
213        assert!(result.is_err());
214    }
215}