先说个背景:我从 LangChain 0.1 时代就开始拿它写 RAG 流水线,后来 LangGraph 一出来就跟着折腾到 0.4、0.5,中间踩过的坑比文档里的示例多得多。最近半年被问得最多的问题就是:LangChain 和 LangGraph 到底啥关系?是不是学新的就行?Dify、CrewAI 又在里面扮演什么角色?这篇文章就一次性把这几件事讲透,包括两者的本质区别、选型思路,以及我认为最靠谱的一条学习路线。
这篇内容不是概念科普,而是基于实际项目经验的总结。适合刚接触 Agent 开发的工程师、正准备做技术选型的团队负责人,以及被各种框架名词绕晕但想真正跑通一个项目的人。
1. LangChain 和 LangGraph 到底差在哪
先说结论:LangChain 和 LangGraph 不是竞争关系,而是底层与上层的关系。LangChain 是一套工具集,LangGraph 是建立在这些基础组件之上的 Agent 运行时。你可以只用 LangChain 不带 LangGraph,但只要你用 LangGraph 构建复杂 Agent,底层几乎一定会用到 LangChain 的模型封装、工具接口和向量存储等组件。
1.1 链式思维和图式思维的本质不同
LangChain 的核心抽象是 Chain,也就是一条直线管道。你定义prompt -> model -> parser,然后数据顺着这条管道流下去,一个萝卜一个坑,前一个步骤的输出就是后一个步骤的输入。这种设计对 90% 的 RAG 场景完全够用:检索、拼 prompt、调模型、解析结果,一气呵成,逻辑清楚,调试也容易。
但 Agent 场景有个硬需求:循环。Agent 需要"调用工具 -> 拿结果 -> 再决定下一步 -> 再调用工具"这种循环能力,甚至还需要条件分支:根据模型输出决定走 A 路线还是 B 路线。传统 Chain 面对这种需求只能靠工程师手写 while 循环,代码很快就变成一团乱麻。
LangGraph 的核心抽象是 Graph,它是一个有向图。每个节点是一个函数或可运行组件,节点之间有普通边和条件边,数据在节点之间流动,但可以绕圈跑、可以跳着走、可以停留等待人工介入。你可以把 LangGraph 理解成一个状态机框架,它的执行模式不再是"一条道走到黑",而是"按状态图规则不断跳转"。
1.2 代码层面的直观对比
举一个最简单的例子:让模型决定是调用搜索工具还是直接回答。用 LangChain 传统写法大概是:
# LangChain 传统写法(示意) def agent_loop(query): messages = [("user", query)] for _ in range(5): # 手写循环上限 response = llm.invoke(messages + tools_schema) if response.tool_calls: for call in response.tool_calls: result = tools[call["name"]].invoke(call["args"]) messages.append(Message(tool_call_id=call["id"], content=result)) else: return response.content return "循环次数超限"循环逻辑、状态维护、退出条件全部手写,一旦分支变多,这套代码维护成本就上来了。
再看看 LangGraph 的标准写法:
from langgraph.graph import StateGraph, START, END from typing import TypedDict, Annotated from langgraph.graph.message import add_messages class AgentState(TypedDict): messages: Annotated[list, add_messages] def tool_node(state: AgentState): """执行上一次模型返回的工具调用""" calls = state["messages"][-1].tool_calls return {"messages": [execute_tool(call) for call in calls]} graph = StateGraph(AgentState) graph.add_node("agent", call_model) graph.add_node("tools", tool_node) graph.add_edge(START, "agent") def route(state: AgentState): if state["messages"][-1].tool_calls: return "tools" return END graph.add_conditional_edges("agent", route, {"tools": "tools", END: END}) graph.add_edge("tools", "agent")节点函数只管拿到 state、处理、返回新的 state 增量,图机制负责调度、分支和循环。你不用自己写 for 循环,不用到处传递临时变量,状态变更在 state 里一目了然。这是两者最核心的差异:LangChain 靠代码组织流程,LangGraph 靠图定义流程。
2. LangGraph 凭什么能管住复杂 Agent
如果你想写一个真正的 Agent,而不是一条固定的流水线,langgraph 的三板斧是:显式状态、图执行、持久化检查点。理解这三件事,你就理解了它的设计灵魂。
2.1 状态声明与更新机制:add_messages 之类的基础
LangGraph 的 StateGraph 要求你先声明一个 StateSchema,通常用 TypedDict 定义。这个 State 会贯穿整个图生命周期,每个节点函数接收的是当前 State,返回的是 State 的增量(Partial State),框架负责把增量合并进完整状态。
合并不是无脑覆盖,而是交由 Reducer 决定。最典型的是消息列表:如果一个节点向 messages 里加了一条消息,另一个节点也加了一条,直接覆盖肯定丢数据。所以add_messages这个 reducer 会把新消息追加进去,并自动处理消息 ID 去重。
class AgentState(TypedDict): messages: Annotated[list, add_messages] # 追加模式 query: str documents: list[str] retry_count: int final_answer: str这里有个容易踩坑的点:字段没有指定 reducer 时,默认就是覆盖模式。多个节点同时对一个普通字段写入,后执行的会覆盖先执行的。如果这个字段同时被多个并行节点操作,你就得自己定义合并逻辑。我一开始就吃过这个亏:用普通字段存中间结果,结果两个并行节点互相覆盖,数据莫名其妙丢了一半。
2.2 条件边和循环的实际价值
条件边就是图里的"决策器"。你在某个节点结束后,传给add_conditional_edges一个路由函数,路由函数返回下一个节点的名字。这样就能实现:模型说要调用搜索工具,图就跳去工具节点;模型说直接给答案,图就走向 END。
循环能力也因此变得很自然:agent -> tools -> agent这条循环边让 Agent 可以反复调用工具,直到模型认为信息足够。更重要的是,你可以给循环加防御机制,比如在 State 里放一个计数器和上限判断,避免模型陷入死循环。这一点在真实项目中特别重要,我见过不加防护的 Agent 因为单个工具反复失败,一次任务烧掉几百次模型调用。
2.3 Checkpointer 与可恢复执行
LangGraph 真正区别于其他框架的地方,是它把图执行的每一步状态都记录下来。这个机制叫 Checkpointing,由 Checkpointer 组件实现。执行过程中,每个节点运行完成后都会把 State 快照保存到存储后端,比如 SQLite、Postgres 或内存(MemorySaver)。
有了检查点,你获得三个能力:
时间旅行:随时回退到历史某个节点状态,重新从那个点继续执行。这在调试幻觉问题、工具调用参数错误时极其有用。
人工介入:在图中用interrupt暂停执行,等待人类审核或输入,审核通过后再继续。这在金融审批、内容审核、需用户确认的 Agent 工作流中几乎是标配。
容错恢复:进程崩溃、网络故障后,可以从最近的检查点恢复,而不是整个任务重新跑。
实践中的建议是:测试阶段可以用MemorySaver图省事,生产环境务必换成可以在进程间共享持久化存储的方案,否则重启就前功尽弃。
3. 选型大白话:LangChain、LangGraph、Dify、CrewAI 到底该学哪个
很多人一上来就想知道"哪个框架最好",我的答案从来都是:脱离场景谈框架就是耍流氓。这四个东西的定位差异十分明显。
3.1 四类框架的定位速查
| 框架 | 定位 | 编程模式 | 适合人群 |
|---|---|---|---|
| LangChain | LLM 应用工具集 | 链式管道(LCEL/Runnable) | 需要快速搭建 RAG、工具调用应用的开发者 |
| LangGraph | Agent 运行时/状态机 | 图结构,显式状态与分支 | 需要构建复杂多步骤、可审计、可恢复 Agent 的团队 |
| Dify | 低代码应用平台 | 可视化编排 + 平台托管 | 想快速出产品、不想写太多代码的业务团队 |
| CrewAI | 多智能体角色协作框架 | 角色(Role)+ 任务(Task)+ 流程(Process) | 需要模拟团队协作、分工明确的多 Agent 场景 |
3.2 实际项目中的选型参考
我的经验是:
- 如果你的核心诉求是"文档问答、资料库检索、简单的意图分类",LangChain 就够了。它组件丰富,文档处理、向量化、检索、提示模板都有现成方案,学习成本低,跑通一个 Demo 基本一个下午。
- 如果你的 Agent 需要多轮工具调用、依赖前序步骤的中间结果、或者需要人工审核后再执行下一步,那直接选 LangGraph。它把状态管理这件事做到位了,代码的可维护性比手写循环高一个数量级。
- 如果团队里大部分成员不是专业后端工程师,或者项目以业务验证为核心目标,Dify 这类低代码平台更高效。我个人用它做内部知识库工具,比从零搭一套 LangChain 应用快得多。
- 如果你想实验"多个 AI 代理角色协作解决一个问题",CrewAI 的上手体验比 LangGraph 轻。但它更像一个面向特定范式的封装,遇到不匹配的场景反而束手束脚。
另外强调一点:LangChain 和 LangGraph 不是互相替代的关系,而是互补。用 LangGraph 写复杂状态机的时候,里面大量的节点任务依然要借助 LangChain 的 LCEL 组件来完成。所以我的结论是:LangChain 是必修课,LangGraph 是核心技能,Dify 是效率工具,CrewAI 是特定场景的补充。
4. 一条从零到能干活的学习路线
相信你已经发现,直接扎进 LangGraph 文档容易被绕晕,因为涉及概念太多:StateGraph、Reducer、Checkpointer、interrupt、conditional edges……如果没有前置铺垫,很容易中途放弃。我根据自己带新人的经验,整理了一条相对平滑的路线。
4.1 阶段一:打地基,先吃透 LangChain 基础用法
目标:能独立构建一个带 RAG 能力的问答应用。耗时约 3 到 5 天,具体看你的 Python 基础。
要掌握的内容:
- PromptTemplate 和 ChatPromptTemplate 的用法
- 如何调用 OpenAI、Anthropic、Ollama 等模型
- 文档加载器 + 文本切分器 + Embedding + 向量库的基本流程
- LCEL 管道:如何用
|把组件串联成 Runnable - Tool 的基本定义方式,理解函数如何被包装成工具
这个阶段不需要碰 LangGraph。重点是理解"LangChain 是一套可以独立使用的组件库"这件事。我见过太多人直接学 LangGraph,结果连Runnable接口都不清楚,最后看文档非常痛苦。
4.2 阶段二:理解 Agent 模式,再动手写一个 ReAct Agent
目标:明白 Agent 的内在逻辑。耗时 3 天左右。
在这个阶段,自己用纯 Python 手写一个 ReAct 循环即可,甚至可以只用几十行代码实现:把 prompt 喂给模型,模型输出 Action,你执行工具,把 Observation 返回给模型,如此循环。
这个练习的意义在于,让你真正理解工具调用(Function Calling)背后发生了什么。你不理解循环的本质,就理解不了 LangGraph 为什么需要有向环。
然后,再用 LangChain 的 legacy Agent 接口跑通一个 Agent,感受一下它和手写 ReAct 的区别。注意:LangChain 老版 Agent 执行器已经逐渐被 LangGraph 生态取代,所以不要在这里花太多时间,跑通即可。
4.3 阶段三:进入 LangGraph,先跑通最简单的有向图
目标:把一个流程从线性管道改写成图结构。耗时 3 天。
跟着官方文档把入门示例写一遍:定义一个 State,定义两个节点,用add_edge连接,用compile()编译,然后执行。再试着加一个条件边,模拟"模型决定是否调用工具"。
这个阶段要刻意练习的是:把旧代码的 for 循环、if else 分支改写成节点 + 条件边。一开始会很别扭,但这是思维方式转变的关键一步。每改完一个分支,就在本地跑测试用例验证 State 的变化。
4.4 阶段四:拿下高阶必杀技能,状态编程 + 检查点 + 人工介入
目标:能设计一个生产可用的复杂 Agent。耗时 1 到 2 周。
重点攻克这些能力:
- 自定义 Reducer,处理并行节点的状态合并
- 配置持久化 Checkpointer(从 SQLite 起步,再试 Postgres)
- 用
interrupt()实现人工审批流程 - 用
Command控制图内的动态跳转和提前终止 - 掌握 LangGraph 的回放与调试手段(LangSmith 是重要的辅助工具)
- 设计健壮的异常处理与循环次数上限
这个阶段的建议:找一个小而完整的业务场景练手,比如"带人工审批的报销单审核 Agent"或"多步骤资料收集 Agent"。完成一个完整的生产级项目,比看十篇教程都有效。
5. 真实踩坑记录与排查思路
最后分享我在实际项目里踩过的坑和对应的排查方法,每条都是血的教训。
5.1 消息列表越跑越短?多半是 Reducer 没配对
症状:Agent 在第二轮循环时,历史消息突然丢失,模型像失忆一样。
排查:先看 State 定义里messages字段有没有声明Annotated[list, add_messages]。如果没有,新节点返回的 messages 会直接覆盖旧值。我犯过一次:手滑把add_messages漏了,线上 Agent 表现忽好忽坏,排查了一下午才发现。
解决方案:所有多节点共享的消息字段都必须使用add_messages或自定义合并函数,否则就乖乖以不同字段名隔离数据。
5.2 条件边不跳转?先检查路由函数返回值是否匹配
症状:节点执行完毕,图却报类似Can not reach END或直接停在原地。
排查:add_conditional_edges里的路由函数返回的字符串,必须与后面的路径映射表的 key 完全一致。包括大小写、空格、下划线。我见过不少人把return "Tool",但映射表里 key 写的是"tool",结果永远匹配不上。
解决方案:定义路由路径映射时,用常量而不是散落的字符串。例如:
ROUTE_TOOLS = "tools" ROUTE_END = "end"在路由函数里直接复用这两个常量,从根上杜绝拼写不一致。
5.3 检查点配置了却没效果?Checkpointer 要在编译时传入
症状:配了MemorySaver,重启进程后状态依然丢失,甚至interrupt之后无法恢复。
排查:检查graph = workflow.compile()这一步是否传入了checkpointer。很多人只定义了checkpointer = MemorySaver(),却忘了在compile(checkpointer=checkpointer)里传参。另外,每次执行都要用同一个graph实例去 invoke,并传入独立的thread_id配置,否则恢复时找不到对应会话。
checkpointer = SqliteSaver.from_conn_string("state.db") app = workflow.compile(checkpointer=checkpointer) config = {"configurable": {"thread_id": "request-123"}} result = app.invoke({"messages": [("user", "帮我查一下报销政策")]}, config)5.4 图跑得慢,成本翻倍?往往是工具调用太频繁
症状:同样一个问题,别人的 Agent 调用 2 次模型就完成,你的却要调 5 次。
排查:观察路由日志,看模型是不是每次都选择调用工具,即便它已经掌握了足够信息。这既和 prompt 设计有关,也和图的拓扑结构有关。
解决方案:三招。第一,在 prompt 里明确"仅在信息不足时调用工具";第二,给工具加权重或评分,引导模型优先试用成本更低的工具;第三,设计"验证节点",在工具调用前先要求模型给出调用理由,减少无效调用。有一次我把第三个策略用在一个内部客服 Agent 上,模型调用次数降了 40%,token 成本直接减半。
5.5 版本兼容性是最隐蔽的坑
LangChain 和 LangGraph 的 API 更新非常频繁,我见过旧代码里StateGraph的导入方式变了,invoke参数签名变了,甚至整个ToolExecutor类都被废弃了。
解决方案:项目一开始就锁定版本号,比如langchain==0.3.x、langgraph==0.4.x,升级前先跑完整的回归测试。另外,多学一点源码,LangGraph 的开源代码结构本身比文档清晰,看不懂文档时直接看类的定义,往往能省很多时间。
6. 我的最终建议
从 LangChain 到 LangGraph 不是简单学一个新框架,而是一次思维方式的升级:从"编写流水线"到"设计状态机"。刚开始会觉得别扭,但一旦你习惯用图来表达 Agent 的每一步,你会发现在处理复杂、多步骤、需要人工参与的任务时,代码的清晰度和可靠性都有明显提升。
我的建议是:先花一周把 LangChain 的 RAG 基础打牢,再花两周硬啃 LangGraph 的小型实战项目,之后遇到任何框架层面的困惑,不要直接百度结论,先翻官方文档里的 API 说明和源码,再对照LangSmith 的追踪日志去查实。这个习惯能帮你避开 80% 的坑。
最后分享一个小技巧:学 LangGraph 时,先别急着写代码,先拿出纸笔把状态图草图画出来,节点、边、条件分支都列清楚。代码永远是从图推导出来的,不是边写边想出来的。这个习惯我从 LangGraph 0.2 用到现在,每个复杂 Agent 都是这么落地的。