0. 先用一个最典型真实案例
用户一句话:上周我买的一个耳机坏了。
这在真实客服里通常不是一步完成,而是一个会反复跳转的流程:
- 先判定意图:售后/退换/维修
- 抽取信息:订单号、购买时间、故障描述、是否在保
- 信息不全就追问(回路)
- 命中规则:7天无理由、15天换货、质保维修(分支)
- 高价值订单或争议单转人工(中断)
- 人工处理后再回来继续(恢复)
- 最后生成工单并通知用户
1. 结论先说清楚
- LangChain:更像“能力层/组件层”,擅长 LLM 调用、抽取、工具调用、RAG。
- LangGraph:更像“流程编排层/状态机层”,擅长分支、回路、中断恢复、可观测审计。
- 最常见生产形态:LangChain 做节点能力,LangGraph 做流程编排。
一句话经典总结:
- 直接写:你在代码里“模拟流程图”。
- LangGraph:你直接“写流程图本身”。
1.1 为什么这类业务必须流程化
以“耳机坏了”为例,客服系统不是回答一个问题,而是在执行一个带责任边界的业务流程:
- 要判定是否命中 7 天退、15 天换、质保修
- 要补齐关键字段(订单号、购买时间、是否在保)
- 要处理高风险单人工审批
- 要保证恢复后不重复退款/不重复建单
不流程化的代价(线上常见):
- 同样问题不同时间回答不一致(规则漂移)
- 多轮对话中字段丢失,反复问用户
- 人工审批后恢复错误,出现重复动作
- 出问题只能看到最终状态,难以解释“为什么这么判”
所以重点不是“能不能跑通”,而是“能不能稳定、可审计、可维护地长期跑”。
1.2 不用 LangGraph vs 使用 LangGraph(代码层差异)
不用 LangGraph,你通常会写成“业务代码里堆流程控制”:
async function handleAfterSales(state: State, userMsg: string) {
state = merge(state, await extract(userMsg))
if (!isComplete(state)) return askForMissing(state)
const policy = await decidePolicy(state)
if (policy === 'high_risk') {
await saveCheckpoint(state)
return 'wait_approval'
}
for (let i = 0; i < 3; i++) {
try { return await createTicket(state) } catch {}
}
return 'failed'
}
使用 LangGraph,你把“流程控制”从业务函数里抽离到图结构:
graph
.addEdge(START, 'extract')
.addConditionalEdges('extract', s => isComplete(s) ? 'policy' : 'clarify')
.addEdge('clarify', 'extract') // 回边
.addConditionalEdges('policy', s => isHighRisk(s) ? 'approval' : 'createTicket')
.addEdge('createTicket', END)
差异本质:
- 不用 LangGraph:你在代码里“模拟流程图”。
- 用 LangGraph:你直接“写流程图本身”。
2. LangGraph 的 6 个优势(结合耳机坏了场景展开)
2.1 流程可视化、可控
耳机售后不是一个 if/else 能搞定的短链路,而是“先问、再判、再分流、可能人工”的业务流。
- 用 LangGraph:节点和边就是业务规则本体,哪些节点可达、哪些路径禁止,天然明确。
- 不用 LangGraph:流程散落在多层函数、条件判断和异常处理里,改规则容易牵一发而动全身。
2.2 天然支持分支和回路
“缺字段就追问,补齐再判断”是售后高频动作。
- 分支:
policy -> refund | replace | repair | human_review - 回路:
extract -> clarify -> extract - 不用 LangGraph:你要手写循环、手写分支、手写退出条件,很容易出现重复提问和漏判。
2.3 状态管理更稳
订单号、购买时间、故障描述、策略命中结果、审批结论,全部都在一个 state 中持续演进。
- 用 LangGraph:状态在流程中统一读写。
- 不用 LangGraph:状态常分散在 session、数据库记录、函数参数里,容易丢字段或覆盖错误。
2.4 支持中断与恢复(生产刚需)
高风险单需要人工审批,2小时后才回来是常态。
- 用 LangGraph:可在审批节点中断,保存 checkpoint;审批回调后按
thread_id从nextNode继续。 - 不用 LangGraph:你要自己实现中断协议、恢复调度、幂等处理、状态机迁移。
2.5 可观测、可审计
运营和技术常问:
- 为什么没走换货?
- 卡在哪一步?
- 谁批准的退款?
用 LangGraph 更容易得到“过程证据链”:
- 走了哪些节点
- 每步输入/输出
- 命中了哪条规则
- 在哪条条件边上分流
不用 LangGraph 也能查,但你要自己搭流程日志体系,投入不小。
2.6 复杂流程更易维护
业务规则一定会变:类目规则、风控阈值、VIP 通道、地区政策。
- 用 LangGraph:通常是加节点/改边,影响范围可控。
- 不用 LangGraph:往往要在多处
if/else补丁,回归风险高。
3. 你反复追问的核心疑问(统一答疑)
3.1 为什么之前示例有 while,我感觉 if 就够
你这个判断是对的,取决于运行模型:
- 单次调用内连续追问:常见
while - 每轮会话都重新调用处理函数:常见
if + 持久化状态
在线客服通常是第 2 种,因此“if 就够”成立,前提是状态已保存。
3.1.1 为什么要强调“大模型通常是多轮对话”
真实客服并不是一次把所有槽位说全。用户常见输入像这样:
- 第 1 轮:
上周买的耳机坏了 - 第 2 轮:
订单号是 123 - 第 3 轮:
京东买的 - 第 4 轮:
过了 10 天
这意味着系统设计天然要支持“回路式收集信息”,而不是一次性提取完成。 也就是你一直在强调的关键点:大模型在业务里通常是多轮交互,不是单轮问答。
所以流程应默认支持:
- 缺信息 -> 追问
- 新信息 -> 回到抽取与校验
- 满足条件 -> 进入规则判定与分流
这正是 LangGraph 回边模型最契合的场景。
3.2 Checkpoint 到底是什么
Checkpoint = 流程存档点,至少包括:
threadId:会话/工单唯一标识nextNode:下一步跑哪个节点state:当前完整上下文status:RUNNING / WAITING_APPROVAL / DONE / FAILED
生产一般直接存数据库(PostgreSQL/MySQL/Redis/MongoDB 等)。
3.3 人工审批 2 小时后怎么继续
常见生产模式:
- 审批节点中断并写 checkpoint(WAITING_APPROVAL)
- 审批系统回调或消息事件带回
threadId + approved - 服务按
threadId读取 checkpoint - 审批通过则从
nextNode继续(不是重跑全流程)
推荐实践:事件回调为主,轮询兜底补偿。
3.4 数据库不是也能查出过程吗
可以,但代价不同:
- 数据库天然强在“结果态”(最终状态、审批人、时间)
- LangGraph 更强在“过程态”(节点路径、分支条件、状态演进、重试轨迹)
所以不是“能不能做”,而是“要不要自己从零造流程观测平台”。
3.5 LangGraph 到底提供了哪些可追踪工具
常用能力(以 JS/TS 为主)包括:
checkpointer + thread_id:持久化线程状态interrupt()+Command({ resume }):中断与恢复getState():查当前状态/下一节点getStateHistory():查历史快照轨迹stream(..., streamMode):实时看节点更新- 搭配 LangSmith:可视化 trace、耗时、错误、对比与排障
4. 最直观对比:不用 LangGraph 会怎样
4.1 代码组织层面
- 不用 LangGraph:
if/else + while + retry + callback + db status混在业务代码里。 - 用 LangGraph:节点函数负责业务动作,图结构负责流程控制。
4.2 排障层面
- 不用 LangGraph:日志分散,靠人拼接路径。
- 用 LangGraph:按
thread_id可直接回放路径和状态演进。
4.3 变更层面
- 不用 LangGraph:改规则常影响多处逻辑。
- 用 LangGraph:多为改边或加节点,变更可控。
4.4 业务代价层面(重点)
- 不用 LangGraph:短期看能上线,长期容易进入“补丁式开发”,规则多了后变得不可解释、不可预测。
- 用 LangGraph:前期多一点流程建模成本,后期在审计、排障、扩展、交接上明显省成本。
可直观看成:
- 不用 LangGraph 的代价是“把复杂度藏进代码”。
- 用 LangGraph 的收益是“把复杂度显式放进流程图”。
经典总结再次强调:
- 直接写:你在代码里“模拟流程图”。
- LangGraph:你直接“写流程图本身”。
5. 一个极简 TS 伪代码(体现回边 + 中断恢复)
type State = {
threadId: string
slots: { orderId?: string; buyDate?: string; issue?: string; inWarranty?: boolean }
policy?: 'refund' | 'replace' | 'repair'
approved?: boolean
}
// 回边逻辑:extract -> clarify -> extract
function routeAfterExtract(s: State) {
return isComplete(s.slots) ? 'policy' : 'clarify'
}
// 审批中断:policy -> approval(interrupt) -> createTicket(resume)
async function approvalNode(s: State) {
const approved = interrupt({ type: 'manual_approval', threadId: s.threadId })
return { ...s, approved }
}
6. 用 Pinia/Vuex 心智模型理解 LangGraph
这不是说它们是同一类工具,而是说它们在设计思想上有很多共通点。
| 前端(Pinia/Vuex) | LangGraph | 共同思想 |
|---|---|---|
store.state | graph state | 单一状态源,避免多份真相 |
action/mutation 改状态 | node 执行后更新状态 | 状态变化必须走显式入口 |
getter | 条件路由(conditional edges) | 基于当前状态做决策 |
| Devtools 时间线 | checkpoint + state history | 状态可回溯、可复盘 |
| store 持久化插件 | checkpointer | 进程重启后可恢复 |
| 模块化 store | 子图/节点拆分 | 复杂系统模块化降耦合 |
| 组件共享同一 store | 节点共享同一 state | 多参与方共享上下文 |
6.1 和你这个耳机售后场景怎么对应
- Pinia/Vuex 里,多个组件读写同一个
store。 - LangGraph 里,
extract / clarify / policy / approval / createTicket都读写同一份state。 - 所以“订单号、购买时间、审批结果”不会在节点间来回丢失。
6.2 更深一层的共同设计哲学
- 可预测性优先:同样输入 + 同样状态,结果应该可重现。
- 显式流转优先:把状态变化和流程跳转写在明面上,不靠隐式副作用。
- 可调试优先:先保证“出问题能解释”,再谈功能跑通。
- 演进友好:需求变更时,优先加模块/加边,而不是在核心逻辑里打补丁。
6.3 但边界也要讲清楚(防误解)
- Pinia/Vuex 主要解决前端 UI 状态管理。
- LangGraph 主要解决跨系统、可中断、可恢复的业务流程执行。
- 可以把 LangGraph 理解为“状态机 + 工作流编排”,不只是“状态容器”。
一句话帮助记忆:
- Pinia/Vuex 解决“页面状态别乱”。
- LangGraph 解决“业务流程别乱”。
7. 最终建议(给当前阶段)
- 如果你在做 PoC 或短链路客服:先用 LangChain 快速落地。
- 如果你要上生产并面对多分支、回路、人工审批、SLA/审计:尽早上 LangGraph 编排。
- 最实用路线:保留 LangChain 节点能力,逐步迁到 LangGraph 流程层。