一次 invoke() 成功,说明不了系统是否可运营
本地调用返回答案,只能说明当前输入走通了一次 LangGraph1 路径。设想值班工程师收到一句“这次很慢”:他需要知道慢在检索节点、模型请求还是重试退避;还要判断检索为空是上游没有数据,还是过滤条件变化;恢复之后的发布是否真的只发生了一次。
如果日志只打印 Agent failed,这些问题没有证据可查。比如一条运行失败时,日志应能告诉你“检索节点在 10:31:04 超时”,Trace 应能展开它之前的拆题和之后未执行的生成节点,指标则能告诉你“过去 10 分钟检索超时率从 2% 升到 18%”。三种视角分别回答单次事实、单次因果链和整体趋势;它们不是三套互相替代的系统。
先给运行一套能贯穿系统的身份
建议把 thread_id、run_id 和 trace_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[反馈与业务记录]一次运行可以用 run_id 关联 thread_id、trace_id、日志和反馈;thread_id 仍然只是运行身份,不能单独当作权限凭证。
一次运行的诊断字段可以包括:
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 的价值边界:不是把函数换成节点,而是把一条可能运行很久、会被人工打断、会遇到外部失败的流程,变成一条有记忆、能恢复、可验证的执行轨迹。如果这些能力并不是问题所需要的,停在普通函数或任务队列反而是更好的决定。
参考资料
- LangGraph Graph API(官方文档,访问日期:2026-08-24)
- LangGraph Persistence(官方文档,访问日期:2026-08-24)
- LangGraph Interrupts(官方文档,访问日期:2026-08-24)
- LangGraph Streaming(官方文档,访问日期:2026-08-24)
Footnotes
-
LangGraph 官方文档:提供可组合、可恢复的状态图执行模型。 ↩