ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python的ReAct机制,让LLM主动调用本地API关灯、发邮件

用Python的ReAct机制,让LLM主动调用本地API关灯、发邮件

用Python的ReAct机制,让LLM主动调用本地API关灯、发邮件

想象这样一个场景:你对电脑说“我准备睡了,关灯并把明天上午的会议提醒发到我邮箱”,然后灯真的灭了,邮件真的发出去了。这不是科幻电影,而是ReAct机制赋予大语言模型(LLM)的“动手能力”。

传统LLM只能被动生成文本,像一位只动口不动手的顾问。而ReAct(Reasoning + Acting)让LLM能主动推理并调用外部工具,真正改变物理世界或数字世界的状态。本文将带你用Python从零搭建一个ReAct智能体,让它能调用本地API关灯(模拟硬件)和发邮件(SMTP),全程代码可运行、可扩展。


一、ReAct是什么?为什么它能让LLM“动手”?

ReAct是2022年提出的一种LLM推理与行动协同范式。核心思想是:让模型在生成最终答案前,先输出“思考(Thought)”和“行动(Action)”的中间步骤,并根据行动结果(Observation)调整后续推理

传统Chain-of-Thought(CoT)只有推理链,没有外部交互;传统工具调用(Toolformer)只有函数调用,缺乏动态推理。ReAct把两者结合,形成“思考→行动→观察→再思考”的闭环。

例如,用户说“关灯并发邮件”,ReAct智能体可能这样思考:

  • Thought 1: 用户要求两个动作,先关灯再发邮件。
  • Action 1:turn_off_lamp()
  • Observation 1:"Lamp is off"
  • Thought 2: 关灯成功,现在发邮件。
  • Action 2:send_email(recipient="me@example.com", subject="明天会议", body="上午10点")
  • Observation 2:"Email sent"
  • Thought 3: 所有任务完成,回复用户。

这种显式的推理轨迹不仅可解释,还能在行动失败时自我修正(如重试或换方案)。


二、整体架构设计

我们的系统由四部分组成:

  1. LLM引擎:选用OpenAI的GPT-3.5/4 API(也可替换为本地开源模型如Qwen)。
  2. 工具函数:Python函数,执行具体操作(关灯、发邮件),模拟或真实调用本地API。
  3. ReAct控制器:核心循环,维护对话历史,解析LLM输出的Thought/Action/Observation,并执行工具。
  4. 用户接口:命令行输入问题,输出最终结果。

为了安全,所有本地API调用都限制在用户明确授权的动作范围内,且邮件发送使用测试环境配置。


三、准备工作:安装依赖与配置

pipinstallopenai python-dotenv

创建.env文件存放OpenAI密钥:

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx

新建config.py

importosfromdotenvimportload_dotenv load_dotenv()OPENAI_API_KEY=os.getenv("OPENAI_API_KEY")

四、定义工具函数:关灯与发邮件

为了演示,我们模拟关灯操作(打印状态),并用SMTP发送真实邮件(建议使用QQ/163邮箱的授权码)。

tools.py

importsmtplibfromemail.mime.textimportMIMETextfromemail.headerimportHeader# 模拟灯的状态lamp_status={"on":True}defturn_off_lamp()->str:"""关闭本地智能灯(模拟)"""globallamp_statusiflamp_status["on"]:lamp_status["on"]=Falsereturn"Lamp is now OFF"return"Lamp is already OFF"defturn_on_lamp()->str:"""开启本地智能灯(模拟)"""globallamp_statusifnotlamp_status["on"]:lamp_status["on"]=Truereturn"Lamp is now ON"return"Lamp is already ON"defsend_email(recipient:str,subject:str,body:str)->str:""" 通过SMTP发送邮件(请替换为你的邮箱配置) """# 配置你的邮箱信息(建议用环境变量)smtp_server="smtp.qq.com"# 以QQ邮箱为例smtp_port=465# SSL端口sender_email="your_email@qq.com"password="your_authorization_code"# 授权码,不是QQ密码try:msg=MIMEText(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())returnf"Email sent to{recipient}successfully"exceptExceptionase:returnf"Failed to send email:{str(e)}"

