资讯详情

ReAct模式结合LangChain:零基础构建能思考、会调工具的AI Agent

📅 2026/10/21 13:41:32 | 华诺云谱 👁 阅读
ReAct模式结合LangChain:零基础构建能思考、会调工具的AI Agent
调了无数次大模型API之后我终于想明白一件事光会“聊天”的模型解决不了真实问题。你问它今天天气它会一本正经告诉你“我无法获取实时数据”你让它算一下某个复杂表达式的值它可能心算到一半就开始胡编。这不是模型笨而是它们天生没有手、没有脚只能凭训练记忆说话。直到我把ReAct思想接到LangChain里用十几行代码给模型安上“查资料的手”和“跑代码的脚”上面的问题才算真正有了解法。这篇内容就是写给零基础读者看的。你可能只写过几天Python见过几次LangChain的报错甚至还没搞明白Agent和Chain有什么区别——没关系我会从“为什么需要ReAct”开始讲到具体怎么用LangChain写一个能自己思考、自己调用工具的Agent再带着你复现一个经典翻车现场把调用过程中那些文档里不写的坑全部踩一遍。整个过程不需要你有深度学习背景只要懂一点Python基础、能跑通一个大模型API就够了。1. ReAct到底解决了什么问题以及为什么零基础也能直接上手先花点时间理解ReAct本身。它不是什么高深莫测的新算法而是一个极其朴素的思路让模型在推理的过程中一边“想”一边“做”把思考过程Reason和动作执行Act交替进行。在ReAct出现之前大家让模型干活的方式主要有两种。一种是Chain of Thought思维链让模型一步步推理再给出答案但它的推理完全发生在模型“脑子里”无法接触外部信息算不出真实数据也查不了实时内容。另一种是把任务拆成固定的Pipeline比如先调用一个检索接口再调用一个总结接口流程固定死任务一变就得改代码。ReAct聪明的地方在于把两者糅合在一起。模型不再一次性输出完整答案而是进入一个循环观察当前问题生成一段Thought思考说明“我现在打算怎么解决这个问题”决定调哪个工具生成Action动作带上工具名和参数工具执行完把结果作为Observation观察结果送回给模型模型根据观察结果继续思考、继续行动直到它认为已经拿到足够多的信息才输出Final Answer你可能会问这不是让模型自己控制一切吗模型乱调工具怎么办这正是LangChain这类框架存在的意义。LangChain把“思考-行动-观察”这个循环封装成了AgentExecutor你只需要负责两件事给Agent注册好可用的工具以及把大模型的API接进来。剩下的循环控制、历史记录管理、解析输出格式框架都已经帮你处理好了。所以“零基础”这件事是成立的。你不必从头实现ReAct的循环逻辑不需要手写Prompt模板也不用自己拼接工具调用结果。你需要做的是理解这个循环的节奏然后跟着示例把代码跑通最后再根据自己的真实场景添加几个工具。就是这么个过程。顺带说一句LangChain这个框架在2023年前后火起来之后迭代速度非常快。你现在如果在网上搜教程会看到两种截然不同的写法一种是创建AgentExecutor另一种是使用langgraph的create_react_agent。这两者不冲突后面我会专门对比。零基础读者最怕的就是看到不同版本代码不知道学哪个我会在本篇里尽量给出当前稳定可用的方案。2. 写Agent之前需要搞清楚的“零基础”准备清单先明确一个事实写ReAct Agent本身不需要太多前置知识。但有两样东西你必须准备好一个能调用的大模型API以及一个能跑Python的环境。下面挨个说清楚。2.1 Python环境和依赖安装Python版本建议3.9以上虚拟环境强烈建议创建。我见过太多人图省事直接装在全局环境里结果不同项目依赖互相打架最后连启动都费劲。创建虚拟环境很简单python -m venv .venv source .venv/bin/activate # Windows下是 .venv\Scripts\activate接下来安装LangChain相关包。这里有个版本细节需要注意。早期你只需要装一个langchain包就能用AgentExecutor但到了LangChain 0.3之后官方把Agent的底层实现逐步迁移到了langgraph推荐用create_react_agent的方式创建Agent。为了让后面的内容能直接跑我建议一次性装齐pip install langchain langchain-core langchain-openai langgraphlangchain是主框架langchain-core是核心抽象层langchain-openai是模型接口适配器langgraph是新一代Agent运行时。这四个包现在的搭配最稳。装完之后你可以快速检查一下版本import langchain print(langchain.__version__)我写这篇文章时LangChain已经到0.3.x系列langgraph是0.2.x。如果你看到大版本号有跳跃优先参考官方迁移文档不要硬套旧代码。2.2 大模型API的选择与配置ReAct Agent本质上还是靠大模型的推理能力驱动所以模型得支持函数调用或至少能稳定输出结构化文本。现在主流的模型API都支持这种用法我之前在几个不同的模型上都跑通过包括主流的闭源模型和某国产开源模型效果差异主要体现在推理稳定性上。配置方式很简单通过环境变量把API密钥传进去import os os.environ[OPENAI_API_KEY] 你的Key如果你用的是国内模型换成对应的环境变量名即可比如某国产模型的API Key设置方式略有不同。为了不暴露你的密钥也为了方便团队协作建议用.env文件配合python-dotenv来加载pip install python-dotenv然后在项目根目录建立.env文件OPENAI_API_KEY你的Key代码里加载from dotenv import load_dotenv load_dotenv()这样密钥不会再硬编码在代码中哪怕将来把代码提交到仓库也不会泄露。2.3 必须理解的两个概念工具与模型绑定在跑第一个Agent之前你只需要理解两个核心概念一个是工具Tool一个是模型绑定bind。工具就是一个Python函数只不过它多了一层描述信息——这个函数是干什么的、参数是什么。比如你写一个加法函数工具的职责就是告诉模型有个叫add的函数接收两个数字参数返回它们的和。模型在思考的时候看到这个描述就知道在适当的时候调用它。模型绑定则是指你使用的模型API必须支持工具调用。你可以在创建模型对象时通过bind_tools方法把可用的工具列表传给模型。LangChain内部会把工具列表转换成模型API要求的格式然后模型在生成回复时如果决定需要调用工具就会输出一个结构化的工具调用请求。这两个概念理解透了Agent的基本工作逻辑就已经通了框架把整个对话历史和工具列表交给模型模型在需要的时候发起工具调用框架执行工具并把结果回传给模型循环往复。3. 第一个可运行的ReAct Agent从零手写完整代码纸上谈兵到此为止现在开始写真正的代码。我会用一个非常容易理解的场景让Agent帮我们计算复杂数学表达式同时查一下某个物理常量。这个场景足够简单又能证明Agent真的会调用工具而不是瞎猜。3.1 定义两个基础工具先定义两个简单的工具函数。第一个是计算器第二个是获取物理常量。import math from langchain_core.tools import tool tool def calculate(expression: str) - str: 计算数学表达式的值支持 - * / ** ( ) 等运算例如 13**7 或 sqrt(2)。 try: # 这里实名eval仅在演示场景使用生产环境千万别这么干 result eval(expression, {__builtins__: {}}, {math: math}) return f计算结果: {result} except Exception as e: return f计算失败: {str(e)} tool def get_physical_constant(name: str) - str: 获取常见物理常数的数值支持光速、重力加速度、普朗克常数等。 constants { 光速: 299792458 m/s, 重力加速度: 9.80665 m/s², 普朗克常数: 6.62607015e-34 J·s, } return constants.get(name, f未找到常数的信息: {name})注意tool装饰器是LangChain提供的工具注册方式。函数名和docstring不是摆设模型就是靠这些描述来判断何时调用哪个工具的。所以docstring要写得清晰准确这一点一定要重视。3.2 创建模型并绑定工具接下来创建模型对象。这里以OpenAI风格的接口为例模型选择上建议用当前主流的gpt-4o-mini或者gpt-4o推理能力越强Agent的循环就越稳定。如果你用的是国产模型或开源模型的API只要兼容OpenAI接口格式改一下base_url和model名称就能跑。from langchain_openai import ChatOpenAI llm ChatOpenAI( modelgpt-4o-mini, temperature0, # 工具调用场景建议temperature设为0降低随机性 ) tools [calculate, get_physical_constant] llm_with_tools llm.bind_tools(tools)temperature设为0是工具调用场景的重要细节。Agent的输出需要稳定一个随机性太强的模型可能这次想到调用工具下次直接开始胡编答案。3.3 通过ReAct提示词创建AgentExecutor早期版本的LangChain中创建ReAct Agent的标准姿势是先加载一个专门的Prompt模板然后构建AgentExecutor。这个路径现在的写法稍作调整但对理解ReAct有帮助我先讲这个经典版本from langchain.agents import create_react_agent, AgentExecutor from langchain_core.prompts import PromptTemplate react_prompt PromptTemplate.from_template( 尽可能回答问题。你可以使用以下工具 {tools} 请使用以下格式 问题: 你需要回答的输入问题 思考: 你应该思考如何解决问题 动作: 你应该采取的动作动作名称应该是 [{tool_names}] 中的一个 动作输入: 动作的输入内容 观察: 动作返回的结果 ... (这个思考/动作/动作输入/观察可以重复N次) 思考: 我现在知道最终的答案了 最终回答: 对原始问题的最终回答 开始 问题: {input} 思考: {agent_scratchpad} ) agent create_react_agent(llm, tools, react_prompt) agent_executor AgentExecutor(agentagent, toolstools, verboseTrue)如果你去翻LangChain的官方文档会发现他们还有一种不带agent变量的AgentExecutor用法。这个经典写法把一个完整的ReAct模板直接可视化地展示在你眼前想法、行动、行动输入、观察这四个字段构成了整个Agent循环的肉身。我觉得零基础读者看这个版本理解得更快模板里已经把思考的过程、动作的格式都写得很直白。现在更推荐的写法是langgraph版本我先把代码贴出来后面章节再详细拆解from langgraph.prebuilt import create_react_agent from langgraph.checkpoint.memory import MemorySaver memory MemorySaver() graph_agent create_react_agent(llm, tools, checkpointermemory)这个版本代码更短功能也更现代。所以接下来我给出的完整可运行示例就用官方当前的推荐方式再结合langgraph的流式输出接口一起跑。3.4 完整可运行示例LangGraph推荐版把上面所有片段串起来下面是一份可以直接复制运行的完整代码import os from dotenv import load_dotenv load_dotenv() import math from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent from langgraph.checkpoint.memory import MemorySaver tool def calculate(expression: str) - str: 计算数学表达式的值支持 - * / ** ( ) 等运算例如 13**7 或 sqrt(2)。 try: result eval(expression, {__builtins__: {}}, {math: math}) return f计算结果: {result} except Exception as e: return f计算失败: {str(e)} tool def get_physical_constant(name: str) - str: 获取常见物理常数的数值支持光速、重力加速度、普朗克常数等。 constants { 光速: 299792458 m/s, 重力加速度: 9.80665 m/s², 普朗克常数: 6.62607015e-34 J·s, } return constants.get(name, f未找到常数的信息: {name}) llm ChatOpenAI( modelgpt-4o-mini, temperature0, ) tools [calculate, get_physical_constant] memory MemorySaver() agent create_react_agent(llm, tools, checkpointermemory) config {configurable: {thread_id: demo_thread}} response agent.invoke( {messages: [{role: user, content: 请帮我计算13的7次方是多少另外光速的数值是多少}]}, configconfig, ) for message in response[messages]: print(f{message.type}: {message.content})运行这段代码你会在控制台看到模型先输出思考过程然后调用calculate工具拿到结果继续调用get_physical_constant工具最后汇总两条信息给出最终答案。整个流程清晰地展示了ReAct的循环思考-行动-观察-再思考-再行动-最终回答。4. Agent内部到底怎么转起来的反编译一下ReAct循环很多教程讲到这里就停了读者跑通一次之后只知道“它能跑”内部完全是个黑盒。但如果你想把Agent用到真实项目里不理解这个循环的细节后面遇到问题根本无从下手。这一节我们把黑盒打开一点。4.1 Thought/Action/Action Input/Observation四元组的完整循环ReAct的每一次迭代本质上都是这四元组的不断重复Thought模型表达当前对问题的判断比如“我需要先计算表达式的值这需要调用calculate工具”Action模型从可用工具列表中选择一个给出动作名称Action Input模型生成该动作所需的参数Observation工具执行后返回的结果这个循环什么时候终止两种情况下会。第一种是模型认为已经收集了足够的信息决定输出Final Answer第二种是达到最大迭代轮次上限或者中间出现解析错误、工具异常等情况Agent强制停下。有一点需要注意在LangChain的实现里Thought和Action有时候不是分开生成的。模型在一次输出中可能同时给出Thought和Action以及Action Input框架解析出动作后执行工具把Observation拼接到对话历史中然后再次把全部历史发给模型。这就是为什么你可以在verbose模式下看到那么完整的思考链条。4.2 LangGraph版本的循环控制机制新版create_react_agent是基于LangGraph的状态机实现的。它把整个Agent循环抽象成一张图模型节点负责生成回复工具节点负责执行工具调用条件边根据模型是否发起了工具调用来决定下一步是继续循环还是结束。状态机的核心是一个消息列表。每次模型生成一条消息如果这条消息带有tool_calls状态机就把消息追加到列表里然后跳到工具节点执行对应工具工具结果作为一条工具消息追加回列表再回到模型节点。模型看到工具消息后继续生成下一条消息如此反复。用MemorySaver作为checkpointer的意义也在这里。它让Agent具备短期记忆能力可以记住多次对话之间的上下文。在API调用中你可以通过thread_id标识不同的会话相同thread_id的请求会共享同一个对话状态。这对多轮对话场景非常重要。4.3 Prompt模板在循环中的角色你可能会好奇我们使用create_react_agent的时候并没有显式提供Prompt模板那ReAct的提示词是从哪来的答案藏在langchain库的react prompt模板包里。create_react_agent在内部加载了一套默认的ReAct提示词其中包括每个工具的名称、描述、参数结构以及输出格式的完整说明。这带来一个实用结论工具函数的docstring写得好不好直接影响Agent的推理质量。如果你给模型看一个含糊的docstring那它遇到问题时就可能想不起来调用这个工具或者传入错误的参数。这和给新同事写交接文档一样描述清楚了干活才靠谱。我自己的一个习惯是写完工具函数之后先单独问模型一句话“你现在有哪些工具可以用分别能做什么”如果模型能把每个工具的用途和参数说得和docstring基本一致就说明你的工具注册没问题。这个自检方法简单高效。5. 一次刻意制造的“失败”演示当你给Agent一个它想硬算的问题讲完原理我们来做一个非常有意思的演示。有经验的工程师都知道大模型在数学计算上经常翻车尤其是复杂的乘方、开方或者多位数的加减乘除。我们现在故意让Agent面对一个它试图心算但其实算不对的问题看看它会不会“想起”自己还有计算器这个工具。我构造了这样一个问题请计算 298374 * 91283 等于多少面对这个问题能力强的模型第一次就会调用calculate工具。但如果你用的模型推理能力弱一些或者你把工具的docstring描述得不清楚模型可能就会开启“硬算模式”在Thought里写下一步步手算然后给一个离谱的答案。这正是ReAct在强调的事模型必须先意识到“我该用工具”然后这个循环才会启动。我们把刚才代码里的问题改成这个运行一次。一个可能的过程是模型先输出Thought说“这是一个大数乘法直接心算容易出错我应该使用calculate工具”然后生成Action调用calculate传入298374 * 91283。工具返回正确结果27237491842。模型再输出Thought确认结果合理然后给出Final Answer。整个过程清清楚楚模型不是靠记忆而是靠工具拿到答案。这个演示的意义在于它直观地展示了LangChain Agent和普通Chain的区别。普通Chain是预先编排好的不会临场决定调用什么Agent则是根据问题动态规划工具使用方式的。所以LangChain里经常说Agent是“规划者”Chain是“执行计划”就是这个原因。为了让这个演示更有教育意义我建议你把temperature调成一个比较大的值比如1.2然后再看看模型面对同一个问题时会不会更倾向于“硬算”。我自己实验下来高temperature下模型确实更容易“自信地胡说”这是一个能让零基础读者快速体感理解temperature作用的简单实验。6. 跑通Demo之后必踩的坑版本差异、解析失败与其他玄学问题我敢打赌只要你自己动手写过一次ReAct Agent百分之百会遇到下面这些坑中的至少一个。提前看到能帮你省好几个小时的排查时间。6.1 版本差异引起的“新手大坑”网上大量教程还在使用旧版写法比如from langchain.agents import initialize_agent, AgentType或者from langchain.agents import load_tools这些代码在LangChain 0.1时代没问题但在0.3版本之后很多导入路径都已经变了。如果你按照旧教程写最常见的结果是ImportError提示找不到某个模块。这不是你代码的问题就是版本换代导致的兼容性变化。我的建议是不要盲目复制老教程里的完整代码。优先看官方文档或者找最近半年内更新的文章。LangChain官方迁移文档里有非常详细的导入路径对照表遇到导入报错时先查官方文档比在搜索引擎里大海捞针高效得多。6.2 Agent最终答案解析失败与重试机制ReAct Agent输出的最终答案必须以Final Answer开头但模型的输出并不总是那么规矩。有时模型会在Final Answer后面继续啰嗦有时会先输出Thought再输出Final Answer甚至有时直接漏掉Final Answer标签。框架内部提供了一个OutputParser负责解析这些输出但它也不是万能的。碰到这种情况有几个实用的办法把verboseTrue打开观察模型实际输出了什么对比Prompt模板里要求的格式往往一眼就能发现偏差在Prompt模板里强化格式说明比如增加“不要在任何字段中出现多余解释”或“Final Answer必须单独一行”把temperature调低随机性降低后输出格式的稳定性会明显提升还有一个容易忽略的点是handle_parsing_errors的参数设置。在处理某些简单任务时就算模型输出格式有误如果你把这个参数配置好LLM也会根据错误信息自行修正输出格式不会直接中断整个Agent执行。这块在实际项目中用得非常多。6.3 模型的工具调用幻觉它以为它调了其实没有这个坑比较隐蔽。有些模型面对复杂问题时会在Thought里说“我应该用calculate工具”但动作部分却没有产生完整的工具调用结构直接生成了文本。框架检测不到有效的tool_calls自然也就不会执行工具。这类问题容易出现在以下两种情况一是模型的工具调用能力不够强二是工具的docstring描述与实际功能不匹配导致模型产生错误的调用意图。解决思路也不复杂换用工具调用能力更强的模型同一套代码通常换个模型就能解决把工具的docstring写得更精确更具体对于关键工具加上简单的测试用例先单独跑一下工具再接入Agent6.4 流式输出时的体验问题Agent的实时输出体验和普通LLM不一样。普通LLM会逐字吐出内容但Agent在工具调用阶段会有一段“沉默期”因为框架正在调用工具、等待返回。如果你的程序没有正确处理这个阶段用户会以为卡死了。LangGraph的streaming接口可以把Agent的各个阶段暴露出来方便前端做精细展示。你在代码里可以这样用async for chunk in agent.astream( {messages: [{role: user, content: 计算13的7次方}]}, configconfig, stream_modeupdates, ): print(chunk)stream_modeupdates会按节点输出更新你能看到模型节点和工具节点交替出现的完整轨迹。前端做展示时就可以在模型节点输出时显示文字在工具节点执行时显示一个“工具调用中”的占位动画体验就好很多。7. 从Demo到能用单Agent架构的边界与下一步该怎么走当你能让Agent顺利处理数学计算和常数查询这类简单任务时下一步的问题很自然就来了这套东西怎么用到真实业务里7.1 单Agent的能力边界必须清醒认识到单Agent架构一旦任务复杂度上升马上会碰壁。最典型的问题是工具太多了。当工具数量超过十几个模型每次都要在大量工具描述里寻找匹配项选择准确率会明显下降。工具描述之间也可能出现语义重叠模型无法判断该用哪个。另一个问题是循环深度。如果一个任务需要模型连续调用五六次工具才能完成过程中任何一次工具返回内容异常都可能让模型陷入混乱甚至开始循环调用同一个工具。LangGraph里有一个recursion limit参数可以控制最大循环次数默认值通常是一个整数你可以在invoke时通过config传参调大config { configurable: {thread_id: demo}, recursion_limit: 50, }但不建议一开始就把这个值调得很大先用默认值暴露问题再根据实际需求逐步放宽比较好。7.2 多工具场景的组织策略工具数量上来之后一个有效的策略是给工具分组建命名空间。比如有搜索类工具、数据库类工具、计算类工具分别用search_、db_、calc_前缀或统一命名。模型在选择工具时更容易识别工具归属准确率会好很多。更进一步的方案是使用Router思路让上游Agent先判断当前问题属于哪个领域再路由到对应的下游Agent。这就是多Agent协作的雏形。LangGraph本身支持多Agent图的构建不过这是进阶内容零基础读者先不用急着上但可以知道方向所在。7.3 人的介入人类确认环节的价值很多真实场景下Agent执行到某个关键步骤时不应当完全自主行动。比如它准备执行一个不可逆的操作或者需要支付一笔款项这时需要人在中间确认。LangGraph提供了interrupt机制可以让Agent在指定节点暂停等待人工审批后继续执行。这个设计思路非常符合真实业务的安全需求。我见过不少同学在Demo里把Agent做得无所不能结果接入业务系统后被安全评审打回来就是因为缺少人类确认机制。如果你从一开始就考虑这一点后续项目推进会顺利得多。7.4 继续深化可以看什么方向跑通ReAct Agent只是Agent开发的入门。继续深入的话有几个明确的进阶方向Memory当前MemorySaver是内存级别的存储进程重启就丢失。生产环境可以考虑独立存储方案结构化输出让Agent返回结构化JSON方便对接下游系统多模态工具不只是文本计算器还可以是图像识别接口、语音合成接口可观测性记录Agent的每一步思考、每一次工具调用、耗时与成本对线上系统必不可少每一条都能单独写一篇长文。你在掌握本文内容之后再按兴趣深入方向就不会跑偏。最后说一个我在实操中发现的小技巧Agent的开发调试过程中别把模型温度设为0当成金科玉律。工具调用场景温度设为0通常更稳但如果你的任务本身需要一定的探索性比如让Agent从多个工具中找到最合适的方案稍高一点的温度反而能带来意外的路径选择。这个参数不是越大越好也不是越小越好关键是结合具体场景去试。我自己的做法是先固定温度0跑通逻辑再慢慢调温度观察效果差异找到当前任务的最优值最后固定下来写入配置。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