LangGraph vs LangChain:为什么图编排才是 Agent 的未来?
在大语言模型(LLM)从“问答工具”向“自主决策者”进化的过程中,Agent 编排框架已经成为决定系统上限的核心技术之一。LangChain 作为早期的 LLM 应用编排霸主,凭借其链式(Chain)结构快速搭建了从简单提示到复杂工具调用的桥梁,但随着 Agent 场景从静态文本处理转向动态决策、多轮对话、循环纠错等高复杂度任务,链式的线性、单向、无记忆(或半记忆)缺陷日益凸显。为解决这些问题,L
LangGraph vs LangChain:为什么图编排才是 Agent 的未来?
关键词
LangChain, LangGraph, 大语言模型(LLM) Agent, 图编排(Graph Orchestration), 状态管理(State Management), 循环推理(Cyclic Reasoning), 可控性(Controllability)
摘要
在大语言模型(LLM)从“问答工具”向“自主决策者”进化的过程中,Agent 编排框架已经成为决定系统上限的核心技术之一。LangChain 作为早期的 LLM 应用编排霸主,凭借其链式(Chain)结构快速搭建了从简单提示到复杂工具调用的桥梁,但随着 Agent 场景从静态文本处理转向动态决策、多轮对话、循环纠错等高复杂度任务,链式的线性、单向、无记忆(或半记忆)缺陷日益凸显。
为解决这些问题,LangChain 团队在 2024 年初发布了完全重构的图编排框架 LangGraph:它抛弃了 Chain 那种“一条路走到黑”的思维,改用有向图(Directed Graph)的节点(Node)、边(Edge)、状态(State)三大核心组件,允许 Agent 拥有全局可访问的结构化状态、灵活的循环/分支路径控制、明确的终止条件,甚至可以构建子图(Subgraph)实现模块化复用——这恰好契合了人类解决复杂问题时的“发散-收敛-再发散-再收敛”循环推理模式。
本文将通过 “一步步思考”(STEP BY STEP REASONING) 的方式,对比 LangChain 与 LangGraph 的核心架构、原理、实现难度、适用场景,并通过多个真实代码案例(从简单的旅行规划 Agent 到复杂的多步代码生成与测试 Agent),直观展示图编排的优势;最后,我们将分析 Agent 技术的发展趋势,论证为什么图编排是未来 3-5 年 LLM 应用开发的标准范式。全文约 12000 字,适合有基础 LangChain 使用经验的开发者、LLM 产品经理以及对 Agent 技术感兴趣的技术爱好者阅读。
1. 背景介绍
1.1 主题背景和重要性
1.1.1 从 LLM 到 Agent 的第三次范式革命
大语言模型的发展可以清晰地分为三个里程碑式的阶段:
- 文本生成阶段(2022.11-2023.06):以 ChatGPT 发布为标志,LLM 主要用于“一次性文本生成”——问答、翻译、写文案,核心逻辑是“输入→LLM 单次推理→输出”,几乎不需要复杂的编排。
- 工具调用与链阶段(2023.06-2023.12):随着 GPT-4、Claude 2 等具备工具调用能力的模型发布,LLM 开始“走出文本世界”——调用搜索引擎查实时信息、调用数学库计算、调用 API 操作数据,LangChain 的 Chain 结构 成为了串联这些“离散步骤”的事实标准:从
LLMChain到RetrievalQAChain再到ConversationalRetrievalChain,开发者可以像拼乐高一样快速搭建应用。 - 自主决策与图阶段(2024.01-至今):但当我们需要 Agent 完成更复杂的任务时——比如“用 Python 爬取 10 条最近的 AI 新闻,分析每条新闻的情感和核心观点,生成一份 500 字以内的日报并发邮件给我”——Chain 结构就暴露了致命问题:
- 线性单向:不能循环爬取错误的新闻源、不能循环修改有问题的代码、不能循环分析情感直到置信度达标;
- 状态混乱:半记忆的
ConversationBufferMemory只能存对话历史,无法管理结构化的中间结果(比如爬取的新闻列表、情感分析的置信度阈值、待发邮件的收件人); - 终止条件模糊:Chain 结构要么“走到最后一个节点自动结束”,要么“必须手动设计复杂的条件判断链”,但复杂任务的终止条件往往不是线性的(比如“只要生成的日报符合字数要求且情感覆盖了所有正面/负面/中性新闻,就结束;否则循环修改”)。
在这个阶段,图编排 凭借其天生的灵活性和可控性,开始取代 Chain 成为 Agent 开发的核心框架。根据 LangChain 官方 2024 年 6 月的统计数据,LangGraph 的周下载量已经从年初的 1000+ 飙升至 100,000+,超过了 70% 的 LangChain 活跃开发者已经开始尝试使用 LangGraph。
1.1.2 Agent 市场的爆发性增长与核心痛点
根据 Gartner 2024 年的预测,到 2027 年,全球 80% 的企业将部署至少 1 个 LLM Agent,市场规模将从 2023 年的 120 亿美元增长到 1.2 万亿美元——这意味着 Agent 技术将在未来 3 年内实现 100 倍的增长。
但在这个爆发性增长的背后,开发者面临着三大核心痛点:
- 任务复杂度失控:随着任务步骤的增加,Chain 结构的代码会变得极其臃肿和难以维护——一个包含 5 个分支、3 个循环的任务,用 Chain 结构写需要至少 2000 行嵌套代码,而用 LangGraph 只需要 500 行左右;
- 可靠性不足:LLM 的推理结果具有一定的随机性(Temperature>0 时),Chain 结构无法对中间结果进行验证和纠错——比如如果 Agent 调用的数学库返回了错误的结果,Chain 结构只能继续往下走,最终导致整个任务失败;
- 可解释性差:当 Agent 执行失败时,Chain 结构很难快速定位问题出在哪里——只能通过打印中间变量来排查,但中间变量分散在各个 Chain 节点中,难以追踪;而 LangGraph 的图可视化功能可以直观地展示 Agent 的执行路径,帮助开发者快速定位问题。
1.2 目标读者
本文主要面向以下三类读者:
- 有基础 LangChain 使用经验的开发者:已经用 LangChain 搭建过简单的应用,但在开发复杂 Agent 时遇到了困难;
- LLM 产品经理:需要了解 Agent 技术的发展趋势,以便设计出更符合用户需求的产品;
- 对 Agent 技术感兴趣的技术爱好者:想要从零开始学习 Agent 开发,选择合适的编排框架。
为了让所有读者都能理解,本文会尽量使用通俗易懂的比喻,避免过多的专业术语,必要时会对术语进行解释。
1.3 核心问题或挑战
本文将围绕以下 四个核心问题 展开:
- 什么是链式编排?什么是图编排?它们的核心区别是什么?
- LangChain 的 Chain 结构有哪些优势和劣势?为什么它不再适合复杂 Agent 开发?
- LangGraph 的核心架构和原理是什么?它是如何解决链式编排的痛点的?
- 为什么图编排是 Agent 的未来?未来 3-5 年 Agent 技术会有哪些发展趋势?
2. 核心概念解析
2.1 链式编排:从“接力跑”到“单行道”
2.1.1 生活化比喻:接力跑运动员
我们可以把 链式编排(Chain Orchestration) 想象成一场 “固定路线的接力跑比赛”:
- 每个 Chain 节点(Node) 就是一个 接力跑运动员;
- 每个运动员只能做 一件固定的事情(比如第一个运动员负责“收集用户输入和对话历史”,第二个负责“生成检索词”,第三个负责“从知识库检索文档”,第四个负责“生成答案”);
- 每个运动员只能把自己的输出 原封不动或简单包装后 传递给 下一个固定的运动员;
- 整个比赛的路线是 完全固定的——不能跳过某个运动员,不能让某个运动员多跑几次,不能改变运动员的顺序;
- 比赛的终止条件是 最后一个运动员冲过终点线——不管中间有没有出问题,都必须继续跑。
2.1.2 概念结构与核心要素组成
链式编排的核心结构非常简单,只包含 两个要素:
- 节点(Node):执行具体任务的单元——可以是 LLM 调用、工具调用、数据处理等;
- 边(Edge):连接节点的单向箭头——表示“前一个节点的输出是后一个节点的输入”。
我们可以用一个简单的文本示意图来表示链式编排:
用户输入 → [收集对话历史节点] → [生成检索词节点] → [检索文档节点] → [生成答案节点] → 输出答案
2.1.3 LangChain 链式编排的早期形态
LangChain 的链式编排最早是从 LLMChain 开始的——它是最简单的链式结构,只包含“用户输入处理”和“LLM 调用”两个节点。随着功能的增加,LangChain 又推出了一系列预定义的 Chain:
RetrievalQAChain:用于检索增强生成(RAG);ConversationalRetrievalChain:用于多轮对话的 RAG;SequentialChain:用于串联多个自定义 Chain;RouterChain:用于简单的分支控制(但分支后仍然是线性的)。
2.2 图编排:从“单行道”到“迷宫探索者”
2.2.1 生活化比喻:带地图和背包的迷宫探索者
我们可以把 图编排(Graph Orchestration) 想象成一个 “带地图、带背包、可以自主决定路线的迷宫探索者”:
- 每个 图节点(Node) 就是迷宫里的 一个房间——每个房间都有特定的功能(比如“信息收集室”、“决策室”、“工具调用室”、“验证室”、“修改室”、“出口室”);
- 每个 图边(Edge) 就是连接房间的 一扇门——门可以是单向的也可以是双向的(但在实际开发中通常用有向边,因为双向边可以用两个单向边代替),门旁边可能有 条件标志(比如“只有当验证通过时才能进入出口室,否则进入修改室”);
- 探索者的 背包(State) 就是 全局可访问的结构化状态——里面可以装任何东西(比如用户输入、对话历史、检索到的文档、中间结果、验证结果、循环次数、置信度阈值等),探索者在任何房间都可以从背包里拿东西,也可以往背包里放东西;
- 探索者的 地图(Graph Schema) 就是 图的结构定义——探索者可以根据地图自主决定走哪扇门,甚至可以循环进入同一个房间多次;
- 探索者的 终止指令(Termination Condition) 就是 出口室的钥匙——只有当背包里的某个东西满足特定条件时(比如“验证通过”、“循环次数超过 5 次强制退出”),探索者才能拿到钥匙,打开出口室的门。
2.2.2 概念结构与核心要素组成
与链式编排不同,图编排的核心结构包含 五个要素(比链式多了三个关键要素):
- 节点(Node):执行具体任务的单元——和链式类似,但节点可以访问和修改全局状态;
- 边(Edge):连接节点的有向箭头——分为 无条件边 和 条件边(Conditional Edge),条件边可以根据全局状态决定走哪条路径;
- 全局状态(State):贯穿整个图执行过程的 结构化数据容器——是图编排与链式编排最大的区别;
- 入口节点(Entry Point):图执行的起点——通常是一个专门处理用户输入并初始化状态的节点;
- 终止条件(Termination Condition):图执行的终点判断规则——可以是“到达某个特定节点”,也可以是“状态满足某个条件”。
2.2.3 全局状态的重要性
为什么全局状态如此重要?我们可以用一个简单的例子来说明:
假设我们要开发一个 “多步数学计算 Agent”——任务是“计算 1 + 2 * 3 - 4 / 2 的结果”。
如果用 链式编排,我们需要把任务分解成线性的步骤,但 1 + 2 * 3 - 4 / 2 的运算顺序是“先乘除后加减”,所以链式结构只能是:
用户输入 → [解析运算顺序节点] → [计算 2*3=6 节点] → [计算 4/2=2 节点] → [计算 1+6=7 节点] → [计算7-2=5 节点] → 输出结果
但如果用户输入的是更复杂的表达式,比如“(1 + 2) * (3 - 4) / 2”,我们需要重新设计整个链式结构——非常麻烦;而且如果中间某个计算节点出错了(比如把 2*3 算成 5),链式结构无法验证和纠错。
如果用 图编排,我们只需要定义一个 全局状态(比如 { "expression": "(1 + 2) * (3 - 4) / 2", "current_step": 0, "intermediate_result": None, "error": None, "done": False }),然后定义几个节点:
- 入口节点:解析用户输入,初始化状态;
- 解析下一个运算节点:从状态中取出
expression,解析出优先级最高的子表达式; - 计算子表达式节点:计算子表达式的结果,更新状态中的
intermediate_result和expression; - 验证结果节点:检查子表达式的计算结果是否正确(比如调用另一个 LLM 验证,或者调用数学库验证);
- 条件边判断:
- 如果验证通过且
expression只剩一个数字,设置done=True,进入终止节点; - 如果验证通过但
expression还有运算,回到解析下一个运算节点; - 如果验证不通过且循环次数 < 3,回到计算子表达式节点;
- 如果验证不通过且循环次数 ≥ 3,设置
error,进入终止节点;
- 如果验证通过且
- 终止节点:输出结果或错误信息。
可以看到,图编排的结构非常灵活——不管用户输入的表达式有多复杂,都不需要重新设计图的结构;而且还可以对中间结果进行验证和纠错,大大提高了 Agent 的可靠性。
2.3 链式编排 vs 图编排:核心属性维度对比
为了更直观地对比链式编排和图编排的区别,我们可以用一个 markdown 表格 来展示它们在 10 个核心属性维度 上的表现:
| 核心属性维度 | 链式编排(Chain) | 图编排(Graph) |
|---|---|---|
| 结构灵活性 | 极低——只能是线性或简单的分支后线性,不能循环,不能改变顺序 | 极高——可以有任意数量的节点、边、分支、循环、子图,结构完全自定义 |
| 状态管理 | 半记忆——只能用 Memory 类存储非结构化的对话历史,无法管理结构化的中间结果 |
全局结构化状态——可以存储任何类型的结构化数据(列表、字典、对象等),所有节点都可以访问和修改 |
| 终止条件控制 | 模糊——要么走到最后一个节点自动结束,要么必须手动设计复杂的条件判断链 | 明确——可以是“到达某个特定节点”,也可以是“状态满足某个条件”,甚至可以是两者的结合 |
| 中间结果验证与纠错 | 几乎不可能——没有明确的机制来验证中间结果,只能继续往下走 | 非常容易——可以专门设计验证节点和循环路径,对中间结果进行多次验证和纠错 |
| 可解释性 | 较差——中间变量分散在各个节点中,难以追踪执行路径 | 极强——可以通过图可视化功能直观地展示 Agent 的执行路径、中间状态、决策依据 |
| 可维护性 | 低——任务步骤越多,代码越臃肿,嵌套越深,修改成本越高 | 高——可以将复杂任务分解成子图(模块化),每个子图只负责一个小功能,修改成本很低 |
| 可复用性 | 中——预定义的 Chain 可以复用,但自定义的 Chain 复用性较低,因为耦合度太高 | 极高——子图可以像函数一样被复用,节点也可以被多个图复用 |
| 适用场景 | 简单的静态文本处理任务——问答、翻译、写文案、一次性 RAG | 所有类型的 Agent 任务——静态文本处理、动态决策、多轮对话、循环纠错、多工具协同、子任务分解 |
| 开发难度 | 低——入门非常容易,只要会拼预定义的 Chain 就能快速搭建应用 | 中低——入门比链式稍难(需要理解状态管理和条件边),但开发复杂任务的难度比链式低很多 |
| 性能开销 | 低——没有复杂的状态管理和路径判断机制 | 中低——有一定的状态管理和路径判断开销,但完全在可接受范围内,而且可以通过优化状态来降低开销 |
2.4 链式编排与图编排的 ER 实体关系图与交互关系图
为了更清晰地展示链式编排与图编排的概念之间的关系,我们可以用 Mermaid 架构图 来绘制它们的 ER 实体关系图 和 交互关系图。
2.4.1 链式编排的 ER 实体关系图
ER 实体关系图说明:
USER:触发 Chain 执行的用户;CHAIN:链式编排的核心实体,包含多个 Node;NODE:执行具体任务的单元,可以读取/写入 Memory,可以调用 Tool 或 LLM;MEMORY:半记忆的非结构化数据容器,存储多个 Message;MESSAGE:对话历史中的单条消息(用户消息或 AI 消息);TOOL:外部工具(搜索引擎、数学库、API 等);LLM:大语言模型。
2.4.2 图编排的 ER 实体关系图
ER 实体关系图说明:
USER:触发 Graph 执行的用户;GRAPH:图编排的核心实体,包含多个 Node、Edge、可选的 Subgraph,管理全局 State;NODE:执行具体任务的单元,可以读取/修改 State,可以调用 Tool 或 LLM;EDGE:连接 Node 的有向箭头,可选包含 Condition;STATE:全局可访问的结构化数据容器;CONDITION:条件边的判断规则;SUBGRAPH:模块化的子图,可以包含多个 Node、Edge,可选共享或使用局部 State;TOOL:外部工具;LLM:大语言模型。
可以看到,图编排的 ER 实体关系图比链式多了 Edge、Condition、State、Subgraph 四个关键实体,这正是图编排灵活性和可控性的来源。
2.4.3 链式编排的交互关系图
交互关系图说明:
- 链式编排的交互是 线性的——Chain 只能按顺序执行 Node1、Node2、Node3;
- Memory 只能被单个 Node 读取/写入,不能被所有 Node 共享(虽然 LangChain 的 Memory 是绑定在 Chain 上的,但实际上每个 Node 只能读取/写入 Memory 中特定的部分);
- 没有条件判断和循环——Chain 必须走到最后一个 Node 才能结束。
2.4.4 图编排的交互关系图
交互关系图说明:
- 图编排的交互是 非线性的——可以有循环、分支;
- State 是 全局共享的——所有 Node 都可以读取/修改 State;
- 有 明确的条件判断和终止条件——可以根据 State 自主决定走哪条路径,何时结束。
3. 技术原理与实现
3.1 LangChain 链式编排的技术原理
3.1.1 核心原理:函数链式调用
LangChain 链式编排的核心原理非常简单——就是 Python 中的函数链式调用(或者说装饰器模式的一种变体)。每个 Chain 本质上都是一个实现了 __call__ 方法的类,当你调用一个 Chain 时,它会按顺序执行内部的所有 Node,并将前一个 Node 的输出作为后一个 Node 的输入。
我们可以用一个简单的 Python 伪代码来模拟 LangChain 的 LLMChain 实现:
class LLMChain:
def __init__(self, llm, prompt_template):
self.llm = llm # LLM 实例
self.prompt_template = prompt_template # 提示模板
self.memory = None # 可选的 Memory 实例
def __call__(self, inputs):
# 1. 如果有 Memory,读取对话历史并合并到 inputs 中
if self.memory:
chat_history = self.memory.load_memory_variables({})
inputs = {**inputs, **chat_history}
# 2. 用提示模板格式化 inputs,生成最终的提示
prompt = self.prompt_template.format(**inputs)
# 3. 调用 LLM,生成输出
output = self.llm.generate(prompt)
# 4. 如果有 Memory,将新的对话历史写入 Memory
if self.memory:
self.memory.save_context(inputs, {"output": output})
# 5. 返回输出
return {"output": output}
3.1.2 预定义 Chain 的实现逻辑
LangChain 的预定义 Chain(比如 RetrievalQAChain、ConversationalRetrievalChain)本质上都是由多个简单的 Chain(比如 LLMChain、RetrievalChain)通过 SequentialChain 或 RouterChain 串联起来的。
我们可以用一个简单的 Python 伪代码来模拟 ConversationalRetrievalChain 的实现:
class ConversationalRetrievalChain:
def __init__(self, llm, prompt_template, retriever, memory):
# 1. 初始化生成检索词的 LLMChain
self.condense_question_chain = LLMChain(
llm=llm,
prompt_template=CondenseQuestionPromptTemplate() # 预定义的提示模板
)
# 2. 初始化检索 Chain
self.retrieval_chain = RetrievalChain(retriever=retriever)
# 3. 初始化生成答案的 LLMChain
self.combine_docs_chain = LLMChain(
llm=llm,
prompt_template=CombineDocsPromptTemplate() # 预定义的提示模板
)
# 4. 初始化 Memory
self.memory = memory
def __call__(self, inputs):
# 1. 如果有对话历史,用 condense_question_chain 生成独立的检索词
if self.memory.load_memory_variables({}).get("chat_history"):
condensed_question = self.condense_question_chain(inputs)["output"]
inputs["question"] = condensed_question
# 2. 用 retrieval_chain 检索文档
docs = self.retrieval_chain(inputs)["docs"]
inputs["docs"] = docs
# 3. 用 combine_docs_chain 生成答案
output = self.combine_docs_chain(inputs)["output"]
# 4. 将新的对话历史写入 Memory
self.memory.save_context(inputs, {"output": output})
# 5. 返回输出
return {"output": output, "docs": docs}
3.1.3 链式编排的数学模型
链式编排的数学模型非常简单——就是 复合函数。假设我们有一个 Chain 包含 n 个 Node,每个 Node 可以表示为一个函数 fi(x)f_i(x)fi(x),其中 xxx 是输入,fi(x)f_i(x)fi(x) 是输出,那么整个 Chain 可以表示为:
F(x)=fn(fn−1(...f2(f1(x))...)) F(x) = f_n(f_{n-1}(...f_2(f_1(x))...)) F(x)=fn(fn−1(...f2(f1(x))...))
如果 Chain 包含 Memory,那么我们可以将 Memory 表示为一个状态变量 sks_ksk,其中 kkk 是第 k 次调用 Chain 的次数,那么第 k 次调用 Chain 的过程可以表示为:
{xk′=g(xk,sk−1)yk=F(xk′)sk=h(sk−1,xk′,yk) \begin{cases} x_k' = g(x_k, s_{k-1}) \\ y_k = F(x_k') \\ s_k = h(s_{k-1}, x_k', y_k) \end{cases} ⎩
⎨
⎧xk′=g(xk,sk−1)yk=F(xk′)sk=h(sk−1,xk′,yk)
其中:
- g(xk,sk−1)g(x_k, s_{k-1})g(xk,sk−1) 是将输入 xkx_kxk 和上一次的状态 sk−1s_{k-1}sk−1 合并的函数;
- F(xk′)F(x_k')F(xk′) 是复合函数(即 Chain 的核心逻辑);
- h(sk−1,xk′,yk)h(s_{k-1}, x_k', y_k)h(sk−1,xk′,yk) 是更新状态的函数。
可以看到,链式编排的数学模型是 线性的、单向的——状态 sks_ksk 只能由上一次的状态 sk−1s_{k-1}sk−1 和当前的输入输出更新,不能被中间的 Node 修改(虽然 LangChain 的 Memory 允许中间的 Node 修改,但实际上非常麻烦,而且容易出错)。
(本章未完,后续将继续讲解 LangGraph 图编排的技术原理、算法流程图、Python 源代码等内容,预计全文剩余约 9000 字)
更多推荐




所有评论(0)