Skip to content

向后兼容性(Backward compatibility) ​

在生产环境中更新 LangGraph 图代码,而不会破坏在飞的运行。

软件需要在生产环境中进行变更。新需求、Bug 修复和重构最终都会落入你的图代码中。由于 LangGraph 会针对现有线程的已持久化状态运行最新部署的图,因此你发布的每项更改相对于现有检查点而言,实际上都是向后兼容的 API 变更。

与将运行固定在其启动时的代码版本的传统工作流引擎不同,LangGraph 会立即将最新的图应用于每个线程,无论是新线程还是从检查点恢复的线程。这非常方便:Bug 修复可以顺畅地传播到在飞的对话和智能体中。这也意味着你必须思考每项更改如何与在先前代码版本下启动的运行进行交互。

有三类兼容性问题需要注意,大致按你遇到它们的顺序排列:

  1. 技术兼容性(Technical compatibility):最常见;新代码必须仍能针对现有状态加载并执行。
  2. 业务兼容性(Business compatibility):不太常见;即使代码已更改,现有运行仍应继续遵循旧的业务逻辑。
  3. 非确定性(Non-determinism):仅适用于 Functional API。

TIP

有关运行时默认支持哪些图拓扑和状态更改的简要摘要,请参阅图迁移。本页面的其余部分涵盖了当更改超出该受支持集合时你可以应用的模式。

技术兼容性 ​

技术兼容性等同于微服务中的 API 破坏性变更。这里的“API”是你的图代码与检查点保存器为现有线程持久化的数据之间的契约。当线程恢复时,LangGraph 反序列化已保存的状态,按名称将其分发给节点,并期望节点返回符合状态架构的值。

常见的技术破坏:

  • 重命名或删除节点:当线程暂停在或即将进入该节点时(例如在 interrupt 处或通过仍路由到旧名称的检查点条件边)。恢复时,LangGraph 找不到该保存名称的节点,运行将失败。恢复运行的起点是执行停止的节点开头,因此缺少的节点将无处恢复。
  • 重命名或删除 State 键:较旧的检查点仍包含该键或下游节点仍在读取该键。
  • 收紧 State 字段:例如将 Optional 字段设为必填、收窄类型,或添加没有默认值的新必填字段。现有检查点将无法满足新架构。

边拓扑本身不会持久化在检查点中。在仍然存在的节点之间添加、删除或重新路由边对于在飞线程是安全的。根据图迁移摘要,唯一可能破坏中断线程的拓扑更改是重命名或删除节点。

推荐模式 ​

  • 将新的状态字段标记为可选(z.string().optional() 或 .nullish()),以便旧检查点仍然可以通过验证。
  • 将删除视为弃用:在架构上保留该字段至少一个排空周期,以便现有检查点可以继续加载。
  • 通过“先增后删(add-then-remove)”进行重命名:将新字段或节点与旧字段或节点并列添加,在弃用窗口期双写或路由到两者,然后在没有在飞线程依赖它时删除旧字段。
  • 在推出之前,使用时间旅行和 graph.getState 在预发部署中针对新代码对现有线程进行抽样检查。

检测在飞线程(in-flight threads) ​

在删除节点、重命名 State 键或进行较旧线程无法容忍的更改之前,你需要了解当前是否有任何线程驻留在即将丢弃的代码版本上。LangGraph 本身不维护线程状态的搜索索引,因此答案取决于你的图在何处运行。

如果你部署到 LangSmith:使用 Agent Server 的线程搜索按状态进行过滤。status 字段接受 idle、busy、interrupted 和 error,因此你可以批量查询 interrupted 或 busy 的线程,并可选地通过元数据过滤器进行筛选。请参阅按线程状态过滤和列出线程。

在 LangGraph 运行的任何地方:使用 LangSmith 跟踪监控在生产中进入和退出了哪些节点。这是某个节点或状态字段在任何活动代码路径中不再可达的最可靠信号。

当你已有 thread_id 时:直接检查该单个线程:

存疑时,请保留已弃用的节点或字段,直到 Agent Server 线程列表和跟踪均显示对其不再有任何活动。

业务兼容性 ​

有时更改在技术上是有效的(每个现有检查点仍可加载并且每个节点仍可解析),但新图的含义与旧图不同。新行为对于新线程是正确的,并且你不想将其追溯应用于在旧逻辑下启动的线程。

例如,假设你的图运行 intake → triage → respond,并且你决定在 triage 和 respond 之间插入一个新的 policy_check 步骤:

  • 已经通过 triage 的线程应直接继续到 respond(旧流程)。
  • 新线程应运行完整的全新流程。

推荐的模式是在线程开始时在状态上记录相关的行为版本(behavioral version),然后使用条件边对其进行分支:

在 triage 之后恢复的旧线程从其保存的状态中读取 flow_version(或回退到 v1 默认值)并跳过 policy_check。新线程从 intake 开始,被打上 flow_version=2 标记,并运行新路径。一旦所有 v1 线程完成,你就可以删除版本标志和条件边。

此模式仅在你在线程开始时、在需要进行版本控制的任何分支之前设置版本时才有效。稍后设置意味着现有线程在需要时将没有设置该版本。

非确定性(Non-determinism) ​

此类别仅适用于 Functional API 以及 Graph API 节点内部的 tasks 或 interrupt 调用。普通 Graph API 节点在恢复时从节点函数的开头重新运行;将副作用设计为幂等的,但除非你在该节点中使用 tasks 或 interrupt,否则无需保留任务调用顺序。

Functional API entrypoint 编译为单个节点,在运行恢复时从头重放 entrypoint 函数体,使用缓存的 task 结果跳过已完成的工作。两种更改会破坏此模型:

  • 添加、删除或重新排序在恢复点之前出现的 task 调用或 interrupt 调用。LangGraph 通过调用在重放中的位置将缓存结果和恢复值与调用相匹配,因此改变该位置可能会导致错误的缓存值在不同的调用中被重放。
  • 在 task 之外引入非确定性操作,例如 time.time()、Math.random() 或内联在 entrypoint 主体中的网络调用。在重放时,这些操作产生的值与首次运行时不同,这可能会改变控制流。

有关更深入的说明和示例,请参阅 Functional API 指南中的确定性和常见陷阱。

如果你需要对具有在飞运行的 entrypoint 进行非细微的代码更改,最安全的选择是:

  • 在部署更改之前让在飞的运行排空。
  • 将任何新逻辑包装在新的 task 中,以便其结果被独立记录检查点。
  • 在 langgraph.json 中以新图名称注册新 entrypoint 以实现新行为,并将新线程路由到该 entrypoint。