Skip to main content

Documentation Index

Fetch the complete documentation index at: https://cndoc-langchain.site/llms.txt

Use this file to discover all available pages before exploring further.

软件需要在生产环境中进行变更。新需求、错误修复和重构最终都会进入你的图代码。由于 LangGraph 使用最新部署的图来运行针对已持久化状态的现有线程,因此你发布的每个变更实际上都是针对现有检查点的向后兼容 API 变更。 与将运行固定在其启动时代码版本的工作流引擎不同,LangGraph 会立即将最新的图应用于每个线程,无论是新线程还是从检查点恢复的线程。这很方便:错误修复无需额外操作即可传播到进行中的对话和代理。这也意味着你必须考虑每个变更如何与在代码的先前版本下启动的运行进行交互。 有三类兼容性问题需要注意,大致按你遇到它们的顺序排列:
  1. 技术兼容性:最常见;新代码必须仍然能够加载并针对现有状态执行。
  2. 业务兼容性:较少见;即使代码已更改,现有运行也应继续遵循旧的业务逻辑。
  3. 非确定性:仅适用于函数式 API
关于运行时默认支持的图拓扑和状态变更的简要概述,请参阅图迁移。本页的其余部分涵盖了当变更超出该支持范围时可以应用的模式。

技术兼容性

技术兼容性相当于微服务中的 API 破坏性变更。这里的“API”是你的图代码与检查点存储器为现有线程持久化数据之间的契约。当线程恢复时,LangGraph 反序列化保存的状态,按名称将其分派到节点,并期望节点返回符合状态模式的值。 常见的技术性破坏:
  • 重命名或移除节点,而线程正暂停在该节点或即将进入该节点,例如在 interrupt 处或通过仍路由到旧名称的检查点条件边。恢复时,LangGraph 无法按其保存的名称找到节点,运行失败。恢复运行的起点是执行停止的节点开头,因此缺失的节点无处可恢复。
  • 重命名或移除状态键,而旧的检查点仍包含该键或下游节点仍在读取该键。
  • 收紧状态字段,例如将 Optional 字段设为必需、缩小类型或添加没有默认值的新必需字段。现有检查点将不满足新模式。
边拓扑本身不会持久化在检查点中。在仍然存在的节点之间添加、移除或重新路由边对于进行中的线程是安全的。根据图迁移摘要,唯一可能破坏中断线程的拓扑变更是重命名或移除节点。

推荐模式

  • 将新状态字段添加为 NotRequired(或 Optional[...] = None),以便旧检查点仍然有效:
    from typing import NotRequired
    from typing_extensions import TypedDict
    
    class State(TypedDict):
        messages: list
        summary: NotRequired[str]
    
  • 将移除视为弃用。即使没有节点读取该字段,也至少在一个排空周期内保持该字段在状态上的定义,以便现有检查点继续加载。
  • 通过先添加后移除进行重命名。在旧字段或节点旁边添加新字段或节点,在弃用窗口期内双写或同时路由到两者,然后在确认没有进行中的线程依赖它之后移除旧的。
  • 保持节点函数对未知键具有容忍性。TypedDict 在运行时忽略额外的键,因此来自旧代码版本的剩余状态不会引发错误,除非节点显式读取缺失的键。
  • 使用时间旅行graph.get_state 在推出之前,在暂存部署中针对新代码抽查现有线程。

检测进行中的线程

在你移除节点、重命名状态键或进行其他旧线程无法容忍的变更之前,你需要知道是否有任何线程当前停留在你即将放弃的代码版本上。LangGraph 本身不维护线程状态的搜索索引,因此答案取决于你的图运行位置。 如果你部署到 LangSmith 使用代理服务器的线程搜索按状态进行筛选。status 字段接受 idlebusyinterruptederror,因此你可以批量查询 interruptedbusy 线程,并可选择使用元数据筛选器进行缩小。参阅按线程状态筛选列出线程 在任何 LangGraph 运行的地方。 使用 LangSmith 追踪来监控生产环境中哪些节点正在被进入和退出。这是节点或状态字段在任何活动代码路径中不再可达的最可靠信号。 当你已经拥有 thread_id 时。 直接检查该单个线程: 如有疑问,请保留已弃用的节点或字段,直到代理服务器线程列表和追踪都显示其不再有活动。

业务兼容性

有时变更在技术上是有效的(每个现有检查点仍然加载,每个节点仍然解析),但新图的含义与旧图不同。新行为对于新线程是正确的,你不想将其追溯应用于在旧逻辑下启动的线程。 例如,假设你的图运行 intake → triage → respond,你决定在 triagerespond 之间插入一个新的 policy_check 步骤:
  • 已经通过 triage 的线程应直接继续到 respond(旧流程)。
  • 新线程应运行完整的新流程。
推荐的模式是在线程启动时在状态上记录相关的行为版本,然后使用条件边对其进行分支:
from typing import NotRequired
from typing_extensions import TypedDict

from langgraph.graph import END, START, StateGraph


class State(TypedDict):
    request: str
    flow_version: NotRequired[int]
    response: NotRequired[str]


def intake(state: State) -> dict:
    # 用当前流程版本标记新线程。恢复到 `intake` 之后的现有线程
    # 保留已保存的任何值。
    return {"flow_version": state.get("flow_version", 2)}


def triage(state: State) -> dict: ...
def policy_check(state: State) -> dict: ...
def respond(state: State) -> dict: ...


def after_triage(state: State) -> str:
    if state.get("flow_version", 1) >= 2:
        return "policy_check"
    return "respond"


builder = StateGraph(State)
builder.add_node("intake", intake)
builder.add_node("triage", triage)
builder.add_node("policy_check", policy_check)
builder.add_node("respond", respond)
builder.add_edge(START, "intake")
builder.add_edge("intake", "triage")
builder.add_conditional_edges("triage", after_triage, ["policy_check", "respond"])
builder.add_edge("policy_check", "respond")
builder.add_edge("respond", END)

graph = builder.compile()
triage 之后恢复的旧线程从其保存的状态中读取 flow_version(或回退到 v1 默认值)并跳过 policy_check。新线程从 intake 开始,被标记为 flow_version=2,并运行新路径。一旦所有 v1 线程完成,你就可以移除版本标志和条件边。 此模式仅在你在线程启动时(在任何需要版本控制的分支之前)设置版本时有效。稍后设置意味着现有线程在需要时将没有设置该值。

非确定性

此类别仅适用于函数式 API图 API 在恢复时在节点边界处重新进入,因此节点代码不会像 Temporal 风格的工作流那样从函数开头“重放”。 相比之下,函数式 API 在运行恢复时从头开始重放 @entrypoint 的主体,使用缓存的 @task 结果来跳过已完成的工作。两种变更会破坏此模型:
  • 添加、移除或重新排序 @task 调用或 interrupt 调用,这些调用位于恢复点之前。LangGraph 通过它们在重放中的位置将缓存结果和恢复值与调用匹配,因此移动该位置可能导致错误的缓存值被重放到不同的调用上。
  • @task 之外引入非确定性操作,例如 time.time()random.random() 或内联在入口点主体中的网络调用。在重放时,这些操作产生的值与首次运行时不同,这可能会改变控制流。
有关更深入的讨论和示例,请参阅函数式 API 指南中的确定性常见陷阱 如果你需要对具有进行中运行的 @entrypoint 进行重大代码变更,最安全的选项是:
  • 让进行中的运行在部署变更之前排空。
  • 将任何新逻辑包装在新的 @task 中,以便其结果独立检查点。
  • langgraph.json 中为新行为注册一个新的入口点,并将新线程路由到它。