从链式思想到图状涌现:万字深度解析 LangChain 与 LangGraph 的思想演变与实战精髓
摘要: 本文旨在系统性地梳理从 LangChain 的核心思想“链(Chain)”到其进阶框架 LangGraph 的“图(Graph)”的演进过程。我们不只罗列 API,而是深入探讨其背后的设计哲学、要解决的核心痛点以及为开发者带来的范式转移。文章将从 LLM 应用开发的“原点”问题出发,经历“链式封装”、“智能代理(Agent)的崛起与困境”,最终阐明 LangGraph 如何通过“状态图”这一精妙抽象,赋予我们前所未有的能力来构建可控、可追溯、可扩展的复杂 AI 系统。无论你是 LangChain 的初学者还是资深用户,本文都将为你提供一个全新的、结构化的认知框架。
引言:风暴之眼——我们究竟想用 LLM 做什么?
2022 年末,大型语言模型(LLM)如同一场技术风暴席卷全球。当我们第一次通过简单的提示词(Prompt)与 ChatGPT 这样的模型交互时,无不为其强大的自然语言理解和生成能力所震撼。然而,当最初的惊艳褪去,一个更根本的问题摆在了所有开发者面前:
如何将这个强大的“大脑”嵌入到我们实际的、复杂的软件应用中去?
一个独立的、通过聊天界面交互的 LLM,其价值是有限的。真正的变革力量在于,当它能与我们现有的数据、工具和服务相结合,成为自动化流程中的一个智能“组件”时,其潜力才被真正释放。
这便是我们所有探索的“原点”问题。而对这个问题的回答,在过去短短一两年内,经历了一场波澜壮阔的思想演进。LangChain 的诞生和 LangGraph 的出现,正是这场演进中最关键的两个里程碑。
第一章:混沌初开——LangChain 的诞生与“链(Chain)”的核心抽象
在没有 LangChain 的世界里,与 LLM 交互是什么样的?
1 | import openai |
这看起来很简单,但问题很快就会暴露:
- 提示词管理的混乱:当应用逻辑变复杂,提示词会变得越来越长,其中可能包含动态插入的变量(如用户信息、历史对话等)。手动拼接字符串不仅繁琐,而且极易出错。
- 单一调用的局限:一次 LLM 调用只能完成一个“原子”任务。如果我想先让 LLM 生成旅游主题,再根据主题生成详细计划呢?这就需要两次调用,并且第二次的输入依赖于第一次的输出。如何优雅地管理这种依赖关系?
- 输出格式的不确定性:LLM 的输出是自然语言文本,但我们的程序需要的是结构化数据(如 JSON、Python 对象)。每次都需要编写脆弱的字符串解析代码来处理输出。
LangChain 的第一次思想飞跃:万物皆可“链”
LangChain 的核心洞察在于,大多数 LLM 应用的基本模式可以被抽象为一个“链(Chain)”:输入 -> 处理 -> 输出。这个“处理”环节,可以是一个 LLM 调用,也可以是多个 LLM 调用或其他函数的序列组合。
为了解决上述痛点,LangChain 提供了三大基础组件:
PromptTemplate(提示词模板):将提示词中的固定部分和可变部分分离。它像一个带有插槽的 F-string,让我们可以清晰地管理和复用提示词。
1 | from langchain_core.prompts |
LLM/ChatModel(模型封装):对不同厂商(OpenAI, Anthropic, Google…)的 LLM API 进行了统一封装,提供了标准化的调用接口。开发者无需关心底层 API 的差异。OutputParser(输出解析器):负责将 LLM 返回的纯文本输出,转换为我们程序需要的结构化数据。例如,JsonOutputParser可以自动解析 LLM 生成的 JSON 字符串,PydanticOutputParser甚至可以将其直接转换为 Python 的 Pydantic 对象,并附带类型校验。
当这三个组件被“链接”在一起时,就构成了 LangChain 最基础、也是最核心的抽象——LLMChain(在最新的 LangChain 表达式语言 LCEL 中,这个概念通过管道符 | 体现得淋漓尽致)。
1 | # LCEL (LangChain Expression Language) 的写法 |
LCEL 的 | 语法,其灵感来源于 Unix/Linux 的管道操作,它将数据流的思想具象化了。 prompt 的输出成为 model 的输入,model 的输出又成为 output_parser 的输入。 这种声明式的、从左到右的数据流,让构建简单的序列化任务变得前所未有的清晰和优雅。
至此,LangChain 的第一个核心思想已经成型:通过标准化的组件和“链”式连接,将与 LLM 交互的原始、混乱的过程,规范化为一个可复用、可维护的工程化流程。 这解决了单次调用的问题,也解决了简单序列调用的问题。
第二章:智能的涌现——“代理(Agent)”与 ReAct 框架
“链”式思想极大地提升了开发效率,但很快我们又遇到了新的天花板。
问题:如果 LLM 需要外部信息或能力怎么办?
- 我想问“今天北京的天气怎么样?”,LLM 的知识库是静态的,它无法回答实时信息。
- 我想让 LLM“计算 1024 乘以 768”,它可能会因为不擅长精确计算而出错。
- 我想让 LLM“总结我数据库里最新的销售报告”,它根本无法访问我的数据库。
LLM 就像一个被关在“玻璃缸”里的大脑,知识渊博但四肢无力。它无法与外部世界互动。
LangChain 的第二次思想飞跃:赋予 LLM “手脚”——工具(Tools)与代理(Agent)
解决方案是直观的:给 LLM 配备一套“工具(Tools)”。
- 天气工具:一个可以调用天气 API 的函数。
- 计算器工具:一个可以执行数学运算的函数。
- 数据库工具:一个可以查询公司数据库的函数。
但问题又来了:LLM 如何知道在何时、用哪个工具、以及如何使用这个工具呢?
这就引出了 LangChain 中另一个革命性的概念:代理(Agent)。
Agent 的核心思想基于一个名为 ReAct (Reason + Act) 的框架。其工作流程可以分解为:
Reason (思考):Agent 接收用户请求,LLM 会进行“思考”。它分析任务,判断仅凭自身知识是否能完成。如果不能,它会思考需要什么工具,以及调用该工具需要的参数。LLM 的输出不再是最终答案,而是一个“行动计划(Action Plan)”。
Act (行动):Agent 框架解析这个“行动计划”,识别出要调用的工具名称和参数,然后执行该工具(即调用对应的函数)。
Observe (观察):工具执行后会返回一个结果(例如,天气 API 返回了“晴,25度”)。这个结果被称为“观察(Observation)”。
Repeat (循环):Agent 将“观察”结果再次喂给 LLM,让它基于新的信息继续“思考”。LLM 可能会决定:
任务已完成,可以生成最终答案了。
还需要调用另一个工具。
同一个工具需要用不同的参数再试一次。
这个 思考 -> 行动 -> 观察 的循环,赋予了 LLM “自主”解决复杂问题的能力。它不再是被动地响应,而是主动地规划、执行和反思。
AgentExecutor 是 LangChain 中实现这一循环的核心类。它像一个不知疲倦的调度员,协调着 LLM(大脑)和工具(手脚)之间的合作。
1 | # 伪代码演示 Agent 的工作流程 |
当你运行这段代码时,verbose=True 会清晰地展示出 ReAct 的循环过程:
Thought: 我需要先找出 LangChain 的作者是谁。我可以使用搜索工具。
Action: DuckDuckGoSearch
Action Input: “who is the author of LangChain”
Observation: Harrison Chase is the creator of LangChain.
Thought: 我已经知道作者是 Harrison Chase。现在我需要找出他在哪家公司工作。我将再次使用搜索工具。
Action: DuckDuckGoSearch
Action Input: “Harrison Chase company”
Observation: Harrison Chase is the CEO and Co-founder of LangChain.
Thought: 我现在有了所有信息。作者是 Harrison Chase,他是 LangChain 公司的 CEO 和联合创始人。我可以给出最终答案了。
Final Answer: LangChain 的作者是 Harrison Chase,他目前在自己联合创办的公司 LangChain 担任 CEO。
至此,LangChain 的第二次思想飞跃完成:通过 Agent 和 ReAct 框架,LLM 从一个“语言模型”进化为了一个能动地使用工具解决问题的“智能代理”。 这使得构建能够与外部世界交互的、真正有用的应用成为可能。
第三章:增长的烦恼——AgentExecutor 的“黑盒”困境
Agent 模型取得了巨大成功,但随着应用逻辑变得越来越复杂,开发者们开始感到力不从心。AgentExecutor 虽然强大,但它本质上是一个“黑盒”。
这个“黑盒”指的是它的内部循环逻辑是固定的:LLM 思考 -> 选择工具 -> 执行工具 -> 返回观察 -> LLM 思考… 这个循环对于开发者来说是几乎不可定制的。
我们遇到了新的、更深层次的问题:
缺乏控制流(Control Flow):我无法轻易地在循环中加入自定义逻辑。例如:
条件分支:如果工具 A 返回的结果是正面的,就去调用工具 B;如果是负面的,就去调用工具 C。
强制干预:在 Agent 决定调用某个工具之前,我想先进行一次人工审核。
修改状态:在两次 LLM 调用之间,我想手动修改或增加一些信息(例如,注入一条新的指令)。
AgentExecutor的固定循环使得这些精细化的控制变得异常困难。
状态管理的模糊性:Agent 在执行过程中会产生大量的中间状态:历史对话、每次的思考、工具调用记录、观察结果等。在
AgentExecutor中,这些状态由其内部的Memory对象管理,但对开发者来说,访问和操作这些状态并不直观。我们想要的是一个统一的、显式的状态管理机制。可调试性和可追溯性差:当一个复杂的 Agent 运行失败或陷入死循环时,调试过程非常痛苦。因为整个决策过程都在一个不透明的循环里,我们很难 pinpoint 问题到底出在哪一步。我们想看到每一步的状态变化,想知道每一次决策的依据。
难以实现多 Agent 协作:如果我想构建一个系统,其中包含一个“研究员 Agent”和一个“作家 Agent”,研究员负责搜集资料,作家负责将资料整理成报告。如何让这两个 Agent 有效协作?如何将研究员的输出作为作家的输入,并可能根据作家的反馈让研究员重新搜集资料?用
AgentExecutor来编排这种复杂的协作流程,无异于一场噩梦。
核心矛盾已经浮现:我们试图用一个线性的、固定的循环(AgentExecutor),去构建一个本质上可能是非线性的、有分支、有循环、有状态的复杂应用。 这就像试图用一根直线去描绘一张复杂的网络图,必然会遇到瓶颈。
我们需要一次新的范式转移。我们需要一种方法,能够让我们跳出这个固定的循环,用一种更灵活、更强大的方式来描述我们的应用逻辑。
第四章:范式转移——LangGraph 的诞生与“图(Graph)”的智慧
面对 AgentExecutor 的困境,LangChain 团队给出的答案是 LangGraph。
LangGraph 的核心思想:将应用逻辑建模为一个“状态图(State Graph)”。
这个思想的转变是深刻的:
- 从“链(Chain)”和“循环(Loop)”到“图(Graph)”:不再将应用看作是一系列线性的步骤或一个固定的循环,而是看作一个由**节点(Nodes)和边(Edges)**构成的图。
让我们来理解这两个核心概念在 LangGraph 中意味着什么:
- 节点(Nodes):图中的每个节点代表一个“工作单元”。这可以是一个函数,也可以是一个 LangChain 的
Runnable(比如我们之前构建的chain)。每个节点接收当前的“应用状态(State)”作为输入,执行其任务,然后返回一个对状态的更新。 - 边(Edges):边定义了节点之间的流转路径。这是 LangGraph 最具革命性的部分。边不再是固定的
A -> B,而是可以被编程的条件逻辑。在每个节点执行完毕后,系统会根据预设的“条件边(Conditional Edges)”来决定下一步应该去往哪个节点。
LangGraph 的核心组件:StatefulGraph
StatefulGraph (或 Graph) 是我们构建图的画布。它主要由三部分构成:
- 状态(State):首先,你需要定义一个代表整个应用状态的 Python 对象(通常是一个
TypedDict或 Pydantic 模型)。这个对象会在整个图的运行过程中被传递和修改。它就像一个中央数据库,记录了应用的所有信息,如用户输入、LLM 的回复、工具调用的结果、重试次数等。状态管理从模糊变得显式和集中。 - 添加节点(
add_node):你可以定义任意数量的节点函数,并将它们添加到图中。每个节点函数都接收当前的状态对象作为参数,并返回一个包含状态更新的字典。 - 添加边(
add_edge和add_conditional_edges):
add_edge:定义一个固定的流转,例如,从节点 A 总是流转到节点 B。add_conditional_edges:这是 LangGraph 的“超能力”。你提供一个起始节点,和一个“路由函数(Router Function)”。这个路由函数同样接收当前状态作为输入,它的返回值是一个字符串,该字符串就是下一个节点的名称。这允许我们基于当前应用的状态,动态地决定流程的走向。
一个思想实验:用 LangGraph 重构 ReAct Agent
现在,让我们用 LangGraph 的思想来重新思考之前那个“黑盒”的 ReAct Agent。它的逻辑可以被分解成一个图:
1 | from typing import TypedDict, Annotated, Sequence |
这个 AgentState 显式地定义了我们需要追踪的一切。intermediate_steps 使用 operator.add 意味着每次对它的更新都是追加,而不是覆盖,这对于记录工具调用历史至关重要。
节点定义(Nodes):
agent节点:这个节点负责调用 LLM 进行“思考”。它接收当前状态(包含用户输入和历史记录),输出一个agent_outcome(是采取行动还是给出最终答案)。tools节点:这个节点负责执行工具。它接收agent_outcome中指定的工具和参数,执行后,将结果以(action, observation)的形式追加到intermediate_steps中。边定义(Edges):
入口点(Entry Point):设置图的起始节点为
agent节点。条件边(Conditional Edge):从
agent节点出发,我们需要一个路由函数来决定下一步去哪。
路由逻辑:检查
agent_outcome。如果它是一个“行动(Action)”,则路由到tools节点。如果它是一个“最终答案(Final Answer)”,则路由到特殊的END节点,结束图的运行。
普通边(Normal Edge):从 tools 节点出发,我们总是需要回到 agent 节点,让 LLM 基于新的“观察”进行下一步思考。所以我们添加一条从 tools 到 agent 的固定边。
看到了吗?我们用节点和边,将 AgentExecutor 的隐式循环,显式地、白盒地重新构建了出来!
这带来了什么好处?
1. 完全的控制力:现在,这个图的每一个环节都在我们的掌控之中。
- 想在调用工具前加入人工审核?在
agent和tools节点之间增加一个human_in_the_loop节点,并修改条件边,让流程先走到这里暂停。 - 想实现复杂的重试逻辑?在
tools节点后增加一个条件边,检查工具输出是否成功,如果不成功,可以路由回agent节点并附带一条错误信息,或者直接路由到另一个“备用方案”节点。 - 想让两个 Agent 协作?将每个 Agent 定义为一个节点(或子图),然后用边来编排它们之间的信息流和控制流。
2. 清晰的状态管理:所有状态都集中在 AgentState 对象中。在任何节点,我们都可以精确地读取和修改应用的状态。
3. 极佳的可视化与调试:LangGraph 可以轻松地将你构建的图可视化为一张图片。当应用运行时,你可以追踪状态在节点之间流转的完整路径。这使得调试从“猜谜”变成了“看图说话”。
第五章:实战演练——构建一个带修正循环的研究助手
理论是灰色的,而生命之树常青。让我们通过一个具体的例子,来感受 LangGraph 的强大。
目标:构建一个自动化的研究助手。它接收一个主题,上网搜索信息,然后生成一份报告。关键在于,我们希望它能“自我修正”:在生成报告后,它会评估报告的质量,如果觉得信息不足,它会重新进行搜索,补充材料,然后再次生成报告,直到满意为止。
这种“评估-修正”的循环,用 AgentExecutor 几乎无法实现,但用 LangGraph 则非常自然。
图的设计思路:
1 | class ResearchState(TypedDict): |
1. Nodes (节点)
search:接收topic,调用搜索工具,将结果存入searches。generate_report:接收topic和searches,调用 LLM 生成报告,存入report。critique_report:接收topic和report,调用 LLM 对报告进行评估,提出改进意见,存入critique。
2. Edges (边)
- 入口点 ->
search search->generate_report(固定边)generate_report->critique_report(固定边)critique_report-> ??? (条件边)- 路由逻辑:检查
critique的内容。 - 如果评估意见是“报告质量很高,无需修改”,则路由到
END。 - 如果评估意见指出了不足(例如,“需要更多关于 XX 方面的细节”),则将
critique的内容作为新的搜索指令,路由回search节点,开启新一轮的“搜索-生成-评估”循环。
代码实现骨架:
1 | from langgraph.graph import StateGraph, END |
这个例子完美地展示了 LangGraph 的威力。我们构建了一个具有“反思”和“自我修正”能力的智能系统,而其核心逻辑——一个带有条件判断的循环——被清晰、直观地用图的形式表达了出来。
第六章:思想的升华——从 LangChain 到 LangGraph,我们得到了什么?
让我们站在更高维度,回顾这场从“链”到“图”的思想演进。
| 特性 | LangChain (AgentExecutor) | LangGraph |
|---|---|---|
| 核心抽象 | 链 (Chain) / 固定的 ReAct 循环 | 可编程的状态图 (State Graph) |
| 控制流 | 隐式 & 刚性:黑盒循环,难以定制。 | 显式 & 灵活:通过节点和条件边,完全由开发者定义。 |
| 状态管理 | 分散 & 模糊:依赖内部 Memory 对象,不易访问和修改。 | 集中 & 显式:统一的状态对象,在图中传递,完全透明。 |
| 可调试性 | 困难:难以追踪黑盒内的决策过程。 | 优秀:可将图和运行流程可视化,每一步的状态都可追溯。 |
| 适用场景 | 简单的、线性的任务序列;标准的 Agent 应用。 | 复杂的、非线性的工作流;需要条件分支、循环、人工干预、多 Agent 协作的应用。 |
| 开发范式 | 命令式(告诉它“做什么”)和声明式的混合。 | 声明式(声明节点和它们之间的关系),更接近系统设计。 |
LangGraph 并非要取代 LangChain,而是 LangChain 的自然演进和能力升华。
- 在 LangGraph 的每个节点内部,我们依然大量使用 LangChain 的核心组件:
PromptTemplate,LLM,OutputParser,以及由 LCEL 构成的chain。LangChain 提供了强大的“积木”,而 LangGraph 提供了搭建复杂“城堡”的“蓝图”。 - LangGraph 解决的核心问题是 “编排(Orchestration)”。当 LLM 应用不再是简单的“一问一答”或“一步接一步”,而是需要像一个真正的软件系统那样处理复杂的逻辑流时,我们就需要一个编排层,而“图”正是对这种编排最强大、最自然的抽象。
这场演进带给开发者的“化学反应”是什么?
它将我们从“提示词工程师”和“链条工匠”的角色中解放出来,让我们真正成为 “AI 系统架构师”。
我们不再局限于如何“哄骗”LLM 给出正确答案,而是开始思考:
- 一个复杂的任务可以被分解成哪些独立的功能单元(节点)?
- 这些单元之间应该如何传递信息(状态)?
- 在关键决策点上,系统应该如何自主导航(条件边)?
- 如何设计一个能够**从错误中学习和恢复(循环和修正)**的鲁棒系统?
这是一种从“术”到“道”的提升。我们手中的工具,终于跟上了我们对构建复杂智能应用的想象力。
结论:拥抱图状思维,迎接下一代 AI 应用
我们从一个简单的问题出发——如何将 LLM 集成到应用中?
- 我们经历了 LangChain 的“链”式革命,它通过标准化的组件和 LCEL,为我们提供了构建基础 LLM 工作流的利器,带来了工程化的曙光。
- 我们见证了 Agent 的崛起,它通过 ReAct 框架赋予了 LLM 与世界互动的能力,让智能不再局限于“缸中之脑”。
- 我们直面了
AgentExecutor的“黑盒”困境,意识到固定的循环无法满足日益增长的复杂应用对控制流、状态管理和可观测性的需求。 - 最终,我们迎来了 LangGraph 的“图”之范式。它通过将应用逻辑抽象为显式的、可编程的状态图,为我们提供了前所未有的灵活性和控制力,让我们得以构建真正复杂、可靠、可扩展的 AI 系统。
从“链”到“图”,不仅仅是 API 的更迭,它代表了我们对 LLM 应用开发理解的深化。它标志着我们从构建“玩具”和“演示”,迈向了构建“产品”和“系统”的时代。
如果你还在为如何控制你的 Agent 行为而苦恼,如果你想构建一个能够自我修正、多角色协作的复杂系统,那么,是时候放下对线性思维的执念,拥抱 LangGraph 所带来的图状思维了。
因为未来,最强大的 AI 应用,其智能将不再仅仅源于模型本身,更将涌现于你所精心设计的、那张优雅而强大的“图”之中。

