
这两年做 AI 应用开发如果你还没碰过 LangChain 生态那确实有点说不过去了。从最简单的“把 Prompt 接到大模型上”到复杂的多步骤工作流、智能体系统LangChain 几乎把开发过程中的方方面面都抽象成了可组合的模块。但问题也出在这LangChain 生态的东西太多了Chain、Agent、Graph、Memory、Tool、LangSmith、LangServe……刚接触的人很容易一上来就被文档淹没不知道从哪下手也不知道这些概念之间的关系。我自己从最早用 LangChain 0.0.x 版本搭文本链到后来用 AgentExecutor 做工具调用再到现在的 LangGraph 状态机编排一路踩过不少坑。如果只让我记住 LangChain 生态里最核心的三件事我会说是链式抽象与组合方式、Agent 的三大核心组件、以及从可控链走向更灵活代理架构的设计思路。这篇文章就把这三块内容完完整整拆开讲一遍适合刚入门 LangChain 的开发者也适合已经在用 LangChain 但还没系统梳理过内部关系的朋友。看完你应该能明白 LangChain 到底在干什么、Agent 为什么能自动决策、以及遇到问题该怎么排查。1. LangChain 生态全景链、代理、图三条主线1.1 从 Chain 到 Agent迭代的到底是什么最早 LangChain 的核心抽象是 Chain也就是“链”。一个链就是一系列步骤的有序组合取用户输入拼进 Prompt 模板发给大模型解析输出再传给下一个步骤。这种线性管道好处是直观、可控、容易调试特别适合固定的业务流程比如“翻译一段文字 → 提取关键词 → 生成摘要”每一步做什么都是写死的。但链解决不了动态决策问题。举个例子你想让模型写一篇包含天气数据的文章链式结构无法在生成过程中突然决定“先查一下上海当前的天气再把结果塞进文章里”。因为步骤是预先定义好的模型没有选择工具的权利。于是 LangChain 引入了 Agent也就是“代理智能体”。Agent 的核心不再是写死的流程而是把决策权交给大模型让它在每一步自己决定该调用哪个工具、参数是什么、结果出来后下一步做什么。这是从“固定流程”到“动态规划”的转变。但注意Agent 并不是对 Chain 的完全替代更像是一种更高层的抽象。Chain 里的那些组件——Prompt 模板、模型调用、输出解析器——在 Agent 里依然存在只是被重新组合成了“思考-行动-观察”的循环。1.2 LangGraph 为什么成了新的底座LangChain 发展到后期官方很明显把重心从 AgentExecutor 这种传统的循环式执行器转移到了 LangGraph 上。原因是 AgentExecutor 实在太“黑盒”了。它内部是一个 while 循环让模型反复输出动作直到任务结束或达到最大轮数。这个循环里发生了什么外部很难干扰也难调试。你想让它跑两步暂停一下等用户确认做不到。你想让某一步失败后自动回退到之前的节点很难写。LangGraph 的设计思路是拿“图”来管状态和流程。你把每个功能模块当成图里的一个节点把节点之间的跳转条件当成边整个 Agent 的运行就变成了一次有向图遍历。好处是每一跳在哪、为什么跳、状态如何变化都变得非常清晰。同时 LangGraph 支持持久化、支持人为中断、支持节点级别的重试和回退这些在真实生产环境里非常重要。所以现在看 LangChain 生态更像是一个三层结构底层用 LangGraph 做编排引擎中间层是 LangChain 提供的模型封装、提示模板、工具封装等开发组件上层则是 Agent 这样的应用形态。1.3 开发者该怎么看待这个生态分岔我自己的体会是别把“应该用 Agent 还是 Chain”当成单选题。这个选择应该基于你对任务确定性的判断如果业务流程完全确定比如固定的 RAG 管道召回 → 拼装 → 生成用 Chain 甚至直接手写代码都行稳定、便宜、好维护。如果流程有一定开放性比如“用户问什么都行可能需要查库、查接口、算数”那就必须上 Agent。如果你的 Agent 流程里有状态流转、条件分支、需要人工确认环节那就直接用 LangGraph。所以与其纠结“LangChain 是不是过时了、是不是要用 LangGraph 替代”不如理解为LangChain 是个组件库LangGraph 是可编排这些组件的过程引擎。两者配合起来才是完整的现代开发方式。下表是我做选型时的大致标准可以参考场景特征推荐方案原因步骤固定、输出结构稳定ChainLCEL 表达式开发快、token 消耗可控、容易定位问题需要模型自主选工具、动态决策AgentReAct 风格灵活性高能处理开放任务多步骤带分支、循环、人工确认LangGraph状态透明、可持久化、支持细粒度控制需要并发跑多个独立任务LCEL 的 RunnableParallel天然并行不需要自己写多线程2. 代理的三大核心组件拆解2.1 规划能力ReAct 不是纸上谈兵一个 Agent 的“智能感”主要来自规划能力也就是模型在每步决策时输出的思考轨迹。目前最主流的实现思路是 ReActReasoning Acting。思想很简单让模型先“想一想”输出思考过程再“做一步行动”决定调用哪个工具观察结果之后再想下一步循环直到得出最终答案。用 LangChain 的create_tool_calling_agent创建 Agent 时背后其实就是在做这么一件事。模型的输出不再只有最终文本而是一个结构化的tool_calls数组里面包含了工具名和参数。框架把这些工具调用分发给对应的函数执行把执行结果作为 Observation 返回给模型。关键点在于模型的规划能力高度依赖工具的描述质量和系统提示词。很多人搭 Agent 效果差不是模型不行而是工具定义太模糊。比如你给模型一个search_weather工具docstring 只写“查询天气”模型就不知道这个工具接受什么参数、返回什么格式、值不值得用。写上“查询指定城市的实时天气参数 city 为城市中文名返回气温、湿度、风力”这类完整描述后调用准确率会明显提升。另外规划并不等于“让模型自由发挥”。在 LangGraph 里你可以限制图结构比如规定节点 A 完成后必须经过一个“判断节点”再到 B 或 C。这就是“可控的智能”模型在节点内部决策但整个拓扑是你定的既保留了灵活性又保证了安全性。2.2 记忆管理短期与长期的信息分层如果没有记忆Agent 就是一个“每次对话都失忆”的状态机。用户上一轮说了“我喜欢简洁的回答”下一轮 Agent 就完全不记得了体验非常割裂。LangChain 生态里的记忆体系主要分两层短期会话记忆和长期知识记忆。短期记忆就是把对话历史传给模型。你可以在 Chain 里通过MessagesPlaceholder或直接传chat_history数组实现。这里有个很现实的问题历史越多token 费用越高响应也越慢。所以实战中几乎不会无限保留全部历史而是用“滑动窗口”保留最近 N 轮或者用“摘要记忆”把早期对话压缩成一段摘要。长期记忆则是把用户的偏好、事实性信息抽出来存进向量数据库或普通数据库需要时再检索回填到上下文里。LangChain 生态里已经有langmem这类专门做语义记忆的库工具会自动从对话中提取值得记住的信息。我的建议是短期记忆尽量简单别一上来就设计复杂结构长期记忆一定做好安全隔离别把 A 用户的记忆检索给 B 用户看到。这里想特别提醒一点所谓的“自动记忆”没有银弹。模型对记忆内容的提取是概率性的它不知道该记什么、该忘什么。更好的做法是把记忆分成几类用户显式声明的事实如“我叫张三”、对话中的偏好推断、历史决策记录。前两类可以自动提取最后一类最好手动控制别让模型自作主张。2.3 工具使用给模型一双真正的手Agent 的价值很大程度来自工具。模型自己不会查数据库、不会调用第三方 API、不会计算复杂的数学公式工具就是它伸向外部世界的“手”。所以怎么定义工具比怎么定义 System Prompt 更重要。LangChain 里定义工具有几种方式。最简单的是在普通函数上加tool装饰器from langchain_core.tools import tool tool def get_weather(city: str) - str: 查询指定城市的实时天气信息。 Args: city: 城市名称例如“北京”“上海”。 # 这里可以替换成真实天气 API 请求 return f{city}晴25℃微风你别小看这个装饰器它做了三件事把函数的签名和 docstring 转成 JSON Schema注册到可执行的工具列表并把调用逻辑封装成统一的接口。这意味着工具本身必须写得非常规范参数类型明确、描述清晰、返回结果稳定。工具调用失败也是一个高频问题。模型输出的 JSON 可能不合法工具内部可能抛异常。所以 LangChain 内部有ToolException机制和自动重试机制。我在实际项目中会把工具封装成“防御式”的函数对返回结果做异常兜底宁可返回一个错误字符串给模型也不要直接让整个流程崩溃。因为模型看到错误信息后还可以自救重试而进程崩溃就没机会了。3. 实操从一条简单链迁移到可运行 Agent3.1 起步版先搭一条可用的 Chain我建议所有初学者先不要碰 Agent先用 Chain 跑通一个最小闭环。LangChain 现代的写法是基于 LCELLangChain Expression Language用管道符串联组件。from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_openai import ChatOpenAI prompt ChatPromptTemplate.from_template( 用一句话解释什么是{concept}适合完全不懂技术的普通人。 ) model ChatOpenAI(modelgpt-4o-mini, temperature0.3) chain prompt | model | StrOutputParser() result chain.invoke({concept: 递归算法}) print(result)这段代码看起来简单但内部做了很多事模板渲染、模型调用、结果转成字符串。用|连接组件意味着每一步的输出自动变成下一步的输入比如prompt输出的 PromptValue 直接作为model的输入。这种设计非常像 Unix 管道哲学每个组件只关心自己的输入输出就行。如果你有多条独立的链想并发执行可以用RunnableParallelfrom langchain_core.runnables import RunnableParallel chain_a prompt_a | model | StrOutputParser() chain_b prompt_b | model | StrOutputParser() parallel RunnableParallel(achain_a, bchain_b) result parallel.invoke({concept: 区块链})它会并行跑两个链最后返回一个包含两个结果字段的字典。对于 RAG 场景中“同时检索总结”这类需求非常实用。3.2 进阶版改造成带工具的 Agent当你需要模型自己决定调用什么工具时就要上 Agent 了。下面这段代码演示了怎么用 LangChain 的create_tool_calling_agent创建一个最简单的 Agentfrom langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai import ChatOpenAI from langchain_core.tools import tool tool def add(a: float, b: float) - float: 计算两个数字的和。 Args: a: 第一个加数。 b: 第二个加数。 return a b tool def to_uppercase(text: str) - str: 将输入的英文文本转为大写。 Args: text: 英文文本。 return text.upper() tools [add, to_uppercase] prompt ChatPromptTemplate.from_messages([ (system, 你是一个乐于助人的助手请使用提供的工具回答问题。), (human, {input}), MessagesPlaceholder(variable_nameagent_scratchpad), ]) model ChatOpenAI(modelgpt-4o-mini, temperature0) agent create_tool_calling_agent(model, tools, prompt) executor AgentExecutor(agentagent, toolstools, verboseTrue) result executor.invoke({input: 帮我计算 25 加 17再把结果转成大写}) print(result[output])注意agent_scratchpad这个占位符它用来存放模型在每轮“思考-行动”循环中产生的中间值。模型先可能输出“我应该调用 add 工具参数是 25 和 17”AgentExecutor 执行后把结果放回agent_scratchpad然后传给模型继续推理下一步。你还看不到的话可以把verboseTrue控制台会打出每步的详细过程。temperature0在 Agent 场景下非常关键。因为 agent 需要遵循指令和输出指定格式温度太高容易“自由发挥”把工具调用的 JSON 格式写歪了。3.3 复杂版用 LangGraph 控制流程分支如果想更灵活地控制流程比如“先查天气如果下雨就提醒带伞否则推荐户外活动”用 LangGraph 写会更清晰。这里给出一个极简的 LangGraph 示例from typing import TypedDict from langgraph.graph import StateGraph, END class State(TypedDict): city: str weather: str advice: str def check_weather(state: State): # 模拟返回天气 weather rainy if state[city] 上海 else sunny return {weather: weather} def rainy_advice(state: State): return {advice: f{state[city]}明天下雨记得带伞。} def sunny_advice(state: State): return {advice: f{state[city]}天气晴好适合安排户外活动。} def decide(state: State): if state[weather] rainy: return rainy_node return sunny_node graph StateGraph(State) graph.add_node(check, check_weather) graph.add_node(rainy_node, rainy_advice) graph.add_node(sunny_node, sunny_advice) graph.add_edge(check, rainy_node) graph.add_edge(check, sunny_node) graph.add_conditional_edges(check, decide) graph.set_entry_point(check) graph.add_edge(rainy_node, END) graph.add_edge(sunny_node, END) app graph.compile() result app.invoke({city: 上海}) print(result[advice])这个图结构一目了然入口节点check执行查天气然后根据天气字段走不同的分支。对比 AgentExecutor 那种黑盒循环LangGraph 的每个节点是在做什么、数据流转到哪里你都看得清清楚楚。而且你还能在任意节点后面加interrupt这让“让用户确认后再继续执行”这类交互变得非常自然。我实际做生产项目时很少会让 Agent 无限自循环直到结束。更多是“先用 LangGraph 规划好主流程主流程里某几步交给模型调用工具”这样既有确定性又有智能性。4. 常见问题与排查技巧实录4.1 链式调用时结果总被上一个节点覆盖用 LCEL 管道时有个容易犯的错如果你多步之间需要保留多个字段只用单个字符串传递会丢数据。比如第一步提取了关键词第二步想同时把原始问题和关键词一起传给模型直接prompt_a | model | prompt_b会导致第二步拿不到原始问题。解决办法是使用RunnablePassthrough或RunnableParallel把字段保留下来from langchain_core.runnables import RunnablePassthrough chain ( {extracted: chain_a, original: RunnablePassthrough()} | prompt_b | model | StrOutputParser() )这里RunnablePassthrough()会把上游的原始输入原封不动地传给下一步避免被中间结果覆盖。这类问题在 RAG 场景特别常见记住一句话LCEL 里每个字段的传递都是显式的不会自动帮你保留旧值。4.2 Agent 陷入死循环或者不停止Agent 最常见的翻车现场就是模型反复调用同一个工具或者来回调用几个工具但始终不产生最终答案。我遇到过最夸张的一次Agent 为了数一个列表有几项自己把len(list)调了七遍。第一道防线是限制迭代次数。AgentExecutor(max_iterations5)或 LangGraph 里设置recursion_limit超过上限直接终止并返回当前结果可以有效避免失控调用。第二道防线是改进 Prompt 和工具描述让模型更清楚什么条件下应该停止。第三道防线是检查工具是否存在副作用比如发送邮件、扣费类操作一旦被循环调用后果很严重。所以写工具时最好带上幂等性和副作用提示。4.3 工具参数老是传错格式模型调用工具时输出的 JSON 偶尔会不合法或者参数类型对不上。比如工具要求city: str模型传了个{city: 123}过来。这跟模型能力有关也跟你工具 Schema 的描述有关。我的处理建议一是把参数设计得尽量简单别整嵌套对象模型传错嵌套 JSON 的概率远高于简单字符串二是在工具函数内部做一次类型转换和容错三是打开 LangSmith 或调试日志观察模型实际传过来的 JSON再针对性地改工具描述。实际上很多参数错误是因为工具描述里没有举例加了“示例city: 北京”之后准确率会提高一截。4.4 怎么监控和调试整个链路调试 LangChain 程序最基础的方式是verboseTrue输出每一步的中间日志。但项目一大这招就不够用了。LangChain 自家的LangSmith是目前最完善的可观测平台能记录每次调用的完整轨迹Prompt 是什么、模型吐了什么、工具结果返回什么、中间用了多少 token、耗时多久。如果不想接入外部平台也可以用环境变量打开调试输出import langchain langchain.debug True然后代码里所有组件调用的细节都会打印到控制台。我自己排查问题时优先看三个地方第一进模型的最终 Prompt 长什么样是不是拼接错了第二模型返回的原始消息里tool_calls字段是否合法第三工具的实际返回内容和异常信息。80% 的问题都在这三个环节里暴露出来。4.5 模型选型和成本控制最后说一个容易被忽略的话题。LangChain 只是一个框架底层模型的选型和成本控制同样决定项目成败。做简单的链式任务用gpt-4o-mini甚至本地小模型就够了没必要每次都上满血版。做 Agent尽量选工具调用能力强的模型如果模型本身不支持函数调用的输出格式Agent 跑起来会非常费劲。成本方面Agent 的 token 消耗通常远高于普通链。因为模型每一轮思考都要重新发送历史消息和工具结果一个简单任务可能消耗几千 token。控制成本的办法一是精简历史消息及时移除无关上下文二是把大任务拆成多个小 Chain 而不是指望一个大 Agent 全包三是尽量复用系统 Prompt 的内容避免重复拼长文本。根据我个人的实际经验很多人之所以觉得 LangChain 难用不是框架本身复杂而是没搞清楚“链、Agent、图”这三层抽象的适用边界。一上来就指望 Agent 解决所有问题结果就是失控、耗 token、难排查。先想清楚你的流程是确定的还是开放的再决定用哪一层抽象。如果大部分流程是确定的就老老实实画状态图、写链、插 Agent如果真有需要模型自主决策的环节再把那一步封装成一个 LangGraph 节点这样整套系统既稳定又有智能。这是我在多个项目里反复验证过最稳妥的路线希望对你有帮助。