从会聊天到能干活:ReAct Agent 推理循环与 LangChain 工程实践

发布时间:2026/11/4 4:44:24
从会聊天到能干活:ReAct Agent 推理循环与 LangChain 工程实践 1. 从会聊天到能干活Agent 到底跨过了哪条线很多人第一次接触 Agent 这个概念时脑子里浮现的画面是一个更聪明的聊天机器人。我一开始也这么想直到真正动手把一个大模型接上工具、让它自己去查数据、算结果、写文件才意识到这两者之间隔着的不是一点点智能差距而是一整套工程范式的切换。普通的大模型对话本质上是文本到文本的映射你给它一段话它给你一段话中间没有任何外部世界的交互。它不知道今天的天气不知道你数据库里有多少条订单更没法帮你把一份报表存到磁盘上。而 Agent 要解决的核心问题是让模型从生成文本进化到执行任务——它需要能感知环境、做出决策、调用工具、观察结果然后根据结果决定下一步干什么。这个循环才是 Agent 的灵魂。我写这篇笔记的出发点很实际网上讲 Agent 概念的文章一抓一大把但真正把为什么需要 ReActAgentExecutor 到底在循环什么tool 装饰器背后发生了什么这些细节讲透的并不多。如果你正在跟着 LangChain 的教程敲代码却对每一行的意图似懂非懂或者你面试时被问到手写一个 ReAct Agent就卡壳那这篇内容应该能帮到你。我会从范式转变讲起把 ReAct 的推理循环拆开再落到 LangChain 里 AgentExecutor 和 tool 的具体实现最后聊聊我在实际调试中踩过的那些坑。需要提前说明的是Agent 这个领域迭代极快不同版本的 LangChain 在 API 上差异不小。我下面讲的核心机制是相对稳定的但具体函数签名你最好对照自己安装的版本来验证。这一点我在踩坑部分会再强调。2. 生成文本和执行任务差的不是模型能力而是架构2.1 一个生活化的类比顾问和助理的区别我特别喜欢用一个类比来解释这个转变。普通的大模型像一个坐在会议室里的顾问你问他问题他凭自己的知识给你建议但他不出门、不打电话、不碰任何系统。而 Agent 更像一个有权限的助理你交代一件事他会自己规划步骤该查资料就查资料该发邮件就发邮件遇到问题还会回来问你。这个区别的关键在于顾问的知识边界是固定的训练时学到的东西而助理的能力边界是可以扩展的你给他什么工具他就能干什么。所以 Agent 的核心不是模型更聪明而是模型被放进了一个可以行动的架构里。2.2 为什么单纯堆上下文解决不了问题有人可能会想那我是不是把工具的使用说明、数据库的内容全都塞进 prompt 里让模型直接输出结果就行了这个思路在小规模场景下勉强能用但很快就会撞墙。第一上下文长度是有限的。你不可能把整个数据库塞进去。第二模型无法执行副作用操作。它只能生成文本没法真的去调用一个 API、写一个文件、发一个请求。第三多步推理的可靠性会急剧下降。让模型一次性输出先查 A再根据 A 算 B最后用 B 生成 C的完整链路中间任何一步出错整个结果就废了而且你没法干预。Agent 架构的价值就在于把这三点都解决了工具调用让模型能触达外部世界循环机制让它可以分步执行并根据反馈调整而每一步的中间结果都是可观测、可干预的。2.3 Agent 的最小构成要素拆到最底层一个能跑起来的 Agent 至少需要四个部件一个会推理的大脑通常就是大语言模型负责理解任务、规划步骤、决定调用哪个工具。一组可调用的工具函数、API、检索器都算它们是 Agent 伸向外部世界的手。一个执行循环负责把模型的决策变成实际的工具调用再把结果喂回给模型。一份记忆或状态记录已经做了什么、得到了什么避免重复劳动或丢失上下文。LangChain 这套框架本质上就是把这四个部件标准化了。理解了这一点再看 AgentExecutor 和 tool就不会觉得它们是一堆莫名其妙的抽象。3. ReAct 循环Agent 推理与行动交替的底层节奏3.1 ReAct 这个名字到底指什么ReAct 是 Reasoning 和 Acting 的合成词出自一篇很有影响力的论文。它的核心思想非常朴素让模型在每一步都先想一下再做一下然后根据做的结果继续想。这个想—做—看的循环就是 Agent 的心跳。具体来说模型在每一轮会输出类似这样的内容Thought: 我需要先知道当前北京的天气 Action: get_weather Action Input: {city: 北京}然后框架会解析出 Action 和 Action Input真正去调用get_weather这个工具把返回结果作为 Observation 拼回上下文Observation: 北京今天晴气温 12 到 24 摄氏度模型看到这个 Observation 后继续下一轮 Thought直到它认为任务完成输出 Final Answer。3.2 为什么必须交替而不是一次性规划这里有个很多人会忽略的设计哲学。你完全可以设计一个先让模型输出完整计划再逐步执行的架构但 ReAct 选择了交替进行原因是现实任务充满不确定性。举个我实际遇到的例子我让 Agent 去查某只股票的最新价格然后根据价格决定是否发提醒。如果一次性规划模型可能会假设查价格一定成功直接规划出后续步骤。但万一接口超时、返回空值、或者股票代码写错了呢交替执行的好处是每一步的 Observation 都会成为下一步决策的依据模型可以动态调整——发现查不到就换个数据源发现价格异常就多查一次确认。这种走一步看一步的策略牺牲了一点效率换来的是鲁棒性。在真实环境里这个取舍几乎总是值得的。3.3 Thought 这一环为什么不能省有些简化版的实现会去掉 Thought直接让模型输出 Action。我试过效果明显变差。Thought 的作用不只是给人类看它实际上是模型的草稿纸。当模型被迫先写出我现在需要做什么、为什么这么做时它的后续决策质量会显著提升。这跟人做复杂题要先在草稿纸上列思路是一个道理。而且 Thought 对调试极其友好——当 Agent 行为异常时你第一眼要看的就是它的 Thought往往问题就藏在那句推理里。提示如果你在生产环境里想省 token可以考虑对 Thought 做截断或摘要但不建议完全去掉。省下的那点成本通常抵不上决策质量下降带来的损失。3.4 循环的终止条件ReAct 循环不会永远跑下去它需要明确的终止条件。常见的有三种模型主动输出 Final Answer这是最理想的正常结束。达到最大迭代次数防止模型陷入死循环LangChain 里通过max_iterations控制。触发异常或超时工具报错、网络中断等。我在实际项目里踩过一个坑早期没设max_iterations结果模型因为一个工具总是返回它看不懂的格式反复调用同一个工具硬生生把 token 烧光了。所以这个参数不是可选项是必选项。具体设多少取决于你的任务复杂度我一般从 10 到 15 起步复杂任务再往上调。4. LangChain 里 AgentExecutor 究竟在循环什么4.1 从一段最小可运行代码说起光讲原理容易飘我们直接看代码。下面是一个用 LangChain 搭建的最简 Agentfrom langchain.agents import AgentExecutor, create_react_agent from langchain.tools import tool from langchain_openai import ChatOpenAI tool def get_weather(city: str) - str: 查询指定城市的天气。输入城市名返回天气描述。 # 实际项目里这里会调用真实天气 API return f{city}今天晴气温 12 到 24 摄氏度 llm ChatOpenAI(modelgpt-4o-mini, temperature0) tools [get_weather] agent create_react_agent(llm, tools, prompt) executor AgentExecutor( agentagent, toolstools, max_iterations10, verboseTrue, ) result executor.invoke({input: 北京今天适合穿外套吗}) print(result[output])这段代码看起来简单但每一行背后都有讲究。create_react_agent负责把工具描述和 ReAct 的提示模板组装起来AgentExecutor负责跑循环verboseTrue让你能看到每一轮的 Thought、Action、Observation。4.2 AgentExecutor 内部的一次完整循环把verboseTrue打开后你会看到类似这样的输出 Entering new AgentExecutor chain... Thought: 用户想知道北京今天适不适合穿外套我需要先查天气 Action: get_weather Action Input: {city: 北京} Observation: 北京今天晴气温 12 到 24 摄氏度 Thought: 气温在 12 到 24 度之间早晚偏凉建议穿外套 Final Answer: 北京今天晴气温 12 到 24 摄氏度早晚偏凉建议穿一件外套。 Finished chain.这背后 AgentExecutor 做的事情拆开看是这样的把用户输入和工具描述拼成 prompt发给模型。解析模型输出判断是 Action 还是 Final Answer。如果是 Action找到对应工具解析参数执行调用。把工具返回结果作为 Observation 拼回上下文。回到第 1 步直到出现 Final Answer 或触发终止条件。理解了这个循环你就能明白为什么 Agent 的调试和普通 LLM 调用完全不同——你要调试的是一个多轮的状态机而不是一次性的输入输出。4.3 解析器最容易被忽视却最容易出问题的一环AgentExecutor 里有个低调但关键的组件输出解析器。模型输出的是一段文本框架需要从中提取出结构化的 Action 和 Action Input。ReAct 用的是基于文本的解析靠正则匹配Action:和Action Input:这些标记。问题就出在这里。如果模型输出的格式稍微偏一点——比如多了一个换行、用了中文冒号、或者把 JSON 写成了 Python 字典——解析就可能失败。我遇到过模型把Action Input写成{city: 北京}和{city: 北京}两种风格混用的情况前者是合法 JSON后者不是解析器直接报错。这也是为什么现在越来越多的实现转向原生工具调用tool calling。让模型直接输出结构化的函数调用请求由 API 层面保证格式比靠正则解析文本可靠得多。LangChain 也支持这种模式如果你的模型支持强烈建议优先用。4.4 max_iterations 和 early_stopping 的取舍max_iterations控制最大循环次数early_stopping_method决定达到上限后怎么办。默认是force意思是强制让模型基于现有信息给出一个答案另一个选项是generate让模型再生成一次。我的经验是对大多数任务force 就够了。因为达到迭代上限通常意味着任务本身有问题工具不好用、任务太模糊这时候强行再生成一次往往也是浪费。倒不如让它给个我尽力了但没完成的答案你去看 verbose 日志定位问题。5. tool 装饰器把普通函数变成 Agent 能理解的能力5.1 工具描述为什么比函数实现更重要新手最容易犯的错误是把工具当成普通函数来写——只关心逻辑对不对不关心描述写得好不好。但在 Agent 场景里模型是唯一的调用者它只能通过描述来判断该不该用这个工具。描述写得含糊模型就会用错工具或者该用的时候不用。看个反面例子tool def process(data: str) - str: 处理数据 ...这个描述等于没说。模型看到处理数据四个字根本不知道什么场景该调用它。正确的写法应该把用途、输入格式、返回内容都讲清楚tool def calculate_discount(original_price: float, discount_rate: float) - float: 根据原价和折扣率计算折后价格。 Args: original_price: 商品原价单位为元必须为正数 discount_rate: 折扣率取值范围 0 到 1例如 0.8 表示八折 Returns: 折后价格单位为元 return original_price * discount_rate5.2 参数类型和 docstring 是怎么被用上的tool装饰器会读取函数的签名和 docstring自动生成工具的 schema。参数名、类型注解、docstring 里的说明全都会变成模型看到的工具描述的一部分。所以你在写类型注解时不能偷懒——price: float和price: str对模型来说是两种完全不同的输入预期。我踩过一个坑有个工具的参数我写成了query: str但实际期望的是一个 JSON 字符串。模型看到str类型就老老实实传了个普通句子进来结果解析失败。后来我改成在 docstring 里明确写输入必须是 JSON 格式的字符串包含 field1 和 field2 两个字段问题才解决。类型注解管不了的事docstring 得补上。5.3 工具返回值的格式陷阱工具返回什么模型就看到什么。如果你返回一个巨大的 JSON模型可能被淹没在无关字段里如果你返回一个格式混乱的字符串模型可能理解不了。我的做法是返回值尽量精简、结构化、自解释。比如查询数据库返回结果时不要直接把原始行丢回去而是整理成共找到 3 条记录1. xxx2. xxx3. xxx这种模型一眼能读懂的形式。如果数据量大考虑只返回摘要加关键字段。还有一个细节工具报错时不要直接抛异常。异常会中断整个 Agent 循环而如果你把错误信息作为正常返回值传回去模型反而有机会自我修正。比如tool def query_order(order_id: str) - str: 根据订单号查询订单状态。 try: result db.query(order_id) return f订单 {order_id} 状态{result.status} except OrderNotFound: return f未找到订单 {order_id}请确认订单号是否正确这样模型看到未找到订单就知道该换个订单号或者向用户确认而不是整个流程崩掉。5.4 工具数量多了怎么办当工具从三五个涨到几十个模型的选择准确率会明显下降。这时候有几个策略分组把工具按领域分组先让模型选组再选具体工具。动态筛选根据当前对话上下文只把相关的工具塞进 prompt。描述优化确保每个工具的描述都有明确的什么时候用和什么时候不用。我在一个项目里工具数量到了二十多个模型经常在相似工具之间选错。后来我给每个工具的描述开头都加了一句当用户需要 XXX 时使用本工具准确率提升很明显。这个技巧看起来土但真的管用。6. 手写一个 ReAct Agent不依赖框架才能真正理解它6.1 为什么要手写一遍用 LangChain 搭 Agent 很快但如果你只会调 API遇到问题就会抓瞎。我强烈建议每个想深入 Agent 的人都手写一遍最小实现。写完之后你对 AgentExecutor 的每一行都会有新的理解。下面是一个不依赖任何 Agent 框架的手写版本只用最基础的模型调用import json import re def run_react_agent(llm, tools, user_input, max_steps10): tool_desc \n.join( f- {name}: {fn.__doc__} for name, fn in tools.items() ) prompt f你可以使用以下工具 {tool_desc} 请用以下格式回答 Thought: 你的思考 Action: 工具名必须是上面列出的之一 Action Input: 工具的输入 ...重复 Thought/Action/Action Input Thought: 我已经知道答案了 Final Answer: 最终答案 用户问题{user_input} for step in range(max_steps): output llm(prompt) if Final Answer: in output: return output.split(Final Answer:)[-1].strip() action re.search(rAction:\s*(.), output) action_input re.search(rAction Input:\s*(.), output) if not action or not action_input: prompt \n格式错误请严格按照要求输出。 continue tool_name action.group(1).strip() tool_input action_input.group(1).strip() if tool_name not in tools: observation f工具 {tool_name} 不存在 else: try: observation tools[tool_name](tool_input) except Exception as e: observation f工具执行出错{e} prompt f\n{output}\nObservation: {observation}\n return 达到最大步数任务未完成6.2 手写版本暴露出的真实问题跑完这段代码你会发现几个用框架时被隐藏的问题第一格式解析极其脆弱。模型稍微不听话正则就匹配不到整个循环就卡住。这就是为什么生产环境要用原生工具调用。第二上下文会越来越长。每一轮的 Thought、Action、Observation 都往 prompt 里塞几轮下来 token 消耗飞快。真实项目里必须做上下文管理比如只保留最近几轮或者对历史做摘要。第三错误处理全靠自己。工具报错、模型输出乱码、参数解析失败每一种情况你都得手动兜底。框架帮你处理了大部分但代价是你不知道它到底做了什么。6.3 从手写到框架的认知跃迁手写一遍之后再回头看 LangChain 的 AgentExecutor你会发现它做的无非就是把上面这段循环工程化、把解析做得更鲁棒、把错误处理做得更完善、把可观测性做得更好。它没有魔法只是把脏活累活封装了。有了这个认知你在用框架时就不会盲目——你知道每一层抽象背后对应的是什么出问题时也知道该往哪个方向查。7. 调试 Agent 时我踩过的那些坑7.1 工具描述里的隐形歧义有一次我做了两个工具search_products和search_articles描述分别是搜索商品和搜索文章。结果模型经常把用户问的找一下关于手机的内容路由到search_articles因为内容这个词让它联想到了文章。问题出在描述太短没有界定边界。后来我把描述改成当用户想购买或查询具体商品信息时使用和当用户想阅读资讯、教程、评测类文章时使用歧义就消失了。工具描述要写使用场景而不只是功能。7.2 模型陷入重复调用的死循环前面提过模型有时会反复调用同一个工具。除了设max_iterations还有个更根本的解法在 Observation 里给出明确的下一步提示。比如工具返回空结果时不要只返回无数据而是返回未找到相关数据建议尝试更换关键词或确认查询条件。模型看到这个提示往往会调整策略而不是原地打转。7.3 参数传递的类型错位模型输出的 Action Input 是文本但你的工具可能期望整数、布尔值或复杂对象。这个转换如果没处理好就会报类型错误。我的做法是在工具内部做一次容错转换tool def set_reminder(minutes: str) - str: 设置一个提醒minutes 为多少分钟后提醒传入数字字符串。 try: m int(minutes) except ValueError: return f无法解析时间参数 {minutes}请传入纯数字 # ... 设置提醒逻辑 return f已设置 {m} 分钟后的提醒把转换和校验放在工具内部比指望模型每次都输出正确类型要可靠得多。7.4 verbose 日志的正确读法verboseTrue输出的日志信息量很大但很多人不知道怎么读。我的习惯是从后往前看先看最后的 Final Answer 对不对如果不对再往前找是哪一步的 Observation 出了问题然后看那一步的 Thought 是不是推理跑偏了。大部分问题都能通过这个顺序快速定位。7.5 版本差异带来的玄学 bugLangChain 的 API 变动频繁initialize_agent、create_react_agent、AgentExecutor这些接口在不同版本里参数名和用法都可能不一样。我遇到过照着旧教程写的代码在新版本里直接报错的情况。所以遇到莫名其妙的报错第一件事是确认版本然后去查对应版本的官方文档而不是在网上搜到的旧文章里找答案。8. 从能跑到好用Agent 落地的几个进阶思路8.1 给 Agent 加上记忆基础的 ReAct Agent 是无状态的每次对话都是全新的。要让它记住之前的交互需要引入记忆组件。最简单的做法是把历史对话拼进 prompt但这样上下文会膨胀得很快。更优雅的方案是用摘要记忆或向量检索记忆只把相关的历史片段召回。我在一个客服场景里用过摘要记忆每轮对话结束后让模型把关键信息压缩成一句话存起来下次对话时把摘要拼进 prompt。这样既保留了上下文又控制了长度。8.2 用 LangGraph 处理更复杂的流程当任务不再是简单的想—做—看循环而是有分支、有并行、有回退的复杂流程时LangChain 的线性 AgentExecutor 就不够用了。这时候可以考虑 LangGraph它把 Agent 建模成一张状态图节点是操作边是流转条件。LangGraph 和 LangChain 的关系简单说就是LangChain 提供了构建 Agent 的积木模型、工具、解析器LangGraph 提供了编排这些积木的流程图。对于需要精确控制流程的场景LangGraph 的表达能力更强。8.3 可观测性不能省Agent 一旦上了生产可观测性就是生命线。你需要知道每次调用花了多少 token、走了几轮循环、调用了哪些工具、哪一步最耗时、失败率是多少。这些数据不仅能帮你优化成本更是排查问题的唯一依据。我一般会在 AgentExecutor 外面包一层日志记录把每轮的输入输出、工具调用、耗时都落盘。出问题时这份日志比任何猜测都有用。8.4 什么时候不该用 Agent最后说个反直觉的观点不是所有任务都适合用 Agent。如果你的任务流程是固定的、可预测的那用传统的 workflow 编排甚至就是几个 if-else会比 Agent 更稳定、更便宜、更快。Agent 的价值在于处理不确定性和开放性——当任务步骤无法预先确定、需要根据中间结果动态决策时它才真正发挥威力。我见过不少项目为了用上 Agent而用 Agent结果把一个本来很简单的流程搞得又慢又不稳定。选型的时候先问自己这个任务真的需要动态决策吗如果答案是否定的老老实实用确定性方案。Agent 开发这条线从理解 ReAct 循环到能独立调试一个多工具 Agent中间要跨的坎不少。但只要把推理—行动—观察这个核心循环吃透剩下的都是工程细节的打磨。我自己的体会是每踩一个坑对这个循环的理解就深一层。等你哪天看到 verbose 日志能一眼看出模型在哪一步跑偏基本就算入门了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询