LangGraph的真实案例理解与前端vuex比对分析

7 阅读9分钟

0. 先用一个最典型真实案例

用户一句话:上周我买的一个耳机坏了

这在真实客服里通常不是一步完成,而是一个会反复跳转的流程:

  1. 先判定意图:售后/退换/维修
  2. 抽取信息:订单号、购买时间、故障描述、是否在保
  3. 信息不全就追问(回路)
  4. 命中规则:7天无理由、15天换货、质保维修(分支)
  5. 高价值订单或争议单转人工(中断)
  6. 人工处理后再回来继续(恢复)
  7. 最后生成工单并通知用户

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_idnextNode 继续。
  • 不用 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 小时后怎么继续

常见生产模式:

  1. 审批节点中断并写 checkpoint(WAITING_APPROVAL)
  2. 审批系统回调或消息事件带回 threadId + approved
  3. 服务按 threadId 读取 checkpoint
  4. 审批通过则从 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.stategraph 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. 最终建议(给当前阶段)

  1. 如果你在做 PoC 或短链路客服:先用 LangChain 快速落地。
  2. 如果你要上生产并面对多分支、回路、人工审批、SLA/审计:尽早上 LangGraph 编排。
  3. 最实用路线:保留 LangChain 节点能力,逐步迁到 LangGraph 流程层。