程云来 / 杭州
Back to Blog·LangGraph
·About 8 min

LangGraph(四):上线前,先证明这条图值得维护

从一次慢、错或重复发布的运行出发,建立 LangGraph 的追踪、测试、成本和并发边界,再判断什么时候普通函数或任务队列更合适。


一次 invoke() 成功,说明不了系统是否可运营

本地调用返回答案,只能说明当前输入走通了一次 LangGraph1 路径。设想值班工程师收到一句“这次很慢”:他需要知道慢在检索节点、模型请求还是重试退避;还要判断检索为空是上游没有数据,还是过滤条件变化;恢复之后的发布是否真的只发生了一次。

如果日志只打印 Agent failed,这些问题没有证据可查。比如一条运行失败时,日志应能告诉你“检索节点在 10:31:04 超时”,Trace 应能展开它之前的拆题和之后未执行的生成节点,指标则能告诉你“过去 10 分钟检索超时率从 2% 升到 18%”。三种视角分别回答单次事实、单次因果链和整体趋势;它们不是三套互相替代的系统。

先给运行一套能贯穿系统的身份

建议把 thread_idrun_idtrace_id 的关系写进接口约定,而不是让每个组件自行生成一套 ID。一次用户请求可以这样关联:run_id 标识本次执行,thread_id 指向可恢复的长期运行,trace_id 串起这次执行产生的日志和 Span:

正在绘制图表…
查看 Mermaid 源码
flowchart LR
    A[用户请求] --> B[run_id]
    B --> C[thread_id]
    B --> D[trace_id]
    C --> E[Checkpoint]
    D --> F[日志与 Trace]
    B --> G[反馈与业务记录]

Mermaid 大图

可滚动查看图表,点击缩放比例可恢复 100%。按 Esc 关闭。

一次运行可以用 run_id 关联 thread_idtrace_id、日志和反馈;thread_id 仍然只是运行身份,不能单独当作权限凭证。

一次运行的诊断字段可以包括:

text
run_id / thread_id / trace_id
  ├─ 节点名与开始、结束时间
  ├─ 状态更新摘要和版本
  ├─ 模型延迟、token 和供应商请求 ID
  ├─ 重试次数、错误分类和预算消耗
  └─ 人工决定、草稿 revision 与最终状态

状态摘要要帮助排查,而不是把完整 prompt、访问令牌和个人信息写入普通日志。敏感原文应按权限进入受控存储;日志只保留模板版本、输入哈希、数量和脱敏摘要。

先测状态转移,再测模型质量

LangGraph 的基础测试不应该把模型的一段自然语言输出完全写死。第一层测试状态和控制流:

  • 正常路径中,字段由正确节点写入;
  • 检索为空、上游超时和模型拒答走预期分支;
  • 使用同一 thread 恢复后从正确节点继续;
  • 人工拒绝回到草稿,不会意外发布;
  • 重试和重复 resume 不产生重复外部写入。

模型、搜索和发布接口可以用 fake / stub 替代,再用少量端到端测试验证真实配置。状态测试回答“流程按约定运行了吗”,模型评估回答“输出质量足够吗”;把二者混成一个快照测试,失败时很难知道是图错了还是模型变了。

一个最低限度的测试表应能覆盖。每一行都应该能在测试里故意触发,而不是只写在上线文档里:

路径固定输入观察结果
正常完成两个有效来源到达 run.completed,发布一次
检索为空空来源列表进入人工或明确失败,不生成伪引用
人工退回审核意见回到草稿节点,revision 增加
重复恢复相同 thread 和 resume发布记录仍只有一条

把成本和并发限制写进运行模型

循环和并行分支会放大资源消耗。“找全资料”可能不断扩大检索范围;两个审核请求也可能同时修改同一个 thread。要提前决定来源数量、草稿长度、单次运行时长、模型 token、费用和并发 thread 的上限。

