LangChain Agents实战:从ReAct循环到工具调用的完整指南
1. 为什么你需要重新理解Agents在LangChain生态里摸爬滚打过一段时间的人大概率会有这样一种体验光是Chain和Memory能解决掉80%的常规任务但一旦遇到必须根据输入动态决定下一步做什么的场景整套逻辑就卡住了。比如你让系统帮我查一下某位开发者近期对外分享过的几个项目链接然后整理成表格如果只是写死一条调用链你会发现要么工具参数写死到没法用要么逻辑分支铺得让人崩溃。这时候LangChain的Agents才是真正的主角。很多新手容易把Agents当成一个高级的Chain这个认知偏差会带来一系列灾难。Agents的核心本质是由大模型动态决策决定调用哪个工具、按什么顺序调用、什么时候停止。它不是一个固定的处理管线而是一个推理-行动-观察的循环系统。说得再直白一点Chain是流程图每个节点固定Agents是自动驾驶路线由模型实时判断。这篇内容不是单纯的API文档堆砌我会把LangChain Agents从底层机制讲清楚把Agent Executor、ReAct推理循环、工具设计、记忆管理、线程控制这些实操中高频踩坑的点全部过一遍最后给一个可以直接落地的完整示例。适合已经掌握LangChain基本链式调用、想真正解锁复杂场景的开发者。2. 核心概念拆解与底层层逻辑2.1 Agent到底是什么如果只记住一句话那就是Agent 大模型 工具集 决策循环。大模型负责阅读理解用户意图、拆解任务、调用工具工具负责真正干活——查数据库、写文件、调接口、算数学决策循环则保证整个过程不是一次性输出而是想一步、做一步、看结果、再想下一步。这里的看结果非常关键。大模型本身有幻觉它可能根本不知道某个接口返回的真实数据是什么。Agents机制让模型先调用工具拿回真实结果再基于真实结果进行下一步推理。这本质上是用外部工具给模型装了一双眼睛。举个例子。你让Agent回答某个地区今天适合穿什么衣服模型先调用天气接口拿到实时温度、风力再调用一个知识库判断这个温湿度区间适合什么衣物。整个过程模型没有猜它的每一步判断都建立在真实工具返回值上。这就是Agent存在的意义——把模型从脑内推理升级为身临其境的行动者。2.2 为什么Chain解决不了这类问题常规Chain的结构是一张有向无环图A节点完了是B节点B完了是C节点。你可以在节点之间做条件判断但条件是提前写死的。比如如果返回码是200就走这个分支这种判断是人类预设的。Agent完全不同。它的路径是运行时动态生成的。模型看完用户输入自己决定第一步是调用搜索工具还是计算工具然后再根据搜索结果决定第二步。这个路径在代码运行之前根本不知道。用生活场景类比一下你用导航软件去一个陌生地方路线是导航实时根据路况调整的而不是出发前就把路径写死。Agent就是那个导航大模型是司机工具是各种路况信息源。明白这个区别之后你就知道什么场景该用Chain、什么场景该用Agent了用户输入是结构化表单数据、处理流程固定不变用Chain便宜稳定。用户输入是自然语言、开放性问题、需要实时获取外部信息、决策分支多用Agent。混合场景先用Agent做意图识别再路由到不同的Chain模板。2.3 复现Agent机制的三个关键模块LangChain的Agent体系主要由三个模块协同完成Agent核心Agent Class负责把用户输入 中间观察结果整理成模型提示词调用大模型输出决策结果。决策结果是一个结构化指令要么执行某一工具并传入参数要么直接给你最终答案。工具集ToolsAgent能调用的函数列表。每个工具必须包含名称、描述、参数schema。描述信息尤其重要它是模型决定该不该用这个工具的唯一依据。Agent Executor执行器整个循环的调度中心。拿着用户输入交给Agent核心 - 拿到决策 - 若决策是调用工具就执行工具把结果作为观察拼回去 - 再交给Agent核心 - 循环往复直到Agent给出最终答案或达到最大迭代次数。这三部件缺一不可。你可以自定义Agent核心也可以复用LangChain现成的create_react_agent之类的方法工具集需要自己关心函数签名Executor则基本是标准组件。3. 手把手搭建第一个可用Agent3.1 环境与依赖准备先确认你的环境。我用的是Python 3.10以上版本LangChain目前版本迭代很快建议直接安装最新稳定版pip install --upgrade langchain langchain-openai langchain-community用得最多的是LangChain的Agents模块。注意一点不同版本间的API变动很频繁。我经历过从initialize_agent到create_react_agent的迁移很多网上教程里的旧写法在新版本里已经报错。唯一可靠的参考是官方文档的API索引以及本地环境的实际报错信息。3.2 设计一个带搜索与计算的真实场景我们不搞那种Hello World式的玩具逻辑直接做一个有实际意义的Agent用户输入一段包含计算和搜索需求的自然语言Agent自己决定调用计算器工具、搜索工具还是两者都要。先定义两个工具。第一个负责数学计算第二个模拟一个本地信息检索服务实际工作中可以替换成数据库查询或内部API调用from langchain_core.tools import tool import math import json tool def calculator(expression: str) - str: 计算数学表达式比如 23*4返回计算结果。 try: # 注实际生产环境请使用更安全的表达式求值方案勿直接使用eval result eval(expression, {__builtins__: {}}, {math: math}) return str(result) except Exception as e: return f计算失败{str(e)} tool def local_search(query: str) - str: 搜索本地知识库返回与query相关的摘要信息。 local_db { python: Python 是一种解释型高级编程语言适合快速开发与数据科学。, langchain: LangChain 是一个大模型应用开发框架核心组件包括Chain、Agent与Memory。, 微服务: 微服务架构将单体应用拆分为独立部署的小服务各服务间通过轻量协议通信。 } for key, value in local_db.items(): if key in query: return json.dumps({相关主题: key, 摘要: value}, ensure_asciiFalse) return 未找到相关结果请换个关键词试试。这里有个非常值得注意的点工具的描述文本极其重要。模型不会看你的函数体它只看你写的...描述来决定要不要调用。描述写得不清楚Agent就乱调用描述写得太宽泛Agent什么都想用它反而不准确。3.3 组装Agent并跑通完整流程接着用LangChain的标准方法创建Agentfrom langchain_openai import ChatOpenAI from langchain.agents import create_react_agent, AgentExecutor from langchain import hub llm ChatOpenAI(modelgpt-4o, temperature0) # ReAct模板是LangChain官方维护在hub上的提示词直接拉取最省事 prompt hub.pull(hwchase17/react) tools [calculator, local_search] agent create_react_agent(llm, tools, prompt) executor AgentExecutor(agentagent, toolstools, verboseTrue, max_iterations5) result executor.invoke({input: 帮我查一下 LangChain 是什么顺便计算一下 12345*6789 等于多少}) print(result[output])你运行之后会看到类似这样的决策过程第一次推理模型认为需要先搜索LangChain的信息于是调用local_search(queryLangChain)。拿到返回结果后第二次推理模型认为还需要计算乘法于是调用calculator(expression12345*6789)。拿到两种结果后第三次推理模型整理成最终回答输出自然语言结果。整个过程就是Agent Executor在背后循环驱动。max_iterations5是保底机制防止模型陷入无限循环这一点在后面排查部分还会细讲。4. 工具设计的成败细节4.1 工具描述即模型的操作手册工具设计是Agents实战里最容易被低估的一环。很多开发者工具写得没毛病但Agent跑起来就是疯了一样乱调工具或者该用的时候不用问题几乎都出在描述上。一个合格的工具描述应该包含三部分信息工具是干什么的、什么场景下用、传入参数的含义。说得再活一点你需要把一个工具描述当作写给一个聪明但一无所知的实习生看的操作手册。对比一下差劲的描述search(query: str)模型根本不知道这个工具查的是什么库。合格的描述search(query: str) - 查询内部项目知识库输入关键词返回相关文档摘要模型在遇到帮我查XX项目的文档时就会优先想到它。参数也有讲究。LangChain会自动从函数签名里提取参数名和类型但如果你想让模型更准确填参可以给参数加描述from langchain_core.tools import tool tool def get_weather(city: str 北京, unit: str celsius) - str: 获取指定城市的天气信息city为城市名unit为温度单位celsius或fahrenheit。 # 实际实现省略 return f{city} 的天气是晴{25} 摄氏度4.2 工具数量与选择的平衡工具不是越多越好。模型在每一步推理时都会把工具列表塞进上下文中工具太多有三个后果提示词体积变大导致推理变慢、模型选择困难导致误调用率上升、上下文窗口被无关信息挤占。实际项目中我控制在一个Agent最多挂5到8个工具。超过这个数我建议先做一级分类用一个路由器Agent判断用户意图类别然后分发到对应专业Agent每个专业Agent只挂自己那几个工具。这本质上就是Agent架构里的分层设计比在同一个Agent里挂20个工具要稳定得多。4.3 工具异常处理的隐藏作用新手最容易忽略的是工具的异常分支。模型是靠工具返回值做下一步推理的如果工具在内部抛异常整个循环就断了。解决办法是工具内部自己捕获异常把错误信息转成字符串返回给模型tool def safe_divide(a: float, b: float) - str: 两个数字相除返回结果。 try: return str(a / b) except ZeroDivisionError: return 错误除数不能为零请检查输入让模型看到错误信息比直接抛异常好得多。模型看到了除数不能为零它会自己调整输入或者用自然语言告诉用户参数有误。整个过程非常贴近人类协作工具说我干不了这个活因为XX原因模型就能当即做出补救。这是LangChain Agents实战里最实用的小技巧之一。5. ReAct模式与其他Agent范式的选型思考5.1 ReAct到底是什么原理ReActReasoning Acting是当前LangChain默认Agent模式的核心思想。它的提示词会要求模型按固定格式循环输出Thought: 我这一步的想法 - Action: 我要调用的工具 - Action Input: 工具参数工具返回后对应Observation: 观察到的结果如此往复直到模型输出Final Answer。这种设计的巧妙之处在于把推理过程和工具调用过程显式分离。如果模型直接生成最终答案你没法判断它的推理依据但ReAct强迫模型先想后做做完再看看完再想每一步都留下可追踪的中间轨迹这对调试Agent系统极其关键。这也是为什么我在实战中优先用ReAct而不是其他复杂Agent范式。它的行为可预期、中间过程可读、调试成本低对大多数业务场景来说性价比已经很高。5.2 什么时候考虑Plan-and-Execute模式LangChain里还有一种Plan-and-Execute模式模型先不做任何工具调用而是先制定一个完整计划比如输出1、2、3步执行步骤然后系统按计划逐步执行。两种范式的区别在于ReAct是逐层决策走一步看一步灵活但有时会漏掉全局最优路线也可能绕路。Plan-and-Execute是全局规划先宏观拆解再逐行动执行适合步骤明确、依赖关系清晰的复杂任务但对意外情况的应变能力差。我打个比方ReAct像开车走一条没走过的路每到路口看一眼导航Plan-and-Execute像先把地图看完确定路线再出发中途有封路就比较棘手。实际项目里任务复杂且环境稳定用规划式任务交互性强、信息动态变化用ReAct。5.3 LangChain新版本里AgentExecutor与LangGraph的选择最近LangChain社区明显在往LangGraph迁移。老牌的AgentExecutor仍然可用但如果你打开LangChain官方最新的Agents入门文档会看到很多示例改用LangGraph构建。原因很简单LangGraph提供了更强的循环控制、状态持久化和人工介入能力。AgentExecutor本质上是一个固定逻辑的while循环你只能在外部调参数LangGraph则把循环的每个节点都暴露出来让你改。自己控制图结构意味着你可以任意插入人工审核节点高风险操作先等管理员确认再执行工具。在不同分支节点之间跳转而不是只能在推理-行动之间单调循环。精细控制状态对象每一步的中间数据都可以持久化存储。我的观点是如果你只是做快速原型验证、小型内部工具用AgentExecutor完全没问题代码量少跑得也稳。但如果你的Agent要上生产、要降级容错、要人机协同建议直接上LangGraph。后面我会给一个LangGraph版本的Agent示例帮你直观对比两者的差异。6. 实战从SQL查询到对话式数据分析Agent下面这个案例是我去年做数据平台建设时随手搭的一个原型非常适合拿来完整拆解。需求是这样的业务方希望像聊天一样查询数据库里的订单数据比如帮我看看上个月华东区的订单总量是多少跟去年同期对比一下变化。传统做法是写一堆查询接口但需求变化太快不如直接交给Agent。6.1 设计工具集与系统提示词这个Agent不需要搜索工具核心工具就两个一个执行SQL查询并返回结果一个负责把一段文本转成指定的查询条件。完整代码如下from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent import sqlite3 tool def run_sql_query(sql: str) - str: 执行只读SQL查询语句返回查询结果。仅允许SELECT语句。 if not sql.strip().lower().startswith(select): return 错误仅支持SELECT查询 conn sqlite3.connect(mock_orders.db) try: cur conn.cursor() cur.execute(sql) cols [desc[0] for desc in cur.description] rows cur.fetchmany(10) return f列名: {cols}\n数据: {rows} except Exception as e: return f执行SQL出错: {str(e)} finally: conn.close() tools [run_sql_query] llm ChatOpenAI(modelgpt-4o, temperature0) agent create_react_agent(llm, toolstools)这里说明一下为什么用LangGraph版本的create_react_agent。因为我想引入人工确认机制Agent推算出的SQL在真正执行之前需要先经过一道审核节点。老版AgentExecutor做不到这一点LangGraph可以。6.2 加入人工审核节点LangGraph的构建方式非常直观定义一个状态对象然后按节点用边连接。以下代码展示如何插入一个人工审核步骤from typing import TypedDict from langgraph.graph import StateGraph, START, END from langgraph.checkpoint.memory import MemorySaver class AgentState(TypedDict): messages: list sql_to_review: str # 已有初始Agent app create_react_agent(llm, toolstools) def build_agent_with_review(): builder StateGraph(AgentState) # 节点1让Agent生成SQL并填充待审字段 # 节点2人工审核 # 节点3如果审核通过则执行查询 # 这里简化示意重点在思路新增一个审核中间态节点 pass实际生产里的人工确认往往通过前端界面实现Agent生成SQL后推送给经办人经办人点击确认才放行点击驳回则把驳回原因传回模型让其重新生成。这个闭环在人机协同场景里价值巨大。谁都不想让大模型在没有任何监督的情况下直接操作线上数据库哪怕只是只读查询。6.3 实际运行效果与踩坑记录如果完全不加审核这个Agent跑起来非常流畅。你输入查一下2024年全年订单总量按月分组模型生成的SQL可能不是最优解但基本上是正确的。它会先推断表结构如果信息不够还可能自己拼一个查询去读元数据表再正式查询业务表。整个过程可视性很强每步都会输出当前在干什么。但这里有几个高频坑要提醒SQL生成容易幻觉表名。如果生产资料里没有表结构信息模型会凭训练记忆猜表名。解决办法是在系统提示词里附上准确的schema定义或者加一个工具让模型先调用list_tables()了解可用表。只读安全必须有硬约束。仅靠提示词约束不安全要在工具函数内部主动检查SQL前缀像上面的run_sql_query一样非SELECT直接拒绝。绝不把数据库写权限交给Agent。查询结果过大。大模型不擅长处理几千行的表格数据一旦返回内容爆炸后续推理质量会断崖式下降。工具返回前手动截断前N条让Agent知道有更多数据但只看到样例它是会用LIMIT自己再查的。7. 常见卡点与排查思路速查表在完成了若干Agent项目之后我整理了下面这份高频问题排查清单。它不一定让你从此不踩坑但能帮你把瞎猜变成按图索骥。现象根因方向排查思路Agent反复调用同一个工具不停止工具返回格式让模型误判下一步还没完成检查工具返回值是否包含明确结论检查max_iterations设置该调工具时不调工具工具描述不清晰或模型上下文被其他内容干扰精简工具描述确认描述包含什么场景下使用调低temperature传递参数总是错参数描述缺失模型只能猜在工具函数签名中给参数加注释必要时用Pydantic的Field描述参数循环太快还没想清楚就行动ReAct提示词质量差拉取官方hub的高质量prompt模板别自己瞎写长任务中途上下文爆了工具返回内容过多工具内部做结果截断与摘要增加上下文窗口用摘要型工具替换原始数据工具不同请求之间的状态混了没有正确隔离会话状态检查Executor是否复用了Memory生产环境用LangGraph的持久化checkpoint按session隔离响应速度太慢推理轮次太多两条路并进优化提示词让模型一次推理更准确限制max_iterations关于max_iterations我多说一句细节。设太大循环失控很烧钱设太小复杂任务完不成。我的经验值是在4到8之间同时工具内部自己做好兜底错误返回让模型能知难而退给出最终答案而不是硬着头皮继续。还有一个容易忽视的系统性问题temperature的设置。Agents任务中强烈建议设为0或接近0。你可以想象温度代表随机性而Agent的每一步决策都直接影响工具调用结果这里的随机性非常危险——同一个问题两次调用一次正常一次乱跳这在生产环境不可接受。创意写作把温度调高可以但Agent的决策环节必须追求确定性。8. 记忆管理很多人会搞砸的那一环8.1 新手理解的Memory和Agent需要的Memory不是一码事如果你用Chain时习惯给链挂一个ConversationBufferMemory那么到Agent这里直接照搬会吃大亏。Agent的每一次循环交互底层消息记录都包含Thought、Action、Observation这些是执行过程中的内部轨迹并不是用户真正想记住的对话内容。如果你把内部轨迹也全部塞进记忆里上下文很快就会膨胀而且会把模型的注意力搅乱。它可能纠结于上一轮的某个开发细节忘了用户最初问的问题。正确的姿势是分层记忆短期记忆放当前会话的用户历史消息用于多轮对话连贯性。长期记忆放用户画像、关键偏好、重要结论需要持久化存储。工作记忆Agent循环内部的中间观察这个只存在单次执行过程中用完即弃。8.2 自己动手实现一个轻量会话记忆不用急着接重型向量库。如果只是做会话级的多轮聊天Agent一个简单的内存字典就够from langgraph.checkpoint.memory import MemorySaver memory MemorySaver() agent create_react_agent( modelllm, toolstools, checkpointermemory # LangGraph内置支持按thread_id做会话隔离 ) config {configurable: {thread_id: user-abc-001}} # 第一次提问 result1 agent.invoke( {messages: [{role: user, content: 帮我查一下最近的订单趋势}]}, config ) # 第二次提问同样的thread_idAgent记得上文的查询上下文 result2 agent.invoke( {messages: [{role: user, content: 再按地区维度细化一下}]}, config )这套机制解决了一个核心问题Agent连续对话的状态隔离。Thread ID相当于每个用户独立的记忆通道互不串扰。多人同时使用时各自对话都在各自的thread里独立循环完全不用为用户A的上下文干掉了用户B的上下文这种事情头疼。不过要提醒一点MemorySaver是内存级存储服务重启数据就没了。生产环境建议切换成Redis或数据库后端的持久化方案。LangGraph的checkpointer本质是一个接口你可以自己实现存储逻辑。9. 从单Agent到多Agent协作的扩展之路9.1 为什么单Agent撑不过复杂业务项目规模一大单Agent很快暴露天花板。首先是负载问题。所有工具塞在一个Agent里模型每一步都要面对全部工具的决策无效推理步骤会大幅增加。实测下来工具超过8个以后调用准确率明显下滑响应延迟也开始让人难以接受。其次是职责耦合问题。数据分析Agent和对话客服Agent混在一起系统提示词要兼顾两套完全不同的行为模式互相干扰。今天调整了客服话术数据分析突然开始啰嗦了这种玄学问题会让人崩溃。单Agent还很难做权限控制。一个Agent拥有全部工具意味着内部一个错误决策可能导致越权操作。对于企业场景来说这是不可接受的架构风险。9.2 主管-专员架构的核心思路我实践下来最稳的模式是主管-专员架构顶层一个主管Agent负责任务分流。它收到用户消息后判断属于哪个领域然后调用对应的专员Agent并把必要上下文传过去。底层多个专员Agent每个只挂2-3个专业工具聚焦单一职责。主管不自己干活它只调度专员不负责全局只做自己那一摊事情。判断用户消息属于哪个领域这件事不需要复杂的语义模型一个高质量的分类提示词加上几个领域示例就足够。如果任务确实横跨多个领域主管可以按顺序依次唤起多个专员或者组装成一个统一的工作流。9.3 多Agent协作中必须处理的两个实际问题第一个是上下文传递的损耗。主管传给专员的不是原始原始消息的简单复制而是经它提炼过的任务描述。如果主管在提炼过程中出错后续所有结果都会错。解决办法是让主管输出结构化任务描述任务目标、输入数据摘要、期望产出格式并强制它引用用户原话中的关键实体。第二个是错误隔离与降级。某个专员Agent挂了不能让整个系统崩掉。我的做法是给每个专员调用加一个超时与失败兜底失败时返回结构化错误消息给主管由主管决定是重新尝试、换一种方式还是直接向用户说明该功能暂时不可用。这套降级思路来自微服务架构的容错理念放在多Agent系统里同样适用。10. 实战避坑经验与扩展方向最后说几个我在实际项目里总结出的经验教训不是大道理都是真金白银换来的。第一个教训不要高估模型的工具选择能力。再强的模型也会在工具选择上失误尤其当工具描述含糊、工具集合庞大、输入信息不完整的时候。你唯一有效的防线就是把自己当成给实习生派活的领导——把每个工具边界写清楚把例外情况在提示词里说透。别指望模型自动理解潜台词。第二个教训把Agent当作可观测系统来建设。Agent的中间轨迹本身就是非常宝贵的调试数据。生产环境一定要把Thought、Action、Observation完整落日志出问题时回放一遍比什么监控都管用。我见过很多团队上线Agent只看最终结果中间过程一旦错了根本无法定位最后只能推翻重来。日志先行这句话在Agent项目里不是口号。第三个教训用评价集来管控Agent质量。Agent的输出不稳定但你可以为典型场景建立一个固定测试集比如50条覆盖不同意图的用户输入每次修改提示词或工具后都跑一遍回归测试。Agent领域没有完美的正确答案但你可以定义可接受的行为边界——什么情况下必须调用某个工具、什么情况下必须拒绝回答、什么情况下可以合理给用户一个备选方案。把边界用自动化断言固化下来你才敢放心迭代系统。至于扩展方向我目前比较关注两条线。一是把Agent的决策轨迹与外部业务指标联动比如跟踪每次工具调用的价值产出实现真正的ROI量化二是把长期记忆从简单的键值存储升级为基于向量检索的结构化知识记忆让Agent在面对老用户时真正做到越用越懂你。这两条线都还在摸索中但方向已经足够清晰。对刚上手Agents的开发者我的建议很明确先用AgentExecutor或LangGraph的create_react_agent搭一个最简单的两工具Agent亲手观察它的决策轨迹理解ReAct循环的天然节奏然后再逐步叠加记忆、人工确认、多Agent协作。别一上来就整复杂架构Agent不是写多就厉害的而是调试出来的。