原生 Python 实现 ReAct Agent(计算器版)
完全不用 LangChain 等第三方框架只用原生 Python 豆包 OpenAI 兼容接口实现一个能自主调用计算器、解决数学题的 ReAct Agent代码结构清晰、注释详细你可以直接运行、修改、扩展。一、核心设计思路1. ReAct 三大核心要素严格遵循论文定义表格要素实现方式Thought思考让 LLM 分析当前状态规划下一步是否需要计算Action行动调用calculator工具传入数学表达式Observation观察计算器返回的计算结果注入下一轮推理2. 计算器工具安全设计用 Python 内置的eval函数但严格限制输入只允许数字、运算符、括号防止代码注入保证安全。二、完整可运行代码import os import re from dotenv import load_dotenv from openai import OpenAI # 1. 初始化配置 # 加载环境变量.env文件里放你的豆包API Key load_dotenv() # 初始化豆包客户端完全兼容OpenAI接口 client OpenAI( api_keyos.getenv(DOUBAO_API_KEY), base_urlhttps://ark.cn-beijing.volces.com/api/v3 ) # 2. 定义工具安全计算器 def safe_calculator(expression: str) - str: 安全的计算器工具只允许数学运算防止代码注入 :param expression: 数学表达式比如 35*2418 :return: 计算结果 # 安全检查只允许数字、运算符、括号、空格 if not re.fullmatch(r^[\d\-*/().\s]$, expression): return 错误表达式包含非法字符只允许数字、运算符、括号 try: # 计算表达式限制全局命名空间防止代码注入 result eval(expression, {__builtins__: None}, {}) return f计算结果{result} except ZeroDivisionError: return 错误除数不能为0 except SyntaxError: return 错误表达式语法错误 except Exception as e: return f错误{str(e)} # 工具字典LLM 可以调用的所有工具 AVAILABLE_TOOLS { calculator: safe_calculator } # 3. ReAct 核心 Prompt 模板 REACT_SYSTEM_PROMPT 你是一个严格遵循 ReAct 框架的智能数学助手你的任务是解决用户的数学问题。 ## 核心规则必须严格遵守不能改变格式 1. 每一轮你只能输出 **Thought** 或 **Action** 中的一种不能同时输出 2. **Thought思考** - 分析当前问题判断是否需要调用计算器 - 如果需要计算明确说明要计算什么表达式 - 如果不需要计算直接给出答案 3. **Action行动** - 只有当你需要计算时才输出 Action - 格式必须严格为Action: calculator(数学表达式) - 数学表达式里不要有中文只保留数字、运算符、括号 4. **Observation观察** - 这是计算器返回的结果你不需要生成 - 你需要根据 Observation 的结果继续思考 5. **Final Answer最终答案** - 当你获取了足够的信息能完整回答用户的问题时输出Final Answer: 你的完整答案 - 最终答案要清晰、完整包含计算过程和结果 ## 可用工具 - calculator(expression)安全计算器输入数学表达式返回计算结果 ## 示例 用户问题35乘以24加上18等于多少 你的执行过程 Thought用户的问题需要计算 35*2418我需要调用计算器工具。 Action: calculator(35*2418) Observation计算结果858 Thought我已经得到了计算结果现在可以给出最终答案了。 Final Answer: 35乘以24加上18的计算过程是35×2484084018858最终结果是858。 现在开始解决用户的问题 # 4. ReAct 核心执行循环 def react_math_agent(question: str, max_rounds: int 5) - str: ReAct 数学 Agent 主函数 :param question: 用户的数学问题 :param max_rounds: 最大执行轮数防止无限循环 :return: 最终答案 # 初始化上下文记忆 messages [ {role: system, content: REACT_SYSTEM_PROMPT}, {role: user, content: question} ] print(f 用户问题{question}\n) print( ReAct Agent 开始执行...\n) # 循环执行 ReAct 流程 for round_num in range(1, max_rounds 1): print(f 第 {round_num} 轮执行 ) # 1. 调用 LLM 生成 Thought/Action response client.chat.completions.create( modeldoubao-lite-32k, # 推荐用 lite 模型速度快、效果好 messagesmessages, temperature0.3, # 数学题用低温度保证严谨 max_tokens500 ) llm_output response.choices[0].message.content.strip() print(f LLM 输出\n{llm_output}\n) # 2. 把 LLM 输出加入上下文记忆 messages.append({role: assistant, content: llm_output}) # 3. 判断是否已经输出最终答案 if Final Answer: in llm_output: final_answer llm_output.split(Final Answer:)[-1].strip() print( 执行完成最终答案 ) return final_answer # 4. 解析 Action调用工具 if Action: in llm_output: # 提取工具名和参数 try: action_part llm_output.split(Action:)[-1].strip() # 匹配 calculator(表达式) 格式 tool_match re.match(r(\w)\(([^])\), action_part) if not tool_match: raise ValueError(Action 格式错误正确格式是Action: calculator(\表达式\)) tool_name tool_match.group(1) tool_param tool_match.group(2) # 检查工具是否可用 if tool_name not in AVAILABLE_TOOLS: raise ValueError(f没有找到工具{tool_name}可用工具{list(AVAILABLE_TOOLS.keys())}) # 调用工具获取 Observation print(f 调用工具{tool_name}参数{tool_param}) observation AVAILABLE_TOOLS[tool_name](tool_param) observation_str fObservation: {observation} print(f 工具返回{observation_str}\n) # 把 Observation 加入上下文记忆 messages.append({role: user, content: observation_str}) except Exception as e: error_msg fObservation: 执行错误{str(e)} print(f❌ {error_msg}\n) messages.append({role: user, content: error_msg}) # 超过最大轮数结束执行 return ⚠️ 执行轮数已达上限未能完成任务请尝试简化问题。 # 5. 测试运行 if __name__ __main__: # 测试问题1简单混合运算 test_question1 35乘以24加上18等于多少 # 测试问题2简单应用题 test_question2 一个长方形的长是12.5厘米宽是8.4厘米它的面积是多少平方厘米 # 测试问题3多步计算 test_question3 小明有100元钱买了3本笔记本每本12.8元又买了2支笔每支8.5元他还剩多少钱 # 运行测试 result react_math_agent(test_question3) print(result)执行结果示例 用户问题小明有100元钱买了3本笔记本每本12.8元又买了2支笔每支8.5元他还剩多少钱 ReAct Agent 开始执行... 第1轮执行 LLM输出 Thought首先需要计算小明买笔记本和笔总共花费了多少钱。笔记本的总费用是3*12.8元笔的总费用是2*8.5元。然后从100元中减去这个总数得到剩余的钱数。我将先计算总支出。 Action: calculator(3*12.82*8.5) 调用工具calculator, 参数: 3*12.82*8.5 工具返回observation计算结果55.400000000000006 第2轮执行 LLM输出 Thought根据计算器的结果小明购买笔记本和笔总共花费了55.4元。接下来我将从他原有的100元中减去这个数以确定他还剩下多少钱。 Action: calculator(100-55.4) 调用工具calculator, 参数: 100-55.4 工具返回observation计算结果44.6 第3轮执行 LLM输出 Thought根据计算器的结果小明在购买了笔记本和笔之后还剩下44.6元。现在我可以给出最终答案了。 Final Answer: 小明有100元钱买了3本笔记本每本12.8元和2支笔每支8.5元。笔记本的总费用是\(3 \times 12.8 38.4\)元笔的总费用是\(2 \times 8.5 17\)元总共花费\(38.4 17 55.4\)元。因此他还剩下\(100 - 55.4 44.6\)元。所以小明最后还剩44.6元。 执行完成最终答案 小明有100元钱买了3本笔记本每本12.8元和2支笔每支8.5元。笔记本的总费用是\(3 \times 12.8 38.4\)元笔的总费用是\(2 \times 8.5 17\)元总共花费\(38.4 17 55.4\)元。因此他还剩下\(100 - 55.4 44.6\)元。所以小明最后还剩44.6元。三、代码结构详解1. 核心模块说明表格模块作用safe_calculator安全的计算器工具用正则限制输入防止代码注入REACT_SYSTEM_PROMPTReAct 核心 Prompt严格定义了执行格式、规则、示例react_math_agentReAct 主循环负责调用 LLM、解析输出、调用工具、管理上下文2. 关键安全设计正则限制输入只允许[\d\-*/().\s]禁止其他字符限制eval命名空间eval(expression, {__builtins__: None}, {})防止访问内置函数异常处理捕获除零、语法错误等常见问题返回友好提示四、运行效果演示以测试问题 3 为例小明有 100 元钱买了 3 本笔记本每本 12.8 元又买了 2 支笔每支 8.5 元他还剩多少钱执行过程五、如何扩展这个 Agent你可以基于这个代码轻松添加更多功能添加更多工具比如搜索工具、文件读写工具、数据库工具只需要在AVAILABLE_TOOLS字典里添加新函数优化 Prompt根据你的需求调整REACT_SYSTEM_PROMPT比如添加更多示例、更严格的规则增加记忆功能用向量数据库存储历史任务经验实现跨会话的记忆复用增加反思机制执行失败后让 LLM 自动反思错误原因修正计划后重新执行