这些限制应成为 State 或路由条件,超限后走明确的 budget_exceeded 或人工接管路径。同一 thread 的并发更新也要具体规定:如果两个审核请求同时到达,系统是让后一个等待、返回版本冲突,还是创建新的 revision?不能让最后一次写入默默覆盖前一次审核决定,因为调用方从响应中看不出哪一个决定最终生效。

用故障反推设计是否完整

症状先看什么暴露的设计问题修复方向
每次请求都从入口开始thread_id 是否稳定传递没把 thread 当恢复标识固定生成、校验、权限绑定和清理
页面出现内部 State事件是否经过白名单把执行输出当用户协议业务事件、脱敏和字段限长
evidence 越积越多reducer、去重和上限只有追加,没有集合边界URL 去重、顺序和数量限制
图一直循环attempt / budget条件边没有出口达到预算后转人工或失败
失败无法复现最后检查点和 Trace只记录最终错误保存状态摘要和外部请求 ID

排查顺序应固定下来:先看最后一个成功检查点,确认 State 和控制流停在哪里;再看节点输入 / 输出摘要和外部请求 ID,确认是哪个依赖失败;最后才看模型原始文本,判断内容质量问题。这样可以先排除“图没有走到生成节点”这类控制流错误,再分析模型答案,而不是一上来搜索几千字 prompt。

什么时候不该使用 LangGraph

LangGraph 增加了 State、Checkpoint、节点边界、事件和测试等维护成本。它只有在这些成本换来必须能力时才值得:

流程特征更合适的选择原因
两个固定函数,没有分支、循环和恢复普通函数调用栈已经足够表达流程
主要问题是定时调度、可靠投递和批量消费任务队列队列提供投递、重试和消费并发模型
需要跨请求暂停、人工决定和恢复LangGraph 或类似状态运行时State 与恢复边界是核心需求
关键副作用没有幂等能力先补业务接口框架不能替外部系统保证一次写入
还没有稳定状态模型和测试先画流程、写状态契约过早引入运行时只会隐藏问题

如果不能明确说出“我们需要恢复、人机协同、条件分支、审计轨迹或长时间运行中的哪一项”,普通方案通常更容易维护。例如只有“调用模型 → 返回文本”的接口,函数调用和超时处理已经足够;只有在流程要跨天等待审核、从检查点恢复或对多个分支做可追踪控制时,LangGraph 的额外 State 和节点管理才有明确回报。技术选型应该由失败现场和验证成本推导出来,而不是因为项目使用了 LLM 就默认选择 LangGraph。

主系列的上线前清单

  • State 字段有类型、写入者、生命周期、大小上限和敏感数据策略。
  • Node 输入 / 输出清楚;副作用节点使用稳定幂等键。
  • 固定边、条件边、循环和终止条件都有测试。
  • thread_id 的生成、传递、权限、保留和清理已定义。
  • Checkpointer、Store 和业务数据库各自的职责没有混用。
  • interrupt / resume 的拒绝、重复提交和断线行为可验证。
  • stream 事件经过白名单、脱敏、顺序和取消处理。
  • 日志和 Trace 能定位 thread、节点、耗时和错误类型。
  • 超时、空结果、恢复冲突和外部写入失败都有处理路径。
  • 已记录什么时候不应该继续使用 LangGraph。

回到第一篇的调研助手

第一篇只证明“状态能在节点之间流动”。现在我们可以给这条流程补上完整的工程判断:状态有明确契约,审核等待有持久化边界,重试停在可重做的节点,发布由幂等键保护,运行又能用 Trace 和测试解释。

这就是 LangGraph 的价值边界:不是把函数换成节点,而是把一条可能运行很久、会被人工打断、会遇到外部失败的流程,变成一条有记忆、能恢复、可验证的执行轨迹。如果这些能力并不是问题所需要的,停在普通函数或任务队列反而是更好的决定。

参考资料

Footnotes

  1. LangGraph 官方文档:提供可组合、可恢复的状态图执行模型。