当前位置: 首页 > news >正文

原生 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_key=os.getenv("DOUBAO_API_KEY"), base_url="https://ark.cn-beijing.volces.com/api/v3" ) # ====================== 2. 定义工具:安全计算器 ====================== def safe_calculator(expression: str) -> str: """ 安全的计算器工具,只允许数学运算,防止代码注入 :param expression: 数学表达式,比如 "35*24+18" :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*24+18,我需要调用计算器工具。 Action: calculator("35*24+18") Observation:计算结果:858 Thought:我已经得到了计算结果,现在可以给出最终答案了。 Final Answer: 35乘以24加上18的计算过程是:35×24=840,840+18=858,最终结果是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( model="doubao-lite-32k", # 推荐用 lite 模型,速度快、效果好 messages=messages, temperature=0.3, # 数学题用低温度,保证严谨 max_tokens=500 ) 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 = f"Observation: {observation}" print(f"📊 工具返回:{observation_str}\n") # 把 Observation 加入上下文记忆 messages.append({"role": "user", "content": observation_str}) except Exception as e: error_msg = f"Observation: 执行错误:{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.8+2*8.5") 🔧调用工具:calculator, 参数: 3*12.8+2*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

你可以基于这个代码,轻松添加更多功能:

  1. 添加更多工具:比如搜索工具、文件读写工具、数据库工具,只需要在AVAILABLE_TOOLS字典里添加新函数
  2. 优化 Prompt:根据你的需求,调整REACT_SYSTEM_PROMPT,比如添加更多示例、更严格的规则
  3. 增加记忆功能:用向量数据库存储历史任务经验,实现跨会话的记忆复用
  4. 增加反思机制:执行失败后,让 LLM 自动反思错误原因,修正计划后重新执行
http://www.jsqmd.com/news/701722/

相关文章:

  • 煌上煌2025年净利润大增102.32% 2026年一季度开局稳健
  • Graphormer模型服务网络优化:降低后端服务间通信延迟
  • Markdown 完全指南:从入门到精通,程序员必会的轻量标记语言
  • Fish Speech-1.5镜像部署标准化:Docker Compose一键启停最佳实践
  • Qwen3-4B-Instruct部署教程:GPU内存不足时的kill进程优先级策略
  • 新手友好!Qwen3-ForcedAligner部署教程:本地运行无网络依赖
  • 3分钟掌握Illustrator智能填充:告别手动排列,拥抱自动化设计
  • Wan2.2-I2V-A14B镜像优化特性:GPU算力专属调度策略技术白皮书
  • 创业,兼职,副业,别总盯着那些大生意,你身边就有很多小麻烦等着你去解决,找到一个做透它,你就能开始赚钱。
  • 如何用罗技鼠标宏实现PUBG零后坐力射击?终极配置指南
  • 为什么你的C++ MCP网关在32核服务器上CPU利用率始终卡在65%?:揭秘NUMA绑定+SO_REUSEPORT+无锁RingBuffer协同失效真相
  • 网络安全SRC漏洞挖掘学习路线 (四):常见漏洞挖掘实操,实现首次挖洞突破
  • PyCharm 大模型开发环境配置:从零到跑通 GPT,这篇就够了
  • Qwen3.5-9B-GGUF效果实测:混合注意力架构下代码生成准确率提升案例
  • FLUX.1-Krea-Extracted-LoRA惊艳效果展示:真实感商业摄影作品集
  • 志特新材2025年归母净利润同比增长122%,2026年首季再迎“开门红”
  • nli-MiniLM2-L6-H768代码实例:调用API实现自动化批量分类任务
  • Java Stream API 在大数据项目中的应用
  • 大模型为什么会“幻觉“?从训练原理到根治方案,一篇彻底讲清楚
  • 别再重装Remote-Containers插件!VSCode 2026内核级连接池重构详解(仅限Early Adopter的5个关键环境变量)
  • AI Agent工具目录:开发者高效选型与集成实践指南
  • Obsidian AI智能体插件:在笔记中构建可编程AI工作流
  • YOLO11涨点优化:卷积优化 | 引入AKConv (Alternating Kernel Convolution),针对不规则形状目标实现降维打击
  • 如何永久保存微信聊天记录:开源工具WeChatMsg完整指南
  • DDrawCompat终极指南:让Windows 11上的经典老游戏重获新生
  • 【C++26合约编程权威指南】:从ISO草案到生产级落地的5大核心陷阱与避坑清单
  • AI网关架构设计:统一管理多LLM提供商的工程实践
  • AI对话应用框架deepchat:模块化设计、工具调用与生产部署指南
  • 如何快速掌握图表数据提取:科研工作者的完整指南
  • Qianfan-OCR效果展示:手写体+印刷体混合文档的端到端结构化输出