公司动态
LangChain Agent执行流程拆解:从ReAct到工具调用
先问大家一个问题当你在开发一个完整的 AI Agent 时最头疼的是什么可能很多人会说“模型能力不够”“工具不好接入”但真正把一个 Agent 从 Demo 推到可落地状态时你会发现最复杂的其实是Agent 的执行流程模型是怎么决定调用哪个工具的工具返回结果之后又要做什么循环什么时候终止如果中间报错了整个流程又该怎么恢复这些问题如果只靠“调 LLM 拼 Prompt”来解决会非常容易失控。LangChain 在 0.6 到 0.9 这个版本区间对 Agent 模块做了大量重构把很多隐式的执行逻辑收敛到了框架内部让开发者可以把更多精力放在业务本身。但框架收敛逻辑并不代表我们不需要理解执行流程。恰恰相反只有把执行链路拆解清楚才能在真正遇到“Agent 不按预期工作”的时候快速定位问题。本文就把 LangChain 6-9 版本中的 Agent 执行流程完整拆开从核心概念、环境准备、组件原理到完整实战和常见排查思路一次性讲透。无论你是刚开始接触 langchain 入门还是在项目中已经用到 ai agent 开发这篇内容都能给你一个清晰的框架。1. 背景与核心概念1.1 什么是 Agent在了解 LangChain 的 Agent 执行流程之前我们需要先明确一件事Agent 到底是什么它和我们平时调大模型接口有什么区别。如果只是调用大模型流程非常简单你给模型一个 Prompt模型返回一个回答。这个回答可能是文本、JSON、代码片段但本质上模型只是在“生成内容”。而 Agent 是一个拥有“决策能力”的智能体。它不只是回答问题而是可以为了完成目标自主选择调用什么工具、按什么顺序执行、如何利用工具返回的信息继续下一步。举个例子用户问“上海今天适合穿什么衣服”。普通模型只能根据它的训练知识给出模糊建议。但一个有工具的 Agent可以调用天气插件查询上海实时天气拿到温度、湿度、风力数据再决定告诉用户穿什么衣服。这个过程中Agent 是“主动”的它在不断决定“下一步做什么”。1.2 LangChain Agent 解决什么问题LangChain 提供了一套标准化的 Agent 构建框架。在 LangChain 6-9 这个版本区间框架解决的问题可以归纳为三块Agent 决策链路让模型根据用户输入和历史信息推理出下一步应该调用哪个工具。工具调用与结果回传封装了 Function Calling 和 Tool Calling 的标准流程让 Agent 可以稳定地触发工具并拿到结构化结果。循环控制Agent 不是跑一次就结束而是反复执行“推理 - 行动 - 观察”的循环直到得到最终答案或达到最大迭代次数。这些能力叠加起来就是 Agent 执行流程的核心骨架。1.3 Agent 与 LangGraph 的关系如果你在搜索 langchain agent 相关内容一定会频繁看到一个叫 LangGraph 的词。它们并不是替代关系而是侧重点不同。LangChain 的 Agent 模块也就是 AgentExecutor提供的是标准化的执行流程封装适合快速搭建 AgentLangGraph 则是一个更底层的编排框架可以让你用图的方式手动控制每个节点的状态转换。简单理解AgentExecutor 自动化的循环引擎 LangGraph 可自定义的编排引擎在 LangChain 6-9 版本中AgentExecutor 底层也已经可以基于 LangGraph 实现但在使用层面AgentExecutor 隐藏了图的复杂度对多数业务场景来说已经足够了。如果你要对执行流程做非常细粒度的控制再考虑直接上手 LangGraph。2. 环境准备与版本说明2.1 基础环境在开始代码实战前先把环境准备好。本文示例基于 Python 3.10LangChain 版本以 0.6.x 至 0.9.x 区间为主。由于该区间内部分 API 仍在迭代实际使用时请以你自己的环境中安装的版本为准。# 建议使用虚拟环境 python -m venv langchain-agent-demo source langchain-agent-demo/bin/activate # 安装 LangChain 本体 pip install langchain # 安装 LangChain 官方社区包 pip install langchain-community # 安装 OpenAI 集成包本文示例使用 OpenAI 兼容接口 pip install langchain-openai # 安装用于加载环境变量的库 pip install python-dotenv如果你使用的是国内可访问的大模型服务比如 DeepSeek、通义千问、Kimi、智谱等只要它兼容 OpenAI 的接口格式也可以通过langchain-openai或自定义ChatOpenAI的base_url来接入。本文示例会预留这部分配置说明。2.2 环境变量配置在项目根目录创建.env文件内容如下# 你的模型 API Key OPENAI_API_KEYyour-api-key-here # 如果你使用的是兼容 OpenAI 接口的服务可以指定 base_url OPENAI_API_BASEhttps://api.example.com/v1 # 实际项目中推荐使用的模型名称 MODEL_NAMEgpt-4o-mini注意OPENAI_API_BASE是可选的。如果使用 OpenAI 官方服务不需要配置这一项。加载方式import os from dotenv import load_dotenv load_dotenv() api_key os.getenv(OPENAI_API_KEY) base_url os.getenv(OPENAI_API_BASE) model_name os.getenv(MODEL_NAME, gpt-4o-mini)2.3 项目结构本文示例项目结构如下langchain-agent-demo/ ├── .env ├── main.py └── tools/ ├── __init__.py └── weather_tools.py我们通过一个“天气查询 计算器”的 Agent 案例完整演示 Agent 执行流程。这个案例虽然业务简单但能覆盖 Agent 执行链路中的大多数关键环节。3. Agent 执行流程整体拆解3.1 一次 Agent 调用的完整链路在没有理解执行流程之前看 Agent 的代码会觉得很“玄学”明明只是调了一个agent_executor.invoke({input: ...})为什么模型自己就知道该用哪个工具实际上一次完整的 Agent 调用在框架内部经历了这样的过程用户输入 ↓ Prompt 组装System Prompt 用户输入 历史记忆 工具描述 ↓ 调用 LLM 进行推理 ↓ LLM 返回 Action决策 ↓ 判断是否有工具调用 ├── 没有工具调用 - 直接生成最终回答 - 结束 └── 有工具调用 ↓ 执行对应 Tool ↓ 获得 Observation观察结果 ↓ 将 Observation 拼接到 Prompt再次调用 LLM ↓ 重复以上过程直到模型生成最终回答或达到最大迭代次数这个循环就是 Agent 执行流程的核心学术上通常称为ReAct 范式Reasoning推理 Acting行动。3.2 核心组件分工在 LangChain 6-9 版本的 Agent 架构中参与执行流程的组件有五个它们各司其职Agent 决策器Agent负责生成“下一步行动”的决策。它本质上是一个绑定了一堆工具描述的大模型。工具ToolsAgent 可以调用的外部能力集合比如查天气、执行代码、搜索网页、操作数据库。执行器AgentExecutor负责编排整个循环。它把 Agent 的决策翻译成实际动作把工具返回的结果再喂给 Agent循环往复。记忆Memory保存 Agent 执行过程中的关键上下文避免模型“失忆”。输出解析器OutputParser把模型输出的原始文本解析成结构化的AgentAction或AgentFinish。这一步非常关键因为大模型输出可能是文本、JSON甚至夹杂着多余的描述。我们用一个表格来总结组件作用执行流程中的位置Agent决策下一步行动每个循环的第一步Tools提供外部执行能力Agent 决定调用时触发AgentExecutor控制循环流程整个流程的调度者Memory保存中间状态和上下文每次调用前读取调用后更新OutputParser解析模型输出Agent 输出之后工具调用之前3.3 工具调用协议LangChain 6-9 版本中Agent 的工具调用主要基于大模型的 Function Calling / Tool Calling 能力。这意味着模型在推理过程中会输出一个结构化的“函数调用请求”而不是普通的自然语言。这个请求通常包含name工具名称arguments参数字典Agent 执行器拿到这个请求后会去工具注册表中找到对应名称的工具然后以arguments为参数执行工具函数。这个设计最大的价值在于工具调用的决策和工具的执行被彻底分离了。模型只负责“决定”不负责“实现”。这样即使底层工具换了实现方式只要保持名称和参数结构一致Agent 的决策逻辑完全不需要改。4. 从零实现一个 Agent完整实战4.1 创建项目结构首先创建项目目录mkdir langchain-agent-demo cd langchain-agent-demo然后按前面提到的结构创建好tools包。4.2 定义工具工具是 Agent 执行流程中真正“干活”的部分。我们先实现两个工具天气查询和加法计算。文件路径tools/weather_tools.pyimport random from datetime import datetime from langchain_core.tools import tool tool def get_weather(city: str) - str: 查询指定城市的实时天气信息。 参数 city: 城市名称例如 北京、上海 返回 包含温度、天气状况、风力等级等信息的字符串 # 这里使用模拟数据实际项目中可以替换为真实天气 API 调用 temperature random.randint(-5, 35) conditions [晴, 多云, 阴, 小雨, 中雨, 大雨, 雪] weather random.choice(conditions) return ( f{city}当前天气{weather}温度 {temperature}℃ f风力 3-4 级。时间为 {datetime.now().strftime(%Y-%m-%d %H:%M)}。 ) tool def add_numbers(a: int, b: int) - int: 计算两个整数的和。 参数 a: 第一个整数 b: 第二个整数 返回 两个整数的和 return a b几点说明tool装饰器是 LangChain 推荐的工具定义方式。它可以把一个普通的 Python 函数包装成 LangChain 认识的 Tool 对象。函数名和 docstring 非常重要。在 Agent 执行流程中函数名会被当作工具的名称docstring 中关于参数和返回值的描述会被发给模型作为模型决策是否调用该工具的依据。参数类型标注也很关键。LangChain 会根据类型标注生成工具的参数 Schema模型在生成 Function Calling 请求时会严格按照这个 Schema 来生成 JSON 参数。4.3 创建 Agent 执行器在项目根目录创建main.py先看整体代码再逐行解释。文件路径main.pyimport os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain.agents import AgentExecutor, create_react_agent from langchain.prompts import PromptTemplate from langchain_core.tools import Tool from tools.weather_tools import get_weather, add_numbers # 加载环境变量 load_dotenv() # 初始化 LLM llm ChatOpenAI( modelos.getenv(MODEL_NAME, gpt-4o-mini), api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_API_BASE), temperature0, ) # 将 tool 装饰的函数包装成 Agent 可用的工具列表 tools [ Tool( nameget_weather, funcget_weather.func, # 注意这里需要传入原始函数 description查询指定城市的实时天气信息参数为城市名称例如北京, ), Tool( nameadd_numbers, funcadd_numbers.func, description计算两个整数的和参数为两个整数 a 和 b, ), ] # 构建 ReAct Agent 的 Prompt prompt PromptTemplate.from_template( 你是是一个智能助手可以调用工具来完成任务。 你可以使用以下工具 {tools} 工具名称列表 {tool_names} 回答时请使用以下格式 问题需要回答的输入问题 思考你应当思考如何回答这个问题 行动你应当采取的动作名称必须是 [{tool_names}] 之一 行动输入行动需要的输入参数必须是一个 JSON 对象 观察工具返回的结果 ... (这个思考/行动/行动输入/观察可以重复 N 次) 思考我已经拿到足够的信息 最终答案对原始问题的最终回复 开始 问题{input} 思考{agent_scratchpad} ) # 创建 Agent agent create_react_agent( llmllm, toolstools, promptprompt, ) # 创建执行器 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, max_iterations5, handle_parsing_errorsTrue, ) # 执行测试 if __name__ __main__: result agent_executor.invoke( {input: 上海今天天气怎么样顺便帮我算一下 123 456 等于多少} ) print(最终回答, result[output])4.4 代码关键点解析这段代码是 Agent 执行流程的入口其中有几个点需要重点理解。第一create_react_agent的作用。它负责把 LLM、工具列表、Prompt 组装成一个真正意义上的 Agent 决策器。这里的 ReAct 是 Agent 的一种实现范式核心思想就是让模型“先思考再行动再观察”循环下去这个 Agent 在执行流程中扮演“大脑”的角色。第二Prompt 中的{agent_scratchpad}。这是理解 Agent 执行流程的关键变量。agent_scratchpad是 Agent 的“草稿纸”它会记录前面所有“思考 - 行动 - 观察”的过程。在循环中每次工具返回结果后框架会把结果追加到agent_scratchpad然后重新传给模型让模型基于完整的执行历史做下一步决策。简单来说agent_scratchpad就是 Agent 的记忆载体。第三AgentExecutor的参数。verboseTrue会打印每次循环中模型的思考过程和工具执行情况是调试 Agent 最有力的工具。max_iterations5限制最大循环次数防止 Agent 陷入死循环。handle_parsing_errorsTrue会在模型输出无法解析时自动尝试修复而不是直接崩溃。4.5 运行与验证执行以下命令运行程序python main.py预期输出会大致分为两部分Agent 执行过程中的循环日志以及最终的回答。由于verboseTrue你会看到类似这样的输出 Entering new AgentExecutor chain... 思考我需要查询上海的天气同时计算两个数字的和。先查天气再算数。 行动get_weather 行动输入{city: 上海} 观察上海当前天气晴温度 18℃风力 3-4 级。时间为 2025-01-10 14:30。 思考天气信息已经拿到接下来计算 123 456。 行动add_numbers 行动输入{a: 123, b: 456} 观察579 思考两个结果都已经拿到我可以在最终答案中一起回复用户。 最终答案上海当前天气晴温度 18℃风力 3-4 级。另外123 456 579。 Finished chain.观察这个输出你会发现一个完整的 Agent 执行流程被分成了两个“思考 - 行动 - 观察”循环正好对应两次工具调用。这就是 Agent 与普通 LLM 调用的最大区别它不是一次性生成回答而是经过多轮“推理 行动”后才形成最终答案。4.6 多种 Agent 范式说明ReAct Agent 是 LangChain 中最经典、也最容易理解的 Agent 范式适合新手入门。但在 LangChain 6-9 版本中Agent 范式并不只有这一种。常见的还有OpenAI Tools Agent专门针对 OpenAI Function Calling 能力优化的范式结构化输出更稳定适合生产环境。Plan-and-Execute Agent先让模型制定一个整体计划再逐步执行。适合任务步骤较多、依赖关系明确的场景。Self-Ask Agent通过不断问自己子问题来完成任务适合需要多跳推理的问答场景。不同的 Agent 范式底层执行流程略有差异但宏观的“推理 - 行动 - 观察”循环是不变的。实际项目中你可以根据任务的复杂度和模型的 Function Calling 能力来选择。记住LangChain 的 Agent 设计允许你切换不同范式而不需要重写业务代码。这也是 LangChain 的核心价值之一。5. Agent 执行流程中的记忆机制5.1 为什么 Agent 需要记忆前面我们提到agent_scratchpad是 Agent 在单次任务中的短期记忆。但在真实业务中用户可能会和 Agent 进行多轮对话比如用户先问“上海天气怎么样”Agent 回复后用户又问“那北京呢”此时 Agent 需要知道“那北京呢”指的是“北京的天气怎么样”这就是跨轮对话的记忆能力。在 Agent 执行流程中Memorize 和agent_scratchpad是两种不同维度的记忆agent_scratchpad单次任务内的执行轨迹记忆。Memory跨任务、跨对话轮的长期记忆。5.2 如何给 Agent 添加记忆LangChain 中AgentExecutor本身不带长期记忆能力需要手动传入memory参数。最常见的方式是结合ConversationBufferMemory。示例代码如下from langchain.memory import ConversationBufferMemory # 创建记忆对象 memory ConversationBufferMemory( memory_keychat_history, return_messagesTrue, ) # 在 AgentExecutor 中传入 memory agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, max_iterations5, memorymemory, )同时在 Prompt 模板中增加{chat_history}变量prompt PromptTemplate.from_template( ... 问题{input} 聊天历史{chat_history} 思考{agent_scratchpad} )这样Agent 在每一轮决策时都能看到之前的对话历史就能理解“那北京呢”是在问天气。5.3 记忆使用的注意事项在实际项目中盲目地给 Agent 加记忆也会带来问题上下文长度膨胀随着对话轮次增加记忆内容越来越长最终会超过模型的上下文窗口。无效信息干扰不是所有历史都对当前决策有帮助冗余信息可能让模型的注意力分散。解决办法通常有两个方向使用ConversationSummaryBufferMemory对老消息做摘要只保留最近 N 条完整消息引入向量数据库只根据语义相似度检索与当前问题相关的历史片段。在 LangChain 6-9 版本中这两类方案都有对应的实现类可以直接使用关键是你要理解“记忆的本质是把 Agent 的历史上下文转化为有用信息”。6. 常见问题与排查思路在 Agent 开发中你遇到的很多问题并不会直接报错而是表现为“Agent 行为不符合预期”。下面我整理了几个高频问题也是 langchain 面试中经常被问到的问题。6.1 Agent 陷入死循环现象Agent 反复调用同一个工具结果都一样然后又继续调用没有要结束的意思。原因模型判断工具结果“不够好”想要再次执行。Prompt 中没有明确告诉模型“工具结果已经拿到应该总结回答”。max_iterations设置过大。排查步骤打开verboseTrue观察每次循环中模型的“思考”内容。检查工具返回的结果是否有效。如果工具本身返回错误或空结果模型可能无法据此完成推理。检查 Prompt 中是否明确给出了停止条件。解决方案适当降低max_iterations例如设置为 3-5。在工具返回的结果中增加明确的状态标识如success: true/false。在 Prompt 中加入规则“如果工具已经返回结果请直接给出最终答案。”6.2 模型不调用工具直接给答案现象用户问了一个需要工具才能回答的问题但 Agent 直接凭自己的知识回答了而且可能是错误的。原因工具描述不清晰模型没有意识到需要调用工具。模型版本太弱Function Calling 能力有限。Prompt 中工具列表的权重不够。解决方案重新描述工具明确说明“该工具用于解决哪类问题”。在 Prompt 中强调“如果需要实时数据必须调用工具”。如果条件允许换用 Function Calling 能力更强的模型。6.3 工具参数解析失败现象模型返回了工具调用意图但生成的参数和工具函数签名不匹配例如缺少必填参数、参数类型错误。原因工具函数的参数名、类型标注与模型从 docstring 中理解的语义有偏差。模型输出了非法的 JSON 格式。解决方案严格使用类型标注并为每个参数写清楚说明。在工具函数中加入参数校验和异常处理避免抛出无法恢复的错误。开启handle_parsing_errorsTrue让 Agent 有机会根据错误信息重新生成。6.4 上下文窗口超限现象在 Agent 执行过程中报错提示context length exceeded。原因agent_scratchpad、记忆和工具结果共同占用的 token 数超过了模型的上下文窗口。解决方案减少max_iterations。使用摘要记忆替代完整缓冲记忆。减小工具返回结果的长度例如只返回关键字段。6.5 常见问题汇总表问题现象常见原因排查思路Agent 死循环模型未理解停止条件 / 工具结果无效查看 verbose 日志优化 Prompt调低 max_iterations不调用工具工具描述不清晰 / 模型能力弱优化工具描述更换模型强化 Prompt 指令参数解析失败函数签名与模型理解不一致 / 非法 JSON完善类型标注和 docstring开启 handle_parsing_errors上下文超限记忆和调试信息占用太多 token使用摘要记忆限制工具返回结果长度工具返回错误未处理工具内部异常直接向上抛出在工具内部捕获异常返回错误信息字符串7. Agent 开发的最佳实践7.1 工具命名与描述规范工具的名称和描述是 Agent 决策的重要依据。在实际开发中建议遵循以下原则工具名使用动词 名词结构例如query_weather、send_email。描述中明确说明工具用途、参数格式、返回格式。如果工具可能失败在描述中说明失败时的返回结构。一个反例和一个正例反例calculate: 计算 正例calculate_sum: 计算两个整数的和参数为 a 和 b返回整数结果7.2 异常处理与容错设计工具函数内部应该具备完善的异常处理能力。tool def query_database(sql: str) - str: 执行 SQL 查询语句返回查询结果。 参数 sql: 合法的 SQL 查询语句 返回 查询结果的 JSON 字符串失败时返回 error 信息 try: # 实际执行 SQL result execute_sql(sql) return json.dumps(result, ensure_asciiFalse) except Exception as e: return json.dumps({error: str(e)}, ensure_asciiFalse)这样设计的好处是即使工具执行失败Agent 也能拿到一个结构化的错误信息并根据这个信息决定下一步是重试、换工具还是告诉用户无法完成。7.3 Prompt 设计要兼顾约束与自由Agent 的 Prompt 与普通 LLM 应用的 Prompt 要求类似但更需要强调“执行规则”还要给模型足够的推理空间。在编写 Agent Prompt 时建议包含以下元素角色定位告诉模型它是什么角色。工具列表说明明确列出所有可用的工具和用途。执行格式要求规定模型的输出格式思考、行动、行动输入、观察、最终答案。结束条件告诉模型“当你拿到足够信息时应该停止循环并给出最终答案”。边界说明告诉模型“如果所有工具都无法解决用户问题应该如实告知”。7.4 日志与可追踪性生产环境中的 Agent 问题非常隐蔽。为了快速定位问题一定要做到“全链路可追踪”。在 LangChain 6-9 版本中verboseTrue只能用于开发调试不适合直接打到生产日志里。更推荐的做法是使用AgentExecutor的callbacks参数自定义日志回调。在每次工具调用时记录工具名、入参、出参、耗时。在 LLM 调用时记录 prompt 和 response。这样当线上 Agent 出现问题时你可以完整回放它的决策过程而不是面对一个黑盒。7.5 安全与权限控制Agent 能调用工具意味着它拥有了“行动能力”。在实际项目中对工具能力必须有严格的权限边界。最小权限原则给 Agent 配置的工具只需要满足业务需求不要暴露无关能力。敏感操作二次确认涉及删除、修改、发送消息等敏感操作工具内部要增加确认机制。输入校验工具函数内部必须对模型传入的参数做合法性校验防止恶意构造参数。7.6 性能与成本优化Agent 的循环执行机制决定了它比普通 LLM 调用更耗时、更耗 token。优化方向主要有三个减少迭代次数通过优化 Prompt 让 Agent 更准确地选择工具减少无效循环。控制上下文长度在传给模型之前对工具结果和记忆做截断处理。优先使用小模型简单的工具推理可以使用小尺寸模型只有复杂推理才调用大模型。在实际项目中可以结合 LangChain 的分流机制根据任务复杂度动态选择模型这样能显著降低成本。8. 总结与实践建议本文围绕 LangChain 6-9 版本中的 Agent 执行流程讲清楚了五个核心问题Agent 是什么以及它和普通 LLM 调用的区别Agent 执行流程的完整闭环“推理 - 行动 - 观察 - 循环”Agent 核心组件Agent、Tools、AgentExecutor、Memory、OutputParser的职责划分如何从零构建一个可运行的 ReAct Agent 并理解它的每一步执行细节常见问题排查思路和 Agent 开发工程化相关的最佳实践。如果你刚开始接触 Agent 开发建议不要急着用 LangGraph 实现复杂编排先把本文中的 ReAct Agent 跑通打开verboseTrue看一遍完整的执行日志再根据业务需要逐步添加记忆、工具和异常处理。只有亲手观察过 Agent 的完整执行流程你才能真正理解“模型决策”和“工具执行”是如何协同工作的。接下来你可以继续深入了解LangChain 官方文档中关于 Agent 不同范式的对比LangGraph 的节点与状态管理机制掌握更底层的编排能力如何设计一套属于自己的 Tool 服务层方便 Agent 接入公司内部系统。Agent 开发的上手门槛并不高但要让 Agent 在真实项目中稳定、可控地执行任务拼的是对执行流程细节的理解和工程化能力。希望这篇文章能帮你迈过最难的那道坎。有任何执行流程相关的问题欢迎在评论区交流。