
用Python的ReAct机制让LLM主动调用本地API关灯、发邮件想象这样一个场景你对电脑说“我准备睡了关灯并把明天上午的会议提醒发到我邮箱”然后灯真的灭了邮件真的发出去了。这不是科幻电影而是ReAct机制赋予大语言模型LLM的“动手能力”。传统LLM只能被动生成文本像一位只动口不动手的顾问。而ReActReasoning Acting让LLM能主动推理并调用外部工具真正改变物理世界或数字世界的状态。本文将带你用Python从零搭建一个ReAct智能体让它能调用本地API关灯模拟硬件和发邮件SMTP全程代码可运行、可扩展。一、ReAct是什么为什么它能让LLM“动手”ReAct是2022年提出的一种LLM推理与行动协同范式。核心思想是让模型在生成最终答案前先输出“思考Thought”和“行动Action”的中间步骤并根据行动结果Observation调整后续推理。传统Chain-of-ThoughtCoT只有推理链没有外部交互传统工具调用Toolformer只有函数调用缺乏动态推理。ReAct把两者结合形成“思考→行动→观察→再思考”的闭环。例如用户说“关灯并发邮件”ReAct智能体可能这样思考Thought 1: 用户要求两个动作先关灯再发邮件。Action 1:turn_off_lamp()Observation 1:Lamp is offThought 2: 关灯成功现在发邮件。Action 2:send_email(recipientmeexample.com, subject明天会议, body上午10点)Observation 2:Email sentThought 3: 所有任务完成回复用户。这种显式的推理轨迹不仅可解释还能在行动失败时自我修正如重试或换方案。二、整体架构设计我们的系统由四部分组成LLM引擎选用OpenAI的GPT-3.5/4 API也可替换为本地开源模型如Qwen。工具函数Python函数执行具体操作关灯、发邮件模拟或真实调用本地API。ReAct控制器核心循环维护对话历史解析LLM输出的Thought/Action/Observation并执行工具。用户接口命令行输入问题输出最终结果。为了安全所有本地API调用都限制在用户明确授权的动作范围内且邮件发送使用测试环境配置。三、准备工作安装依赖与配置pipinstallopenai python-dotenv创建.env文件存放OpenAI密钥OPENAI_API_KEYsk-xxxxxxxxxxxxxxxxxxxxxxxx新建config.pyimportosfromdotenvimportload_dotenv load_dotenv()OPENAI_API_KEYos.getenv(OPENAI_API_KEY)四、定义工具函数关灯与发邮件为了演示我们模拟关灯操作打印状态并用SMTP发送真实邮件建议使用QQ/163邮箱的授权码。tools.pyimportsmtplibfromemail.mime.textimportMIMETextfromemail.headerimportHeader# 模拟灯的状态lamp_status{on:True}defturn_off_lamp()-str:关闭本地智能灯模拟globallamp_statusiflamp_status[on]:lamp_status[on]FalsereturnLamp is now OFFreturnLamp is already OFFdefturn_on_lamp()-str:开启本地智能灯模拟globallamp_statusifnotlamp_status[on]:lamp_status[on]TruereturnLamp is now ONreturnLamp is already ONdefsend_email(recipient:str,subject:str,body:str)-str: 通过SMTP发送邮件请替换为你的邮箱配置 # 配置你的邮箱信息建议用环境变量smtp_serversmtp.qq.com# 以QQ邮箱为例smtp_port465# SSL端口sender_emailyour_emailqq.compasswordyour_authorization_code# 授权码不是QQ密码try:msgMIMEText(body,plain,utf-8)msg[Subject]Header(subject,utf-8)msg[From]sender_email msg[To]recipientwithsmtplib.SMTP_SSL(smtp_server,smtp_port)asserver:server.login(sender_email,password)server.sendmail(sender_email,[recipient],msg.as_string())returnfEmail sent to{recipient}successfullyexceptExceptionase:returnfFailed to send email:{str(e)}为了安全实际生产环境应限制recipient为白名单且不要在代码中硬编码密码改用环境变量。五、工具描述与注册LLM需要知道有哪些工具可用、每个工具的参数和用途。我们构建一个工具注册表供ReAct控制器查询。tool_registry.pyfromtoolsimportturn_off_lamp,turn_on_lamp,send_email TOOLS[{name:turn_off_lamp,func:turn_off_lamp,description:关闭智能灯。无参数。,parameters:{}},{name:turn_on_lamp,func:turn_on_lamp,description:开启智能灯。无参数。,parameters:{}},{name:send_email,func:send_email,description:发送邮件。需要提供收件人、主题和正文。,parameters:{recipient:string, 收件人邮箱地址,subject:string, 邮件主题,body:string, 邮件正文}}]defget_tool_description():生成供LLM理解的工具列表描述desc你可以使用以下工具函数每次只调用一个工具。\nfortinTOOLS:descf-{t[name]}:{t[description]}\nift[parameters]:descf 参数:{t[parameters]}\nreturndescdefexecute_tool(name:str,**kwargs):根据工具名和参数执行对应函数fortinTOOLS:ift[name]name:returnt[func](**kwargs)returnfError: Tool{name}not found六、构建ReAct提示模板ReAct的关键在于提示工程。我们需要指导LLM按固定格式输出Thought、Action、Action Input并在最后输出Final Answer。prompt_builder.pyfromtool_registryimportget_tool_description SYSTEM_PROMPT你是一个能调用工具来完成任务的人工智能助手。你的回答必须遵循以下格式 Thought: 你对于当前情况的思考。 Action: 要调用的工具名称必须是以下列表中的之一。 Action Input: 工具的输入参数以JSON格式提供如果没有参数则写{{}}。 在收到工具的观察结果Observation后你可以继续思考并调用下一个工具或者输出最终答案。 当你已经获得足够信息并可以回答用户时使用以下格式 Thought: 我现在知道最终答案了。 Final Answer: 对用户的最终回复。 工具列表 {tool_description} 请严格遵守格式不要输出多余内容。 defbuild_initial_prompt(user_query:str)-list:tool_descget_tool_description()system_msgSYSTEM_PROMPT.format(tool_descriptiontool_desc)return[{role:system,content:system_msg},{role:user,content:user_query}]七、解析LLM输出LLM可能返回多行文本我们需要从中提取Thought、Action、Action Input或Final Answer。这里用正则表达式解析。parser.pyimportreimportjsondefparse_llm_output(text:str): 解析LLM的输出返回类型和内容 返回: (thought, content) 或 (action, name, kwargs) 或 (final, answer) linestext.strip().split(\n)thoughtNoneactionNoneaction_inputNonefinal_answerNoneforlineinlines:ifline.startswith(Thought:):thoughtline[len(Thought:):].strip()elifline.startswith(Action:):actionline[len(Action:):].strip()elifline.startswith(Action Input:):rawline[len(Action Input:):].strip()# 尝试解析JSONtry:action_inputjson.loads(raw)except:# 如果解析失败当作空字典或简单字符串处理ifraw{}orraw:action_input{}else:# 尝试提取键值对非严格JSONaction_input{input:raw}elifline.startswith(Final Answer:):final_answerline[len(Final Answer:):].strip()iffinal_answerisnotNone:return(final,final_answer)elifactionisnotNone:return(action,action,action_inputor{})else:# 如果没有明确Action但有Thought可能模型需要重新提示return(thought,thoughtortext)实际中LLM可能将Action Input写成{recipient:mexx.com,subject:提醒,body:开会}我们需要兼容。八、ReAct主循环推理-行动-观察控制器维护消息历史每次循环将用户输入和工具执行结果拼入对话直到LLM输出Final Answer或达到最大步数。react_loop.pyimportopenaifromconfigimportOPENAI_API_KEYfromprompt_builderimportbuild_initial_promptfromparserimportparse_llm_outputfromtool_registryimportexecute_tool openai.api_keyOPENAI_API_KEYdefrun_react_loop(user_query:str,max_steps5):messagesbuild_initial_prompt(user_query)step0final_answerNonewhilestepmax_steps:print(f\n--- Step{step1}---)# 调用LLMresponseopenai.ChatCompletion.create(modelgpt-3.5-turbo,messagesmessages,temperature0.2,# 低温度保证格式稳定)llm_outputresponse.choices[0].message.contentprint(LLM Output:\n,llm_output)# 解析result_typeparse_llm_output(llm_output)ifresult_type[0]final:final_answerresult_type[1]breakelifresult_type[0]action:_,action_name,action_kwargsresult_typeprint(fCalling tool:{action_name}with{action_kwargs})# 执行工具observationexecute_tool(action_name,**action_kwargs)print(Observation:,observation)# 将LLM输出和观察结果加入历史messages.append({role:assistant,content:llm_output})messages.append({role:user,content:fObservation:{observation}})else:# 如果解析为thought但没有action强制要求模型给出actionmessages.append({role:assistant,content:llm_output})messages.append({role:user,content:请提供 Action 和 Action Input 来继续。})step1iffinal_answerisNone:final_answer抱歉我在限定步骤内无法完成任务。returnfinal_answer九、组合运行关灯并发邮件现在把所有模块拼起来写一个main.pyfromreact_loopimportrun_react_loopif__name____main__:user_inputinput(请输入您的指令: )resultrun_react_loop(user_input)print(\n 最终答案 )print(result)测试一次对话用户输入关灯然后给我自己的邮箱发一封邮件主题是“晚安”内容是“今天辛苦了明天上午10点开会”。运行日志简化--- Step 1 --- LLM Output: Thought: 用户要求关灯并发送邮件。先关灯。 Action: turn_off_lamp Action Input: {} Calling tool: turn_off_lamp with {} Observation: Lamp is now OFF --- Step 2 --- LLM Output: Thought: 关灯成功。现在发送邮件。收件人是用户自己的邮箱主题“晚安”内容“今天辛苦了明天上午10点开会”。 Action: send_email Action Input: {recipient:your_emailqq.com,subject:晚安,body:今天辛苦了明天上午10点开会。} Calling tool: send_email with {recipient: your_emailqq.com, subject: 晚安, body: 今天辛苦了明天上午10点开会。} Observation: Email sent to your_emailqq.com successfully --- Step 3 --- LLM Output: Thought: 我现在知道最终答案了。 Final Answer: 已完成关灯并发送邮件到您的邮箱请查收。 最终答案 已完成关灯并发送邮件到您的邮箱请查收。十、进阶本地API真实调用上述turn_off_lamp是模拟。若要真实控制智能灯如Philips Hue或小米只需替换函数实现通过HTTP请求调用本地网关API。例如使用requests库importrequestsdefturn_off_lamp_real():urlhttp://192.168.1.100/api/your_token/lights/1/statepayload{on:False}resprequests.put(url,jsonpayload)ifresp.status_code200:returnLamp turned off via APIelse:returnfAPI error:{resp.text}同理邮件部分可用公司内部的邮件中继API。十一、安全性考量ReAct智能体调用本地API时必须防范指令注入和越权操作。建议参数白名单send_email的recipient限制为预先授权的邮箱列表。工具沙箱对执行环境进行隔离避免eval或exec危险函数。人工确认对关键操作如发邮件、开锁增加二次确认机制。日志审计记录所有Thought/Action/Observation便于追溯。十二、扩展支持多工具并行与流式输出当前每次只调用一个工具效率较低。可改进为在Action行支持多个工具调用如Action: turn_off_lamp, send_email。使用异步执行asyncio并行调用无依赖的工具。使用流式APIstreamTrue实时输出思考过程提升体验。十三、总结本文用200多行Python代码实现了完整的ReAct智能体让它能主动调用本地API关灯和发邮件。你学到的不仅是代码更是一种范式迁移LLM从文本生成器进化为任务执行者。ReAct的核心价值在于可解释性每一步思考都透明可见。容错性观察结果可反馈到下一步推理。可扩展性添加新工具只需注册函数和描述。未来你可以将ReAct连接到日历、数据库、物联网中枢甚至通过RPA控制企业软件。当LLM真正“动手”时自动化的边界将被重新定义。现在打开你的终端跑通这段代码对你的电脑说一句“关灯”——物理世界会给出回应。推荐阅读我的电子文档/书籍管理