为了安全,实际生产环境应限制recipient为白名单,且不要在代码中硬编码密码,改用环境变量。


五、工具描述与注册

LLM需要知道有哪些工具可用、每个工具的参数和用途。我们构建一个工具注册表,供ReAct控制器查询。

tool_registry.py

fromtoolsimportturn_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="你可以使用以下工具(函数),每次只调用一个工具。\n"fortinTOOLS:desc+=f"-{t['name']}:{t['description']}\n"ift['parameters']:desc+=f" 参数:{t['parameters']}\n"returndescdefexecute_tool(name:str,**kwargs):"""根据工具名和参数执行对应函数"""fortinTOOLS:ift["name"]==name:returnt["func"](**kwargs)returnf"Error: Tool{name}not found"

六、构建ReAct提示模板

ReAct的关键在于提示工程。我们需要指导LLM按固定格式输出:Thought、Action、Action Input,并在最后输出Final Answer。

prompt_builder.py

fromtool_registryimportget_tool_description SYSTEM_PROMPT="""你是一个能调用工具来完成任务的人工智能助手。你的回答必须遵循以下格式: Thought: 你对于当前情况的思考。 Action: 要调用的工具名称,必须是以下列表中的之一。 Action Input: 工具的输入参数,以JSON格式提供(如果没有参数则写{{}})。 在收到工具的观察结果(Observation)后,你可以继续思考并调用下一个工具,或者输出最终答案。 当你已经获得足够信息并可以回答用户时,使用以下格式: Thought: 我现在知道最终答案了。 Final Answer: 对用户的最终回复。 工具列表: {tool_description} 请严格遵守格式,不要输出多余内容。 """defbuild_initial_prompt(user_query:str)->list:tool_desc=get_tool_description()system_msg=SYSTEM_PROMPT.format(tool_description=tool_desc)return[{"role":"system","content":system_msg},{"role":"user","content":user_query}]

七、解析LLM输出

LLM可能返回多行文本,我们需要从中提取ThoughtActionAction InputFinal Answer。这里用正则表达式解析。

parser.py

importreimportjsondefparse_llm_output(text:str):""" 解析LLM的输出,返回类型和内容 返回: ('thought', content) 或 ('action', name, kwargs) 或 ('final', answer) """lines=text.strip().split('\n')thought=Noneaction=Noneaction_input=Nonefinal_answer=Noneforlineinlines:ifline.startswith("Thought:"):thought=line[len("Thought:"):].strip()elifline.startswith("Action:"):action=line[len("Action:"):].strip()elifline.startswith("Action Input:"):raw=line[len("Action Input:"):].strip()# 尝试解析JSONtry:action_input=json.loads(raw)except:# 如果解析失败,当作空字典或简单字符串处理ifraw=="{}"orraw=="":action_input={}else:# 尝试提取键值对(非严格JSON)action_input={"input":raw}elifline.startswith("Final Answer:"):final_answer=line[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":"me@xx.com","subject":"提醒","body":"开会"},我们需要兼容。


八、ReAct主循环:推理-行动-观察

控制器维护消息历史,每次循环将用户输入和工具执行结果拼入对话,直到LLM输出Final Answer或达到最大步数。

react_loop.py

importopenaifromconfigimportOPENAI_API_KEYfromprompt_builderimportbuild_initial_promptfromparserimportparse_llm_outputfromtool_registryimportexecute_tool openai.api_key=OPENAI_API_KEYdefrun_react_loop(user_query:str,max_steps=5):messages=build_initial_prompt(user_query)step=0final_answer=Nonewhilestep<max_steps:print(f"\n--- Step{step+1}---")# 调用LLMresponse=openai.ChatCompletion.create(model="gpt-3.5-turbo",messages=messages,temperature=0.2,# 低温度保证格式稳定)llm_output=response.choices[0].message.contentprint("LLM Output:\n",llm_output)# 解析result_type=parse_llm_output(llm_output)ifresult_type[0]=="final":final_answer=result_type[1]breakelifresult_type[0]=="action":_,action_name,action_kwargs=result_typeprint(f"Calling tool:{action_name}with{action_kwargs}")# 执行工具observation=execute_tool(action_name,**action_kwargs)print("Observation:",observation)# 将LLM输出和观察结果加入历史messages.append({"role":"assistant","content":llm_output})messages.append({"role":"user","content":f"Observation:{observation}"})else:# 如果解析为thought但没有action,强制要求模型给出actionmessages.append({"role":"assistant","content":llm_output})messages.append({"role":"user","content":"请提供 Action 和 Action Input 来继续。"})step+=1iffinal_answerisNone:final_answer="抱歉,我在限定步骤内无法完成任务。"returnfinal_answer

九、组合运行:关灯并发邮件

现在把所有模块拼起来,写一个main.py

fromreact_loopimportrun_react_loopif__name__=="__main__":user_input=input("请输入您的指令: ")result=run_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_email@qq.com","subject":"晚安","body":"今天辛苦了,明天上午10点开会。"} Calling tool: send_email with {'recipient': 'your_email@qq.com', 'subject': '晚安', 'body': '今天辛苦了,明天上午10点开会。'} Observation: Email sent to your_email@qq.com successfully --- Step 3 --- LLM Output: Thought: 我现在知道最终答案了。 Final Answer: 已完成关灯并发送邮件到您的邮箱,请查收。 === 最终答案 === 已完成关灯并发送邮件到您的邮箱,请查收。

十、进阶:本地API真实调用

上述turn_off_lamp是模拟。若要真实控制智能灯(如Philips Hue或小米),只需替换函数实现,通过HTTP请求调用本地网关API。

例如,使用requests库:

importrequestsdefturn_off_lamp_real():url="http://192.168.1.100/api/your_token/lights/1/state"payload={"on":False}resp=requests.put(url,json=payload)ifresp.status_code==200:return"Lamp turned off via API"else:returnf"API error:{resp.text}"

同理,邮件部分可用公司内部的邮件中继API。


十一、安全性考量

ReAct智能体调用本地API时,必须防范指令注入和越权操作。建议:

  1. 参数白名单send_emailrecipient限制为预先授权的邮箱列表。
  2. 工具沙箱:对执行环境进行隔离,避免evalexec危险函数。
  3. 人工确认:对关键操作(如发邮件、开锁)增加二次确认机制。
  4. 日志审计:记录所有Thought/Action/Observation,便于追溯。

十二、扩展:支持多工具并行与流式输出

当前每次只调用一个工具,效率较低。可改进为:

  • 在Action行支持多个工具调用,如Action: turn_off_lamp, send_email
  • 使用异步执行(asyncio)并行调用无依赖的工具。
  • 使用流式API(stream=True)实时输出思考过程,提升体验。

十三、总结

本文用200多行Python代码实现了完整的ReAct智能体,让它能主动调用本地API关灯和发邮件。你学到的不仅是代码,更是一种范式迁移:LLM从文本生成器进化为任务执行者

ReAct的核心价值在于:

  • 可解释性:每一步思考都透明可见。
  • 容错性:观察结果可反馈到下一步推理。
  • 可扩展性:添加新工具只需注册函数和描述。

未来,你可以将ReAct连接到日历、数据库、物联网中枢,甚至通过RPA控制企业软件。当LLM真正“动手”时,自动化的边界将被重新定义。

现在,打开你的终端,跑通这段代码,对你的电脑说一句“关灯”——物理世界会给出回应。
推荐阅读:我的电子文档/书籍管理

返回列表