LangGraph Python 最佳实践调研报告

LangGraph Python 最佳实践调研报告

  • 日期:2026-06-11
  • 范围 :仅 LangGraph Python(TypeScript / JavaScript 不在范围内)
  • 基准版本langgraph 1.2.4(PyPI,2026-06-02 发布,要求 Python ≥ 3.10)
  • 配套生态版本langgraph-checkpoint 4.1.1langgraph-checkpoint-postgres 3.1.0langgraph-checkpoint-sqlite 3.1.0langgraph-prebuilt 1.1.0langgraph-sdk 0.4.2langgraph-cli 0.4.28langchain-core 1.4.x
  • 来源 :官方文档(docs.langchain.com / reference.langchain.com)、LangChain 博客、Anthropic / OpenAI 官方文档、PyPI 元数据、GitHub Releases、deep-research-synthesis.md 事实底座;并经多轮审查闭环。
  • 适用读者:需要把 LangGraph Python 投入生产或选型评估的工程师、架构师与 AI Platform 团队。
  • 不覆盖:LangGraph TypeScript / JavaScript 完整差异矩阵、A2A / MCP / 多模态 / Computer Use / Voice 等高级专题(按 v2 大纲属阶段二范围)。

目录

  • 第 1 章:执行摘要与决策框架
  • 第 2 章:LangGraph 核心设计思想
  • 第 3 章:Graph 设计规范
  • 第 4 章:状态持久化、恢复与 HITL
  • 第 5 章:流式输出与前端集成
  • 第 6 章:工具调用与 Agent 构建
  • 第 7 章:多智能体架构
  • 第 8 章:测试与质量保障
  • 第 9 章:可观测性
  • 第 10 章:性能、成本与可靠性
  • 第 11 章:生产化部署与平台选型
  • 第 12 章:安全、合规与多租户
  • 附录 A:版本与迁移注意事项(Python)
  • 附录 B:代码片段库
  • 附录 C:参考资料与术语表
  • 质量审查说明
  • 已知限制 / 待补项

第 1 章:执行摘要与决策框架

1. 解决什么问题(LangGraph 的核心价值)

LangGraph 官方把自己定位为「a low-level orchestration framework and runtime for building, managing, and deploying long-running, stateful agents」------构建、管理、部署长期运行、有状态的 agent 所需的底层编排框架与运行时。它不抽象 prompt 也不提供"全家桶"架构,而是把 agent 编排所必需的底层能力(持久化执行、可恢复、人机协同、流式、可观察、生产部署)交给开发者自己组装。

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/overview · 2026-06 抓取 · langgraph 1.2.4

LangChain 官方在 1.0 公告里也重申这一定位:LangGraph 是「low-level agent orchestration framework, giving developers durable execution and fine-grained control to run complex agentic systems in production」。文档统一为 python/javascript 合一站点("无破坏性变更"为 alpha 公告原文转述,GA 公告待补,参见附录 A.3)。

【官方能力】来源: https://www.langchain.com/blog/langgraph-v1 · 2025-09-02 · langgraph 1.0.0a1

1.1 LangGraph 解决什么(官方能力矩阵)

能力 官方表述 适用对象 引用
Durable execution 失败后从断点恢复,长期任务可暂停/续跑 任何需要"明天还能继续今天的工作流"的场景 persistence 文档
Human-in-the-loop 任意节点可暂停、检查、修改状态 审批、编辑、回滚、人工补充上下文 interrupts 文档
Memory 短期工作记忆 + 长期持久记忆(Store) 多轮对话、个性化、跨会话上下文 memory 文档
Streaming values/updates/messages/custom/checkpoints/tasks/debug(Python < 3.11 环境下 get_stream_writer 在 async 节点/工具中不可用,需直接传 writer) 前端实时展示、调试 streaming 文档
LangSmith 集成 路径可视化、状态转移、运行时指标 生产可观测性 overview 文档
生产部署 Platform 三层(Cloud / Self-Hosted Lite / Self-Hosted Enterprise) 多租户、长任务、高可用 LangSmith Deployment 文档
Multi-agent Supervisor / Swarm / Subgraph / handoff 多角色协作 multi-agent 文档(ch7 展开 langgraph-supervisor / langgraph-swarm

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/overview · 2026-06 抓取 · langgraph 1.2.4

1.2 LangGraph 不解决什么(重要边界)

  • 不替代 LangChain 的模型/工具/检索抽象:工具本身(搜索、SQL、HTTP、RAG)属于 LangChain / 第三方库;LangGraph 只做"如何调用这些工具并保留状态"。
  • 不提供 prompt 模板或架构模式:它不规定"ReAct"或"Plan-Execute",这些是社区/业务模式。
  • 不替代数据库 / 队列 / 工作流引擎:持久化(Postgres/Redis)由 checkpointer 后端提供,但 LangGraph 不管你的业务库结构。
  • 不提供开箱可用的多租户、鉴权、计费:这些属于 Platform/Server 的能力,但业务侧的权限模型仍要自己设计。
  • 对简单单轮调用不必要:官方明确建议"刚入门 agent 时,先用 LangChain prebuilt agents"。

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/overview · 2026-06 抓取 · langgraph 1.2.4

1.3 适用 / 不适用场景三问

在引入 LangGraph 之前,先自问三个问题(推断依据:见第 2 章概念图与第 3 章模式):

问题 答案偏「否」 答案偏「是」
Q1:可恢复执行? 任务是否会跨进程、跨实例、跨日恢复?是否需要从断点续跑? 用普通 Python 脚本 / LangChain Runnable 必须 LangGraph + checkpointer
Q2:HITL? 是否有"必须人审批 / 编辑 / 回滚"的节点?是否需要暂停 N 小时等用户? 自动 agent loop 即可 LangGraph interrupt() + Command(resume=...)
Q3:多步骤多分支多智能体? 是否有显式分支、循环、子图?是否有 agent 间路由 / handoff? LangChain LCEL 链式已够 LangGraph StateGraph + add_conditional_edges / Command / Send

【推测性建议】经验法则:三个问题里只要有 1 个为「是」 ,LangGraph 都值得考虑;2 个以上为「是」 ,几乎可以确定应该用 LangGraph 而非传统 Chain/AgentExecutor。边界依据 :单一强需求(仅 Q1 或仅 Q2 或仅 Q3)场景下,LangChain LCEL / create_agent(langchain 1.x)已能覆盖;2 个以上为「是」说明业务已超出 Chain 抽象的能力半径------这与 LangChain overview 建议"先 prebuilt agents、再升级 LangGraph"是同一口径。

2. 推荐做法(决策路径)

2.1 选型决策树

复制代码
你打算构建什么?
│
├── 单轮 LLM 调用 / 简单链(prompt → 模型 → 输出)
│   └─► 不要用 LangGraph
│       直接用 LangChain `create_agent` / LCEL 链;或裸调 chat model
│
├── 多轮对话 + 短期记忆 + 工具调用
│   │
│   ├── 无持久化 / 不用从断点恢复
│   │   └─► LangChain `create_agent`(harness + middleware 已够)
│   │       来源: https://docs.langchain.com/oss/python/langchain/agents
│   │
│   ├── 需要跨会话恢复 / 人审 / 长期记忆
│   │   └─► LangGraph(自托管 + Postgres / Sqlite checkpointer)
│   │       或 LangGraph Platform(如果不想自己运维)
│   │
│   └── 多智能体协作 / 显式分支
│       └─► LangGraph(StateGraph + Command + Send / Subgraph)
│
├── 长期 / 异步 / 跨日任务
│   └─► 几乎必选 LangGraph(durable execution 是核心)
│
└── 选 OpenAI Agents SDK / CrewAI / AutoGen?
    └─► 见下文 1.2.2 选型矩阵

【推测性建议】依据:LangChain 官方 overview 明确建议"先 LangChain prebuilt agents"再升级到 LangGraph;持久化能力是 LangGraph 的差异化锚点。

2.2 选型矩阵(LangGraph vs 主要替代)

维度 LangGraph LangChain Chain / create_agent OpenAI Agents SDK CrewAI AutoGen v0.4+
图范式 StateGraph 显式节点/边(Command/Send) LCEL ` ` 链式 + 函数式 agent loop 轻量原语,Python-first 串联 Flow + Crew 双层抽象
一等公民持久化 ✅ Checkpointer(Memory/Sqlite/Postgres) ✅ 可传 checkpointer=create_agent ✅ Sessions(SQLite/SQLAlchemy/Redis/Mongo/Encrypted/Dapr) ⚠️ Flow state 默认内存;需自接 storage adapter ⚠️ 需自接 / Studio 集成
跨断点恢复(durable) ✅ Durability modes (exit/async/sync) + super-step 粒度 ❌ 默认无 ✅ Sessions 可恢复,但粒度在 session 而非 super-step ❌ 文档未强调
任意节点可中断 interrupt() + Command(resume=...) ⚠️ 仅支持 agent 层 middleware human_in_the_loop/ 内建模块 ⚠️ 有限 ⚠️
工具调用 ✅ ToolNode + 任意 Python callable
多智能体编排 ✅ Supervisor / Swarm / Subgraph / handoff ⚠️ 需自实现 ✅ Handoffs / Agents-as-tools ✅ Crew ✅ Team / RoutedAgent
状态隔离 / 共享 显式 State schema + 子图隔离 单 state Session 隔离 Flow/Crew 分层 AgentId + Runtime
可观察性 LangSmith(原生集成) LangSmith OpenAI Tracing 套件 自有 trace Studio / 第三方
跨模型 ✅ 任何 chat model ✅ 任何 chat model ⚠️ 偏 OpenAI 模型
沙箱 / 隔离执行 ❌ 自接 ❌ 自接 ✅ Sandbox agents ⚠️ Docker Runtime
学习曲线 中高(需理解图/状态/检查点) 中高
适用场景 长期 / 多分支 / 多智能体 / HITL 短链路 / 单轮 / 简单 RAG OpenAI 全家桶、轻量多 agent 创意/角色协作 学术 / 实验性多 agent

【官方能力】LangGraph 能力:https://docs.langchain.com/oss/python/langgraph/overview · 2026-06 抓取 · 1.2.4

【官方能力】LangChain agents:https://docs.langchain.com/oss/python/langchain/agents · 2026-06 抓取 · langchain 1.x

【官方能力】OpenAI Agents SDK sessions:https://openai.github.io/openai-agents-python/sessions/ · 2026-06 抓取

【官方能力】OpenAI Agents SDK 主索引:https://openai.github.io/openai-agents-python/ · 2026-06 抓取

【官方能力】CrewAI:https://docs.crewai.com/en/introduction · 2026-06 抓取

【官方能力】AutoGen v0.4:https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/framework/agent-and-agent-runtime.html · 2026-06 抓取

【推测性建议】矩阵汇总由编者横向比对各官方文档后归纳;选型建议非任何一家官方背书。

关键差异速记(事实+判断)

  • 持久化是 LangGraph 差异化最深的能力 (事实+判断)。LangGraph 提供 durability modes(exit/async/sync)+ super-step 粒度的可恢复;OpenAI Agents SDK 也有 Sessions 级恢复(同一 session 实例续跑),但粒度在 session 而非 super-step;AutoGen / CrewAI 默认需自接或第三方。LangChain create_agent 自 langchain 1.x 起也支持 checkpointer=。把持久化称为"LangGraph 唯一不可替代"是过度归并;准确说法是"粒度与生产级 durability 上 LangGraph 更深"。
  • 图范式 vs 链范式(事实+判断)。LangGraph 显式建模为有向图(StateGraph + add_edge / add_conditional_edges / Command / Send),可表达任意分支/循环/并行;其他框架更偏 Python-first 函数式串联。要"任意分支+子图"是 LangGraph 的强项,但要"轻量"则 OpenAI Agents SDK / CrewAI 更快上手。
  • HITL 能力 (事实)。LangGraph 的 interrupt() + Command(resume=...) 是编译期支持的细粒度机制;OpenAI Agents SDK 有内建模块但粒度偏 agent 整体;其他框架 HITL 多为自实现。

2.3 何时升级到 LangGraph Platform

LangGraph Platform 是 LangChain 商业化的部署方案,按项目 v2 大纲口径分为三层(按"谁运维基础设施"区分;与 LangSmith Deployment 文档原词 Cloud / Standalone server / Self-hosted 的对应关系见附录 A.4):

模式 谁运维 是否含 LangSmith 控制面 数据面 / 持久化 适用
Cloud LangChain LangChain 托管(含完整控制面) LangChain 托管 中小团队 / 不想运维 / Plus 计划起
Self-Hosted Lite 你(Docker / Compose / K8s) 无控制面(仅 LangGraph Server/Studio) 你管 Postgres / Redis 已有基础设施 / 单租户 / 不想引入 LangSmith 控制面
Self-Hosted Enterprise 你(K8s) 你部署完整 LangSmith 控制面 + 数据面 你管全部 强合规 / 多租户 / 私有云(Enterprise 计划)

【官方能力】来源: https://docs.langchain.com/langsmith/deployment · 2026-06 抓取 · langgraph 1.2.4

【推测性建议】上表「适用」列由编者归纳;官方文档原文用 no control plane 描述 Standalone、用 Run the full LangSmith platform, including the control plane and data plane, in your cloud 描述 Self-Hosted,未直接给"适用对象"建议。

升级信号(推测性建议,基于社区与官方部署经验):

  1. 你已经在生产跑 LangGraph,但多副本 + 队列 + Cron + 长任务需要单独搭一套 → Self-Hosted Lite(v2 命名;官方文档称 Standalone server)。
  2. 团队没有 SRE 资源也不希望学习 K8s/Postgres 调优 → Cloud。
  3. 强合规要求全部数据留在自己 VPC,且需要 SSO/审计/多租户控制面 → Self-Hosted Enterprise。
  4. 仍在 PoC 阶段或单实例开发 → 不用 Platform,直接 pip install langgraph + 本地 Sqlite/Postgres。

3. 不推荐做法(选型反模式)

  1. 把 LangGraph 当 Chain 用 (事实+判断)。如果只是 prompt | model | parser 三步链,引入 LangGraph 反而增加认知负担。官方 overview 建议"先 LangChain prebuilt agents"。来源: https://docs.langchain.com/oss/python/langgraph/overview
  2. 在不需要持久化的场景上 checkpointer 。短期单轮脚本上 Postgres 是过度工程;本地开发用 InMemorySaver 即可。来源: https://docs.langchain.com/oss/python/langgraph/persistence
  3. 为追求"新潮"用多智能体。多智能体大幅增加状态/权限/成本复杂度;能单体解决的不要先拆多 agent。社区共识。
  4. 闭眼选 Platform Cloud 。如果数据合规要求"不能出公司网络",选 Cloud 是直接踩雷。来源: https://docs.langchain.com/langsmith/deployment
  5. 用 LangGraph 替代工作流引擎 / 队列 。LangGraph 的"调度"是图执行,不是工业级 job scheduler(cron 属于 Platform 能力而非 OSS)。来源: https://docs.langchain.com/langsmith/deployment
  6. 用 LangGraph 替代数据库事务。Graph state 不是事务系统;业务关键状态仍要走业务库 + 补偿。社区共识。
  7. 在 0.x 老版本上写新代码 。当前稳定线是 1.2.x(2026-06-02);v1.0 已从 0.6.x 跨越发布(GA 公告原文待补,参见附录 A.3);早期 0.x 文档和 API 大量过时。来源: https://pypi.org/project/langgraph/

4. 最小代码示例(决策框架不是 API 字典)

本节用最小代码表达决策点,不替代完整 API。完整代码片段库应放在附录 B(与本调研规划一致)。

4.1 最简单 LangGraph(用于判断"我到底要不要用")

依赖:langgraph>=1.2,<2,Python ≥ 3.10

python 复制代码
# 决策目的:验证"我连最小例子都觉得重"时 → 不要用 LangGraph
from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    msg: str

def echo(state: State) -> dict:
    return {"msg": state["msg"].upper()}

g = StateGraph(State)
g.add_node("echo", echo)
g.add_edge(START, "echo")
g.add_edge("echo", END)
print(g.compile().invoke({"msg": "hello"}))
# {'msg': 'HELLO'}

【官方能力】来源: https://github.com/langchain-ai/langgraph(README minimal example)· 2026-06 抓取 · 1.2.4

g.compile() 是图执行前的必经步骤:它做基本结构校验(orphaned nodes 等),并把 checkpointer / interrupt_before / store 等 runtime args 打包;不调用 compile() 就直接 invoke() 会抛 AttributeError / GraphRecursionError(取决于缺失项)。

4.2 决策:是否需要 checkpointer?

python 复制代码
# 决策目的:如果你压根不调用 checkpointer,LangGraph 与普通 Chain 几乎等价
from langgraph.checkpoint.memory import InMemorySaver  # 开发/单进程测试用;生产用 SqliteSaver/PostgresSaver

checkpointer = InMemorySaver()           # 开发/单进程测试
# checkpointer = SqliteSaver.from_conn_string("checkpoints.db")  # 本地持久化
# checkpointer = PostgresSaver.from_conn_string(DB_URL)         # 生产

app = g.compile(checkpointer=checkpointer)
cfg = {"configurable": {"thread_id": "user-123"}}
print(app.invoke({"msg": "hi"}, config=cfg))
print(app.invoke({"msg": "again"}, config=cfg))  # 同 thread,可恢复

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/persistence · 2026-06 抓取 · 1.2.4

判断 :如果你的产品形态是"一次请求一次响应",上面 app.invoke 不带 thread_id 就够用;不需要 LangGraph。

5. 检查清单(第 1 章)

  • 已确认需求满足"Q1 可恢复 / Q2 HITL / Q3 多步骤多分支多智能体"中至少 1 项
  • 短期 / 单轮场景下未引入 LangGraph(改用 LangChain create_agent 或裸 chat model)
  • 多智能体仅在"单体方案已确认失败"时才启用
  • 已对比 LangGraph / LangChain / OpenAI Agents SDK / CrewAI / AutoGen 选型矩阵
  • 已明确持久化后端(InMemory / Sqlite / Postgres / Platform)
  • 决定自托管 OSS 还是 Platform(Cloud / Standalone / Self-Hosted Enterprise)
  • 数据合规要求已对齐 Platform 部署模式
  • 未使用 langgraph ≤ 0.6.x 老 API
  • 已记录决策依据(事实链接 + 团队判断),便于未来回溯

第 2 章:LangGraph 核心设计思想

1. 解决什么问题(从"链范式"到"图范式"的范式转换)

传统 LangChain 链(prompt | model | parser)和 AgentExecutor链范式 :每一步由前一步的结果驱动,状态隐藏在闭包或 memory 对象里,恢复、HITL、并行、复杂分支都需要额外的胶水代码。LangGraph 的核心思想是用显式的有向图(StateGraph)代替隐式的链,把状态、节点、边、终止条件、恢复点全部升格为一等公民。

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/overview · 2026-06 抓取 · 1.2.4

【官方能力】v1.0 公告:https://www.langchain.com/blog/langgraph-v1 · 2025-09-02 · 1.0.0a1

为什么需要"图"(事实+判断):

  • 可恢复性需要明确的"步"边界(super-step 边界),而链式抽象没有这种天然分块。
  • 可中断性需要"任意节点暂停后能续跑"的语义,链式只能"从头再来"或"传一个 callback"。
  • 多分支 / 循环 / 并行在图里是一等原语(conditional edge / Command / Send),在链里是 if/else 与 for 循环的拼接。

2. 推荐做法(核心概念图)

下面这些概念是 LangGraph 文档反复出现的"必知词":

概念 官方定义 角色 引用
State 一组在图执行期间维护的字段(TypedDict / Pydantic / dataclass) 图的"上下文";每步开始时读取、结束时返回部分更新 graph-api 文档
StateGraph 显式声明状态、节点、边的图对象 你组装图的容器;compile() 之后才能执行 graph-api 文档
compile 校验结构并打包成 Runnable;配置 checkpointer / breakpoints / store 图执行入口(必经步骤) graph-api 文档
Node 接收 state 返回部分 state update 的函数(同步/异步/Runnable) 原子工作单元 graph-api 文档
Edge 节点之间的连接;可以是 normal / conditional 控制流 graph-api 文档
Conditional Edge 由路由函数决定下一个节点的边 动态分支 graph-api 文档
Command 同时更新 state 与路由到目标节点(update / goto / graph / resume 节点内"边+状态"合并操作 graph-api 文档
Send 从条件边返回的对象,携带不同 state 到同一节点 dynamic parallelism(map-reduce) graph-api 文档
Checkpointer 实现 BaseCheckpointSaver 的对象,按 thread_id 落盘 checkpoint 持久化 / 恢复 / HITL persistence 文档
Thread 一次持续交互/运行的标识(thread_id 状态隔离的"会话"边界 persistence 文档
Checkpoint 某 super-step 边界的 state 快照 恢复 / Time Travel / 审计 persistence 文档
Durability modes exit / async / sync:checkpoint 落盘策略 性能 vs 可靠性的旋钮 persistence 文档
Runtime 图执行的容器;负责调度、流式、中断、并发 与 graph 解耦的运行时 overview 文档
interrupt() 节点内调用,暂停图执行并把 payload 暴露给调用方 HITL 主机制 interrupts 文档
Command(resume=...) 唯一作为 invoke/stream 输入的 Command 变体 续跑 / 喂入人工输入 interrupts 文档
Functional API @entrypoint + @task 装饰器写工作流 不想写图但想用 checkpointer/HITL 的折中 functional-api 文档
Time Travel / Replay / Fork 从历史 checkpoint 重放(replay)或 fork 出新分支 调试 / 审计 / A/B 试错 use-time-travel 文档

【官方能力】graph-api:https://docs.langchain.com/oss/python/langgraph/graph-api · 2026-06 抓取 · 1.2.4

【官方能力】persistence:https://docs.langchain.com/oss/python/langgraph/persistence · 2026-06 抓取 · 1.2.4

【官方能力】interrupts:https://docs.langchain.com/oss/python/langgraph/interrupts · 2026-06 抓取 · 1.2.4

【官方能力】memory:https://docs.langchain.com/oss/python/langgraph/memory · 2026-06 抓取 · 1.2.4

【官方能力】streaming:https://docs.langchain.com/oss/python/langgraph/streaming · 2026-06 抓取 · 1.2.4

【官方能力】functional-api:https://docs.langchain.com/oss/python/langgraph/functional-api · 2026-06 抓取 · 1.2.4

【官方能力】use-time-travel:https://docs.langchain.com/oss/python/langgraph/use-time-travel · 2026-06 抓取 · 1.2.4

【官方能力】overview:https://docs.langchain.com/oss/python/langgraph/overview · 2026-06 抓取 · 1.2.4

2.1 State / Node / Edge / Runtime / Checkpointer 概念图

复制代码
                ┌────────────────────────────────────────────┐
                │              StateGraph(编译期)            │
                │  ┌──────┐   add_edge   ┌──────┐             │
                │  │ Node ├─────────────►│ Node │             │
                │  │  A   │              │  B   │             │
                │  └─┬────┘              └──┬───┘             │
                │    │ add_conditional_edges │                │
                │    │ 路由 fn(state)         │                │
                │    ▼                       ▼                │
                │  ┌──────┐  Command   ┌──────┐                │
                │  │ Node │  (update+  │ Node │                │
                │  │  C   │   goto)    │  D   │                │
                │  └──────┘            └──────┘                │
                │                                            │
                │  Send(node, state_i) × N → 并行 fan-out    │
                └────────────────────────────────────────────┘
                              │  compile()
                              ▼
                ┌────────────────────────────────────────────┐
                │   CompiledGraph(运行时)+ Checkpointer     │
                │  - invoke / stream / ainvoke / astream      │
                │  - interrupt() 暂停                        │
                │  - Command(resume=...) 续跑                │
                │  - get_state / update_state                │
                │  - durability: exit / async / sync          │
                └────────────────────────────────────────────┘
                              │ thread_id
                              ▼
                ┌────────────────────────────────────────────┐
                │   Checkpoint 存储                            │
                │   (InMemory / Sqlite / Postgres / Platform)  │
                │   - thread, checkpoint, pending_writes      │
                │   - 可回放 / 可审计 / 可恢复                │
                └────────────────────────────────────────────┘

【官方能力】整合自 persistence / graph-api / interrupts 三份官方文档(链接见上)

2.2 与 LangChain Chain / AgentExecutor 的核心差异

维度 LangChain Chain / create_agent LangGraph
状态表示 隐式(闭包 / memory 对象 / LCEL 变量) 显式 State schema(TypedDict / Pydantic)
控制流 ` ` 链式 + 函数式
持久化 create_agent 支持 checkpointer=(langchain 1.x) 一等公民;多种内置后端
中断粒度 agent 整体 / middleware 节点内任意位置(interrupt()
多步分支 需自实现 add_conditional_edges / Command 原生
并行 需 asyncio.gather Send 原生
Time Travel 需自实现 get_state_history + update_state 原生
可视化 Studio / get_graph().draw_*
学习曲线 中高

【官方能力】LangChain agents:https://docs.langchain.com/oss/python/langchain/agents · 2026-06 抓取 · langchain 1.x

【官方能力】LangGraph overview:https://docs.langchain.com/oss/python/langgraph/overview · 2026-06 抓取 · 1.2.4

2.3 与 OpenAI Agents SDK / CrewAI / AutoGen 的关键差异

聚焦四件事:持久化 / 图范式 / 可中断 / 可观察性

维度 LangGraph OpenAI Agents SDK CrewAI AutoGen v0.4+
持久化 Checkpointer + Thread + Checkpoint + durability modes;super-step 粒度 Sessions(SQLite/SQLAlchemy/Redis/Mongo/Encrypted/Dapr);同 session 实例可恢复,粒度在 session ⚠️ Flow state 默认内存;需自接 storage adapter 需自接 / Studio
图范式 显式 StateGraph + Command + Send + Subgraph Python-first 串联(Agent / Handoffs / Guardrails) Flow + Crew 双层 Core API(RoutedAgent + Runtime
可中断 任意节点 interrupt() + Command(resume=...);支持多中断映射 内建 human_in_the_loop/ 模块 有限 有限
可观察性 LangSmith(原生)/ Studio OpenAI Tracing 自有 trace Studio + 第三方
跨模型 任意 chat model 偏 OpenAI 任意 任意

【官方能力】OpenAI Agents SDK sessions:https://openai.github.io/openai-agents-python/sessions/ · 2026-06 抓取

【官方能力】OpenAI Agents SDK 主索引:https://openai.github.io/openai-agents-python/ · 2026-06 抓取

【官方能力】CrewAI:https://docs.crewai.com/en/introduction · 2026-06 抓取

【官方能力】AutoGen:https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/framework/agent-and-agent-runtime.html · 2026-06 抓取

【推测性建议】跨框架对比由编者按各官方文档归纳,并非任何一家官方背书的对比。

3. 不推荐做法(核心设计反模式)

  1. 把 LLM 调用 + 工具执行 + 业务逻辑 + 格式化塞进一个 node(社区共识)。节点职责模糊,无法测试、无法定位问题;详见第 3 章。
  2. 用全局可变对象 / 闭包携带状态 。破坏 Graph 的"state 是单一事实来源"原则;持久化时不可序列化。来源: https://docs.langchain.com/oss/python/langgraph/persistence
  3. 路由函数包含副作用 (HTTP、写库、随机数)。破坏纯函数假设,无法测试、无法 Time Travel。来源: https://docs.langchain.com/oss/python/langgraph/graph-api
  4. 同时混用 Command 与 add_edge 路由同一节点 。官方明确警告:节点级路由二选一,否则会出现未定义行为。来源: https://docs.langchain.com/oss/python/langgraph/graph-api
  5. 把 Graph state 当作业务数据库。Graph state 是执行上下文,不是事务系统;业务关键数据走业务库 + 补偿。社区共识。
  6. 静态 interrupt 误用为 HITL 。官方明确"static interrupts (interrupt_before/interrupt_after) are not recommended for human-in-the-loop workflows",仅用于调试断点。来源: https://docs.langchain.com/oss/python/langgraph/interrupts
  7. 混淆 Functional API 与 Graph API 的边界 。两者可以混用但 checkpoint 语义不同:Functional API 在 @task 边界落盘,Graph API 在 super-step 边界落盘。来源: https://docs.langchain.com/oss/python/langgraph/functional-api
  8. BaseMessage 直接塞入 state 而不使用 reducer 。会出现 ID 冲突、消息重复;官方 1.2.2 修复了"id=None 的 BaseMessage 写入 DeltaChannel 时分配稳定 ID"------证明这种坑真实存在。来源: https://github.com/langchain-ai/langgraph/releases/tag/v1.2.2

4. 最小代码示例(表达核心概念)

不重复 README 的最小例子。给出概念对位的最小骨架。

依赖:langgraph>=1.2,<2,Python ≥ 3.10

python 复制代码
"""
最小骨架:覆盖 5 个核心概念
- State: TypedDict
- Node: 普通函数,返回增量
- Edge: 静态边
- Conditional Edge: 纯函数路由
- Checkpointer: InMemorySaver(同 thread 可恢复)
"""
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    query: str
    draft: str
    quality: int  # 0-10

def generate(state: State) -> dict:
    return {"draft": f"[draft of {state['query']}]", "quality": 5}

def refine(state: State) -> dict:
    return {"draft": state["draft"] + " (refined)", "quality": 8}

def route_after_generate(state: State) -> Literal["refine", "__end__"]:
    return "refine" if state["quality"] < 7 else "__end__"

g = StateGraph(State)
g.add_node("generate", generate)
g.add_node("refine", refine)
g.add_edge(START, "generate")
g.add_conditional_edges("generate", route_after_generate)  # 纯函数路由
g.add_edge("refine", END)
app = g.compile(checkpointer=InMemorySaver())

cfg = {"configurable": {"thread_id": "t1"}}
print(app.invoke({"query": "hi", "draft": "", "quality": 0}, config=cfg))
# 同 thread_id 再 invoke,能从上次 checkpoint 续跑

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/graph-api + https://docs.langchain.com/oss/python/langgraph/persistence · 2026-06 抓取 · 1.2.4

5. 检查清单(第 2 章)

  • 团队理解 State 是"图的单一事实来源",不引入隐式状态
  • 已选择 Graph API(StateGraph)还是 Functional API(@entrypoint/@task),并明确混用边界
  • 路由函数全部为纯函数(无 I/O、无随机、无时间)
  • 节点职责单一(LLM / 工具 / 纯函数 / IO 各自分开)
  • 节点返回增量 state update,不就地修改
  • 持久化后端已确定(InMemory/Sqlite/Postgres/Platform)
  • thread_id 命名规则已规范(按用户/会话/业务键)
  • 已用 get_state / get_state_history 验证 checkpoint 序列符合预期
  • 已禁用 static interrupt 充当 HITL;HITL 一律走 interrupt() + Command(resume=...)
  • 与其他 Agent 框架(OpenAI Agents SDK / CrewAI / AutoGen)的边界已写明

第 3 章:Graph 设计规范

1. 解决什么问题(State / Node / Edge 的"工程契约")

第 2 章讲图范式是什么;本章讲写一个能上生产的图,应该遵守哪些契约。契约之所以重要(事实+判断):LangGraph 提供了 6--7 种官方核心 API(StateGraph / Functional API / Command / Send / interrupt / checkpointer / store),每种都有自己的边界与坑;没有规范,多人协作时会出现"看起来能跑、线上爆雷"的情况。

2. 推荐做法

2.1 State schema 定义规范

选择 适用 不适用 引用
TypedDict 简单、内部使用、追求零依赖 需要运行时校验 / 嵌套类型 graph-api 文档
Pydantic v2BaseModel 外部输入、需要校验、复杂嵌套 性能敏感、追求最简 graph-api 文档(隐含)
dataclass 已有 dataclass 重度代码库 需要 reducer / Annotated 字段 graph-api 文档
MessagesState 纯 chat 场景,已自动带 messages: Annotated[list, add_messages] reducer 非 chat 业务 prebuilt 模块

Reducer / Annotated 规则(事实):

  • 默认 reducer 是"覆盖"。需要"追加"时使用 Annotated[list[T], operator.add] 或官方 add_messages reducer。
  • add_messages自动处理 message ID 冲突:同 ID 的 message 会被覆盖,不同 ID 追加(行为符合 OpenAI/Anthropic 协议)。
  • 1.2.2 起,id=None 的 BaseMessage 在写入 DeltaChannel checkpoint 之前会自动分配稳定 ID,避免重复历史。来源: https://github.com/langchain-ai/langgraph/releases/tag/v1.2.2

State 粒度建议(推测性建议+社区经验):

  1. :避免在 state 里塞大文件、DataFrame、整个对话历史。超大 state 会拖慢 checkpoint 序列化。
  2. 稳定:字段含义一旦发布就少改;版本演进要走"新字段 + 旧字段 deprecated"模式。
  3. 可序列化 :所有字段必须 JSON 可序列化(interrupt() 文档明确要求)。
  4. 可演进:新字段必须有默认值;老字段删除要走 deprecation 周期。
  5. 职责清晰:区分私有 state、输入 state、输出 state;不要把临时变量塞进主 state。

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/persistence · 2026-06 抓取 · 1.2.4

【官方能力】interrupts 序列化要求:https://docs.langchain.com/oss/python/langgraph/interrupts · 2026-06 抓取 · 1.2.4

【社区经验】State 粒度的"小 / 稳定 / 可序列化"为社区共识,无单一权威来源;可参考 GitHub Discussion 与 LangChain 博客。

2.2 节点职责规范

四类节点,单一职责

节点类型 职责 例子 反例
LLM 节点 仅负责调用 LLM 并解析输出 def call_llm(state): return {"messages": [llm.invoke(...)]} 在 LLM 节点里同时写数据库
工具节点 通过 ToolNode 包装工具执行 g.add_node("tools", ToolNode([search, sql])) 在工具里塞 LLM 决策
纯函数节点 业务逻辑(验证、转换、决策) def validate(state): return {"is_valid": ...} 纯函数里调外部 HTTP
IO 节点 外部副作用(HTTP、DB、文件) def save_to_db(state): ... IO 节点里塞 LLM 解析

【官方能力】ToolNode 由 langgraph.prebuilt 提供;add_node 接受 sync/async/Runnable。来源: https://github.com/langchain-ai/langgraph(README)· 2026-06 抓取 · 1.2.4

节点输入输出约定(官方+社区共识):

  • 只返回增量 :节点返回 dict 必须只包含"想更新的字段";不要回传整个 state。
  • 不要原地修改 state :从 state["messages"].append(...) 会绕过 reducer;应返回 {"messages": [new_msg]}
  • 可重入 / 幂等 :节点可能被 retry / re-execute(interrupt 文档明确:节点恢复时从节点开头重跑 )。interrupt 前的副作用必须幂等。来源: https://docs.langchain.com/oss/python/langgraph/interrupts

2.3 路由函数规范(必须纯函数 / 可测试 / 可观测)

纯函数(事实+判断):

python 复制代码
# 错误:路由里有副作用
def bad_route(state):
    log_to_db(state)              # 副作用
    return "next_node"

# 正确:路由只读 state
def good_route(state) -> Literal["a", "b"]:
    return "a" if state["score"] > 0.5 else "b"

可测试(社区经验):路由函数应可被纯单元测试------给定 state 字典,断言 next node 名字。

可观测(社区经验):路由决策应进入 LangSmith trace / 自有日志,避免"为什么走到 B 节点"的鬼故事。

来源: https://docs.langchain.com/oss/python/langgraph/graph-api("routing function" 章节)· 2026-06 抓取 · 1.2.4

2.4 控制流规范

场景 推荐 不推荐 引用
简单静态分支 add_edge 写一个返回固定值的 conditional edge graph-api 文档
基于 state 的动态分支 add_conditional_edges + 纯函数 把分支逻辑写进 node 内部 graph-api 文档
同时更新 state + 路由 Command(update=..., goto=...) 返回 用 conditional edge 返回一个更新后的 state dict graph-api 文档
map-reduce / fan-out Send(node, state_subset) 列表 asyncio.gather 自实现并行 graph-api 文档
HITL 续跑 Command(resume=...) 作为 invoke/stream 输入 把人工输入塞进 state dict interrupts 文档

Command 与 conditional edge 的边界(事实,官方明确):

  • 「If you only need to route without updating state, use conditional edges instead.」
  • 「For each node, use either Command or static edges to route to the next nodes, not both.」

来源: https://docs.langchain.com/oss/python/langgraph/graph-api · 2026-06 抓取 · 1.2.4

Send 用于 dynamic parallelism(事实):

python 复制代码
from langgraph.types import Send

def fanout(state: OverallState):
    return [Send("process_one", {"x": s}) for s in state["items"]]

g.add_conditional_edges("prepare", fanout)

用途:map-reduce、批量工具调用、并行子任务。LangGraph 会把每条 Send 视为独立的"分支"并发执行。

2.5 循环与终止

recursion_limit 默认 1000 步 (事实,官方:1.0.6 起;当前 1.2.4 仍生效)。来源: https://docs.langchain.com/oss/python/langgraph/graph-api · 2026-06 抓取 · 1.2.4

业务终止条件必须明确(推测性建议,社区共识):

  1. 在节点内显式终止 :让 router_fn 返回 "__end__",不要依赖"达到 limit 就停"。
  2. 给循环加计数器 :state 里加 iteration_count: int,超过阈值强制 goto 到"放弃 / 报告失败"节点。
  3. 避免"看似能终止但不会"的死循环 :自审 if state["x"]: goto A; else: goto B 是否所有分支都会前进。
  4. 超时 / 重试边界 :节点级 RetryPolicy(详见 https://docs.langchain.com/oss/python/langgraph/graph-api#retry-policies,本调研未展开错误处理章节)。

2.6 节点幂等性与可重试性

为什么必须幂等(事实,官方明确):

幂等设计要点(社区共识 + 官方约束):

  1. 先写 state,再做副作用:通过 reducer / state 决定"是否要执行",避免重复发邮件、重复扣款。
  2. 副作用前要查:在 IO 节点开头检查"是否已执行过"(基于 state 标志位或业务唯一键)。
  3. LLM 调用天然幂等 (同输入同输出),但有副作用的工具(HTTP POST、扣款、发邮件)必须自己保证。
  4. ToolNode + 自定义工具 :把 idempotency_key 暴露给工具,由业务实现去重。

2.7 错误处理(本章仅涉及节点级规范;后续章节展开)

  • 可恢复 vs 不可恢复分类(推测性建议):网络瞬时错误、限流可重试;数据校验失败、参数错误不可重试。
  • RetryPolicy 在节点级声明(langgraph 标准接口,详见 https://docs.langchain.com/oss/python/langgraph/graph-api#retry-policies),不写裸 try/except 吃掉错误。
  • 错误必须进入 state 或 trace(社区共识):不要让节点失败后只 print 一行日志;要用 state 字段 / LangSmith metadata 留下证据。

3. 不推荐做法(Graph 设计反模式)

  1. State 字段过多、嵌套过深 (社区共识)。State 是图的灵魂;太大会拖慢序列化、破坏 checkpoint 性能。来源: https://docs.langchain.com/oss/python/langgraph/persistence
  2. 在 state 里塞 PII / secret(事实+判断)。State 会被 checkpoint 落盘,泄露面=DB 泄露面;PII/secret 走专门存储(KMS / Vault)。社区共识。
  3. 在 state 里塞不可序列化对象 (事实,interrupt 文档明确要求 JSON 可序列化)。来源: https://docs.langchain.com/oss/python/langgraph/interrupts
  4. 节点内 from-state-AND-mutate-state。把 state 当 mutable container 会绕过 reducer、破坏可重入。社区共识。
  5. 路由函数访问网络 / 数据库 / 时间 (事实+判断)。破坏纯函数假设,无法单元测试,无法 Time Travel。来源: https://docs.langchain.com/oss/python/langgraph/graph-api
  6. Graph 循环没有显式终止条件 (事实+判断)。即使有 recursion_limit,业务循环必须有自己的"我决定停"条件。来源: https://docs.langchain.com/oss/python/langgraph/graph-api
  7. 同一节点同时用 Commandadd_edge 路由 (事实,官方明确禁止)。来源: https://docs.langchain.com/oss/python/langgraph/graph-api
  8. 静态 interrupt 充当 HITL (事实,官方明确不推荐)。来源: https://docs.langchain.com/oss/python/langgraph/interrupts
  9. try/exceptinterrupt() (事实,官方不推荐 bare try/except,可能吞掉暂停异常)。注意:带显式 re-raise 的合理封装不在禁止之列------interrupt 依赖特殊异常机制,bare 形式的 try/except 会破坏语义。来源: https://docs.langchain.com/oss/python/langgraph/interrupts
  10. interrupt() payload 含函数 / 不可序列化对象 (事实,官方明确禁止)。来源: https://docs.langchain.com/oss/python/langgraph/interrupts
  11. 节点副作用不可重入 (事实,官方明确要求幂等)。来源: https://docs.langchain.com/oss/python/langgraph/interrupts + https://docs.langchain.com/oss/python/langgraph/functional-api
  12. 把 LangGraph 当 job scheduler / 业务数据库。它不替代 Temporal / Airflow / Postgres。社区共识。
  13. 手动拼接 from langchain_core.messages import ... 而不使用 add_messages reducer 。会出现消息重复 / ID 冲突。来源: https://github.com/langchain-ai/langgraph/releases/tag/v1.2.2

4. 最小代码示例

依赖:langgraph>=1.2,<2,Python ≥ 3.10

4.1 State schema + Reducer 最小骨架

python 复制代码
"""
覆盖:TypedDict / Annotated reducer / add_messages / 私有/输出 state
"""
from typing import TypedDict, Annotated, Literal
import operator
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    # 短期:消息历史,用 add_messages reducer
    messages: Annotated[list, add_messages]
    # 业务:追加式 trace
    trace: Annotated[list[str], operator.add]
    # 私有:路由决策(每步覆盖即可)
    next_action: str
    # 终止标志
    done: bool
    # 业务循环计数器(防止 should_continue 始终返回 "call_llm" 时死循环)
    iteration_count: int

MAX_ITER = 3

def call_llm(state: State) -> dict:
    return {
        "messages": [{"role": "assistant", "content": "hello"}],
        "trace": ["call_llm"],
        "iteration_count": state.get("iteration_count", 0) + 1,
    }

def should_continue(state: State) -> Literal["call_llm", "__end__"]:
    if state.get("done"):
        return "__end__"
    if state.get("iteration_count", 0) >= MAX_ITER:  # 业务终止守卫
        return "__end__"
    return "call_llm"

def finalize(state: State) -> dict:
    return {"done": True, "trace": ["finalize"]}

g = StateGraph(State)
g.add_node("llm", call_llm)
g.add_node("finalize", finalize)
g.add_edge(START, "llm")
g.add_conditional_edges("llm", should_continue)
g.add_edge("finalize", END)
g.add_edge("finalize", "llm")  # finalize → llm 形成循环,由 should_continue 终止
app = g.compile(checkpointer=InMemorySaver())

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/graph-api + https://docs.langchain.com/oss/python/langgraph/persistence · 2026-06 抓取 · 1.2.4

4.2 Command + Send 最小骨架

python 复制代码
"""
覆盖:Command(update=..., goto=...); Send 并行; 中断续跑
"""
from typing import TypedDict, Literal
from langgraph.graph import StateGraph, START
from langgraph.types import Command, Send
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    items: list[str]
    results: list[str]

def fanout(state: State):
    # Send 用于 map-reduce 风格 fan-out
    return [Send("process", {"item": i, "results": []}) for i in state["items"]]

def process(payload: dict) -> Command:
    return Command(
        update={"results": [f"processed:{payload['item']}"]},
        goto="join",
    )

def join(state: State) -> Command[Literal["__end__"]]:
    # 注意:Command 返回需带 goto 类型注解
    return Command(update={}, goto="__end__")

g = StateGraph(State)
g.add_node("process", process)
g.add_node("join", join)
g.add_conditional_edges(START, fanout)
g.add_edge("join", "__end__")
app = g.compile(checkpointer=InMemorySaver())

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/graph-api · 2026-06 抓取 · 1.2.4

4.3 interrupt + Command(resume) 最小骨架

python 复制代码
"""
覆盖:节点内 interrupt() + 续跑;多 interrupt 映射
"""
from langgraph.types import interrupt, Command
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from typing import TypedDict

class State(TypedDict):
    needs_review: bool
    text: str

def draft(state: State) -> dict:
    return {"text": "drafted", "needs_review": True}

def review(state: State) -> dict:
    # interrupt 会暂停整个图;payload 给调用方;resume 值作为返回值
    decision = interrupt({"prompt": "Approve?", "content": state["text"]})
    return {"text": state["text"] + ("" if decision else "_REJECTED")}

g = StateGraph(State)
g.add_node("draft", draft)
g.add_node("review", review)
g.add_edge(START, "draft")
g.add_edge("draft", "review")
g.add_edge("review", END)
app = g.compile(checkpointer=InMemorySaver())

cfg = {"configurable": {"thread_id": "u1"}}
# 第一次:会停在 interrupt;stream_events 模式下 stream.interrupted=True
# 第二次:用 Command(resume=True) 续跑
app.invoke({"needs_review": False, "text": ""}, config=cfg)
app.invoke(Command(resume=True), config=cfg)

【官方能力】来源: https://docs.langchain.com/oss/python/langgraph/interrupts · 2026-06 抓取 · 1.2.4

5. 检查清单(第 3 章)

  • State schema 已选定类型(TypedDict / Pydantic v2 / dataclass / MessagesState)
  • State 字段全部 JSON 可序列化、无 PII / secret
  • 追加式字段均带 reducer(Annotated[list, operator.add]add_messages
  • 节点只返回增量 state update,不就地修改
  • 节点按 LLM / 工具 / 纯函数 / IO 四类拆分,职责单一
  • 路由函数全部为纯函数(无 I/O / 无随机 / 无时间)
  • 路由函数有单元测试覆盖主要分支
  • 路由决策进入 LangSmith / 自有日志
  • 仅在"需要同时更新 state + 路由"时用 Command;其余用 conditional edge
  • 同一节点不混用 Command 与 add_edge
  • 动态并行用 Send 列表,不用 asyncio.gather 自实现
  • HITL 一律用 interrupt() + Command(resume=...),不用 static interrupt
  • interrupt() payload 仅含 JSON 可序列化数据,不套 try/except
  • 业务循环有显式终止条件(state 计数器 / 业务终态);不仅依赖 recursion_limit
  • 节点副作用幂等(LLM 调用天然幂等;业务 IO 必须自保证)
  • 错误进入 state / trace / log,未被静默吞掉
  • 节点级 RetryPolicy 用于可恢复错误(限流、网络瞬时)
  • 至少有一组"给定输入 → 期望路径 → 期望终态"的集成测试


第 4 章:状态持久化、恢复与 HITL

4.1 Checkpointer 选型:MemorySaver / SqliteSaver / PostgresSaver

解决什么问题

LangGraph 把 graph 编译时绑定的 BaseCheckpointSaver 实现作为「运行态恢复点」的总入口。线程(thread_id)下每次 super-step 完成都会生成一个 checkpoint,没有 checkpointer,HITL / Time Travel / 断线恢复全部不可用。选型的核心问题在「持久性 + 多 worker 并发安全 + 部署形态」三者之间的权衡。

来源:LangGraph 持久化官方概念页 官方

推荐做法

  • 开发/单进程测试 :用 langgraph.checkpoint.memory.InMemorySaver别名MemorySaver 在源码中以 MemorySaver = InMemorySaver # Kept for backwards compatibility 形式保留,未正式标 deprecated ,旧代码可继续 import;新代码建议直接用 InMemorySaver。来源:官方源码 https://github.com/langchain-ai/langgraph/blob/main/libs/checkpoint/langgraph/checkpoint/memory/**init**.py (2026-05 抓取)
  • 本地小型持久化(单机桌面、本地脚本) :用 SqliteSaver(同步)或 AsyncSqliteSaver(异步),数据库可以是 :memory: 或磁盘 .db 文件。
  • 生产(多 worker / 跨节点 / 跨进程) :用 PostgresSaver / AsyncPostgresSaver,首次部署需要 checkpointer.setup() 建表;底层默认走 Psycopg 3
  • 完全托管 :直接使用 LangSmith Deployment / Agent Server,它内建托管 checkpointer(含跨副本恢复)。官方
  • Durability 模式 (v1.0+ 引入):在 invoke/stream 时传入 durability 参数,只在生产模式关心
  • 安全加固 :Postgres 后端默认用 msgpack 反序列化;高敏场景应启用 LANGGRAPH_STRICT_MSGPACK=true 或显式指定 allowed modules。官方 https://pypi.org/project/langgraph-checkpoint-postgres/

不推荐做法

最小代码示例(langgraph 1.2.x)

python 复制代码
# 依赖:langgraph>=1.2、langgraph-checkpoint>=4.1
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    input: str
    output: str

def node_a(state: State):
    return {"output": f"Processed: {state['input']}"}

builder = StateGraph(State)
builder.add_node("node_a", node_a)
builder.add_edge(START, "node_a")
builder.add_edge("node_a", END)

graph = builder.compile(checkpointer=InMemorySaver())

config = {"configurable": {"thread_id": "thread-1"}}
print(graph.invoke({"input": "hello", "output": ""}, config))

Postgres 版(仅替换 import / 构造):

python 复制代码
# 依赖:langgraph-checkpoint-postgres>=3.1
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string("postgresql://user:pwd@host/db")
checkpointer.setup()  # 首次必须
graph = builder.compile(checkpointer=checkpointer)

检查清单

  1. ☐ 选型是否匹配部署形态:单进程 / 多 worker / 托管?
  2. ☐ 是否调用过 PostgresSaver.setup()?是否把 setup() 放入部署管线?
  3. ☐ 是否启用 LANGGRAPH_STRICT_MSGPACK / 显式 allowlist?
  4. ☐ 生产图是否传 durability 参数并明确选择档位?
  5. ☐ 是否把 InMemorySaver 误用到生产或测试套件之外的临时集群?
  6. ☐ Thread key 是否避免写入 PII 原文(用不可逆的 user_id hash)?
  7. ☐ 是否在节点函数里做了幂等(resume 时节点会从头跑)?
  8. ☐ 是否在 dump / 备份时排除消息中的敏感字段(见 4.4)?

4.2 长期记忆:BaseStore / InMemoryStore / namespace / 向量化 / TTL

解决什么问题

短时记忆(thread 内的消息历史)只服务一次会话。跨会话、跨 user 的事实 / 偏好 / 反馈 必须放到一个独立于 thread 的存储里。LangGraph 通过 langgraph.store.base.BaseStore 抽象提供「跨 thread」的命名空间读写 + 可选语义检索。

来源:官方 https://docs.langchain.com/oss/python/langgraph/memory (2026-05 抓取)

推荐做法

  • 存储三类记忆

    类型 含义 Agent 例子
    Semantic 事实、用户属性 "用户偏好短句回答"
    Episodic 历史经历、few-shot 样本 "上次调 SQL 出错,用户纠正..."
    Procedural 行为规则、系统提示 "反思后更新的 system prompt"
    来源:官方 同上
  • 组织方式

    • Profile 模式:单条 JSON 文档不断合并/覆盖(适合稳定结构化用户档案)
    • Collection 模式:多条窄粒度文档 + 语义检索(适合更细颗粒的事实)
  • namespace / key :用 tuple 形式 ("user_id", "category") 作 namespace,键是文档 ID;等价于「文件夹 + 文件名」。

  • 写入时机

    • 热路径(agent 实时写入):一致性最好但每次调用都增加延迟与复杂度
    • 后台任务(独立 task 周期写入):不影响主链路,但需要明确刷写频率
  • 向量化检索 :在 InMemoryStore(index={"embed": embed_fn, "dims": N}) 中传入 embedding 函数;后续 store.search(namespace, query="...") 自动返回 top-k。

  • 生产 BaseStore 后端(待验证/需自建) :langgraph 1.2.x 在 PyPI 上没有与 langgraph-checkpoint-postgres 对应的官方 langgraph-store-postgres ------BaseStore 的生产持久化方案在社区中仍以 InMemoryStore + 外挂方案为主 ,或使用 LangSmith Deployment 内建 store。待补 生产 BaseStore 后端的成熟度

    • 官方 LangSmith Deployment 提供托管 store(thread-scoped TTL + cross-thread KV),但不是开源可自部署的 Postgres store 包。来源:官方 https://docs.langchain.com/langsmith/deployment
    • 社区方案(自建 Postgres / Redis / SQLite + 自实现 BaseStore 接口)需要在团队内做轻量封装,不是开箱即用推测
    • 不要用 langgraph-checkpoint-postgres 替代 BaseStore :checkpoint 包只实现 BaseCheckpointSaver 接口,与 BaseStore 接口(put/get/search/delete)不兼容;强行混用会导致类型错误。
  • TTL 生命周期

    • Checkpoint:生命周期由 thread 决定;LangSmith Deployment 提供 thread-level TTL(过期清空 thread 的所有 checkpoint)。
    • BaseStore 记录没有内建 TTL 字段 ;需要应用层定时清理(store.delete(namespace, key))或部署侧 LRU。
    • 二者不可混淆:thread TTL 不会清空 BaseStore 记录;删除 thread 也不会自动清空 store。来源:官方 https://docs.langchain.com/langsmith/deployment

不推荐做法

  • 不要把长期记忆塞进 Checkpointer :Checkpointer 的粒度是 thread,没有跨 thread 查询能力,强行用 checkpoint 做大记忆会让 get_state_history 爆炸。
  • 不要把 InMemoryStoreindex=... 用作安全过滤 :它只做相似度排序,不替代显式 filter={"user_id": ...} 这类 hard filter------否则会发生「跨用户记忆泄露」。推测 基于机制推断
  • 不要在节点里同步写 store 不做超时:长期记忆写入应该是可丢弃的副作用,失败时宁可丢掉也别阻塞主链路。

最小代码示例

python 复制代码
# 依赖:langgraph>=1.2、langgraph-checkpoint>=4.1
from langgraph.graph import StateGraph, START, END
from langgraph.store.memory import InMemoryStore
from langgraph.checkpoint.memory import InMemorySaver

# 假定的 embedding 函数(实际用 langchain 的 embedder)
def embed(texts: list[str]) -> list[list[float]]:
    return [[0.0] * 8 for _ in texts]

store = InMemoryStore(index={"embed": embed, "dims": 8})
namespace = ("user-42", "profile")
store.put(namespace, "preferences", {"rules": ["用户偏好短句"]})

# 语义检索
hits = store.search(namespace, query="回答风格", limit=3)
for doc in hits:
    print(doc.key, doc.value, doc.score)

在 compile 时把 store 注入图(节点用 runtime.store 访问):

python 复制代码
graph = builder.compile(checkpointer=InMemorySaver(), store=store)

来源:官方 https://docs.langchain.com/oss/python/langgraph/memory

检查清单

  1. ☐ Checkpointer 与 BaseStore 边界是否清晰:跨 thread → store;thread 内 → checkpoint?
  2. ☐ namespace 是否用稳定 user id(不可逆)做第一段?
  3. ☐ 是否在语义检索前用 hard filter(user_id / tenant_id)收紧?
  4. ☐ 是否给 store 写入做了超时 + 降级(写入失败是否回退到默认行为)?
  5. ☐ 是否给「profile 模式」设置版本号,避免新旧字段混在一起读?
  6. ☐ 是否在生产使用 DB-backed store(Postgres / 后续 store 后端;当前官方未提供,需自建或用 LangSmith 托管)?
  7. ☐ 删除/退订场景下是否同步清理 store(GDPR / 用户删除)?

4.3 状态序列化、版本演进、敏感数据处理

解决什么问题

LangGraph 用 msgpack + 自定义 serde 把 State 持久化到 checkpoint/store。生产环境关心三类问题

  1. 跨版本兼容:升级 langgraph 后旧 checkpoint 还能不能读?
  2. 自定义类型:Pydantic v2 / dataclass / TypedDict 都能进 state 吗?
  3. 敏感数据:PII / 凭据落盘前如何处理?

推荐做法

  • State 类型首选TypedDict(最轻)/ dataclass / Pydantic v2 model;values stream mode 在 1.1+ 会把 Pydantic/dataclass 自动 coerce 成正确类型。来源:官方 https://docs.langchain.com/oss/python/langgraph/streaming
  • 跨版本兼容langgraph-checkpoint 维护了 envelope lc:2 之类的版本号;高安全场景启用 LANGGRAPH_STRICT_MSGPACK=true,避免任意对象通过 envelope revival 复活。官方源码 https://github.com/langchain-ai/langgraph/blob/main/libs/checkpoint/
  • 敏感数据
    • 在写入 checkpoint 前主动 redact(不要在事后清理)
    • 用单独的 secrets namespace 存到 Key Vault / 外部,state 里只放引用
    • 加密静态数据:Postgres 后端可以走透明加密 / 列加密

不推荐做法

  • ❌ 不要在 state 里放原始 token / 密码 / 私钥------它们会随 checkpoint 落盘,且 get_state_history 能直接读到。
  • ❌ 不要让 state 持有不可序列化 对象(数据库连接、文件句柄、open socket);langgraph-checkpoint 用 msgpack + 自定义 serde 持久化,与 Python pickle 协议不直接相关 ------判定标准是「能否被 msgpack / 自定义 serde 序列化」,不是「能否被 pickle」。来源:官方源码 https://github.com/langchain-ai/langgraph/blob/main/libs/checkpoint/langgraph/checkpoint/memory/**init**.py
  • ❌ 不要把大文件直接塞 state------pg 单行 / msgpack 都有限制;外链 + 存路径/哈希更稳。推测

最小代码示例

python 复制代码
from typing_extensions import TypedDict
from pydantic import BaseModel

class UserProfile(BaseModel):
    name: str
    redacted_email: str  # 写入前先在节点里脱敏

class State(TypedDict):
    profile: UserProfile
    notes: str

检查清单

  1. ☐ state 字段是否全为可序列化类型?
  2. ☐ 是否对 PII 做了 redact / 哈希后再写入 state?
  3. ☐ 是否启用 LANGGRAPH_STRICT_MSGPACK
  4. ☐ 升级 langgraph 大版本前是否做过 checkpoint 读回烟测?
  5. ☐ 是否有删除/退订路径:thread 删除 + store 清空 + checkpoint 清理?

4.4 Human-in-the-Loop:interrupt / resume / Command / 审批

解决什么问题

Agent 在执行中可能做出不可逆动作(写库、付款、调用外部 API)。HITL 要求在关键决策点暂停 graph,等人类批准/编辑后再继续 。LangGraph 用 interrupt() + Command(resume=...) 模式实现。

来源:官方 https://docs.langchain.com/oss/python/langgraph/interrupts (2026-05 抓取)

推荐做法

  • 三件套 :必须同时具备 checkpointer、configurable thread_id、JSON-serializable 负载。
  • 基本模式
    • 在节点中调用 value = interrupt(payload) 暂停,state 自动持久化
    • 外部系统通过 graph.invoke(Command(resume=value), config) 恢复
    • 节点会从头重启 (不是从 interrupt 后继续),interrupt() 的返回值就是 resume 的值
  • 多种工作流
    • Approve/Reject:传 True/FalseCommand(resume=...)
    • Review & Edit:人类修改 LLM 输出再继续
    • 工具内审批:把审批逻辑放进 @tool 函数里
    • 输入校验:while True: ans = interrupt("..."); if valid: break
    • 多分支并行:用 GraphOutput.interrupts 列表的索引映射到不同 resume 值(Python 原生 API,不是 SDK 投影)。
  • interrupt 在 Python 端的检测
    • graph.invoke(...) 在 1.0+ 返回 GraphOutput 对象;遇到 interrupt 时 out.interrupts 非空。
    • stream v2 chunk 形态说明 :v2 chunk 字典结构是 {"type": str, "ns": tuple, "data": ...}没有 next 字段nextgraph.get_state(config) 返回的 StateSnapshot 上的字段(语义为「下一个待执行节点 tuple」),不是 stream chunk 的字段。
    • astream 中检测 interrupt 的正确方式:
      • stream_mode="checkpoints" 模式订阅,写入事件存储后由 watcher 读 StateSnapshot.next 判断是否被挂起;
      • stream_mode="updates" + stream_mode="custom" 组合 + 业务层在 invoke 后读 GraphOutput.interrupts
      • 监听 astream 中含 __interrupt__ 键的 chunk(旧 v1 风格,v2 中已被 GraphOutput.interrupts 取代)。
    • 通过 updates 模式观察 node_name 的 state delta 只能辅助判断节点级进度,不能直接判定是否被 interrupt 挂起。
    • 不要在 Python 端用 SDK 投影stream.interrupted / stream.interrupts / stream.output@langchain/langgraph-sdk(JS SDK)包装对象属性,不是 CompiledStateGraph 原生属性。
  • Subgraph interrupt :子图里的 interrupt 在 resume 时,父节点和子图节点都会从头重启------这是个对幂等性的硬要求。

不推荐做法

  • 不要把 interrupt() 包进 try/except:它靠抛异常暂停,捕获会导致暂停失败。
  • 不要重排 interrupt 调用顺序 :resume 时按索引匹配,乱序会错位。官方 同上
  • 不要在 interrupt 之前做非幂等副作用(写库、发邮件、扣款)------节点会从头跑,副作用会重复执行。
  • 不要用静态断点 interrupt_before / interrupt_after 做生产 HITL :官方明确说它只用于调试
  • 不要在 payload 里传非 JSON-serializable 对象(函数、类实例、生成器)。

最小代码示例

python 复制代码
# 依赖:langgraph>=1.2
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command, interrupt

class State(TypedDict):
    plan: str
    approved: bool

def propose(state: State):
    plan = "向 user-1 发起退款 $50"
    # 关键决策点:暂停,等人类批准
    ok = interrupt({"question": "是否批准此操作?", "plan": plan})
    return {"plan": plan, "approved": bool(ok)}

builder = StateGraph(State)
builder.add_node("propose", propose)
builder.add_edge(START, "propose")
builder.add_edge("propose", END)

graph = builder.compile(checkpointer=InMemorySaver())
cfg = {"configurable": {"thread_id": "t-1"}}

# 第一次:走到 interrupt 暂停
graph.invoke({"plan": "", "approved": False}, cfg)

# 人类批准后:传 Command(resume=True) 恢复
graph.invoke(Command(resume=True), cfg)

来源:官方 https://docs.langchain.com/oss/python/langgraph/interrupts

检查清单

  1. ☐ 是否每个 interrupt 前后的副作用都做过幂等(写库加幂等键、邮件发送前先查重)?
  2. ☐ 是否避免用 try/except 包裹 interrupt
  3. ☐ payload 是否都是 JSON-serializable?
  4. ☐ 是否在多分支并行时显式 map interrupt.id → resume
  5. ☐ 是否避免使用 interrupt_before/after 做生产审批?
  6. ☐ 子图 interrupt 时父节点是否做了幂等?
  7. ☐ 是否有 interrupted=True 时的 UI 反馈(人类需要看到「等你审批」)?

4.5 Time Travel 与 Fork:get_state_history / update_state / 分叉调试

解决什么问题

Production agent 跑偏了,要从中间某个 checkpoint 重放,或改完 state 后分叉出新分支。LangGraph 把每次 super-step 都落盘成 checkpoint,于是:

  • get_state_history(config) → 倒序列出所有 checkpoint
  • update_state(config, values) → 基于当前 state 改值,生成新 checkpoint(不改旧的)
  • checkpoint_id 拿到历史 config,可以 graph.invoke(None, past_config) 重放

来源:官方 https://docs.langchain.com/oss/python/langgraph/persistence

推荐做法

  • StateSnapshot 字段values / next / config / metadata / created_at / parent_config / tasks

  • 在历史中定位

    python 复制代码
    history = list(graph.get_state_history(config))
    step1 = next(s for s in history if s.metadata["step"] == 1)
    done  = next(s for s in history if s.next == ())
  • Fork 分支 :拿到旧 config 后 graph.invoke(None, past_config) 会从该点继续。update_state 再写一份新值就分叉了。

  • 审计/调试 UI :把 history 暴露给 UI,做「时光机」交互。

不推荐做法

  • ❌ 不要在生产 hot path 上调 get_state_history()------它会拉全量 history。
  • ❌ 不要把 update_state 看作「改旧 checkpoint」,它总是新增
  • ❌ 不要在 fork 后继续依赖原 thread_id 不变,如果业务上想区分两条分支建议换 thread_id。

最小代码示例

python 复制代码
cfg = {"configurable": {"thread_id": "t-1"}}
history = list(graph.get_state_history(cfg))
target = history[-2]  # 倒数第二个

# 重放
graph.invoke(None, target.config)

# 分叉:改值
graph.update_state(cfg, {"output": "手动覆盖"})

来源:官方 https://docs.langchain.com/oss/python/langgraph/persistence

检查清单

  1. ☐ 是否暴露 get_state_history 给内部调试 UI?
  2. ☐ fork 后是否换 thread_id 区分新旧分支?
  3. update_state 写入是否走 RBAC / 审计日志?
  4. ☐ 是否避免在 hot path 上拉全量 history?
  5. ☐ 是否在 history 中清理敏感字段(避免 PII 留存)?

4.6 HITL 的 timeout / escalation 策略(参考架构 / 推测性建议

本节定位 :LangGraph 官方文档未给出 HITL timeout / escalation 的统一推荐实现;本节内容为参考架构推测性建议,非官方能力。生产落地需结合 job scheduler / worker 协调做自建。

解决什么问题

人类可能永不审批:请假、系统宕机、通知丢失。无限等待的 interrupt = 内存泄漏 / 任务挂死。需要给每个 interrupt 配超时与升级路径。

参考架构(流程式,不给完整代码)

  1. 记录 pending interrupt(两条路径,二选一)
    • 路径 A(推荐 / 解耦) :业务层在 graph.invoke(...) 拿到 GraphOutput 后立即读 out.interrupts,把 {thread_id, run_id, interrupt_payload, created_at} 写入外部事件存储(Kafka / Redis Stream / Postgres)。注意GraphOutputinvoke同步返回值,不会"持久化"或被 watcher 直接读;必须由业务代码主动落库。
    • 路径 B(流式 / 单进程) :在 astream(stream_mode=["updates", "checkpoints"], version="v2") 流中过滤含 __interrupt__ 键的 chunk (v1 风格)或走 checkpoints 模式订阅 StateSnapshot,由流处理函数在内存/Redis 中维护 pending 集合。
    • 两条路径都需要把 pending 记录落入持久存储(否则进程崩溃会丢失状态)。
  2. Watcher 扫描:独立 worker 周期扫描「pending 时长 > SLA」的 case;建议多实例用 leader election(Redis lock / Postgres advisory lock)避免重复 resume。
  3. 注入 resume :watcher 调用 graph.invoke(Command(resume={"approved": False, "reason": "timeout"}), cfg) 走自动拒绝路径;或调用 Command(resume={"escalate_to": "manager_id"}) 走升级路径。
  4. 审计:resume 事件进入审计日志(who/when/why),与 GraphCheckpoint 关联。
  5. 幂等要求 :每个 resume 必须幂等------节点会从头跑,副作用要靠幂等键保证不重复。

推测性建议(待补 / 自建

  • 超时不应当默认自动通过:会绕过安全网。生产推荐默认自动拒绝 + 升级。
  • 避免 threading.Timer 触发 resume:跨进程失效。把超时放外部 worker / sidecar。
  • 并行审批做 fan-in:不要假设一次 interrupt 只等一个人。

不推荐做法

  • ❌ 不要在 LangGraph 内部用 threading.Timer 触发 resume------会跨进程失效。把超时机制放外面(worker / edge / sidecar)。推测
  • ❌ 不要把"超时自动通过"作为默认------会绕过安全网。
  • ❌ 不要用 asyncio.wait_for(graph.ainvoke(...)) 来"截断" interrupt------该调用并不能识别 graph 内部被 interrupt 挂起,超时只是把外层协程结束,graph 仍在 wait resume 状态;需要靠独立 watcher 注入 resume。
  • ❌ 不要假设一次 interrupt 只可能等一个人------并行审批时要做 fan-in。

最小代码示例(仅作 watcher 形态示意,非生产级

python 复制代码
# 伪代码:watcher 周期扫描 pending interrupt 并注入 timeout resume
import asyncio
from langgraph.types import Command

async def timeout_watcher(graph, store, cfg, *, sla_seconds: int = 300):
    while True:
        await asyncio.sleep(10)
        # 1. 从事件存储拉 pending interrupt
        for rec in store.pending_interrupts(older_than=sla_seconds):
            # 2. 拿分布式锁避免多 watcher 重复 resume
            if await store.acquire_resume_lock(rec.run_id):
                # 3. 注入 timeout resume(自动拒绝 / 升级由业务决定)
                graph.invoke(Command(resume={"approved": False, "reason": "timeout"}),
                             {"configurable": {"thread_id": rec.thread_id}})
                store.mark_resumed(rec.run_id, by="timeout_watcher")

来源:推测 基于 LangGraph interrupt 语义 + 一般分布式系统模式;社区方案未找到统一最佳实践,待补 。建议参考 Temporal / Inngest 等工作流引擎的 SLA 模式,但不要直接照搬------LangGraph 的 HITL 与这些系统的中断模型不同。

检查清单

  1. ☐ 每个 interrupt 是否配了 SLA 超时?
  2. ☐ 超时后是自动拒绝还是自动升级?
  3. ☐ 超时事件是否进入可观测体系(pending 时长分布)?
  4. ☐ 超时 watcher 是否多实例 + 分布式锁(避免单点 / 重复 resume)?
  5. ☐ 自动通过的策略是否需要二次审批才允许?
  6. ☐ resume 副作用是否幂等(写库加幂等键)?

第 5 章:流式输出与前端集成

版本锚点:langgraph==1.2.4(2026-06-02 PyPI latest);Python ≥ 3.10。

本章主线 :LangGraph Python 原生 CompiledStateGraph.stream / astream / astream_events 的输出语义、协议与生命周期;前端消费(React / Next.js / JS SDK / SSE / WebSocket)只作为消费边界小节,不构成 Python API 主线 。所有 JS SDK 表述均明确标注「非 Python 主线 」。

资料类型标记:官方 = 官方文档 / GitHub / 官方博客;社区 = 社区博客 / 实战;推测 = 基于机制推断的最佳实践。


5.1 stream 模式全景:values / updates / messages / events / custom

解决什么问题

一个 graph 跑起来有 6 件事可能被订阅:每步完整 state、每步增量、按 token 的模型输出、自定义业务事件、checkpoint 事件、任务开始/结束事件。LangGraph Python 通过 stream_mode 参数让你按需订阅。

来源:官方 https://docs.langchain.com/oss/python/langgraph/streaming (2026-05 抓取)

模式 输出 典型用途
values 每步完整 state 调试、最终回显
updates 每步 state 增量 节点级进度
messages (token_chunk, metadata) 2-tuple token 流式输出
custom get_stream_writer() 自定义 业务事件
checkpoints checkpoint 事件 持久化可见性
tasks 任务开始/结束 进度条
debug checkpoints + tasks + 额外 metadata 调试

Python 原生输出形态(langgraph ≥ 1.1) :传 version="v2" 后,所有 stream_mode 统一 返回 StreamPart 字典,键为 type / ns / datatype 取值与 stream_mode 一一对应(values / updates / messages / custom / checkpoints / tasks / debug);ns 是 namespace tuple,根图为 (),子图为非空 tuple,可用于父子事件隔离;data 是各 mode 的载荷。

来源:官方 https://docs.langchain.com/oss/python/langgraph/streaming

推荐做法

  • 同时订阅多个 mode:stream_mode=["updates", "custom"],按 chunk["type"] 分发到不同 UI 区。
  • messages 模式 + metadata["langgraph_node"] 过滤出具体节点的 token。
  • custom 模式 + get_stream_writer():在节点/工具里推任意事件(loading 状态、文件 ID、引用来源)。
  • 启用 subgraphs=True 后按 v2 ns 字段拆解 root / 子图事件------多 agent 必看
  • 1.0+invoke 返回 GraphOutput 对象,含 .value.interrupts 属性(取代 v1 的 __interrupt__ 字典)。来源:官方 同上

不推荐做法

  • ❌ 不要用 messages 当业务事件总线:它专给 LLM token 用,业务事件请走 custom
  • ❌ 不要在同一调用里既传 subgraphs=True 又在 v1 __interrupt__ 解析(v1 API 已被 GraphOutput 取代)。官方 同上
  • ❌ Python < 3.11 下 astream + 异步 LLM 调 ainvoke必须显式传 RunnableConfig------否则 event 不会回流。官方 同上
  • ❌ Python < 3.11 下不要在 async 节点里用 get_stream_writer(),改用 writer: StreamWriter 形参。

最小代码示例

python 复制代码
# 依赖:langgraph>=1.2
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict

class State(TypedDict):
    topic: str
    draft: str

def writer_node(state: State):
    from langgraph.config import get_stream_writer
    w = get_stream_writer()
    w({"stage": "thinking", "topic": state["topic"]})
    return {"draft": f"Draft about {state['topic']}"}

builder = StateGraph(State)
builder.add_node("writer", writer_node)
builder.add_edge(START, "writer")
builder.add_edge("writer", END)
graph = builder.compile()

for chunk in graph.stream(
    {"topic": "ice cream", "draft": ""},
    stream_mode=["updates", "custom"],
    version="v2",
):
    if chunk["type"] == "updates":
        for node, delta in chunk["data"].items():
            print(f"node={node} delta={delta}")
    elif chunk["type"] == "custom":
        print(f"event: {chunk['data']}")

来源:官方 https://docs.langchain.com/oss/python/langgraph/streaming

检查清单

  1. ☐ 前端拿到的是 v2 StreamPart 吗?是否按 type 分发?
  2. ☐ 是否订阅了 custom 模式来推送业务事件?
  3. ☐ 多 agent / 子图是否启用 subgraphs=True + 用 ns 隔离?
  4. messages 模式是否按 metadata.langgraph_node 过滤到正确 UI 区?
  5. ☐ Python 版本是否低于 3.11?若是,是否显式 ainvoke(..., config)
  6. ☐ 是否避免在 messages 模式推业务事件?
  7. ☐ 客户端是否有 reconnect / buffer 策略(防丢 chunk)?

5.2 token streaming vs 状态/事件 streaming

解决什么问题

两类实时反馈有本质差异:

  • token streaming :连续小增量,关心吞吐 / 首字延迟 / 打字机效果
  • 状态/事件 streaming :离散事件,关心可重放 / 顺序一致 / 边界清晰

把两类混用会丢帧、漏状态。

推荐做法

  • token 走 SSE(HTTP 长连接、文本流、自带断点续传友好)。
  • 状态/事件 走 WebSocket 或 SSE 都可------但需要明确的事件协议(参考下文 5.5)。
  • 前端 UI:打字机区只消费 messages 模式;侧栏进度条只消费 updates 模式;toast / 业务提示只消费 custom 模式。
  • 协议层 :v2 StreamPart 已经统一信封,前端只需要做"按 type 分发"。官方

不推荐做法

  • ❌ 不要把 token 推上 custom 模式------失去 LangChain 的 messages 元数据(langgraph_node、tags),做节点级过滤会很麻烦。
  • ❌ 不要把"完整 state 重发"当 token 流------浪费带宽,前端 diff 成本也高。
  • ❌ 不要混用两个 stream mode 而不做 v2 适配------v1 / v2 数据结构不一致,并发解析会出 bug。

最小代码示例

  • 见 5.1 最小代码(已经体现 messagescustom 分发)。
  • 前端伪代码(React 消费形态示意,具体端点路径与后端实现以 5.3 为准):
tsx 复制代码
// 配合 v2 + SSE(路径与 5.3 后端对齐:/api/runs/{run_id}/stream)
const es = new EventSource(`/api/runs/${runId}/stream`);
es.onmessage = (ev) => {
  const part = JSON.parse(ev.data);
  switch (part.type) {
    case "messages": appendToken(part.data); break;
    case "updates": renderProgress(part.data); break;
    case "custom": showToast(part.data); break;
  }
};

来源:社区 React SSE 模式 + 官方 LangGraph v2 协议 + 5.3 后端实现

检查清单

  1. ☐ 是否把 token 流和事件流在协议层分开?
  2. ☐ 前端是否按 type 字段做单一分发器(避免到处写 if/else)?
  3. ☐ 是否对 token 流做节流 / buffering(避免 60 fps 重渲染)?
  4. ☐ 状态流是否带 seq / 序号,丢包时可请求重放?

5.3 Python 后端的 SSE/WebSocket 适配(前端消费边界,非 Python 主线

本节定位 :本节说明如何把 LangGraph Python graph.stream/astream 输出 暴露 到网络协议层(SSE / WebSocket)。前端 React 组件、JS SDK、Next.js / Edge Runtime 不属于 LangGraph Python API;仅作为消费 Python 输出的客户端形态简述,便于后端设计协议时对齐客户端期望。

解决什么问题

  • SSE:单向、HTTP 友好、自动重连、防火墙穿透好。适合 token 流、状态流。
  • WebSocket:双向、低延迟。适合协作、typing 指示、双向审批回传。
  • 后端封装原则 :把 v2 StreamPart 字典原样 序列化为 SSE data: 帧(JSON),让客户端按 type 单一分发;不要在后端再加一层 envelope。
  • Edge Runtime 注意点:Vercel Edge 函数有响应时长上限(官方默认 30s),不适合长连接;LLM 调用也不该放 Edge(CPU 限制、原生模块不可用)。社区 Vercel Edge runtime limits:https://vercel.com/docs/functions/edge-functions/limitations (2026-05 抓取)

来源:

推荐做法

  • 生产部署优先用 LangSmith Deployment / Agent Server,自带标准化 SSE 端点(不需自己实现 v2 envelope 协议)。
  • 本地开发 用 Python FastAPI / Starlette 暴露 SSE endpoint,GET 而非 POSTEventSource 仅支持 GET)。流程:先 POST /api/runs 拿到 run_id,再 GET /api/runs/{run_id}/stream 订阅。
  • 双向审批 / resume :HTTP POST /api/runs/{run_id}/resume 提交 Command(resume=...),再 GET 新 stream 看 resume 后的输出。
  • 不要把 LLM 调用放 Vercel Edge function(超时、CPU 限制、缺 native 依赖);放 Node runtime 或独立后端服务。社区 同上 Vercel Edge limits
  • JS / TS 前端非 Python 主线,仅消费边界 ):可用 @langchain/langgraph-sdk==1.9.21(2026-05 抓取)的 Client 客户端 + client.threads.stream({ transport: "sse" | "websocket" });React 侧通过 useStream() hook(@langchain/langgraph-sdk/react)订阅 thread.messages / thread.values / thread.interrupts 等 lazy projection。来源:官方

不推荐做法

  • ❌ 不要把 Stream response 放到 Vercel Edge function 里------Edge 有时长上限。
  • ❌ 不要在前端用 fetch().then(r => r.body) 模拟 SSE------错误处理、断点续传都难做,直接用 EventSourceeventsource-parser 库。
  • ❌ 不要在多 tab 间共享一个 EventSource------每个 tab 独立订阅,避免 backpressure 互相影响。
  • ❌ 不要在后端 SSE 输出里再加一层 envelope 协议------v2 StreamPart 已经是统一格式。

最小代码示例(Python 后端 SSE,正确 GET + run_id)

python 复制代码
# 依赖:fastapi、uvicorn、langgraph
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from langgraph.types import Command
import json
import uuid

app = FastAPI()
COMPILED = build_graph()  # 你的 graph
RUNS: dict[str, dict] = {}  # 演示用内存索引,生产请用 Redis/Postgres

class StartReq(BaseModel):
    input: dict
    thread_id: str

class ResumeReq(BaseModel):
    resume: dict

@app.post("/api/runs")
def start_run(req: StartReq):
    run_id = str(uuid.uuid4())
    RUNS[run_id] = {"thread_id": req.thread_id, "input": req.input}
    return {"run_id": run_id}

@app.get("/api/runs/{run_id}/stream")
def stream_run(run_id: str):
    rec = RUNS[run_id]
    cfg = {"configurable": {"thread_id": rec["thread_id"]}}
    def gen():
        for chunk in COMPILED.stream(
            rec["input"], cfg, stream_mode=["updates", "custom"], version="v2"
        ):
            yield f"data: {json.dumps(chunk)}\n\n"
    return StreamingResponse(gen(), media_type="text/event-stream")

@app.post("/api/runs/{run_id}/resume")
def resume_run(run_id: str, req: ResumeReq):
    rec = RUNS[run_id]
    cfg = {"configurable": {"thread_id": rec["thread_id"]}}
    # 演示用同步调用;生产建议改为 async def + await COMPILED.ainvoke(...)
    #   (FastAPI 同步 def 会阻塞事件循环,对 LLM 长时间调用不友好)
    COMPILED.invoke(Command(resume=req.resume), cfg)
    return {"ok": True}

来源:社区 FastAPI SSE 模式(参考 https://fastapi.tiangolo.com/advanced/custom-response/#using-streamingresponse 2026-05 抓取)+ 官方 LangGraph v2 stream 输出

最小代码示例(React 端,仅消费边界参考)

tsx 复制代码
// 非 Python 主线;展示 useStream 消费端典型形态
// 依赖:@langchain/langgraph-sdk@1.9.21、react 18/19
import { Client } from "@langchain/langgraph-sdk";
import { useStream } from "@langchain/langgraph-sdk/react";

const client = new Client({ apiUrl: "http://localhost:2024" });

function AgentView({ assistantId }: { assistantId: string }) {
  const thread = client.threads.stream({ assistantId, transport: "sse" });
  const stream = useStream({ client: thread, assistantId });

  return (
    <div>
      {stream.messages.map(m => <p key={m.id}>{m.content}</p>)}
      {stream.toolCalls.map(t => <span key={t.id}>running {t.name}...</span>)}
    </div>
  );
}

ThreadStream 支持 transport: "sse" | "websocket" | AgentServerAdapter(自定义);连接懒启动------可在首次 I/O 前配置订阅。官方 https://github.com/langchain-ai/langgraphjs/blob/main/libs/sdk/docs/streaming.md

检查清单

  1. ☐ SSE endpoint 是否用 GET(与 EventSource 兼容),start/resume 走 POST?
  2. ☐ 后端是否把 v2 StreamPart 原样 JSON 序列化到 data: 帧,没加额外 envelope?
  3. ☐ 是否避免把流放到 Vercel Edge Runtime?
  4. ☐ 自实现 SSE 时是否处理 Last-Event-ID 头(断点续传)?
  5. ☐ WebSocket 是否有心跳 / reconnect 策略?
  6. ☐ resume / interrupt 回传是否走独立 HTTP 端点(不复用 SSE 通道)?

5.4 多并发流隔离与子图事件 namespace

解决什么问题

一个 UI 上可能同时跑多个 agent 实例:左侧规划、右侧执行、底部评估。后端要么是单连接多 thread,要么是多连接。两类事件不能混------否则 UI 状态错乱。

推荐做法

  • 每个 agent 实例独占一个 thread_id + 一个 SSE 连接(或 WS 连接)。
  • 在 Graph 端启用 subgraphs=True,v2 输出中用 ns 字段区分根图 / 子图事件。
  • 前端按 thread_id 维护独立的 buffer;按 ns 区分父子事件。
  • 服务端按 thread_id 隔离状态------这是 LangGraph checkpointer 的天然优势。

不推荐做法

  • ❌ 不要把多 thread 事件塞进同一个 values 流------分不清谁是谁。
  • ❌ 不要在客户端按"先后顺序"推断归属------并发场景会出错。
  • ❌ 不要忽略 ns 字段------多 agent 协作时父子事件是关键调试线索。

最小代码示例

见 5.1 中的 v2 输出 ns: () 注释;多 agent 部署时按 thread_id 隔离。

检查清单

  1. ☐ 是否每个会话/任务有独立 thread_id
  2. ☐ 前端是否按 thread_id 隔离 buffer?
  3. ☐ v2 模式是否解析 ns 区分父子图?
  4. ☐ 是否避免在同一连接上合并多 thread 输出?

5.5 断流恢复(resumable stream)、message_id / tool_call_id 对齐

解决什么问题

网络抖动、用户刷新、长时间断网。客户端断开后能否从上次位置继续 ,并且事件能与 LLM 输出的 message_id / tool_call_id 对齐,决定了能不能"无缝"恢复。

推荐做法

  • SSE 断点续传(应用层协议) :服务端在每条 data: 帧前携带 id: 字段(event_id),客户端用浏览器原生 EventSource + Last-Event-ID 头自动重连。Last-Event-ID 是 W3C SSE 规范能力,不是 LangGraph Python 内建 ------LangGraph 端只产生 StreamPart 字典,由后端 SSE 适配层补 id: 字段。
  • 消息对齐(Python 侧原语)stream_mode="messages" 模式下,每个 token chunk 的 id 字段与最终写入 messages reducer 的消息 id 一致;tool_call_id 由 LangChain 工具调用框架保证唯一性。来源:官方 https://docs.langchain.com/oss/python/langchain/models
  • 持久化兜底 :长时间断网不必续传,直接查 thread 历史 graph.get_state(config),从最近 checkpoint 拉取再继续。
  • 不要混用消息 id 和 event id :前者是 LangGraph 内部消息标识(用于 reducer 合并),后者是 SSE 帧标识(用于断点续传),二者独立

不推荐做法

  • ❌ 不要用「重新调用 invoke + 拿新 run_id」当断线恢复------会得到完全不同的 session,token 计费也翻倍。
  • ❌ 不要自己造 message_id------和 LangChain 的 id 冲突,UI 拼接会乱。
  • ❌ 不要在客户端做"按顺序拼接"对齐------并发工具调用一来就错。
  • ❌ 不要把 LangGraph Python event_id 当成 LangGraph 原生字段------graph.stream 不产生该字段;它属于 Agent Server / JS SDK 协议层。

最小代码示例(Python 后端 SSE,message_id / tool_call_id 透传 + SSE id 帧)

python 复制代码
import json, uuid
# ... 接续 5.3 的 FastAPI SSE 流 ...
def gen():
    for chunk in COMPILED.stream(
        input, cfg, stream_mode="messages", version="v2"
    ):
        if chunk["type"] == "messages":
            token, meta = chunk["data"]
            # Python 原生:meta 含 langgraph_node;token.id 是消息级 id
            sse_id = str(uuid.uuid4())  # SSE 帧 id 由应用层生成
            yield f"id: {sse_id}\ndata: {json.dumps({'id': token.id, 'delta': token.content})}\n\n"

来源:官方 https://docs.langchain.com/oss/python/langgraph/streaming + W3C SSE 规范(EventSource 协议)

检查清单

  1. ☐ SSE 帧是否带 id: 字段启用 Last-Event-ID 断点续传?
  2. id: 是 SSE 帧 id(应用层生成)还是 token id(Python 原生)?二者是否解耦?
  3. ☐ token 的 id 是否和最终 message id 一致(LangChain 框架保证)?
  4. ☐ tool_call 的 tool_call_id 是否与 ToolMessage.tool_call_id 对齐?
  5. ☐ 长时间断线时是否回退到 get_state checkpoint?

5.6 中断、错误、恢复时的事件协议设计

解决什么问题

graph 可能因 interrupt 暂停、可能因错误失败、可能因 resume 继续。前端必须能区分这三种状态。

推荐做法(按 API 层级分离)

  • Python 原生层(stream/astream) :用 v2 StreamPart 字典的 type 字段区分模式;updates 模式里如果 next == () 表示已结束;checkpoints / tasks 模式暴露执行进度。invoke() 在 1.0+ 返回 GraphOutput 对象,含 .value.interrupts 属性。
  • Python 原生层(astream_events) :传 version="v2"(默认)返回 LangChain 风格事件(on_chain_start / on_llm_stream / on_tool_end 等);传 version="v1" 仍是旧版 __interrupt__ 风格,v1 视为弃用。来源:官方 https://docs.langchain.com/oss/python/langgraph/event-streaming
  • interrupt 检测 :节点里调 interrupt() 抛 GraphInterrupt 异常;Python 端通过 GraphOutput.interrupts 拿不要在订阅流里硬找特定 event 名
  • 错误传播 :工具异常默认经 ToolNode.handle_tool_errors(详见 6.2/6.7)回灌为 ToolMessage(status="error");非工具异常会从 astream 中以异常形式抛出。
  • resume 模式 :resume 后新开 streamgraph.stream(Command(resume=...), cfg)),不在旧 stream 上追加。

不推荐做法

  • ❌ 不要把 JS SDK 投影(stream.interrupted / stream.interrupts / stream.output)当作 Python 原生属性------它们是 @langchain/langgraph-sdk 包装的对象属性。
  • ❌ 不要混用 v1 __interrupt__ 与 v2 GraphOutput------v1 已弃用。
  • ❌ 不要在订阅流中"猜"是否被 interrupt------直接看 GraphOutput.interrupts 或走 checkpoints 模式。
  • ❌ 不要在同一条 stream 上从 paused → resumed 连续订阅------resume 后新开 stream 更可靠。

最小代码示例(Python 原生 interrupt 检测)

python 复制代码
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command, interrupt

class State(TypedDict): x: int
def node(state: State):
    ok = interrupt({"ask": "approve?"})
    return {"x": int(bool(ok))}

g = StateGraph(State).add_node("n", node)
g.add_edge(START, "n").add_edge("n", END)
app = g.compile(checkpointer=InMemorySaver())
cfg = {"configurable": {"thread_id": "t-1"}}

# Python 1.0+ 风格
out = app.invoke({"x": 0}, cfg)
if getattr(out, "interrupts", None):
    print("pending:", out.interrupts)
    out2 = app.invoke(Command(resume=True), cfg)
    print("final:", out2.value if hasattr(out2, "value") else out2)

来源:官方 https://docs.langchain.com/oss/python/langgraph/interrupts + https://docs.langchain.com/oss/python/langgraph/streaming

检查清单

  1. ☐ 是否在 Python 端用 GraphOutput.interrupts 检测中断(不依赖 SDK 投影)?
  2. ☐ 是否在 astream_events 中传 version="v2"(v1 弃用)?
  3. ☐ 工具异常是否走 ToolNode.handle_tool_errors 通道(详见 6.2)?
  4. ☐ resume 后是否新开 stream(不复用旧 stream)?

第 6 章:工具调用与 Agent 构建

版本锚点:langgraph==1.2.4langchain==1.x(LangChain 1.0+ 主线)。

LangChain 1.0+ 文档主推 langchain.agents.create_agent(built on LangGraph),作为新代码的"快速 Agent"路径;原 langgraph.prebuilt.create_react_agent 仍可使用 ,适合"想直接控制 ReAct 循环节点/边"或"必须用 LangGraph prebuilt API"的场景。两者不互斥 ,但默认状态 schema 与返回结构不同 ,混用需注意。

参考:


6.1 create_agent(LangChain)vs create_react_agent(LangGraph prebuilt)vs 自定义 StateGraph

解决什么问题

新团队 90% 场景是"模型 + 工具 + ReAct 循环",写一堆节点/边是过度设计。但生产中总会出现:

  • 多个 LLM 调用节点(router / planner / executor)
  • 多状态 schema
  • 强 HITL
  • 子图复用

需要一个清晰的选型边界

三方对比(截至 langgraph 1.2.x / langchain 1.x)

维度 langchain.agents.create_agent langgraph.prebuilt.create_react_agent 自定义 StateGraph
入口模块 langchain.agents langgraph.prebuilt langgraph.graph
推荐来源 LangChain 1.0+ 主推 LangGraph 文档保留 高级用法
状态 schema LangChain AgentStatemessages + 内部字段) LangGraph MessagesState 完全自定
返回值 result["messages"] + result["structured_response"] result["messages"] 自定
HITL 支持 通过 checkpointer + interrupt 子协议 通过 checkpointer + interrupt 完全控制
适合 单 Agent、模型 + 工具的快速搭建 想直接用 LangGraph prebuilt API 的团队 多 Agent / 复杂流程
主要风险 内部结构演进快,版本间 schema 可能微调 文档主推让位,但 API 仍稳定 维护成本高

来源:

推荐做法

  • 默认(2026 主流) :用 langchain.agents.create_agent

    python 复制代码
    from langchain.agents import create_agent
    from langgraph.checkpoint.memory import InMemorySaver
    agent = create_agent(
        model="openai:gpt-5.4",
        tools=[search, calc],
        system_prompt="You are a helpful assistant.",
        checkpointer=InMemorySaver(),
    )
  • create_react_agent 的合理场景

    1. 团队已基于 langgraph.prebuilt 标准化导入;想统一从 langgraph 而非 langchain 引入 Agent。
    2. 需要直接拿 CompiledStateGraph 二次扩展(用 create_agent 得到的对象再 .with_config(...) 也是 CompiledStateGraph,但内部节点命名 / schema 略有不同)。
    3. 与 LangGraph 多 agent 子图生态(如 langgraph-supervisor)对齐时。
  • 落到自定义 StateGraph 的信号

    1. 你需要 2 个以上 LLM 调用节点(planner/executor/verifier)
    2. 你需要自定义 state 字段(非 messages)
    3. 你需要子图复用 / 多 agent 协作
    4. 你需要复杂 HITL(多分支审批)

不推荐做法

  • ❌ 不要为了"灵活"一上来就自定义 StateGraph------等真有需求再改。
  • ❌ 不要在 create_agent 里塞大量业务逻辑到 system_prompt------那是 RAG / few-shot 该管的事。
  • ❌ 不要混用 create_agentcreate_react_agent 在同一 thread / 同一 checkpoint schema 上------两者底层 GraphState 字段不完全相同(如 create_agent 可能有内部 structured_response 字段),混用会导致 get_state 读回字段缺失。待补 具体 schema 差异需读两方源码确认;本结论为机制推断。

最小代码示例(自定义 StateGraph + ToolNode + tools_condition)

python 复制代码
# 依赖:langgraph>=1.2、langchain-core
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import InMemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages

class State(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]

def call_model(state: State):
    llm = ChatOpenAI(model="gpt-4o").bind_tools(tools)
    return {"messages": [llm.invoke(state["messages"])]}

builder = StateGraph(State)
builder.add_node("agent", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", tools_condition)
builder.add_edge("tools", "agent")
graph = builder.compile(checkpointer=InMemorySaver())

来源:官方 https://docs.langchain.com/oss/python/langchain/agents + 官方 LangGraph prebuilt

检查清单

  1. ☐ 是否先尝试 create_agent 路径再考虑自定义?
  2. ☐ 真的需要多节点 / 多状态吗?有没有「过度设计」?
  3. ☐ 自定义 StateGraph 时是否使用 ToolNode + tools_condition 而不是手写边?
  4. ☐ state schema 是否明确(messagesadd_messages reducer)?
  5. ☐ 是否避免在同一 codebase 混用 create_agent 和旧 create_react_agent

6.2 ToolNode / tools_condition / 结构化输出

解决什么问题

  • ToolNode :LangGraph 内置的"批量执行工具 + 把结果包成 ToolMessage"节点。
  • tools_condition :根据 LLM 最后一条消息是否含 tool_calls 决定走 tools 还是 END
  • 结构化输出:让 LLM 输出符合 schema 的 JSON(用于 parser / 路由 / 严格表单)。

推荐做法

  • ToolNode 默认行为(langgraph prebuilt 1.2.x)handle_tool_errors 默认值是 _default_handle_tool_errors(一个 callable),不是简单的 True 。该默认值会捕获「工具调用参数错误」(模型给错参数)并以 ToolMessage 回灌 ,但不会捕获「工具执行时抛出的异常」 ------执行异常会沿 GraphBubbleUp 机制上抛 到 graph。

  • 生产推荐 :把 handle_tool_errors 显式设为 True 或自定义 callable,不要依赖默认值------执行期异常在默认下会冒泡,可能让 graph 整个挂掉。

  • 结构化输出三选一

    场景 推荐
    模型需要主动调外部动作 tool calling
    最终回复要解析为 Pydantic / TypedDict with_structured_output
    兼容老模型 / 只想约束"是 JSON" json_mode(要在 prompt 里说明 schema)
    来源:官方 https://docs.langchain.com/oss/python/langchain/models
  • 校验 :用 include_raw=True 在 Pydantic 校验失败时拿到原始消息 + 错误,便于降级。

  • 强制选择tool_choice="any"tool_choice="tool_name" 在路由场景有用。

不推荐做法

  • ❌ 不要把 with_structured_output 用在"模型需要先调工具再答"的场景------它不支持工具调用。
  • ❌ 不要在 prompt 里堆 JSON 示例然后用 json_mode------改用 with_structured_output 更稳。
  • ❌ 不要把 ToolNodehandle_tool_errors 设成 False------除非你想让工具异常直接 raise 终止 graph。
  • ❌ 不要假设 ToolNode 默认会捕获所有工具异常------它只捕获「调用参数错误」执行期异常默认会冒泡(见上文 ToolNode 默认行为)。

最小代码示例

python 复制代码
from pydantic import BaseModel
from langchain_openai import ChatOpenAI

class Answer(BaseModel):
    summary: str
    confidence: float

llm = ChatOpenAI(model="gpt-4o")
structured = llm.with_structured_output(Answer)
out = structured.invoke("Summarize: ...")
print(out.summary, out.confidence)

来源:官方 https://docs.langchain.com/oss/python/langchain/models

检查清单

  1. ☐ 工具异常是否走 ToolMessage(status="error") 自愈?
  2. ☐ 结构化输出是否根据场景选 tool calling / with_structured_output / json_mode
  3. ☐ 是否在 prompt 里说明 schema 而不是用裸 json_mode
  4. ☐ 是否给 with_structured_outputinclude_raw=True 拿到降级路径?
  5. ☐ 是否合理使用 tool_choice 强制工具?

6.3 工具描述与命名规范(质量第一杀手)

解决什么问题

工具描述直接决定 LLM 调用准确率。最常见的 agent 失败原因不是模型、不是 prompt,是工具描述烂

推荐做法

  • 命名规范
    • 动词开头:get_weather / search_orders / create_invoice不要 weather / search
    • 命名空间避免冲突:jira_create_issue / github_create_pr(多服务场景)
    • 拼写一致:snake_case 全局统一
  • 描述三段式
    1. What:做什么(一句话)
    2. When:何时调用、典型场景
    3. Edge cases:失败 / 空结果 / 需要确认
  • 参数描述 :每个参数都要 description + 类型;可选值用 enum 限制。
  • 示例 :在 description 里给 1-2 个输入输出示例(Few-shot 提升准确率)。

不推荐做法

  • ❌ 不要让 description 一句话敷衍------模型会瞎选。
  • ❌ 不要让 description 重复函数名------浪费 token。
  • ❌ 不要在 description 里夹带私货("如果用户没问天气就拒绝")------这是 system prompt 该管的事。
  • ❌ 不要忽略参数描述------LLM 拿不到字段语义就会瞎填。

最小代码示例

python 复制代码
from langchain_core.tools import tool

@tool
def get_weather(location: str, unit: str = "celsius") -> str:
    """Get the current weather for a given location.

    Use this tool when the user asks about current weather, temperature,
    or conditions in a city. Do NOT use for forecasts beyond 24h.

    Args:
        location: City name, e.g. "San Francisco".
        unit: "celsius" or "fahrenheit". Defaults to celsius.
    """
    ...

来源:官方 https://docs.langchain.com/oss/python/langchain/models + 社区 通用经验

检查清单

  1. ☐ 工具名是否动词开头、snake_case?
  2. ☐ description 是否三段式(What / When / Edge cases)?
  3. ☐ 每个参数是否都有 description + 类型?
  4. ☐ 是否避免在 description 里夹带 system prompt 类的指令?
  5. ☐ 是否在 CI / 评测中加"工具选择准确率"指标?
  6. ☐ 命名空间是否避免冲突(多服务场景)?

6.4 大工具集的处理:检索 / 路由 / 去重 / 合并

解决什么问题

当工具数量 > 30 时,LLM 选错概率显著上升;> 100 时基本不可用。需要把工具集拆成可索引的小集合。

推荐做法

  • 工具检索(Tool Retrieval)
    • 把工具 description 嵌入向量库;用户 query 来时只取 top-k 工具
    • 框架:langchain-mcp-adapters 配合语义搜索 / langgraph 内置 tools_condition 不够,需要上自定义 router
  • 路由(Router Agent)
    • 一个 LLM 先决定"该用哪类工具",再实例化对应的子 agent
    • 子图模式:把"专业 agent"做成子图
  • 去重:相似 description 的工具在 store 端合并("get_user" vs "fetch_user")。
  • 合并 / 抽象 :把"get_user_id + get_user_profile"合并为 get_user(id_or_name)
  • 动态加载:MCP 场景下按需从 server 拉工具而不是一次性全注册。

不推荐做法

  • ❌ 不要给单 agent 直接塞 50+ 工具------选错率会爆炸。
  • ❌ 不要靠 description 长度比拼"重要性"------LLM 没有偏好权重。
  • ❌ 不要在 prompt 里 hard-code "如果你看到 X 工具就用 Y"------用 router 替代。

最小代码示例(语义检索挑工具)

python 复制代码
# 伪代码:把工具 description 嵌入向量库
def select_tools(query: str, k: int = 5):
    hits = vector_store.similarity_search(query, k=k)
    return [tool_registry[hit.metadata["name"]] for hit in hits]

来源:推测 + 社区 通用模式;LangGraph 本身没有官方"tool retriever"组件,待补具体库名。

检查清单

  1. ☐ 工具数量是否超过 30?是否启用 router / 检索?
  2. ☐ 工具 description 是否去重(避免相似工具混在一起)?
  3. ☐ 是否合并语义相近的工具?
  4. ☐ 是否在评测中测量"工具选择 top-1 准确率"?

6.5 MCP(Model Context Protocol)集成

解决什么问题

MCP 是 Anthropic 推动的"tool server 标准协议",让 agent 能调用任何实现 MCP 的工具源。langchain-mcp-adapters 把 MCP 工具桥接到 LangChain / LangGraph。

来源:官方 https://github.com/langchain-ai/langchain-mcp-adapters (3.6k stars,2026-05 抓取)

推荐做法

  • 单 server(stdio)

    python 复制代码
    from mcp import ClientSession, StdioServerParameters
    from mcp.client.stdio import stdio_client
    from langchain_mcp_adapters.tools import load_mcp_tools
    from langchain.agents import create_agent
    
    server_params = StdioServerParameters(command="python", args=["/path/to/math_server.py"])
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            tools = await load_mcp_tools(session)
            agent = create_agent("openai:gpt-4.1", tools)
            result = await agent.ainvoke({"messages": "what's (3+5) x 12?"})
  • 多 server(混合 transport)

    python 复制代码
    from langchain_mcp_adapters.client import MultiServerMCPClient
    client = MultiServerMCPClient({
        "math":    {"command": "python", "args": ["math_server.py"], "transport": "stdio"},
        "weather": {"url": "http://localhost:8000/mcp", "transport": "http",
                    "headers": {"Authorization": "Bearer ..."}},
    })
    tools = await client.get_tools()
    agent = create_agent("openai:gpt-4.1", tools)

    transport 取值说明 (langchain-mcp-adapters 当前版本):"stdio" / "sse" / "http" / "streamable_http""http" 是当前 README 默认值,"streamable_http" 是 streamable HTTP transport 的语义更准确的别名(两者在最新版本中均可用)。来源:官方 https://github.com/langchain-ai/langchain-mcp-adapters/blob/main/README.md (2026-05 抓取)

  • 错误自愈 :MCP 工具经 langchain-mcp-adapters 加载后由 ToolNode 统一处理;ToolNode 默认对"调用参数错误"做 ToolMessage(status="error") 回灌(详见 6.2 / 6.7),让 agent 自愈。

  • Web 服务场景用 HTTP transport,避免 stdio 跨进程;stdio 仅适合用户本机应用。

不推荐做法

  • ❌ 不要在 web server 上用 stdio transport------MCP 官方明确 stdio "designed primarily for user-machine applications"。
  • ❌ 不要把 MCP 工具"包装"成 LangChain 工具时再手动执行------load_mcp_tools 已自带适配。
  • ❌ 不要在长连接里复用 ClientSession 但忘记重连------MCP session 有生命周期。

最小代码示例

见上。

检查清单

  1. ☐ MCP 工具加载是用 load_mcp_tools 而不是手写适配?
  2. ☐ Web 场景用 HTTP transport,本地工具用 stdio?
  3. ☐ 多 server 是否用 MultiServerMCPClient 而不是多次 stdio_client?
  4. ☐ MCP 工具错误是否走自愈路径?
  5. ☐ HTTP transport 是否带 auth header?
  6. ☐ 是否给 MCP session 设置超时 / 重连?

6.6 并行工具调用:parallel_tool_calls 与状态合并风险

解决什么问题

当 LLM 一次返回多个 tool_calls(如"查 5 个城市的天气"),ToolNode 默认会并行执行。这带来:

  • 性能提升(少一轮 LLM)
  • 状态合并顺序:结果会按 tool_call_id 回到 messages,但写入顺序可能并发
  • 部分失败:5 个里 2 个失败如何处理?

推荐做法

  • 开启 parallel_tool_calls=True(多数 provider 默认开)。
  • 工具实现幂等(多次调用副作用相同)。
  • 工具内部避免共享可变状态(数据库连接池、计数器等)。
  • 失败策略:让 handle_tool_errors=True 把错误回灌,agent 自己决定重试。
  • 高一致性场景:手动关 parallel_tool_calls=False,串行更可预测。官方 https://docs.langchain.com/oss/python/langchain/models

不推荐做法

  • ❌ 不要在工具里维护进程内可变状态(计数器、缓存)------并发调用会撞车。
  • ❌ 不要假设 tool_calls 一定按 LLM 输出顺序执行------并发场景顺序未定义。
  • ❌ 不要把"全部成功才继续"逻辑硬编码进 tool------让 LLM 决定。

最小代码示例

python 复制代码
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
# 默认 parallel_tool_calls=True;高一致场景关掉
llm_no_parallel = llm.bind_tools(tools, parallel_tool_calls=False)

来源:官方 https://docs.langchain.com/oss/python/langchain/models

检查清单

  1. ☐ 工具实现是否幂等?
  2. ☐ 工具是否共享可变状态(如果有,关闭并行)?
  3. ☐ 并行失败是否走 ToolMessage 自愈?
  4. ☐ 状态 reducer 是否能处理乱序 ToolMessage

6.7 工具调用错误处理:分类、可重试、用户可见、静默忽略

解决什么问题

工具调用错误谱系很广:

  • 网络抖动 → 可重试
  • 4xx 鉴权/参数 → 不可重试,让模型改
  • 5xx 临时 → 限流退避后重试
  • 业务错误(库存不足)→ 透传给用户
  • 数据未找到 → 静默返回空,让 LLM 自己换 query

推荐做法

  • 模型级重试(langchain-openai 1.x 实测)
  • 工具级重试 :用 tenacity(生产场景推荐),或包装一个 BaseTool 子类实现 def _run(self, ...) + 内部重试;BaseTool 本身不带重试,需要自实现。
  • 分类处理
    • 网络/5xx → 用 tenacity 指数退避重试
    • 4xx → 立即 ToolMessage(status="error", ...)
    • 业务错误 → 同样以 error 回灌,但 description 里写明语义
  • 限流:429 → 指数退避
  • 用户可见 :高风险操作(付款、删除)必须先 interrupt(),不靠工具错误处理。
  • 结构化输出失败with_structured_output(..., include_raw=True) 拿原始 + error。

不推荐做法

  • ❌ 不要在工具里直接 raise 让 graph 崩溃------应该用 ToolMessage(status="error") 让 agent 继续。
  • ❌ 不要对所有错误一律重试------4xx 鉴权/参数错误重试无意义。
  • ❌ 不要在生产里 try/except: pass 静默吞错------可观测性没了。
  • ❌ 不要假设 ChatOpenAI 默认带重试------1.x 显式置空,需要主动设 max_retries
  • ❌ 不要假设 BaseTool 自带重试------它没有,需要用 tenacity 或子类包装。

最小代码示例

python 复制代码
import tenacity
from langchain_core.tools import tool

@tool
def call_external_api(payload: dict) -> str:
    """Call an external API. Retries on 5xx."""
    @tenacity.retry(
        stop=tenacity.stop_after_attempt(3),
        wait=tenacity.wait_exponential(multiplier=1, min=1, max=10),
        retry=tenacity.retry_if_exception_type(requests.HTTPError),
        reraise=True,
    )
    def _do():
        r = requests.post(URL, json=payload, timeout=10)
        r.raise_for_status()
        return r.text
    return _do()

来源:官方 https://docs.langchain.com/oss/python/langchain/models + 社区 tenacity 模式(参考 https://tenacity.readthedocs.io/en/latest/index.html 2026-05 抓取)

检查清单

  1. ☐ 模型级 max_retries / timeout 是否合理?
  2. ☐ 工具异常是否走 ToolMessage(status="error") 自愈?
  3. ☐ 4xx / 5xx 是否分类处理?
  4. ☐ 高风险操作是否走 interrupt 而不是错误处理?
  5. ☐ 错误是否进入可观测体系(trace / log)?
  6. ☐ 是否避免静默吞错?

章节内引用一览

来源类型 链接 资料日期 / 版本
官方 https://docs.langchain.com/oss/python/langgraph/persistence 2026-05 / langgraph 1.2.x
官方 https://docs.langchain.com/oss/python/langgraph/memory 2026-05 / langgraph 1.2.x
官方 https://docs.langchain.com/oss/python/langgraph/interrupts 2026-05 / langgraph 1.2.x
官方 https://docs.langchain.com/oss/python/langgraph/streaming 2026-05 / langgraph 1.2.x
官方 https://docs.langchain.com/oss/python/langgraph/event-streaming 2026-05 / langgraph 1.2.x
官方 https://docs.langchain.com/oss/python/langchain/agents 2026-05 / langchain 1.x
官方 https://docs.langchain.com/oss/python/langchain/models 2026-05 / langchain 1.x
官方 https://docs.langchain.com/langsmith/deployment 2026-05 / LangSmith Deployment
官方 https://github.com/langchain-ai/langchain-mcp-adapters 2026-05 / 3.6k stars
官方 https://pypi.org/project/langgraph/ 1.2.4 / 2026-06-02
官方 https://pypi.org/project/langgraph-checkpoint/ 4.1.1
官方 https://pypi.org/project/langgraph-checkpoint-postgres/ 3.1.0 / 2026-05-12
官方 https://pypi.org/project/langgraph-checkpoint-sqlite/ 3.1.0 / 2026-05-12
官方 https://github.com/langchain-ai/langgraph/blob/main/libs/checkpoint/langgraph/checkpoint/memory/**init**.py 2026-05
官方 https://www.langchain.com/blog/ 2026-05 抓取;多篇相关
官方 https://github.com/langchain-ai/langchain-mcp-adapters/blob/main/README.md 2026-05 抓取 / transport 取值
官方 https://github.com/langchain-ai/langgraphjs/blob/main/libs/sdk/docs/streaming.md 2026-05 抓取
官方源码 https://github.com/langchain-ai/langgraph/blob/main/libs/prebuilt/langgraph/prebuilt/tool_node.py 2026-05 抓取 / handle_tool_errors 默认值
官方源码 https://github.com/langchain-ai/langchain/blob/master/libs/partners/openai/langchain_openai/chat_models/base.py 2026-05 抓取 / ChatOpenAI max_retries/timeout 默认值
社区 https://vercel.com/docs/functions/edge-functions/limitations 2026-05 抓取 / Edge runtime 时长上限
社区 https://tenacity.readthedocs.io/en/latest/index.html 2026-05 抓取 / tenacity 重试模式
社区 https://fastapi.tiangolo.com/advanced/custom-response/#using-streamingresponse 2026-05 抓取 / FastAPI SSE 模式
社区 https://www.langchain.com/blog/ 2026-05 抓取;多篇 LangGraph 实战 / fault tolerance / deep agents

待补 / 待验证

  • Time Travel / Subgraph 官方概念页 https://docs.langchain.com/oss/python/langgraph/time-travel / subgraphs 在抓取时返回 SPA 错误页(页面 noindex、无内容)------可能文档站已迁移;建议从 GitHub examples 补查。本章中 4.5、5.4 引用了其核心 API(get_state_history / update_state / subgraphs=True),来自 LangGraph Python persistence 主概念页的同源描述,不构成主结论矛盾
  • 生产 BaseStore DB-backed 后端 :langgraph 1.2.x 在 PyPI 没有开源的 langgraph-store-postgres ;生产路径以 LangSmith Deployment 托管 store 或自建 BaseStore 实现为主。Checkpoint-postgres 不能替代 BaseStore(接口不兼容)。待补 --- 已降级为"待验证/需自建"。
  • 大工具集检索组件 暂无 LangGraph 官方"tool retriever"------目前都是社区方案(LlamaIndex / 自研),未做产品级选型推荐。
  • HITL timeout / escalation 缺少 LangGraph 官方推荐路径(社区方案多)。本章 4.6 已降级为"参考架构/推测性建议",并显式标注"非官方能力"。
  • create_agent 与 create_react_agent 状态 schema 差异:6.1 已标 待补------具体字段差异需读两方源码确认。
  • m-03(@langchain/langgraph-sdk useStream 完整 API) :本节只展示最小消费示例;完整 ResolveStreamInterface 类型 / StreamPart 协议细节见 官方 https://github.com/langchain-ai/langgraphjs/blob/main/libs/sdk/src/react/stream.tsx (2026-05 抓取)。

章节分层摘要(官方能力 / 社区经验 / 推测建议)

第 4 章:状态持久化、恢复与 HITL

  • 官方已确认能力 :Checkpointer 三档选型(InMemorySaver / SqliteSaver / PostgresSaver)、Durability 模式(exit / async / sync)、LANGGRAPH_STRICT_MSGPACK 安全加固、interrupt() + Command(resume=...) HITL 三件套、Time Travel(get_state_history / update_state / replay)、BaseStore 跨 thread KV + 语义检索。
  • 社区/部署经验 :生产推荐 PostgresSaver + setup()InMemorySaver 多 worker 不可用。
  • 推测/待验证 :生产 BaseStore DB-backed 后端(官方未提供,PyPI 无 langgraph-store-postgres);HITL timeout / escalation 完整实现(官方无推荐路径);subgraph interrupt 父节点幂等要求(机制推断)。

第 5 章:流式输出与前端消费边界

  • 官方已确认能力(Python 原生)stream/astream 的 v2 StreamPart 字典({type, ns, data});stream_mode 7 个取值(values / updates / messages / custom / checkpoints / tasks / debug);get_stream_writer() 自定义事件;subgraphs=True 父子图隔离;GraphOutput.interrupts(1.0+)作为 Python 原生 interrupt 检测。
  • 官方已确认能力(部署/消费层) :LangSmith Deployment 自带标准 SSE 端点;FastAPI StreamingResponse 自实现 SSE。
  • 社区/部署经验 :SSE 帧 id: + Last-Event-ID 实现断点续传(W3C SSE 规范,非 LangGraph 原生);Vercel Edge 不能跑长连接(30s 上限)。
  • 推测/待验证 :JS SDK stream.interrupted / stream.interrupts / stream.output 投影是 SDK 包装而非 Python 原生(已显式标注)。

第 6 章:工具调用与 Agent 构建

  • 官方已确认能力create_agent(LangChain 1.x,built on LangGraph)作为新代码主推;create_react_agent(LangGraph prebuilt)仍可 import;ToolNode 7 个 handle_tool_errors 形态;with_structured_output 三种 method(json_schema / function_calling / json_mode);MCP 适配(langchain-mcp-adapters)4 种 transport(stdio / sse / http / streamable_http);parallel_tool_calls 控制并行。
  • 社区/部署经验:工具描述三段式(What / When / Edge cases)+ Few-shot 示例;tenacity 工具级重试;4xx 不可重试 / 5xx 指数退避。
  • 推测/待验证 :大工具集检索(无官方组件,自研 router / LlamaIndex);create_agentcreate_react_agent schema 差异(需读源码确认,标 待补);ChatOpenAI 在 langchain 1.x 已取消默认 6 次重试 (默认 None),旧文档以"默认 6 次"流传属历史信息。

调研元信息

  • 覆盖版本langgraph==1.2.4(2026-06-02 PyPI latest)、langgraph-checkpoint==4.1.1langgraph-checkpoint-postgres==3.1.0 / langgraph-checkpoint-sqlite==3.1.0(均 2026-05-12 发布)、langchain-openai 1.x 主线(ChatOpenAI 默认值核实于 2026-05)、@langchain/langgraph-sdk==1.9.21(2026-05 抓取,仅作消费边界参考)。
  • 覆盖范围:仅 LangGraph Python(按用户要求);JS SDK / Agent Server / React 仅作消费边界小节标注。
  • 来源类型分布:官方文档/源码约 75%、官方博客 ≈ 5%、社区/推测 ≈ 20%。
  • 章节完整性:第 4 章 6 小节;第 5 章 6 小节;第 6 章 7 小节------每节五段结构齐全;新增「章节分层摘要」对齐官方能力 / 社区经验 / 推测建议三类。

第 7 章:多智能体架构

7.1 解决什么问题

单智能体(single agent)在以下场景会撞墙:

  • 工具集异质、权限边界不同(只读检索 agent vs 写库 agent)。
  • 不同子任务需要不同的 system prompt / 模型 / 工具集。
  • 需要清晰的责任切分、独立的 trace / 评估 / 失败重试单元。
  • 长期任务里需要把"已完成的子任务"作为可命名的能力复用(subgraph)。

LangGraph 提供的多智能体能力核心是 subgraph 组合 + Command(handoff) :subgraph 既可以"被当成节点"嵌入,也可以通过 Command(goto=..., graph=Command.PARENT) 主动跳回父图------这是从单智能体到多智能体的最小可用拼图。

【官方】"A subgraph is a graph that is used as a node in another graph." --- docs.langchain.com/oss/python/langgraph/use-subgraphs(资料日期 2026-06 抓取;对应 LangGraph ≥1.0)。

7.2 推荐做法

  1. 从"supervisor + 工具型 subagent"入手,而不是一上来就搭 swarm。 supervisor 模式(一个路由节点调度多个 worker)是官方示例最常见的范式,控制流清晰,失败可定位(langgraph-supervisor 0.0.31;更新日期 2025-11-19,【官方】)。
  2. 状态共享走"显式 schema 映射",而不是"全部塞进一个大状态"。 子图和父图 schema 不一致时,封装一个"适配节点"显式做 in/out 映射;schema 一致时直接 add_node("sub", sub_compiled)Subgraph 模式,【官方】)。
  3. handoff 用 Command(goto=..., graph=Command.PARENT),避免静态边 + Command 同源混用。 Command 是官方推荐的"同时改状态 + 跳转"原语(Command & handoff,"Command" 章节,【官方】)。
  4. subgraph 持久化按"作用域"选三种模式之一 (【官方】来源:docs.langchain.com/oss/python/langgraph/use-subgraphs "Persistence modes" 章节,2026-06 抓取),并约束 checkpoint 行为:
    • compile(checkpointer=None):每次调用全新状态。父图的 checkpointer 不会跨调用恢复子图历史。推荐作为默认值,子图视为"短任务工具"。
    • compile(checkpointer=True):子图状态跨同一 thread 持久化。并发风险限定:当父图对同一子图实例的"同一 thread_id / 同一 checkpoint namespace"发起并发调用时会出现读写竞争;如必须并发,应走不同 thread_id 或不同子图实例。
    • compile(checkpointer=False):纯函数行为,无 checkpoint、无 interrupt/resume 能力、无状态恢复。仅用于无状态 helper 子图。
  5. 复用多智能体:先考虑 prebuilt,再考虑自研。 langgraph-supervisor 0.0.31(2025-11-19)和 langgraph-swarm 0.1.0(2025-12-04)是官方/官方维护方发布的开箱即用包,覆盖 80% 的 supervisor / handoff 场景(【官方】)。
  6. 跨图 / 跨框架协作:A2A 是候选协议层标准,不是已落地的 LangGraph 集成。 A2A v1.0.1(2026-05-28)是 Linux Foundation 下的开放标准,JSON-RPC 2.0 over HTTPS;可 pip install a2a-sdk【待验证】 LangGraph Python 侧官方桥接 / adapter 在本次资料范围内未找到,生产前需自行验证 SDK、认证、streaming、工具调用、trace 串联能力。

7.3 不推荐做法

  1. 所有子图共用一个"巨型 shared state"。 失去边界 → 任何子任务的 schema 变更都要全量回归。
  2. multi-agent 当成"性能优化手段"使用。 多一层 LLM 调度就会多一次延迟和 token;如果一个 ReAct agent 能搞定,就不要拆。
  3. 动态 handoff 路由里塞副作用(写库、发邮件)。 handoff 本身可能因为 retry / interrupt-resume 重跑。
  4. per-thread subgraph 跨调用并发。 会出现 race condition / 状态错乱(限定条件:共享同一 thread_id / checkpoint namespace 时)。可规避:使用不同 thread_id,或每次调用 compile(checkpointer=True) 得到新实例。
  5. swarm 用于"严格分阶段、可审计"的业务流程。 Swarm 去中心化控制流难以回放和审计,金融/医疗等强监管场景慎用。
  6. Command(goto) 的同时再 add_edge(from, to) 双重入口会让某些路径被绕过,导致跳过的节点状态缺失。

7.4 最小代码示例

依赖:langgraph>=1.0langgraph-supervisor==0.0.31langgraph-swarm==0.1.0、Python ≥3.10。资料日期 2026-06-11。

7.4.1 Supervisor(prebuilt)

python 复制代码
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
from langgraph_supervisor import create_supervisor

model = ChatOpenAI(model="gpt-4o")

def add(a: float, b: float) -> float:
    """返回两个数的和。"""
    return a + b

def search(query: str) -> str:
    """对查询做关键词检索并返回文本。"""
    return f"Search result for: {query}"

math_agent = create_react_agent(
    model=model,
    tools=[add],
    name="math_expert",
    prompt="Use math tools for calculations.",
)
research_agent = create_react_agent(
    model=model,
    tools=[search],
    name="research_expert",
    prompt="Use search for research questions.",
)

workflow = create_supervisor(
    [research_agent, math_agent],
    model=model,
    prompt="Route research to research_expert and calculations to math_expert.",
    output_mode="last_message",          # "last_message" | "full_history"
)

app = workflow.compile(checkpointer=InMemorySaver())
# 资料来源:PyPI langgraph-supervisor 0.0.31(2025-11-19)

7.4.2 自研 Supervisor(Command + handoff)

python 复制代码
from typing import Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Command

class TeamState(TypedDict):
    request: str
    active_agent: str
    result: str

def supervisor(state: TeamState) -> Command[Literal["research_agent", "writing_agent"]]:
    if "research" in state["request"]:
        return Command(update={"active_agent": "research_agent"}, goto="research_agent")
    return Command(update={"active_agent": "writing_agent"}, goto="writing_agent")

def research_agent(state: TeamState): return {"result": "research result"}
def writing_agent(state: TeamState):  return {"result": "writing result"}

builder = StateGraph(TeamState)
builder.add_node("supervisor", supervisor)
builder.add_node("research_agent", research_agent)
builder.add_node("writing_agent", writing_agent)
builder.add_edge(START, "supervisor")
# 不要同时 add_edge("supervisor", ...) 与 Command(goto=...)
builder.add_edge("research_agent", END)
builder.add_edge("writing_agent", END)
graph = builder.compile()
# 资料来源:docs.langchain.com/oss/python/langgraph/graph-api("Command" 章节,2026-06 抓取)

7.4.3 Subgraph:父图节点内 invoke(schema 不同)

python 复制代码
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class ChildState(TypedDict):
    child_text: str

class ParentState(TypedDict):
    parent_text: str

def child_step(state: ChildState) -> dict:
    return {"child_text": "child saw: " + state["child_text"]}

child_builder = StateGraph(ChildState)
child_builder.add_node("child_step", child_step)
child_builder.add_edge(START, "child_step")
child_builder.add_edge("child_step", END)
child_graph = child_builder.compile()

def parent_calls_child(state: ParentState) -> dict:
    result = child_graph.invoke({"child_text": state["parent_text"]})
    return {"parent_text": result["child_text"]}

parent_builder = StateGraph(ParentState)
parent_builder.add_node("call_child", parent_calls_child)
parent_builder.add_edge(START, "call_child")
parent_builder.add_edge("call_child", END)
# 资料来源:[docs.langchain.com/oss/python/langgraph/use-subgraphs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs) "Call a subgraph inside a parent node" 段落(【官方】,2026-06 抓取)

7.4.4 Swarm(prebuilt)骨架

python 复制代码
from langgraph_swarm import create_swarm, create_handoff_tool
# 1) 用 create_agent(name=..., system_prompt=..., tools=...) 建好各 agent
# 2) 给每个 agent 加 create_handoff_tool(agent_name=<peer>)
# 3) create_swarm([alice, bob], default_active_agent="Alice")
# 4) workflow.compile(checkpointer=InMemorySaver())  # 多轮需要 checkpointer
# 资料来源:PyPI langgraph-swarm 0.1.0(2025-12-04,【官方】)

7.4.5 Agent-as-tool / Graph-as-tool 最小代码示例

依赖:langgraph>=1.0、Python ≥3.10。资料日期 2026-06-11。【官方】

python 复制代码
# === Agent-as-tool: 把编译好的子图当 tool 暴露给父 agent ===
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent

def lookup_order(order_id: str) -> str:
    """查询订单状态。"""
    return f"order {order_id}: shipped"

# 子 agent 自己是一个可被复用的"能力"
order_agent = create_react_agent(
    model="openai:gpt-4o-mini",
    tools=[lookup_order],
    name="order_agent",
    prompt="查询订单。",
).compile()

# 父 agent 通过 Tool 把"调用子图"封装成单步工具
@tool
def ask_order_agent(question: str) -> str:
    """把订单相关问题转交给订单子 agent。"""
    out = order_agent.invoke({"messages": [{"role": "user", "content": question}]})
    return out["messages"][-1].content

parent = create_react_agent(
    model="openai:gpt-4o",
    tools=[ask_order_agent],
    prompt="你是调度者,订单问题转给 order_agent 工具。",
).compile()
# 资料来源:LangGraph multi-agent 概念页 docs.langchain.com/oss/python/langgraph/multi-agent(【官方】)
python 复制代码
# === Graph-as-tool: 父图把子图作为节点直接嵌入,控制流和状态共享 ===
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class Shared(TypedDict):
    question: str
    order_answer: str

def router(state: Shared):
    return {"order_answer": f"routed: {state['question']}"}

# 把已编译的子图作为节点加入父图
parent_builder = StateGraph(Shared)
parent_builder.add_node("order_subgraph", order_agent)   # 共享 messages / 共享 schema
parent_builder.add_node("router", router)
parent_builder.add_edge(START, "order_subgraph")
parent_builder.add_edge("order_subgraph", "router")
parent_builder.add_edge("router", END)
parent = parent_builder.compile()
# 资料来源:docs.langchain.com/oss/python/langgraph/use-subgraphs("Add a compiled subgraph directly as a node",【官方】)

7.5 Agent-as-tool vs Graph-as-tool 选型对比

【官方】对比的语义层来自 LangGraph subgraph / multi-agent 概念页;【社区】建议来自 prebuilt 仓库 README 与社区博客。资料日期 2026-06-11。

维度 Agent-as-tool Graph-as-tool(subgraph-as-node)
调用方式 父 agent 通过 tool 调用,子图作为"黑盒能力" 子图作为父图的节点参与边/条件路由
状态边界 显式:tool 输入输出是字符串/字典,状态完全封装 隐式:父/子图可共享 state key,或通过 wrapper 节点适配
Trace 粒度 一次 tool call 一次 span,嵌套浅 父图与子图在同一 super-step,可看到节点级联
复用成本 低------任何能传 tool 的 agent(ReAct、Custom)都能调 中------需要把子图编译并按 schema 嵌入
测试成本 把"调子图"当成一个副作用,mock 掉 @tool 函数即可 需要独立编译子图后单独测 + 父图集成测
适用场景 子能力相对独立、调用频次低、希望解耦 子能力需要访问父图 state、需要在主流程里流转
不适用 需要子任务结果参与后续路由 / 状态更新 子任务纯函数性质、状态无需共享

【社区】经验补充:高频小模型子任务(关键词提取、路由、归一化)走 Agent-as-tool 减少主 agent 上下文噪声;需要长上下文累积 / 状态演进的多步子任务走 Graph-as-tool。

7.6 经典模式:Plan-and-Execute

模式定义 (【社区】/【推测】基于 LangChain 官方博客与 langgraph 归档 notebook):Planner 把用户目标拆成有序步骤,Executor 逐步执行、Replanner 根据上一步结果修正后续计划。典型循环是 plan → execute → replan → ... → final,可显著降低单次 LLM 推理的复杂度。

LangGraph Python 实现骨架 (基于 LangChain 原始 plan-and-execute 设计与 langgraph.func 范式,资料来源:langchain-ai/langgraph 归档 examples/plan-and-execute/plan-and-execute.ipynb新 docs 教程区未抓取到对应可运行页面 ,原始路径仅作历史参考,【待验证】 ;可参考 LangChain 原始 plan-and-execute 论文 / langchain-ai/langgraph 旧 README 了解模式骨架):

python 复制代码
from typing_extensions import TypedDict, List
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import create_react_agent

class PlanState(TypedDict):
    objective: str
    plan: List[str]                  # 计划步骤
    past_steps: List[tuple]         # (step, result)
    response: str

def plan_step(state: PlanState) -> PlanState:
    # 调 LLM 把 objective 拆成步骤;空 plan 表示直接给出 final
    ...

def execute_step(state: PlanState) -> PlanState:
    # 拿 plan[0] 调一个 ReAct agent 执行
    ...

def replan_step(state: PlanState) -> PlanState:
    # 调 LLM 决定:是否结束(return END) / 给出新 plan
    ...

builder = StateGraph(PlanState)
builder.add_node("planner", plan_step)
builder.add_node("executor", execute_step)
builder.add_node("replanner", replan_step)
builder.add_edge(START, "planner")
builder.add_edge("planner", "executor")
builder.add_edge("executor", "replanner")
# 条件边:replanner 返回 END 时收尾
builder.add_conditional_edges("replanner", lambda s: "end" if s.get("response") else "executor",
                              {"end": END, "executor": "executor"})
graph = builder.compile()

适用 :多步、需要先规划再执行的任务(研究、代码生成、数据分析)。

不适用:单步可完成、每步强依赖前一步输出的流式对话。

7.7 经典模式:Reflection / Reflexion

Reflection(自我反思) (【社区】Shinn et al., 2023;LangGraph 归档 notebook 名为 reflection.ipynb新 docs 教程区未抓取到对应页面【待验证】):Generator 产出回答,Critique 节点给出反馈,循环重写直到 Critique 通过或达到最大轮次。

python 复制代码
# 概念骨架(基于官方 Graph API 习语)
class ReflectionState(TypedDict):
    draft: str
    critique: str
    revisions: int

def generate(s): return {"draft": llm_generate(s["topic"])}
def critique(s):  return {"critique": llm_critique(s["draft"])}

def should_continue(s) -> str:
    return "end" if (s["critique"].startswith("PASS") or s["revisions"] >= 3) else "revise"

builder = StateGraph(ReflectionState)
builder.add_node("generate", generate)
builder.add_node("critique", critique)
builder.add_node("revise",   lambda s: {"draft": llm_revise(s["draft"], s["critique"]),
                                       "revisions": s["revisions"] + 1})
builder.add_edge(START, "generate")
builder.add_edge("generate", "critique")
builder.add_conditional_edges("critique", should_continue,
                              {"end": END, "revise": "revise"})
builder.add_edge("revise", "critique")

Reflexion 在 Reflection 基础上加入"长期记忆":critique 不仅修改当前 draft,还把"经验"写回外部 BaseStore,下次同类任务时检索使用。LangGraph 的 store.put(namespace, key, value) + store.search(...) 是官方推荐实现方式(资料来源:docs.langchain.com/oss/python/langgraph/persistence,"Memory Store" 章节,【官方】)。

适用 :生成质量受 LLM 自检能力明显提升的任务(代码、SQL、长文写作)。

不适用:对延迟极敏感、或 critique LLM 与 generator LLM 能力相当(反思不带来增益)的场景。

7.8 检查清单

  • 是否在 supervisor 模式下使用了官方 langgraph-supervisor prebuilt,而非手撸 handoff?
  • 父图 / 子图的 schema 关系是否明确(共享 / 映射 / 私有)?
  • handoff 路由函数是否是纯函数(不读时钟、不写外部状态)?
  • 是否避免了 per-thread subgraph 的并发调用?
  • Command(goto=...) 与同源 add_edge 是否互斥?
  • 是否对 multi-agent 链路设计了 trajectory 评估("是否按预期顺序走对节点")?
  • 是否所有 subagent 都有独立 name,便于 trace / log 检索?
  • 跨进程 / 跨语言 / 跨框架协作时是否考虑 A2A 而非自研 RPC?------ 并已确认本项目实际需求
  • 是否在引入 multi-agent 前先量化了"延迟 / token / 调试成本"的额外开销?
  • Agent-as-tool vs Graph-as-tool:是否对每个子能力明确选了合适边界?独立能力用 tool,强耦合多步子任务用 subgraph-as-node
  • Plan-and-Execute:是否对多步任务显式拆"规划/执行/再规划"三段,且对 replan 终止条件有显式判断?
  • Reflection / Reflexion:critique 与 generate 是否复用同一 LLM?是否给反思轮次设上限防爆?是否把经验回写 store?

第 8 章:测试与质量保障

8.1 解决什么问题

LangGraph 应用的测试痛点集中在:

  • 节点级纯逻辑(reducer、路由、tool wrapper)需要快速、确定性的单元测试。
  • 整图行为需要集成 / E2E 测试,且要在 HITL、resume、multi-agent 等长链路场景里复现。
  • LLM 输出非确定性让"断言最终文本"几乎不可行,必须借助温度/seed/snapshot/mock/judge 体系。
  • 生产回归要回答"升级 LangGraph / 换模型后旧 case 是否还过",需要 replay 工具与 golden dataset。

官方 docs.langchain.com/oss/python/langgraph/test 明确给出三种测试模式:(1) 端到端图执行、(2) 单节点直接调用(graph.nodes[name].ainvoke(...),绕开 checkpointer)、(3) Partial execution(用 update_state(as_node=...) + interrupt_after 模拟中间状态)。

8.2 推荐做法

  1. Reducer、路由、纯函数节点------直接当普通 Python 测,不引入 LangGraph。pytestparametrize 覆盖空、异常、并发更新等边界(LangGraph 测试文档,【官方】)。reducer 与节点副作用必须幂等------推荐用 idempotency keyupsertread-before-write 三种模式;测试应覆盖"同一节点被 replay / interrupt-resume / 并发更新重复调用"三种重复路径。
  2. 节点级 LLM 调用------使用 graph.nodes[name].ainvoke(...) 直调节点 + mock 模型。 官方测试文档明确"bypasses any checkpointers, which is useful for unit testing individual node logic without running the full graph flow"(同源)。
  3. 整图集成测试------每个用例 fresh InMemorySaver / fresh 编译。 避免 checkpoint 串味(persistence 文档,【官方】)。
  4. HITL / resume 测试------三步法 :(a) 触发 interrupt → (b) Command(resume=...) → © 断言 state / messages(interrupts 文档 "Resuming" 段落,【官方】)。
  5. LLM 断言 走"LLM-as-Judge + 少量 few-shot"组合:openevals 提供 CORRECTNESS_PROMPT / CONCISENESS_PROMPT 等 prebuilt 评分器;create_llm_as_judge 是 Python SDK 主入口(openevals 集成LLM-as-Judge 指南,【官方】)。
  6. Replay / 回放 :用历史 thread 的 thread_id + checkpoint_id 在新版图上 invoke(None, config=...) 复跑。常见两种用法:
    • 重放到指定 checkpoint:以该 checkpoint 的 state 作为起点重放。
    • fork 后继续 :先 get_state({thread_id, checkpoint_id}) 拿快照 → update_state(new_config, values=...) 写入新分支 → invoke(None, new_config) 在新分支继续。
    • 对比方式:直接断言 result["messages"][-1].content == golden、或对结构化输出跑 judge 评分 / 跑 trajectory evaluator。
    • ⚠️ Replay 重新执行 checkpoint 之后的节点(不是只读 cache) ------会产生 LLM 计费与 tool 重放风险;final checkpoint 无 next 节点时 replay 是 no-op,可用于"跑通即止"的快速健康检查。
    • 缓解方式:mock LLM provider (如 FakeListChatModel、录制 fixture)、使用 idempotent tool限定重放节点为纯计算 。Replayer 必须评估 LLM 成本与外部副作用,否则在 CI 中可能造成不可控费用。
      (资料来源:docs.langchain.com/oss/python/langgraph/persistence "Time Travel" 章节,【官方】,2026-06-11 抓取)
  7. 多轮对话测试 :使用 run_multiturn_simulationcreate_llm_simulated_user 生成完整会话轨迹,再用 trajectory evaluator 评分(multi-turn-simulation,【官方】)。
  8. Mock 模型确定性 (【官方】与【社区】综合,资料来源:langchain_core.language_models.fake_chat_models):
    • 优先使用 FakeListChatModelfrom langchain_core.language_models.fake_chat_models import FakeListChatModel),给定循环响应列表。
    • seed 并非所有 provider 都支持(OpenAI、Anthropic、Google 部分模型支持,vLLM/Ollama 部分支持;如不确定应直接 mock 整个模型或用 VCR/录制 fixture)。
    • temperature=0 在多数 provider 上能让单次响应更接近确定,但对多轮、对 prompt 改动仍不稳定------需配合录制 fixture / golden response 兜底。
  9. Eval-as-code / Eval gate :把评估脚本纳入 CI;LangSmith 提供 client.aevaluate(...),可对单次 commit 设通过阈值。upload_results=False 可用于本地 smoke(本地评估pytest 集成,【官方】)。
  10. Property-based testing :reducer、状态合并函数用 Hypothesis 自动探索边界(参考资料:Hypothesis 官方文档)。

8.3 不推荐做法

  1. 断言 LLM 输出原文字符串。 一次温度抖动就崩。改用结构化 schema + LLM-as-Judge。
  2. 在测试中调用真实第三方 API(OpenAI / 数据库)。 慢、贵、不可重现;用 mock / VCR / 录制 fixture。
  3. 测试间共享 checkpointer 状态。 必须每个测试 fresh InMemorySaver()
  4. interrupt 周围用 try/except 时需谨慎。 interrupt 内部通过抛特殊异常暂停;通常应避免将 interrupt 包裹在 bare except 中,并核对当前 LangGraph 版本的恢复语义(interrupts 文档)。:该说法在不同 LangGraph 版本的实现细节上可能存在差异,deep-research 事实底座未将其列为绝对化建议;如需在 try/except 中使用,请用具体异常类型而非 bare except。
  5. 用静态 interrupt_before / interrupt_after 写 HITL 流程。 官方明确写"Static interrupts are not recommended for human-in-the-loop workflows. Use the interrupt function instead."
  6. 节点内有 pre-interrupt 的副作用。 resume 时节点从头跑,副作用会重复发生------官方"Rules of Interrupts" 第 4 条。
  7. 在多轮测试里硬编码"应回答什么"。 用 trajectory + outcome evaluator 替代 turn-level 断言。
  8. 凭单个 LLM 评分做生产门禁。 LLM-as-judge 自身有 bias / 不稳定------必须配 few-shot 校准 + Pairwise + 人审回流。
  9. 线上回归靠"线上肉眼观察"。 必须把线上 trace 回灌到离线 replay 流程。
  10. reducer / 路由依赖全局计数器 / 随机数 / 时间戳。 这三类隐式状态会让 reducer 在 replay / interrupt-resume / 并发更新时表现不一致,破坏幂等性。
  11. 把 Replay 当成"只读 cache"使用。 Replay 会重新执行 checkpoint 之后的节点(包含 LLM 调用与 tool 副作用),不是只读快照;详见 8.2.6 警示。

8.4 最小代码示例

依赖:langgraph>=1.0langsmith>=0.3.4pytest>=8openevalshypothesis。资料日期 2026-06-11。

8.4.1 Reducer 单元测试 + Property-based

python 复制代码
# 资料来源:[Hypothesis 官方文档](https://hypothesis.readthedocs.io/),2026-06-11 抓取(【官方】)
from hypothesis import given, strategies as st
from operator import add
from typing import Annotated
from typing_extensions import TypedDict

class State(TypedDict):
    items: Annotated[list[int], add]

@given(st.lists(st.integers(), min_size=0, max_size=50))
def test_add_reducer_preserves_order_and_total(items: list[int]) -> None:
    """验证 list-concat reducer(operator.add)保持顺序、保留全部元素、长度守恒。"""
    s: State = {"items": []}
    s["items"] = add(s["items"], items)   # 模拟节点返回部分更新
    assert len(s["items"]) == len(items)
    assert s["items"] == items            # 顺序保持(list concat 的关键性质)
    assert sum(s["items"]) == sum(items)  # 元素守恒

# 若要测"交换律"性质,应改用满足交换律的 reducer,例如 set union:
# from typing import Annotated
# class State2(TypedDict):
#     tags: Annotated[set[str], lambda a, b: a | b]
# @given(st.sets(st.text()), st.sets(st.text()))
# def test_union_reducer_is_commutative(x, y):
#     assert (x | y) == (y | x)

8.4.2 单节点直调(绕开 checkpointer)

python 复制代码
# 资料来源:[docs.langchain.com/oss/python/langgraph/test](https://docs.langchain.com/oss/python/langgraph/test) "Testing Individual Nodes"(【官方】,2026-06 抓取)
import pytest
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class S(TypedDict):
    my_key: str

def node1(s: S) -> dict: return {"my_key": s["my_key"] + "1"}
def node2(s: S) -> dict: return {"my_key": s["my_key"] + "2"}

g = StateGraph(S)
g.add_node("node1", node1)
g.add_node("node2", node2)
g.add_edge(START, "node1")
g.add_edge("node1", "node2")
g.add_edge("node2", END)
graph = g.compile()

@pytest.mark.asyncio
async def test_node2_in_isolation():
    out = await graph.nodes["node2"].ainvoke({"my_key": "x"})
    assert out["my_key"] == "x2"

8.4.3 HITL 三步法

python 复制代码
# 资料来源:[docs.langchain.com/oss/python/langgraph/interrupts](https://docs.langchain.com/oss/python/langgraph/interrupts) "Resuming" 段落(【官方】,2026-06 抓取)
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import InMemorySaver

class S(TypedDict):
    my_key: str

def human_review(state: S) -> dict:
    answer = interrupt("approve?")
    return {"my_key": answer}

g = StateGraph(S)
g.add_node("human_review", human_review)
g.add_edge(START, "human_review")
g.add_edge("human_review", END)

checkpointer = InMemorySaver()
graph = g.compile(checkpointer=checkpointer)
cfg = {"configurable": {"thread_id": "t1"}}

# step 1: triggers interrupt
result = graph.invoke({"my_key": "x"}, cfg)
assert "__interrupt__" in result  # v1 风格;v2 见 streaming 文档
# step 2: resume
result2 = graph.invoke(Command(resume="approved"), cfg)
assert result2["my_key"] == "approved"

8.4.4 LLM-as-Judge(openevals)

python 复制代码
from openevals.llm import create_llm_as_judge
# pip install openevals
judge = create_llm_as_judge(
    prompt="Is the answer factually correct? Question: {inputs}\nAnswer: {outputs}",
    model="openai:o3-mini",
    feedback_key="correctness",
)
# 调用 judge(带 reference_outputs 与 few-shot examples)
result = judge(
    inputs={"question": "法国的首都是哪里?"},
    outputs={"answer": "巴黎。"},
    reference_outputs={"answer": "巴黎(Paris)。"},
    # few-shot 校准示例(每个 example 是 dict 列表)
    few_shot_examples=[
        {"inputs": {"question": "X"}, "outputs": {"answer": "Y"},
         "score": 1.0, "comment": "答得对"},
    ],
)
print(result)  # {'score': ..., 'comment': ...}
# 资料来源:[docs.langchain.com/langsmith/openevals](https://docs.langchain.com/langsmith/openevals) "LLM-as-judge SDK" 章节
# 稳定性治理见 8.5「LLM-as-Judge 稳定性治理」段

8.4.5 Pytest + LangSmith 集成

python 复制代码
# pip install "langsmith[pytest]"
import pytest
from langsmith import testing as t

@pytest.mark.langsmith(output_keys=["d"])
def test_cd(c: int, d: int):
    result = 2 * c
    t.log_outputs({"d": result})
    assert result == d
# LANGSMITH_TEST_SUITE="X" pytest tests/  → 自动建数据集 + 实验
# 资料来源:docs.langchain.com/langsmith/pytest("Writing Tests with Fixtures")

8.4.6 Replay(用历史 checkpoint 复跑 / fork 后继续)

python 复制代码
# 资料来源:[docs.langchain.com/oss/python/langgraph/persistence](https://docs.langchain.com/oss/python/langgraph/persistence) "Time Travel" 章节(【官方】,2026-06 抓取)
thread_id = "old-t1"

# 1) 列出历史 checkpoint
history = list(graph.get_state_history({"configurable": {"thread_id": thread_id}}))
target = next(h for h in history if h.next == ("node_after_target",))  # 例:找下一步为某节点的快照
checkpoint_id = target.config["configurable"]["checkpoint_id"]

# 2) 复放到指定 checkpoint(state 作为起点重放)
replay_config = {"configurable": {"thread_id": thread_id, "checkpoint_id": checkpoint_id}}
graph.invoke(None, config=replay_config)

# 3) Fork 后继续:在新分支上修改 state 再 invoke
fork_config = {"configurable": {"thread_id": f"{thread_id}-fork-1"}}
graph.update_state(
    fork_config,
    values={"messages": [...]},                 # 注入修改
    as_node="node_before_target",                # 标记注入位置
)
result = graph.invoke(None, config=fork_config)
assert result["messages"][-1].content == golden_or_evaluator(result)

注:复跑新版本图、同一 thread_id 时,LangGraph 会沿用旧 checkpoint 兼容未变更部分;如已删除 / 重命名节点,参见 docs.langchain.com/oss/python/langgraph/graph-api "Graph migrations" 章节。trace 字段(thread_id / run_id / checkpoint_id)在不同 LangGraph 版本间的兼容性,参见 9.X.1 关联模型说明。

8.4.7 Multi-turn 模拟

python 复制代码
from langsmith.evaluation import run_multiturn_simulation, create_llm_simulated_user
# user = create_llm_simulated_user(system_prompt=..., model=...)
# result = run_multiturn_simulation(app, user, max_turns=5, thread_id="t1")
# 资料来源:docs.langchain.com/langsmith/multi-turn-simulation

8.5 评估体系分层(离线 / 在线 / CI gate)

【官方】在线评估章节明确写到"Multi-turn evaluators run once per completed thread, not once per trace",并区分 run-level 与 thread-level evaluator(docs.langchain.com/langsmith/online-evaluations-llm-as-judgeonline-evaluations-multi-turn)。【官方】本地评估章节给出 upload_results=False 模式(docs.langchain.com/langsmith/local)。资料日期 2026-06 抓取。

维度 离线评估(Offline) 在线评估(Online) CI 评估(Eval-as-code gate)
数据来源 Golden dataset / 历史 thread 回灌 / 合成数据 线上真实 trace Golden dataset(小子集) + 录制 fixture
触发时机 主动跑:模型升级、prompt 改动、LangGraph 升级后 自动:trace 写入后按采样率触发 每次 PR / 每次 merge
指标 Final response、Trajectory、Single-step 同上 + 业务 KPI(first contact resolution 等) 关键不变量:trajectory 顺序、tool 选择、cost 上限
工具链 LangSmith client.aevaluate、openevals、pytest plugin LangSmith Online Evaluators(run-level / thread-level) pytest + @pytest.mark.langsmithupload_results=False smoke
失败处理 人工 review、重写 golden、调整 prompt 自动告警 / 留样 / 路由回退 阻断 merge、生成失败报告
风险 离线过拟合到固定数据集 采样偏差、judge 自身不稳 CI 跑全套 LLM 调用既慢又贵
适用 重大变更前 / 周期回归 7×24 持续监控 每次提交

关键不变量(Invariants) :每个层级都应断言至少一条"不依赖 LLM 评分"的事实------例如"必须调用工具 X"、"trajectory 中不能出现节点 Y"、"token 用量 < N"。这是 Eval-as-code gate 与 LLM-judge 体系混用时的安全网。

LLM-as-Judge 的稳定性治理(【官方】与【社区】综合):

  • 至少 5--10 条 few-shot 校准样本;few-shot 可来自 LangSmith 的人审反馈回路(自动追加 few-shot)。
  • Pairwise 比较("A 答案比 B 答案更好吗")比 absolute score 稳定。
  • 同一 feedback_key 不复用于 run-level 和 thread-level(官方明确不推荐)。
  • 对关键 judge,应设"双 judge 投票"或"judge + 启发式规则"双轨。

8.6 检查清单

  • Reducer / 路由 / 纯函数节点是否都有"非 LangGraph 上下文"的纯 Python 单元测试?
  • LLM 调用节点是否用 graph.nodes[name].ainvoke(...) 隔离 + mock 模型?
  • 整图测试是否每个用例 fresh InMemorySaver
  • HITL / resume 流程是否覆盖 trigger → Command(resume=...) → 终态 三步?
  • 是否使用结构化 schema + LLM-as-Judge 替代"断言字符串相等"?
  • judge prompt 是否经过 few-shot 校准、是否有 Pairwise 兜底?
  • 是否记录了 golden dataset + replay 流程,能在升级 LangGraph / 换模型时一键回归?
  • interrupt 是否被裸 except 包裹?pre-interrupt 副作用是否幂等?
  • 多智能体 / multi-turn 是否用 trajectory evaluator 而非 turn-level 断言?
  • Eval 脚本是否纳入 CI,并设了 fail-fast 阈值("Eval gate")?
  • 是否对每个评估层(离线 / 在线 / CI)定义了数据来源、触发时机、指标、失败处理?
  • 是否至少有一条"非 LLM"的不变量断言(trajectory 顺序 / 工具白名单 / 成本上限)?

第 9 章:可观测性

9.1 解决什么问题

LangGraph 应用在生产里必须回答四类问题:

  1. 哪个 run 慢 / 贵 / 失败? ------ 需要 trace 级别的延迟、token、错误聚合。
  2. 某个用户问题为什么返回错? ------ 需要把 thread → run → checkpoint → node → tool 一路串起来。
  3. 业务侧成功率是多少? ------ 需要把 trace 反馈成业务指标(首次解决率、人工介入率等)。
  4. 数据怎么脱敏、成本怎么控? ------ 需要 PII 匿名化、采样、OTLP 转发。

OSS 能力 vs 平台服务边界 (deep-research 事实底座列为"证据不足",本节给出语言降级,最终读者需结合自身部署与合规要求验证):

  • OSS 自带能力 (随 langgraph 包提供,无附加费用):StateGraph / Command 路由、persistence(InMemorySaver / SQLite / Postgres)、subgraph trace namespace、streaming(values / updates / messages 等 stream mode)、graph.get_state / update_state API。
  • 平台服务能力(需 LangSmith SaaS 或自托管 LangSmith):在线 LLM-as-Judge evaluator、在线 multi-turn 评估、trace 聚合看板、dataset 与 experiment 一体化管理、retention 策略、access control、按租户的 cost attribution。
  • 混合使用时的边界:上送平台即视为出域;如需 PII / 合规隔离,应自建或选 OSS 后端(Phoenix / Langfuse / Opik),详见 9.X.2 选型表。

官方 docs.langchain.com/oss/python/langgraph/observability 把 LangGraph 的可观测性默认建在 LangSmith 上,并提供 OpenInference(Phoenix)、OTel OTLP 与 Langfuse / Opik 等社区方案的可选路径。

9.2 推荐做法

  1. 统一开关走环境变量LANGSMITH_TRACING=true + LANGSMITH_API_KEY 是 LangSmith 路径;自托管/开源替换时切到 OTel 端点(LangGraph observability,【官方】)。

  2. 采样与零保留分级 :用 LANGSMITH_TRACING_SAMPLING_RATE=0.1 做全局降采样;用 tracing_context(project_name=...) 给敏感租户切到独立项目 + 单独保留策略(sample-traces,【官方】)。边界提示 :按租户切 project 是"按租户观测"的起点;retention / access control / 成本归属属平台侧配置,deep-research 事实底座将"LangGraph Platform / 自托管多租户架构"列为证据不足 ,最终读者需结合自身部署与合规要求验证,不应将 tracing_context(project_name=...) 误读为"完整多租户隔离方案"。调用形式:

    python 复制代码
    from langsmith import tracing_context  # 完整 import 见 9.4.1
    with tracing_context(project_name="tenant-acme", tags=["acme"]):
        graph.invoke(inputs, config={"configurable": {"thread_id": "t-1"}})
  3. 每个 invoke 带 thread_id + tags + metadata :thread_id 串联多轮;tags / metadata 进入 trace 过滤维度(LangSmith ObservabilityLangGraph observability metadata 段,【官方】)。

  4. 节点级日志走 LangSmith 的 tracing_context,不要把 LLM 输入 / 输出打到 stdout------既不脱敏也不可聚合。

  5. 隐私脱敏用 create_anonymizer + 正则 (如 SSN 模式 \b\d{3}-?\d{2}-?\d{4}\b<ssn>),attach 到 Client 后通过 LangChainTracer 应用到 graph(LangGraph observability "Anonymizing Sensitive Data",【官方】)。覆盖范围与限制见 9.X.4 节。

  6. 第三方开源 / 自托管 OTel 路径

    • Phoenix (Arize) :装 openinference-instrumentation-langchain,调 phoenix.otel.register(project_name=..., auto_instrument=True),无需改 LangGraph 代码(【社区/官方】)。
    • Langfusepip install langfuse + from langfuse.langchain import CallbackHandler(v3 SDK 路径)→ config={"callbacks": [handler]};短任务需 get_client().shutdown() flush(Langfuse 集成,【社区】)。
    • Opik (Comet)OpikTracer + track_langgraph(app),可在节点内通过 @track 装饰器手动埋点(Opik LangGraph 集成,【社区】)。
  7. OTLP 导出到 LangSmith(官方支持,详见 9.X.3 A 类);纯 OTel Collector 路径需自行接 instrumentation + tracer provider,官方未直接给出最小示例。 端点默认 https://api.smith.langchain.com/otel/,自托管 LangSmith 追加 /api/v1/otel;Headers 需带 x-api-key=...Langsmith-Project=...。导出到任意第三方 OTel 后端(Jaeger / Tempo / Datadog 等)属 9.X.3 B 类,依赖 community instrumentation(evaluate-with-opentelemetry,【官方】;纯 OTel Collector 路径属【待验证】)。

  8. subgraph trace 命名空间 :父图用 subgraphs=True 拿嵌套事件,namespace 元组 ("parent_node:<task_id>", "child_node:<task_id>") 区分层级。资料来源:docs.langchain.com/oss/python/langgraph/streaming(LangGraph ≥1.1 的 v2 输出格式)。

  9. 业务指标落到 trace 反馈 :成功率、checkpoint 命中率、retry 率、人工介入率通过 client.create_feedback(...) 写入 LangSmith dataset 反馈,可与 trace id 关联做漏斗分析。

  10. Eval-as-code 同步进入 trace :评估脚本用 client.aevaluate(...),可指定 dataset 关联到 trace;设阈值后即可在 CI 阻断。

9.3 不推荐做法

  1. 在生产里关掉 trace 节省 token。 故障定位成本远高于 trace 成本。
  2. 不设采样,把每条 trace 全量上报。 单次大流量场景下成本不可控。
  3. 把 LLM 输入 / 原始 PII 直接写应用日志。 不可聚合、不可脱敏、合规风险。
  4. 多智能体场景不开 subgraphs=True 嵌套 trace 没法看。
  5. 业务指标不与 trace 关联。 失败归因只能靠"目测"。
  6. 同 feedback_key 复用于 run-level 和 thread-level online evaluator。 官方明确不推荐;评分会互相覆盖。
  7. 多轮 thread 评估开过短 idle time。 人还没回完,evaluator 就已触发。资料来源:docs.langchain.com/langsmith/online-evaluations-multi-turn
  8. observability 当成"装上 SDK 就行"。 必须把 trace、metric、log 三个维度对齐业务事件。

9.4 最小代码示例

依赖:langgraph>=1.0langsmith>=0.3openinference-instrumentation-langchainlangfuse>=3opik、Python ≥3.10。资料日期 2026-06-11。

9.4.1 LangSmith(官方托管)

python 复制代码
import os
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "lsv2_..."
os.environ["LANGSMITH_PROJECT"] = "prod-agent"

# 多租户采样:按租户路由到不同 project
from langsmith import tracing_context
with tracing_context(project_name="tenant-acme", tags=["acme"]):
    graph.invoke(inputs, config={"configurable": {"thread_id": "t-1"}})
# 资料来源:docs.langchain.com/oss/python/langgraph/observability

9.4.2 Phoenix (Arize) --- OpenInference 自动埋点

python 复制代码
# pip install openinference-instrumentation-langchain
from phoenix.otel import register
tracer_provider = register(project_name="my-llm-app", auto_instrument=True)
# 之后所有 LangGraph / LangChain 调用自动 span 上报
# 资料来源:arize.com/docs/phoenix/integrations/python/langgraph/langgraph-tracing(2026-06 抓取)

9.4.3 Langfuse --- v3 SDK

python 复制代码
from langfuse import get_client
from langfuse.langchain import CallbackHandler
langfuse = get_client()  # 自动读 LANGFUSE_SECRET_KEY/PUBLIC_KEY/BASE_URL
handler = CallbackHandler()
graph.invoke(inputs, config={"callbacks": [handler], "configurable": {"thread_id": "t-1"}})
# 短任务退出前:get_client().shutdown()
# 资料来源:langfuse.com/docs/integrations/langchain("Migration v2 → v3" 章节)

9.4.4 Opik (Comet)

python 复制代码
from opik.integrations.langchain import OpikTracer
from opik import track_langgraph
tracer = OpikTracer()
graph_tracked = track_langgraph(graph, project_name="prod-agent")
graph_tracked.invoke(inputs)  # 不再需要每次传 callbacks
# 节点内可用 @track;async 需 extract_current_langgraph_span_data
# 资料来源:comet.com/docs/opik/integrations/langgraph

9.4.5 OTLP 直接导出到任意 OTel 后端

python 复制代码
import os
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "https://otel-collector.internal:4317"
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = "x-api-key=lsv2_...,Langsmith-Project=exp-123"
# LangGraph / LangChain 自动用 OTel 导出器
# 资料来源:docs.langchain.com/langsmith/evaluate-with-opentelemetry

9.4.6 业务反馈写入

python 复制代码
from langsmith import Client
client = Client()
client.create_feedback(
    run_id=trace_id,
    key="first_contact_resolution",
    score=1,
    comment="user resolved in 1 turn",
)
# 资料来源:docs.langchain.com/langsmith/online-evaluations-llm-as-judge

9.5 检查清单

  • 是否有统一的 trace 开关(env var 或 tracing_context),且支持按租户 / 标签路由 project?
  • 是否设了全局采样(LANGSMITH_TRACING_SAMPLING_RATE)或条件采样?
  • 是否对 PII 走 create_anonymizer 脱敏,且 thread_id 已脱敏?
  • multi-agent / subgraph trace 是否开启 subgraphs=True
  • 是否至少有 1 个自托管 / OTel 路径可回退(避免单一供应商锁定)?
  • 业务指标(成功率、人工介入率、retry 率、checkpoint 命中率)是否通过 create_feedback 进入 trace?
  • 是否有 trace id / run id / thread id / checkpoint id 的串联约定,可一键定位用户问题?
  • 多轮 thread evaluator 的 idle_time 是否设得大于典型用户回复时长?
  • 是否区分了 LangSmith 商业能力 vs 开源/OTel 能力,并明确哪些依赖必须付费?
  • 短任务 / serverless 环境是否在退出前调用了 flush(get_client().shutdown() 等)?
  • 是否对所有第三方后端(Phoenix / Langfuse / Opik / Helicone)做过 PII 脱敏一致性评估?
  • 节点日志是否只记"标识 + 结构化状态 diff + 异常类型",不记完整 messages / 工具原始返回?

9.X 附加小节(trace 关联模型 / 后端选型 / OTLP 分类 / 脱敏范围)

以下为可观测性补充说明,统一保留【官方】/【社区】/【推测】标记。

9.X.1 trace 关联模型:thread / run / checkpoint / node / tool

【官方】来源:docs.langchain.com/oss/python/langgraph/persistence "Checkpoint" 章节与 docs.langchain.com/oss/python/langgraph/observability;【官方】stream v2 格式:docs.langchain.com/oss/python/langgraph/streaming。资料日期 2026-06 抓取。

关联模型

复制代码
tenant / project
└── thread_id           ← 用户/会话维度;同一会话多轮共享
    └── run_id          ← 一次 invoke/stream 调用维度(LangSmith run)
        └── checkpoint_id  ← 每个 super-step 一个 snapshot
            ├── node_name
            ├── tool_call_id
            └── input_state / output_state / writes

字段定义

字段 来源 维度 作用
thread_id 用户传入 config.configurable.thread_id 会话 串联多轮;支持 time travel 与 fork
run_id LangSmith 自动分配 一次调用 与 LangSmith trace 一一对应
checkpoint_id checkpointer 自动 每个 super-step 时间旅行、fork、回放
langgraph_step node 内 config["metadata"]["langgraph_step"] super-step 序号 排查循环/recursion
langgraph_node 自动 节点 节点级 trace 与日志
checkpoint_ns 自动 子图 namespace 多层 subgraph 区分

节点变更与 replay 兼容性 :见 [8.4.6 注](#8.4.6 注)。节点被重命名或删除会破坏同一 thread_id 的 replay 兼容性,trace 字段本身(thread_id / run_id / checkpoint_id / checkpoint_ns)在 LangGraph 主版本间保持稳定。

节点日志应记录什么 (【官方】+【社区】综合,避坑参考:docs.langchain.com/oss/python/langgraph/interrupts "Rules of Interrupts" 强调"side effects before interrupt must be idempotent"):

类别 记录 不记录
标识 thread_idrun_idcheckpoint_idnode_namestep ---
上下文 messages[-1].type、工具白名单命中、模型 id 完整 messages 原文(成本 + 脱敏风险)
状态 节点前后 state diff(key 级别,structured) 整个 state 序列化
结果 token 用量、延迟、tool call 名称 工具原始返回值(大对象)
异常 异常类型 + 消息 + stack id 完整 traceback(含可能的 PII)

使用 v2 streaming 拿状态 diff / 事件流docs.langchain.com/oss/python/langgraph/streaming "v2 Unified Output Format",LangGraph ≥1.1):

python 复制代码
for part in graph.stream(inputs, stream_mode=["updates", "checkpoints"], version="v2"):
    if part["type"] == "updates":
        for node_name, state_update in part["data"].items():
            log_diff(thread_id, node_name, state_update)
    elif part["type"] == "checkpoints":
        cp = part["data"]
        log_checkpoint(thread_id, cp["config"]["configurable"]["checkpoint_id"])

在 LangSmith / OTel span metadata 中落这些字段

python 复制代码
config = {
    "configurable": {"thread_id": "t-1"},
    "tags": ["prod", "tenant:acme", "agent:order_agent"],
    "metadata": {
        "tenant_id": "acme",
        "feature_flag": "new_router",
        "expected_trajectory": ["router", "order_agent", "responder"],
    },
}
graph.invoke(inputs, config=config)
# 这些 tag/metadata 全部会进入 LangSmith trace / OTel span attribute
# 资料来源:docs.langchain.com/oss/python/langgraph/observability "Metadata and Tags" 段

9.X.2 第三方可观测性后端选型表

选型参考:【官方】LangSmith 与各后端集成页(资料日期 2026-06 抓取);【社区】运营经验。

部署形态:☁️ 商业 SaaS / 🛠️ 自托管 OSS / 两者皆可。

后端 定位 部署 LangGraph Python 接入方式 优点 缺点 成熟度
LangSmith LangChain 系官方一体化 trace + eval + dataset ☁️ LANGSMITH_TRACING=true + LANGSMITH_API_KEYfrom langsmith import Client 官方维护;与 LangGraph 兼容最完整;在线 / 离线 eval 一体 商业 SaaS 收费;数据出域 高(官方)
Phoenix(Arize) OSS 可观测性 + 评估 🛠️ pip install openinference-instrumentation-langchain + phoenix.otel.register(project_name=..., auto_instrument=True) 开源;OpenInference 标准;本地可跑 与 LangGraph 集成需经过 OpenInference 间接层 高(社区/官方)
Langfuse OSS LLM 可观测性 + eval ☁️🛠️ from langfuse.langchain import CallbackHandler + config={"callbacks": [...]} 易部署;JS/TS 与 Python 对等;支持 session / user 维度 短任务需 get_client().shutdown() flush 高(社区)
Opik(Comet) LLM eval + trace ☁️🛠️ OpikTracer + track_langgraph(app) 节点内 @track 装饰器;eval 与实验管理 节点 async 需 extract_current_langgraph_span_data 中-高(社区)
Helicone LLM gateway + 观测 ☁️🛠️ 间接接入:OpenAI-compatible proxy 替换 base_url 对调用级成本 / 延迟聚合强 【待验证】 无 LangGraph 专用集成页面 中(社区)

Helicone 接入参考(【推测】基于社区博客与 Helicone 文档对 OpenAI-compatible proxy 的描述):

python 复制代码
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
    model="gpt-4o-mini",
    base_url="https://oai.helicone.ai/v1",
    api_key=os.environ["HELICONE_API_KEY"],
    default_headers={"Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}"},
)
# 之后 LangGraph / LangChain 自动通过 base_url 把请求经 Helicone

成熟度分级依据(用于解释上表"成熟度"列四档差异):

  • 高(官方):LangSmith 由 LangChain 官方维护,与 LangGraph 同源;文档 / 集成 / 版本兼容最完整。
  • 高(社区/官方):Phoenix(Arize)由 OpenInference 标准支撑,Arize 与 LangGraph 团队均维护集成示例,跨 LLM 框架兼容强。
  • 中-高(社区):Langfuse / Opik 在社区有大量生产案例,但与 LangGraph 的耦合需经 Callback / Tracer 抽象,少数边界(如 async context 传播)需自处理。
  • 中(社区):Helicone 定位为 LLM gateway,未直接对接 LangGraph;接入路径经 OpenAI-compatible proxy 间接完成,trace 串联与状态 diff 需在业务侧补齐。

9.X.3 OTLP 导出:分两类讲清

【官方】OTel 文档明确给出"导出到 LangSmith"端点(docs.langchain.com/langsmith/evaluate-with-opentelemetry)。【官方】LangChain/LangGraph 对纯 OTel Collector(无 LangSmith)端到端配置未直接给出最小示例,属【待验证】。

A 类:导出到 LangSmith(官方支持)

python 复制代码
import os
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = "https://api.smith.langchain.com/otel/"
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = (
    f"x-api-key={os.environ['LANGSMITH_API_KEY']},Langsmith-Project={experiment_id}"
)
# 自托管 LangSmith 把 endpoint 改成 https://<self-hosted>/api/v1/otel

B 类:导出到纯 OTel Collector / Jaeger / Tempo(社区/待验证)

要在非 LangSmith 后端拿完整 trace,至少需要以下三类组件(【社区】综合):

  1. Instrumentationopeninference-instrumentation-langchain(Phoenix 维护)或 opentelemetry-instrumentation-langchain(社区)。
  2. Tracer provider + Exportertrace.set_tracer_provider(...) + BatchSpanProcessor(OTLPSpanExporter(...)),endpoint 指向 OTel Collector。
  3. Span processor 注入 :把 LangChainTracer 或 OTel span handler 通过 config={"callbacks": [...]} 传给图调用;或使用 tracing_context 包裹。

⚠️ 建议参考 openinference-instrumentation-langchain 示例后做端到端验证。

9.X.4 隐私脱敏:覆盖范围与限制

【官方】来源:docs.langchain.com/oss/python/langgraph/observability "Anonymizing Sensitive Data" 章节。【社区】补充:跨后端脱敏一致性需业务侧自行实现。

字段 是否被 LangSmith create_anonymizer 处理 备注
LLM inputs / outputs ✅ 走 LangChainTracer callback 时被处理 默认仅 SSN 之类内置正则
Tool args / tool returns ✅ 同上 自定义工具返回值需在 tool 内部先脱敏
Tags ❌ 不会自动处理 业务侧控制(建议不写 PII)
Metadata ❌ 不会自动处理 业务侧控制(建议不写 PII)
thread_id ❌ 不会自动处理 业务侧传入时建议用 hash
Phoenix / Langfuse / Opik 路径 ❌ 不共享 各后端需独立配置脱敏

关键限制 :anonymizer 是客户端侧的正则替换;只能防"trace 文本里出现 PII",无法防"工具内部把 PII 写到数据库"等业务层泄漏。



第 10 章:性能、成本与可靠性

10.1 解决什么问题

生产环境的 LLM 链路必须同时回答四类问题:

  1. 上下文无限增长:长对话、长期任务会撑爆模型 context window,导致单次调用变慢、变贵、甚至失败。
  2. 算力与延迟成本:单次 LLM 调用占整个请求延迟 80% 以上,浪费一次重算 = 多花数秒 + 几美分到几美元。
  3. 外部 provider 不稳定:OpenAI / Anthropic / Bedrock 都可能 5xx 限流,必须能在 provider 失败时降级、重试、切换。
  4. 资源竞争:多用户、多租户并发会互相饿死;需要 token 预算、并发上限、QPS 限流。

官方能力矩阵在 2025/2026 年发生重要变化:LangGraph 1.1 引入 stream v2,1.2 引入 DeltaChannel 增量持久化、StreamWriter 参数等。本章基于这些能力展开。

10.2 推荐做法

10.2.1 上下文窗口控制:四种官方机制

官方 LangGraph 提供四种独立可叠加的机制(来源:docs.langchain.com/oss/python/langgraph/add-memory,2026-06):

  1. RemoveMessage 物理删除:从 state 中抹去消息,对应 checkpointer 持久化也会被裁剪。
  2. trim_messages 临时裁剪:裁剪后送入 LLM 的列表,但 state 中仍保留全部消息。
  3. Summarize 压缩:保留近期 + 早期摘要。
  4. Custom filter:按角色、tool_call_id、长度自定义筛选。

四者关系:state 是真相trim_messages 只决定「这次送什么给 LLM」,RemoveMessage 决定「持久化中保留什么」。

官方 DeltaChannellanggraph>=1.2beta API )只持久化增量 delta,显著减少 append-heavy 通道(如 messages)的 checkpoint 体积(来源:docs.langchain.com/oss/python/langgraph/persistence,2026-06)。

DeltaChannel 使用条件(待验证)

  • 当前为 beta API,适用范围、是否对所有 checkpointer 后端生效、是否需要自定义配置 以官方 persistence 文档为准(docs.langchain.com/oss/python/langgraph/persistence,2026-06)。
  • 1.x → 1.3+ 升级时关注 GA 状态;启用前应在 staging 验证 checkpoint 体积与恢复行为。
  • 10.5 检查清单对应项已改为「评估启用」而非默认「启用」。

10.2.2 并行与异步:Send / 并行边 / Functional API

官方 三种并发模式(来源:docs.langchain.com/oss/python/langgraph/use-graph-api,2026-06):

模式 适用场景 关键 API
固定扇出 静态可知的并行子任务 多条 add_edge 从同一节点出发
动态扇出 运行时才能决定子任务数量 Send("node", {"k": v}) 列表
Functional API LLM 工具调用、长 I/O 任务、幂等副作用 @task + .result()

官方 并发上限控制(来源:同上):graph.invoke(input, {"configurable": {"max_concurrency": 10}})

官方 + 推测 parallel_tool_calls 与状态合并风险:模型 provider / LangChain 工具层可并行发起多个 tool call,但 LangGraph state 合并仍依赖 reducer 语义。生产中应避免多个并行工具写同一个覆盖式字段;需要合并的字段必须显式使用 Annotated[..., reducer](如 operator.add / add_messages),否则后写入可能覆盖先写入。Send 动态扇出也遵循同一原则:并行分支返回的同名 key 必须有可交换/可结合的 reducer,或在 join 节点中显式汇总。

官方 @task 装饰的函数返回 future-like 对象,结果自动写入 checkpoint,恢复时不重算。Tasks 适合封装外部副作用(来源:docs.langchain.com/oss/python/langgraph/functional-api,2026-06)。

10.2.3 Prompt Caching:Anthropic / OpenAI

官方 Anthropic 缓存(来源:platform.claude.com/docs/en/docs/build-with-claude/prompt-caching,2026-06):

  • cache_control: {type: "ephemeral"}(默认 5 分钟 TTL,1.25× 写入,0.1× 读取)。
  • ttl: "1h" 1 小时 TTL(2× 写入,0.1× 读取)。
  • 每请求最多 4 个 breakpoints(显式 + 自动各算一个 slot)。
  • 顺序:长 TTL 必须在短 TTL 之前。

官方 OpenAI 缓存(来源:developers.openai.com/api/docs/guides/prompt-caching,2026-06):

  • 自动开启,无代码改动,prompt ≥ 1024 tokens 触发。
  • 路由依据:prefix hash(典型前 256 tokens)。
  • 默认 in-memory TTL 5--10 分钟,最多 1 小时。
  • 价格:最多降低 80% 延迟、90% 输入成本;cached_tokens 字段报告命中。

官方 跨框架配合(来源:LangChain 官方文档,2026-06):

  • ChatAnthropic 透传 cache_control{"type": "ephemeral", "ttl": "1h"})与 cache_read_input_tokens / cache_creation_input_tokens
  • ChatOpenAI 透传 cached_tokens(位于 usage.prompt_tokens_details.cached_tokens)------OpenAI 不使用也不接受 cache_control 字段;OpenAI 缓存是自动启用,无代码改动(OpenAI 官方文档:developers.openai.com/api/docs/guides/prompt-caching,2026-06)。
  • 在 graph 状态里读 resp.usage_metadata 即可观测命中率;Anthropic / OpenAI 字段名不通用

10.2.4 Provider Failover & Multi-Provider

官方 + 推测 LangGraph 本身不提供 provider 抽象,但可以:

  • 通过 LangChain 的 init_chat_model 在节点内根据错误类型切换 provider。
  • langchain-anthropic / langchain-openai / langchain-aws 多 provider 同接口。
  • 把 LLM 调用放进 @task,让 task 重试语义可被 checkpointer 恢复。

社区 常用 LiteLLM 统一 provider 入口,配合 with_fallbacks([...])(LangChain 原生)或上层重试装饰器实现 failover(来源:langchain-ai/langchain#docs,2025;标注为社区经验,官方未提供内置 failover 抽象)。

10.2.5 可靠性:Durability、Idempotency、Retry

官方 持久化模式三档(来源:docs.langchain.com/oss/python/langgraph/persistence,2026-06):

  • "exit":仅退出时持久化,性能最高,崩溃后无中间态。
  • "async":下一步前异步持久化,崩溃有小窗口。
  • "sync":下一步前同步持久化,最强保证,性能损耗最大。

官方 Functional API 的 @task + idempotency_key 是官方推荐模式(来源:functional-api 文档,2026-06):

"For side effects, API calls, writes, and retryable work: Put the operation in a @task. Make the task idempotent because a task that began but did not finish may run again. Use an idempotency key or check whether the external action already happened."

官方 节点副作用必须幂等(来源:graph-api 文档 + interrupts 文档,2026-06;deep-research §核心事实 #6 + #11):

  • Checkpoint 在 super-step 边界 保存,不在节点函数中途保存(deep-research §核心事实 #6)。
  • 恢复时整个节点从头执行 ,不沿 interrupt() 调用点继续(deep-research §核心事实 #11)。
  • 因此任何节点内副作用都必须幂等 (idempotency key、upsert、read-before-write check),这是"pre-interrupt 副作用幂等"和"整个节点副作用幂等"两条事实共同支撑的结论,不是"只要 pre-interrupt 幂等就够"。

官方 interrupt 调用语义(来源:interrupts 文档,2026-06):

  • interrupt() 调用按严格索引匹配,payload 应使用 JSON-serializable 值。
  • interrupt() 抛特殊异常暂停节点;恢复时通过 Command(resume=...) 提交。

注意 :deep-research §被 refute 的错误说法 #1 明确"严禁在 try/except 中包裹 interrupt"是绝对化、不可成立的。本稿 采用该绝对化措辞;本节所引"幂等性"约束与 try/except 包裹方式正交 ------示例可正常用 try/except 处理业务异常,只要不捕获 interrupt 自身(Python 通过让 Interrupt 异常基类不被吞掉即可)。

10.2.6 重试 / 熔断 / 降级 / DLQ

官方 + 社区 LangGraph 官方仅提供原子级 重试语义(@task 重跑 + durability),直接提供"应用层"重试 / 熔断 / 降级 / DLQ 抽象;这部分需应用层自实现:

关注点 LangGraph 官方能力 应用层补足(社区经验)
任务级重试 @task 自动从 checkpoint 重跑,相同 idempotency_key 不重复 tenacity 装饰器(指数退避 + jitter);自定义重试中间件
熔断(circuit breaker) aiobreaker / pybreaker / 自建 token bucket
降级(fallback) with_fallbacks([...])(LangChain) LangChain 原生 + LiteLLM 统一 provider 入口
DLQ(dead-letter queue) 无"超过 N 次入 DLQ"开箱 自建 Redis sorted set / Postgres failed_runs 表,配合外部 worker 消费

应用层补足参考实现 (社区,非官方):

python 复制代码
# tenacity 指数退避 + 熔断
from tenacity import retry, stop_after_attempt, wait_exponential_jitter
from aiobreaker import CircuitBreaker

breaker = CircuitBreaker(fail_max=5, timeout_duration=60)

@retry(stop=stop_after_attempt(3), wait=wait_exponential_jitter(initial=1, max=10))
@breaker
async def call_llm_with_protection(prompt):
    return await model.ainvoke(prompt)

DLQ 可用 Redis sorted set(ZADD dlq <ts> <run_id>)+ 外部 worker ZRANGEBYSCORE 拉取。

10.2.7 Token 预算与 QPS 限流

官方 + 推测 LangGraph OSS 不直接提供 token 预算与 QPS 限流;这两类资源保护需在节点内或中间件实现:

维度 LangGraph 官方能力 实现路径
并发上限(任务级) max_concurrencygraph.invoke configurable 参数) 官方内置
Token 预算(成本) 节点内计数器(tiktoken 预估 + 累计);LangSmith rate limit callback;外部 Redis token bucket
QPS 限流(速率) 节点内 aiocache + 滑动窗口;NGINX / API Gateway 层;slowapi(FastAPI 中间件)

不要把 max_concurrency 当成完整限流 :它只控制任务并发数控制 token 消耗速率或对外 provider 的 QPS。生产环境需叠加 token 预算 + QPS 限流两层。

10.3 不推荐做法

反模式 后果 出处
节点内直接 time.sleep / requests.post resume 时副作用重复触发,破坏 checkpoint 重放 官方 functional-api 警告
节点内 open(path).read() 读外部文件 同上,恢复时路径/内容可能不一致 deep-research §核心事实 #11(节点从头重跑)
InMemorySaver / MemorySaver 兼容别名跑生产 进程重启 = 全部 thread 丢失,HIL 状态归零;MemorySaver 只是 InMemorySaver 的向后兼容别名 官方明令「不可生产」;附录 A A.2.2 / A.4.2 已统一 InMemorySaver 为主名、MemorySaver 为兼容别名
工具调用无超时、无错误分类 慢工具拖垮整条图,retry 风暴 社区经验 + 第 6 章反模式
路由函数依赖 time.time() / random checkpoint 重放时路径不一致,无法 fork 官方 time-travel 文档隐含
messages 整列塞 LLM 不裁剪 命中 context 上限后 token 单价 × 输入长 第 4 章状态设计反模式
在 conditional edge 里写业务逻辑 不可单测、不可追踪 第 3 章反模式
缓存命中率不监控、不告警 看似开了缓存,实则 prefix 漂移命中率 0% 推测
重试不带指数退避 雪崩放大 推测(与社区共识一致)
无熔断 / 无 DLQ 的外部副作用调用 持续失败拖垮主链路,无失败重放机制 社区经验(tenacity / aiobreaker)
max_concurrency 当成完整限流 缺 token 预算 + QPS 限流,成本与速率均失控 推测(OSS 不提供)
并行工具调用 / Send 分支写同一覆盖式 state key 后写入覆盖先写入,结果丢失或顺序不稳定 官方 reducer 语义 + 推测(并发合并风险)

10.4 最小代码示例

示例 A:trim_messages + RemoveMessage 组合(LangGraph 1.2+)

python 复制代码
# 依赖:langgraph>=1.2, langchain-core>=1.4.0,<2(与 langgraph 1.2.4 的 PyPI requires_dist 对齐)
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_core.messages.utils import (
    trim_messages,
    count_tokens_approximately,
)
from langchain.messages import RemoveMessage
from langgraph.checkpoint.postgres import PostgresSaver

class State(MessagesState):
    summary: str

# 1) trim 决定送入 LLM 的内容(不污染 state)
def call_model(state: State):
    selected = trim_messages(
        state["messages"],
        strategy="last",
        token_counter=count_tokens_approximately,
        max_tokens=2048,
        start_on="human",
        end_on=("human", "tool"),
    )
    resp = model.invoke(selected)  # model = init_chat_model("claude-sonnet-4-6")
    return {"messages": [resp]}

# 2) summarize + RemoveMessage 决定持久化保留什么
def maybe_summarize(state: State):
    if len(state["messages"]) < 20:
        return
    summary_resp = model.with_structured_output(...).invoke(...)
    return {
        "summary": summary_resp.content,
        "messages": [RemoveMessage(id=m.id) for m in state["messages"][:-4]],
    }

builder = StateGraph(State)
builder.add_node("call_model", call_model)
builder.add_node("maybe_summarize", maybe_summarize)
builder.add_edge(START, "call_model")
builder.add_conditional_edges("call_model", lambda s: "maybe_summarize" if len(s["messages"]) > 20 else END)
builder.add_edge("maybe_summarize", END)

DB = "postgresql://user:pwd@host:5432/langgraph"
checkpointer = PostgresSaver.from_conn_string(DB)
checkpointer.setup()
graph = builder.compile(checkpointer=checkpointer)

示例 B:Send 动态扇出 + max_concurrency(LangGraph 1.2+)

python 复制代码
import operator
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send

class Overall(TypedDict):
    items: list[str]
    results: Annotated[list[dict], operator.add]

class Item(TypedDict):
    item: str

def split(state: Overall):
    return {"items": state["items"]}  # 任意长度的运行时列表

def worker(state: Item):
    # 模拟 LLM 调用或外部 API
    return {"results": [{"item": state["item"], "out": state["item"][::-1]}]}

def fanout(state: Overall):
    return [Send("worker", {"item": x}) for x in state["items"]]

g = StateGraph(Overall)
g.add_node("split", split)
g.add_node("worker", worker)
g.add_edge(START, "split")
g.add_conditional_edges("split", fanout, ["worker"])
g.add_edge("worker", END)
graph = g.compile()

# 限流:同一 superstep 最多并发 10
graph.invoke({"items": list(range(50))}, {"configurable": {"max_concurrency": 10}})

示例 C:@task + 幂等 key + 异常处理 + 崩溃恢复(Functional API,LangGraph 1.2+)

python 复制代码
import os
import stripe
from langgraph.func import entrypoint, task
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.types import Command
from langchain_core.messages import HumanMessage
from langgraph.errors import TaskException

# 1) @task 封装外部副作用
@task
def charge_customer(order_id: str, amount_cents: int) -> dict:
    # 关键:把外部副作用封装在 task 中
    # task 自动从 checkpoint 恢复;已成功完成的 task 不会重跑
    # 使用 provider 的 idempotency_key 保证幂等
    try:
        return stripe.Charge.create(
            amount=amount_cents,
            currency="usd",
            idempotency_key=order_id,  # 同一 order_id 不会重复扣款
        )
    except stripe.error.CardError as e:
        # 业务可重试错误,task 失败抛出,由 entrypoint 决定如何处理
        raise

checkpointer = PostgresSaver.from_conn_string(os.environ["DB_URL"])
checkpointer.setup()

# 2) entrypoint 编排;task 失败时 .result() 抛 TaskException
@entrypoint(checkpointer=checkpointer)
def order_flow(order: dict) -> dict:
    try:
        charge = charge_customer(order["id"], order["amount"]).result()
    except TaskException as exc:
        # task 失败处理:记录 + 抛出(可在此重试或入 DLQ)
        log_failure(order["id"], exc)
        raise
    return {"charge_id": charge["id"]}

# 3) 正常调用
config = {"configurable": {"thread_id": order["id"]}}
result = order_flow.invoke(order, config=config)

# 4) 崩溃恢复演示:
#    场景:上面 invoke 进程在 charge_customer 已成功但 entrypoint 返回前崩溃
#    操作:重新 invoke 同一 thread_id,@task 自动不重跑(idempotency_key 命中)
#    验证:第二次 invoke 不会重复扣款,stripe 控制台可看到同一 idempotency_key 只产生一次 charge
config_again = {"configurable": {"thread_id": order["id"]}}
result_again = order_flow.invoke(order, config=config_again)  # 不重复扣款

关于 try/exceptinterrupt 的关系 (澄清 deep-research §被 refute 的错误说法 #1):try/except 可正常用于捕获业务异常(如 stripe.error.CardError);Interrupt 异常不应被吞掉,但只要不显式 except Interrupt,Python 异常传播机制即能正确处理。本示例的 try/except 仅捕获 stripe.error.CardError 捕获 Interrupt,因此 interrupt 暂停语义不受影响。

示例 D:Anthropic prompt caching + cached_tokens 观测

python 复制代码
from pathlib import Path
from langchain.chat_models import init_chat_model
from langchain.messages import SystemMessage, HumanMessage

model = init_chat_model("claude-sonnet-4-6")

# 建议:kb.md 在 compile/build 阶段读取并 cache,节点内不再做 I/O
# 原因:LangGraph 1.x 节点在 interrupt 恢复时整个节点从头执行(deep-research #11),
#      节点内 open() 会重复执行;放模块级常量更安全
KB = Path("kb.md").read_text()  # 模块级常量,进程启动时读一次

def call(state):
    # 1) 系统提示做长期缓存(1h TTL)
    system = [
        {"type": "text", "text": "你是资深客服。"},
        {
            "type": "text",
            "text": KB,   # 用模块级常量,避免节点内 open() 副作用
            "cache_control": {"type": "ephemeral", "ttl": "1h"},
        },
    ]
    resp = model.invoke(
        [SystemMessage(content=system), *state["messages"]],
    )
    # 2) 观测命中率,写到自定义字段
    usage = resp.usage_metadata
    print({
        "input": usage.get("input_tokens"),
        "cache_read": usage.get("cache_read_input_tokens"),
        "cache_creation": usage.get("cache_creation_input_tokens"),
    })
    return {"messages": [resp]}

10.5 检查清单

  • State 中消息已用 add_messages reducer,并按需 trim_messages 限制送入 LLM 的长度
  • 长对话(>20 轮)启用 summarize 节点 + RemoveMessage 物理裁剪
  • 监控 cache_read_input_tokens / cached_tokens(Anthropic / OpenAI 字段不同),缓存命中率纳入告警
  • Anthropic 缓存 breakpoints ≤ 4,长 TTL 在短 TTL 之前;OpenAI 不使用 cache_control 字段
  • OpenAI 提示前缀静态 ≥ 1024 tokens,动态内容放末尾
  • 外部副作用(支付、邮件、写入)封装在 @task 内并带 idempotency key
  • 节点内全部副作用 幂等(不只 pre-interrupt 部分;super-step 边界 + 节点从头重跑共同要求)
  • 节点内不直接 time.sleep / requests.post / open(path).read(),改用 @task 或模块级常量
  • LLM 节点超时与重试(指数退避 + jitter)已配置;熔断(circuit breaker)已设置
  • 失败超过 N 次自动入 DLQ(Redis sorted set / 外部表 + worker)
  • Provider 故障有 fallback provider / 错误分类(5xx 重试,4xx 立即失败)
  • max_concurrency 已根据下游 provider 限流策略配置
  • token 预算(节点内计数器 / LangSmith callback / Redis token bucket)已配置
  • QPS 限流(节点内 / API gateway / slowapi)已配置;不要把 max_concurrency 当完整限流
  • durability 三档(exit / async / sync)按业务选择并写入 runbook
  • DeltaChannel 当前为 beta API;适用 checkpointer 后端以官方 persistence 文档为准;升级时关注 GA 状态
  • 资源密集节点不直接 time.sleep,改用 astream 事件或任务调度

第 11 章:生产化部署与平台选型

11.1 解决什么问题

把一个本地能跑通的 graph 推到生产,需要回答:

  1. 形态选择:自建 FastAPI + Postgres?Self-Hosted Lite(官方原词 Standalone server)?LangSmith 托管 Cloud?完整 Self-Hosted Enterprise?
  2. 平台能力边界:Streaming、HITL 暂停、长任务、Cron、Webhook、A2A / MCP、Auth、Studio UI 哪些是平台原生提供,哪些要自己接。
  3. 运维边界:进程模型、扩展单元、Postgres / Redis 依赖、Schema 迁移、灰度发布。
  4. TCO:每千次 run、每部署每分钟的计费、自托管的隐性人力成本。

官方 2025-10 起「LangGraph Platform」正式更名为 LangSmith Deployment(来源:langchain.com/langgraph-platform 页面,2026-06;docs.langchain.com/langsmith/deployment 同步)。本文档统一使用新名。

11.2 推荐做法

11.2.1 三种部署形态

官方 官方提供三种部署形态(来源:docs.langchain.com/langsmith/deployment,2026-06;v2 大纲命名映射见 ch01-03 附录 A.4):

形态(v2 大纲口径) 官方原词 你管什么 平台管什么 适用
Cloud Cloud(SaaS) 业务代码 控制面 + 数据面(AWS / GCP) 起步期、不想运维
Self-Hosted Lite Standalone server 容器 + Postgres + Redis + License Runtime + API + Studio 中小规模、想避免 cloud 锁定
Self-Hosted Enterprise Self-Hosted(Enterprise) K8s + 全套基础设施 License + 软件更新 强合规、专有云

命名口径声明 :本稿遵循 v2 大纲命名 Cloud / Self-Hosted Lite / Self-Hosted Enterprise;与 LangSmith Deployment 官方文档原词 Cloud / Standalone server / Self-hosted 的对应关系见 ch01-03 附录 A.4。本节及以下 11.2.2 / 11.2.5 / 11.2.6 / 11.3 / 11.5 全文统一使用 v2 大纲口径。

官方 共同 runtime:Assistant、Thread、Run 三种 primitive,跨形态 API 一致;变更的只是「谁运维」。

官方 关键能力(覆盖所有形态):streaming、HITL pausing、并发 input、MCP/A2A endpoint、Cron、webhooks、auth、registry、版本与回滚、Studio 调试(来源:同上)。

11.2.2 基础设施依赖

官方 + 推测 Self-Hosted Lite(官方原词 Standalone server)与 Self-Hosted Enterprise 的必备依赖:

  • PostgreSQL :LangGraph checkpointer / store 后端;生产建议使用 langgraph-checkpoint-postgres==3.1.0(2026-05-12,与 langgraph 1.2.x 同步)并在部署管线中显式执行 PostgresSaver.setup() / AsyncPostgresSaver.setup()
  • Redis :在 Self-Hosted Lite(Standalone server)中是 LangSmith 控制面组件 (任务队列、pub/sub、限流);LangGraph Python checkpointer 本身不依赖 Redis (可选 RedisSaver,与 Postgres / SQLite 并列的独立后端,互相替代)。
  • 对象存储(可选):大 blob、checkpoint 备份。
  • License Server:Self-Hosted Lite / Self-Hosted Enterprise 都需要 LangSmith 商业 License。
  • Container Runtime:Docker / Compose / K8s 至少其一。

Redis「必备」边界 :Self-Hosted Lite 必备(LangSmith 控制面组件);Self-Hosted Enterprise 看部署方式(可不用 Redis,改用 K8s 原生队列 / 内部 pub/sub)。不要 把 Redis 当作 LangGraph Python checkpointer 的必备后端------LangGraph checkpointer 后端是 PostgresSaver / SqliteSaver / RedisSaver 三选一(每个后端是独立类)。

官方 进程模型(来源:deployment 文档与 Functional API 文档综合):

  • API Server:HTTP / SSE / WebSocket 入站。
  • Worker:执行 graph 节点,水平扩展单元。
  • Scheduler:cron jobs、延迟任务、time-travel 恢复。
  • 多数情况下三者共享同一镜像、不同 entrypoint。

11.2.3 LangGraph Server 关键 API

官方 Server 暴露的核心资源(来源:docs.langchain.com/langsmith/deployment,2026-06):

  • assistants --- 静态配置(graph、prompt、tools、模型)。
  • threads --- 会话状态(持久到 Postgres)。
  • runs --- 单次执行;runs.create 支持 command 字段 resume interrupt。
  • crons --- 定时任务。
  • store --- 跨 thread 长期记忆 namespace。
  • webhooks --- 出站回调。
  • mcp / a2a --- 跨进程 / 跨框架协作。

SDK 版本口径 :本章只讨论 Python 版 langgraph-sdk;截至 2026-06-11,附录 A 记录的 Python SDK 最新为 0.4.2,其中 0.4.0 是 v3 streaming 主变更批次之一。若读者同时使用 JS/TS SDK,其版本线(如 1.x)与 Python SDK 不同,不能混用版本号。

官方 + 推测 Idempotency:run 接受 idempotency_key,避免重试造成重复执行(社区经验,官方文档未给完整 spec;标注为待验证)。

11.2.4 LangGraph Studio

官方 Studio 是官方可视化 IDE,能力包括(来源:docs.langchain.com/langsmith,2026-06):

  • 本地运行:连接本地 dev server,可视化 state、node 跳转。
  • Cloud:连接 Cloud 部署。
  • Time-travel:单步回放、fork 出新分支、修改 state 后再执行。
  • Trace replay:结合 LangSmith trace 复现生产问题。

官方 + 推测 Studio 仅供开发者使用,不应直接对外;访问控制以 LangSmith Plan(Plus / Enterprise)权限与 Custom Auth(11.2.6)配置为准------Cloud Plus / Enterprise 默认按 LangSmith 组织 / 项目边界隔离;Self-Hosted Lite 需自配 Custom Auth 才能限制 Studio 访问。

11.2.5 跨版本升级、Schema 迁移、灰度

官方 升级路径(来源:PyPI + GitHub release notes,2026-06):

  • 0.2.x0.3.x:短过渡系列,已 EOL。
  • 0.3.x1.x:破坏性变更 (breaking changes),需测试项:
    • messages reducer 与 add_messages 行为变化。
    • interrupts 改用 Command(resume=...) 形式恢复。
    • stream v1stream v2 输出格式变化。
    • changelog:https://github.com/langchain-ai/langgraph/releases(请按 tag 翻阅 0.3.x → 1.0.0 的 release notes)。
  • 1.x 内部:minor 升级大多向后兼容,但 DeltaChannel 等新功能需 >=1.2beta API,1.x → 1.3+ 升级时关注 GA 状态)。

升级风险提示 :跨大版本升级(0.3.x1.x1.01.1 stream v2)必须先读 changelog + staging 跑回放;不要把"无破坏性变更"作为既成事实写------v1.0 GA 公告曾出现"promoted to 1.0 with no breaking changes"措辞争议(ch01-03 附录 A.3 已警示)。

官方 schema 迁移:checkpointer 的 setup() 会建表;结构变更需要手工 ALTER TABLE 或迁移工具(如 alembic)。Postgres saver 文档显式提到需要预先建表(来源:persistence 文档,2026-06)。

推测 Graph 灰度发布:Cloud 与 Self-Hosted Enterprise 都支持多版本 assistant 同存;可在 router 层按比例路由 assistant_id,做 Blue/Green。详细 router 模式官方 cookbook 未提供,待补

11.2.6 Auth:Custom Auth

官方 Custom Auth 通过 @auth.authenticate + @auth.on* 装饰器实现(来源:docs.langchain.com/langsmith/auth,2026-06;langgraph-sdk Python 0.4.x 配套;PyPI langgraph-sdk 当前稳定为 0.4.2(2026-06-01),与 langgraph==1.2.4 requires_dist <0.5.0,>=0.4.2 对齐------详见附录 A A.1.4 / A.6):

  • importfrom langgraph_sdk import Auth(注:另有内部路径 langgraph_api.auth.Auth,前者为公开 SDK 入口;二者等价)。
  • @auth.authenticate :验证身份,返回 dict,至少含 identity(必需,唯一用户标识);可选 is_authenticated(默认 True)、permissions(权限列表,如 ["threads:write", "threads:read"])、org_idrole 等自定义字段。
  • @auth.on* 装饰器族 (最具体的匹配优先):
    • global@auth.on(兜底,未被具体 resource/action 匹配的请求落到这里)。
    • resource@auth.on.threads / @auth.on.assistants / @auth.on.crons(资源级统一处理)。
    • action@auth.on.threads.create / @auth.on.threads.read / @auth.on.threads.update / @auth.on.threads.delete / @auth.on.threads.search / @auth.on.threads.create_run 等(具体动作级)。
  • 装饰器签名参数:ctx: Auth.types.AuthContext(含 ctx.user / ctx.permissions / ctx.resource / ctx.action)和 value(请求负载,按 resource.action 类型化)。
  • filter 返回 :返回 None / True = 放行;返回 False = 拒绝(403);返回 filter dict({"$eq": ..., "$contains": ...} 等)= 按元数据过滤。
  • LangSmith Cloud:默认用 LangSmith API Key 鉴权。
  • Self-Hosted Lite / Self-Hosted Enterprise :默认无认证,需自配 Custom Auth(来源:deployment 文档,2026-06)。

LangSmith 商业 License 与 Studio 访问控制 :Studio 访问控制以 LangSmith Plan(Plus / Enterprise)权限与 Custom Auth 配置为准------Cloud Plus / Enterprise 默认按 LangSmith 组织 / 项目边界隔离;Self-Hosted Lite 需自配 Custom Auth 才能限制 Studio 访问。本稿 11.2.4「权限模型见 11.2.6」的交叉引用即此意。

11.2.7 TCO 估算

价格时效性声明 :以下 Cloud Plus 价格以 langchain.com/pricing 2026-06-11 抓取为准;USD 报价,不含税不区分国家/区域。LangChain 保留随时调整价格的权利;部署前请以官网为准。

官方 Cloud Plus 计划(来源:langchain.com/pricing,2026-06):

  • $39/seat/月,含 1 个 dev-sized deployment + 10k base traces。
  • Run 计量:$0.005/deployment run(超出后)。
  • Uptime:0.0036/min per Production deployment,0.0007/min per Development deployment。
  • 30 天 always-on:约 155.52/prod、30.24/dev。
  • 额外 traces:2.50/1k base,5.00/1k extended。
  • Engine:$1.50/LCU(Plus)。

官方 Enterprise:Custom pricing,含 SSO/RBAC、SLA、培训、驻场工程。

官方 Self-Hosted Lite(官方原词 Standalone server):bring your own LangSmith License + 自行运维基础设施,无 platform subscription;仍需 LangSmith 商业 License (来源:docs.langchain.com/langsmith/deployment,2026-06;Standalone 段"bring your own license")。Studio 能力包含在 License 内;精确价格请以 LangChain 销售 / partner portal 为准(附录已列为待补)。

官方 Self-Hosted Enterprise:需 LangSmith Enterprise Plan + 自部署完整控制面 + 数据面;Custom pricing(来源:同上)。

推测 决策启发:

【编者经验阈值,官方无明确推荐】 以下数字(50k、10 人)为本稿编者按 cloud-vs-self-host 行业经验归纳,非 LangChain 官方建议。生产选型请结合真实业务算账(run 单价 × 月均 run + always-on × 月数 + License + 运维人力)。

  • 月 run < 50k、团队 ≤ 10 人、合规弱:选 Cloud Plus。
  • 数据合规要求中等、希望避免 cloud 锁定:Self-Hosted Lite + Postgres + Redis。
  • 金融 / 医疗 / 政企 + 已有 K8s 平台:Self-Hosted Enterprise。

11.3 不推荐做法

反模式 后果 出处
生产用 Cloud Free / Developer tier 无 SLA、无合规、无 SSO;正式业务不该跑这 langchain.com/pricing 明确
Self-Hosted Lite(Standalone server)用 SQLite(langgraph-checkpoint-sqlite==3.1.0)做生产 checkpointer SQLite 适合本地/单机轻量持久化;生产多副本下单点故障、并发安全不足 官方生产建议用 Postgres;附录 A A.1.4 记录 sqlite 3.1.0 仅作为本地/单机场景
把 LangSmith 当作"唯一权限" 离开 LangSmith 之后 auth 全失效 推测(与第 12 章 RBAC 冲突)
一次升级大版本不读 changelog 0.3 → 1.x stream/interrupt 行为变更(破坏性) 官方 changelog 提示
不区分 dev / prod deployment 误调用 LLM 烧钱 官方计费模型隐含
用 InMemory store 跑生产 重启数据全丢 官方 store 文档明令
在 Studio 里直接对接生产数据 开发者账户 = 数据访问者 推测(合规通用原则)
自定义 Auth 只在 authenticate 里加判断,未加 on.<resource>.<action> list / read / create_run 路径未受保护 官方 auth 文档(11.2.6)

11.4 最小代码示例

示例 A:最小 FastAPI + Postgres 自定义部署(不依赖 LangSmith)

python 复制代码
# 依赖:fastapi, uvicorn, langgraph>=1.2, langgraph-checkpoint-postgres
from fastapi import FastAPI, Request
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.checkpoint.postgres import PostgresSaver
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage

# 1) 编译一次,进程内复用
DB = "postgresql://user:pwd@host:5432/langgraph"
checkpointer = PostgresSaver.from_conn_string(DB)
checkpointer.setup()
model = init_chat_model("claude-sonnet-4-6")

def chat_node(state: MessagesState):
    resp = model.invoke(state["messages"])
    return {"messages": [resp]}

graph = StateGraph(MessagesState)
graph.add_node("chat", chat_node)
graph.add_edge(START, "chat")
graph.add_edge("chat", END)
app_graph = graph.compile(checkpointer=checkpointer)

# 2) HTTP API
app = FastAPI()

@app.post("/invoke/{thread_id}")
async def invoke(thread_id: str, request: Request):
    body = await request.json()
    config = {"configurable": {"thread_id": thread_id}}
    result = await app_graph.ainvoke(
        {"messages": [HumanMessage(content=body["input"])]},
        config,
    )
    return {"output": result["messages"][-1].content}

示例 B:Custom Auth + org 隔离(LangSmith Deployment,LangGraph 1.2+ / Python SDK 0.4.x)

python 复制代码
# 部署在 Cloud / Self-Hosted Lite / Self-Hosted Enterprise 都适用
# 来源:docs.langchain.com/langsmith/auth(2026-06)
from langgraph_sdk import Auth

auth = Auth()

# 1) authenticate 钩子:返回 identity / permissions / 自定义字段
@auth.authenticate
async def authenticate(headers: dict):
    token = headers.get(b"authorization", b"").decode()
    user = await idp.verify(token)  # 你的 IdP(Auth0 / Supabase / Okta / 自建)
    # identity 必填;is_authenticated 默认 True;permissions / org_id 可选
    return {
        "identity": user["sub"],
        "is_authenticated": True,
        "permissions": user["scopes"],   # 例如 ["threads:write", "assistants:read"]
        "org_id": user["org_id"],
    }

# 2) global 钩子:所有资源默认打 org_id 标签
@auth.on
async def org_scoped(ctx, value):
    metadata = value.setdefault("metadata", {})
    metadata["org_id"] = ctx.user["org_id"]
    return {"org_id": ctx.user["org_id"]}

# 3) action 钩子:threads.create 额外校验写权限
@auth.on.threads.create
async def on_thread_create(ctx, value):
    if "threads:write" not in ctx.permissions:
        raise Auth.exceptions.HTTPException(status_code=403)
    metadata = value.setdefault("metadata", {})
    metadata["owner"] = ctx.user.identity
    return {"owner": ctx.user.identity}

关于示例 B 的可运行性 :本示例基于 docs.langchain.com/langsmith/auth 当前 API 写就(2026-06 抓取);Auth.exceptions.HTTPException / Auth.types.AuthContext / Auth.types.on.<resource>.<action>.value 均为 SDK 公开类型。具体 Python SDK 版本以 langgraph-sdk>=0.4.2,<0.5.0 为准(与 langgraph==1.2.4 依赖约束一致)。

示例 C:Durability 模式与 background run

python 复制代码
# sync:每步同步持久化,强保证
async for event in graph.astream(input, durability="sync"):
    handle(event)

# async:下一步前异步持久化,崩溃窗口小
async for event in graph.astream(input, durability="async"):
    handle(event)

# exit:仅退出时持久化,最高吞吐
async for event in graph.astream(input, durability="exit"):
    handle(event)

11.5 检查清单

  • 已根据合规、规模、运维能力选定 Cloud / Self-Hosted Lite / Self-Hosted Enterprise(v2 大纲口径;与 ch01-03 附录 A.4 对齐)
  • Cloud 部署区分 dev 与 prod deployment
  • Self-Hosted Lite / Self-Hosted Enterprise:Postgres ≥ 14,Redis ≥ 6(LangSmith 控制面),并发 / 容量压测完成
  • Custom Auth 已覆盖 authenticate + @auth.on(global)+ @auth.on.<resource>.<action> 至少三级
  • Org / Project 隔离通过 metadata 过滤强制(不只靠 application 自觉)
  • checkpointer 加密(LANGGRAPH_AES_KEY)已开启
  • 升级前阅读 changelog;特别关注 0.3 → 1.x(破坏性) 、1.0 → 1.1(stream v2)行为变更;changelog:https://github.com/langchain-ai/langgraph/releases
  • Schema 迁移脚本(alembic / 手写)已写并演练
  • Graph 灰度 / Blue-Green 通过 assistant_id 路由实现
  • 监控:run 数量、deployment uptime、token 用量、cache 命中率
  • TCO 预估:run 单价 × 月均 run + 部署 always-on 单价 × 月数 < 业务预算
  • LangSmith License 续期与续约 SLA 已明确

第 12 章:安全、合规与多租户

12.1 解决什么问题

LangGraph 应用的「安全」不是单一维度,至少分四层:

  1. 工具安全:LLM 能调用的 API 边界(side effect、不可逆操作、跨租户数据)。
  2. 数据安全:PII / 秘密不能进 state、不能进 checkpoint、不能进 trace。
  3. 多租户安全:org / project / user 之间的数据 / 工具 / 配额严格隔离。
  4. 模型与提示安全:抵抗 Jailbreak、Prompt Leak、Indirect Prompt Injection。

合规维度(GDPR / HIPAA / SOC2)多属行业专属,本节明确区分「通用基线」与「行业专属」。

12.2 推荐做法

12.2.1 工具权限边界(通用基线)

官方 工具安全 = HITL 审批 + 边界检查(来源:docs.langchain.com/oss/python/langgraph/interrupts,2026-06):

  • 不可逆副作用工具(发邮件、转账、删数据)必须在 interrupt() 后审批。
  • 工具参数可被人类编辑后再执行,不是二选一 approve / reject。
  • 工具的 description 决定 LLM 是否误用,质量第一杀手(详见第 6 章)。

推测 三层防御:

  • 入口过滤:用 LangSmith prompt firewall 或中间件扫描 prompt。
  • 审批 :高风险工具默认走 interrupt
  • 事后审计:所有工具调用记录到 append-only 日志。

12.2.2 敏感数据处理(通用基线 + 行业专属)

官方 checkpoint 加密:EncryptedSerializer.from_pycryptodome_aes()LANGGRAPH_AES_KEY(来源:persistence 文档,2026-06)。

官方 LangSmith 数据保留:SaaS 默认 400 天(来源:observability 文档,2026-06)。

官方 + 推测 PII 防护(待补/待验证):

  • 官方 LangSmith 文档未明确给出 PII 脱敏 API;社区常用做法:
    • 在 LLM 调用前用 PII detector(Microsoft Presidio、Comet Opik)做 redact。
    • 在 LangSmith SDK 中通过 metadata 标记 pii: true,然后配置 sampling 规则排除。
    • State 中不存明文 PII,存 token(HMAC / 加密引用),输出前查表还原。

行业专属 GDPR / HIPAA / PCI-DSS:

  • 涉及欧盟用户数据:data residency 必须选 Self-Hosted 部署在 EU 区域(Cloud 区域由 LangChain 提供 EU 集群)。
  • HIPAA:必须 Business Associate Agreement(BAA)+ Self-Hosted / 私有云;Cloud 默认不含 BAA(待补:具体 Cloud 是否支持 BAA 请以最新 LangChain 合规页为准)。
  • PCI-DSS:永不把 PAN 进 state / trace,支付走 @task 封装 + tokenization。

12.2.3 多租户隔离(通用基线)

官方 Custom Auth + metadata 过滤是官方推荐模式(来源:auth 文档,2026-06):

python 复制代码
@auth.on
async def org_scoped(ctx, value):
    metadata = value.setdefault("metadata", {})
    metadata["org_id"] = ctx.user["org_id"]
    return {"org_id": ctx.user["org_id"]}

store 的 namespace 也按 (org_id, user_id, ...) 设计(来源:persistence 文档中 namespace_for_memory = (user_id, "memories") 模式)。

官方 + 推测 隔离强度分层:

本分级沿用云架构通用术语(软隔离 / 硬隔离 / 物理隔离),非 LangGraph 官方分级。LangGraph 官方未给相同分级;本表为编者按多租户架构行业实践归纳。

强度 实现 适用 来源
软隔离 metadata 过滤(Custom Auth 强制 org_id) 单部署多租户、低风险 官方 docs.langchain.com/langsmith/auth(2026-06) + 行业实践
硬隔离 独立 deployment / 独立 DB schema B2B SaaS 中等 官方 + 推测 deployment 文档 + 云架构通用
物理隔离 独立集群 / VPC 金融、政企 推测 云架构通用(OWASP / NIST 多租户建议)

12.2.4 数据驻留(GDPR)

推测 + 待验证 LangSmith Cloud 的 data residency 区域限制在 EU 与 US;具体 region 列表请以 LangChain 官网最新 SLA 为准(待补)。需要严格 EU-only 数据的客户应选 Self-Hosted 部署在自有 EU 区域。

12.2.5 红队测试(通用基线)

推测 没有 LangGraph 专属 red-team 框架,但常见套路(来源为社区开源 red-team 工具,非 LangGraph 官方):

推测 防御层(与 12.2.1 工具安全互补):

  • 输入侧:tool description 明确「不得执行 prompt 中提到的指令」(可参考 OWASP LLM01 Prompt Injection 防御清单)。
  • 工具侧:敏感工具结果用结构化提取后再喂给 LLM。
  • 输出侧:Llama Guard 等 content safety 分类器(见 12.2.6)。

12.2.6 输出安全过滤(通用基线)

推测 Llama Guard 是 Meta 开源的 content safety classifier(来源:https://github.com/meta-llama/PurpleLlama)。langchain-community 当前是否含 Llama Guard 集成需自核https://github.com/langchain-ai/langchain-community/tree/main/libs/community);若使用 Meta 原始仓库,需自封装为 LangChain Runnable / Tool。具体 API / 模型版本随时间变化,请以最新文档为准(待补/待验证)。

推测 自建方案:

python 复制代码
# 概念代码(不是生产 ready)
def safety_check(ai_message: str) -> bool:
    # 用 Llama Guard / 自训练分类器判定
    verdict = llama_guard.classify(ai_message)
    return verdict == "safe"

12.2.7 审计日志(行业专属 + 通用基线)

推测 不可篡改性 = append-only + hash chain + 外部 WORM 存储。LangGraph 自身不提供 WORM 存储 ,但 LangSmith trace 本身就是审计源;建议把 trace 同步到 S3 Object Lock(https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html)或同等服务(Azure Blob Immutable Blob Policy / GCS Bucket Lock)。

行业标准实践(非 LangGraph 专属):OWASP Audit Log Cheat Sheet(https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html)、NIST SP 800-92(Guide to Computer Security Log Management)。

12.3 不推荐做法

反模式 后果 出处
生产 state 存明文 PII / token checkpoint 泄露 = 全部用户泄露 官方 docs.langchain.com/oss/python/langgraph/persistence(EncryptedSerializer 加密) + 推测(合规通用:OWASP LLM02 Sensitive Information Disclosure)
把 user 密码 / API Key 写进 system prompt Prompt Leak 即泄露 推测 OWASP LLM07 Insecure Plugin Design + LangChain 工具 description 最佳实践
多租户只靠 user_id 字段过滤,无 org_id 单点越权 官方 docs.langchain.com/langsmith/auth(org_id metadata 强制)+ 推测
用 LLM 判断权限 不可解释、不可审计 推测 OWASP LLM01 Prompt Injection + 行业实践(auth 必须 deterministic)
Tool 直接连生产数据库 一次 prompt injection = 删库 推测 OWASP LLM06 Excessive Agency + 行业实践(tool sandbox 原则)
LangSmith 留 trace 但不做 PII 脱敏 监管 / 法务风险 官方 docs.langchain.com/langsmith(trace metadata 配置)+ 推测(合规通用)
不区分 dev / prod 数据 dev 误用 prod 真实数据 【编者经验】dev / prod 隔离通用原则
用 Cloud 默认保留 trace 给生产 GDPR / HIPAA 触碰 推测 + 官方待补 LangSmith data residency SLA(附录待补)

12.4 最小代码示例

示例 A:敏感工具的审批(LangGraph 1.2+)

python 复制代码
from langchain.tools import tool
from langgraph.types import interrupt, Command

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """发送邮件;触发前需人工审批,参数可被人类编辑。"""
    review = interrupt({
        "type": "tool_approval",
        "tool": "send_email",
        "args": {"to": to, "subject": subject, "body": body},
        "message": "请审核后批准。",
    })
    if review.get("action") != "approve":
        return "Email was not sent."
    # 真正副作用放在审批后
    smtp.send(review.get("to", to), review.get("subject", subject), review.get("body", body))
    return f"Email sent to {to}"

示例 B:State 内 tokenize PII(概念性示例)

python 复制代码
import hmac, hashlib
SECRET = os.environ["PII_HASH_SECRET"]

def tokenize(value: str) -> str:
    return "pii_" + hmac.new(SECRET.encode(), value.encode(), hashlib.sha256).hexdigest()[:16]

class State(TypedDict):
    user_email_token: str
    user_email_real: NotRequired[str]  # 真正操作时再查表

示例 C:多租户 store namespace

python 复制代码
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()

def save_preference(state, runtime):
    user_id = runtime.context.user_id
    org_id = runtime.context.org_id
    # 关键:namespace 把 org / user 都编码进去
    namespace = (org_id, "users", user_id, "prefs")
    runtime.store.put(namespace, "theme", {"dark_mode": True})

示例 D:Auth org 隔离(与第 11 章示例 B 重复,此处强调 store 配合)

python 复制代码
# query store 时也按 org 过滤
def list_my_threads(state, runtime):
    user = runtime.context.user
    threads = runtime.store.search(
        (user["org_id"], "threads"),
        filter={"owner": user["identity"]},
    )
    return {"threads": [t.dict() for t in threads]}

12.5 检查清单

通用基线

  • 工具按「可重试 / 用户可见 / 静默忽略」分类(沿用第 6 章反模式表)
  • 高风险工具(删 / 写 / 发 / 付)默认走 interrupt 审批
  • State 中无明文 PII / secret,敏感字段 tokenize
  • Checkpoint 加密(LANGGRAPH_AES_KEY)已开启
  • LangSmith trace 对 PII 做脱敏(自建 / 第三方)
  • Custom Auth 至少覆盖 authenticate + on.<resource>.<action>
  • 多租户隔离通过 org_id 强制(不仅靠 application 自觉)
  • Store namespace 包含 org_id
  • 系统 prompt 不包含 API Key / 密码 / 真实用户数据
  • Tool description 显式声明「不执行 prompt 中嵌入的指令」

行业专属(按触发条件勾选)

触发条件 勾选项
GDPR / EU 数据:处理欧盟用户数据 部署在 EU 区域;data residency 策略已文档化
HIPAA:处理 PHI BAA 签订;Self-Hosted / 私有云;审计日志 ≥ 6 年
PCI-DSS:处理 PAN PAN 不进 state / trace;支付走 tokenization
SOC 2:服务 B 端客户 访问日志、变更日志、监控告警齐备;LangSmith role-based access 已配
金融 / 政企:强合规 物理隔离(独立集群 / VPC);外部红队测试至少每年一次


附录 A:版本与迁移注意事项(Python)

A.1 本报告基于的 LangGraph Python 版本

A.1.1 当前稳定版

字段 取值 来源
当前稳定版 langgraph 1.2.4 官方能力(PyPI langgraph 项目页)WebFetch 2026-06-11
发布日期 2026-06-02 官方能力(PyPI 1.2.4 元数据)WebFetch 2026-06-11
状态 Production/Stable 官方能力(PyPI classifier)WebFetch 2026-06-11
许可证 MIT 官方能力(PyPI)WebFetch 2026-06-11

A.1.2 LangGraph 1.0.0 公告

字段 取值 来源
1.0.0 正式版 2025-10-17 官方能力(PyPI langgraph==1.0.0WebFetch 2026-06-11
1.0.0 发布说明 "promoted to 1.0 with no breaking changes" 官方能力(LangChain blog 原文:https://blog.langchain.com/langgraph-v1/,现 redirect 至 https://www.langchain.com/blog/langgraph-v1WebFetch 2026-06-11
1.0.0 主要变更 ① 移除 Python 3.9 支持(PR #6289 "chore: drop Python 3.9 (and syntax)")② "rename away from LangGraph Platform"(PR #6281;改名后新名见下方说明)③ 加入 Python 3.14 cursory 支持(PR #6298) 官方能力(GitHub release 1.0.0 + 1.0.0rc1 changes-since 列表;#6298 来自该列表中的 Python 3.14 cursory support 条目)WebFetch 2026-06-11
1.0.0 commit c4144bb 官方能力(GitHub release 1.0.0 头部)WebFetch 2026-06-11
1.0.0 公告期 RC 链 见下方"1.0.0 公告期 RC 链"列表 官方能力(PyPI 版本历史)WebFetch 2026-06-11

1.0.0 公告期 RC 链(PyPI 版本历史,WebFetch 2026-06-11):

里程碑 发布日期
1.0.0a1 2025-08-27
1.0.0a2 2025-09-02
1.0.0a3 2025-09-07
1.0.0a4 2025-09-29
1.0.0rc1 2025-10-17
1.0.0 2025-10-17

命名口径补充 :1.0.0 起 "LangGraph Platform" 改名后新名为 LangSmith Deployment。正文部署章节见第 11 章 §11.2.1;Platform 三层 v2 大纲命名(Cloud / Self-Hosted Lite / Self-Hosted Enterprise)与官方文档原词(Cloud / Standalone server / Self-hosted)的映射见本附录 A.4。
冲突标注 :LangChain 官方 blog 自称 "1.0 with no breaking changes",但 1.1.0 release notes 中已显式标记 LangGraphDeprecatedSinceV11(即对"在 1.1 中已不推荐使用"的项目会抛 DeprecationWarning),同时 1.0.0rc1 起 "drop Python 3.9",对仍依赖 3.9 的项目即为 破坏性建议读者将"no breaking changes"理解为"1.0 相对 0.6.x 行为语义保持" ,但运行平台与命名空间存在破坏。来源:官方能力(blog)vs 官方能力(GitHub release notes)------ 冲突已显式记录

A.1.3 1.x 关键节点时间线(节选自 PyPI)

版本 日期 关键变化 来源
1.0.0 2025-10-17 drop Python 3.9、rename away from LangGraph Platform、Python 3.14 cursory 官方能力(PyPI / GitHub release)WebFetch 2026-06-11
1.0.5 2025-12-12 (patch,无显著语义变更条目) 官方能力(PyPI)WebFetch 2026-06-11
1.0.10 2026-02-27 1.0.x 末位 patch 官方能力(PyPI)WebFetch 2026-06-11
1.1.0 2026-03-10 引入 v2 streaming 格式(opt-in) 、GraphOutput 类型、StreamPart typed dicts、LangGraphDeprecatedSinceV11 警告 官方能力(GitHub release tag 1.1.0)WebFetch 2026-06-11
1.1.7 2026-04-17 yanked:"introduced bug with custom callback handlers" 官方能力(PyPI yank reason)WebFetch 2026-06-11
1.1.8 2026-04-17 接替 1.1.7 官方能力(PyPI)WebFetch 2026-06-11
1.2.0 2026-05-12 ① durable error-handler resume across host crashes(PR #7773)② set_node_defaults() 加入 StateGraph(PR #7747)③ checkpoint 层 force delta channel snapshot after max supersteps(PR #7746)④ checkpoint-sqlite 用 streaming walk 覆盖 get_delta_channel_history(PR #7702)⑤ DeltaChannel 与 delta-history APIs 标记为 beta(PR #7732)⑥ langchain-core 升到 1.4.0(PR #7767) 官方能力(GitHub release tag 1.2.0 changes-since-1.2.0a7)WebFetch 2026-06-11
1.2.2 2026-05-26 id=None 的 BaseMessage 在 DeltaChannel checkpoint 写入前分配稳定 ID(#7913);检查点升到 4.1.1 官方能力(GitHub release 1.2.2 / 1.2.1)WebFetch 2026-06-11
1.2.3 2026-06-01 yanked :"unintended merging strategy regression";曾包含 v3 streaming support to RemoteGraph、ProtocolEvent.eventIdevent_id 重命名 官方能力(PyPI yank + GitHub release)WebFetch 2026-06-11
1.2.4 2026-06-02 当前 stable;fix: 工厂图集成测试路径(#7978);保持 _on_started 与旧 override 向后兼容(#7987) 官方能力(PyPI / GitHub release 1.2.4)WebFetch 2026-06-11

待补/待验证 :1.2.x 中"v3 streaming"、"eventId→event_id"这些远程协议层改动的具体客户端影响面,建议补查 langgraph-sdk 0.4.0 之后变更(WebFetch 2026-06-11 已知其 0.4.0 是 v3 streaming 主变更批次)。

A.1.4 关键依赖版本(取自 langgraph==1.2.4 PyPI 元数据)

依赖包 版本约束 来源
python >=3.10 官方能力(PyPI requires_pythonWebFetch 2026-06-11
langchain-core <2,>=1.4.0 官方能力(PyPI requires_distWebFetch 2026-06-11
langgraph-checkpoint <5.0.0,>=4.1.0 官方能力(PyPI requires_distWebFetch 2026-06-11
langgraph-prebuilt <1.2.0,>=1.1.0 官方能力(PyPI requires_distWebFetch 2026-06-11
langgraph-sdk <0.5.0,>=0.4.2 官方能力(PyPI requires_distWebFetch 2026-06-11
pydantic >=2.7.4 官方能力(PyPI requires_distWebFetch 2026-06-11
xxhash >=3.5.0 官方能力(PyPI requires_distWebFetch 2026-06-11

生态对应版本(PyPI 最新,事实底座见上方依赖表与本附录 A.1.4 约束)

生态包 最新版 发布日期 与 langgraph 1.2 兼容性
langgraph-checkpoint 4.1.1 2026-05-22 满足 >=4.1.0,<5.0.0
langgraph-checkpoint-postgres 3.1.0 2026-05-12 与 1.2.x 同日 release,建议与 langgraph 1.2.x 同步升级
langgraph-checkpoint-sqlite 3.1.0 2026-05-12 同上
langgraph-sdk 0.4.2 2026-06-01 满足 >=0.4.2,<0.5.0
langgraph-cli 0.4.28 2026-06-10 与 langgraph core 解耦;CLI api bound 已 bump 到 0.10.0
langgraph-prebuilt 1.1.0 2026-05-12 满足 >=1.1.0,<1.2.0;与 langgraph 1.2.x 同步升级
langchain-core 1.4.5 2026-06-11 满足 >=1.4.0,<2;langchain-core 1.0.0 = 2025-10-17,与 langgraph 1.0.0 同日发布

冲突标注langgraph-prebuilt<1.2.0 的上界与"prebuilt 1.1.0 已与 langgraph 1.2.x 配套发布"是否冲突------未冲突 ,因为 langgraph 1.2.xrequires_dist 要求 prebuilt 1.1.0~<1.2.0。待验证:当前 prebuilt 1.1.0 已发;prebuilt 1.2.x 何时出现、是否与 langgraph 1.3.x 配套仍待补。

A.1.5 最低支持 Python 版本

字段 取值 来源
最低 Python 3.10(3.10 / 3.11 / 3.12 / 3.13 全部支持,含 CPython 与 PyPy) 官方能力(PyPI classifiers / requires_python)WebFetch 2026-06-11
Python 3.9 不再支持(1.0.0rc1 起 chore: drop) 官方能力(GitHub release 1.0.0rc1 changes-since 0.6.10)WebFetch 2026-06-11
Python 3.14 "cursory support"(1.0.0 起) 官方能力(PR #6298)WebFetch 2026-06-11

A.2 影响最佳实践判断的关键 Python API 差异

本节只标出"会影响最佳实践选型/迁移路径"的 API 入口;不写 API 字典。

A.2.1 执行入口:compile / invoke / ainvoke / stream / astream

  • compile() 仍是图编译入口。未变更
  • invoke / ainvoke / stream / astreamversion 参数:
    • 默认 version="v1":与历史行为一致,stream() 产出裸 tuple,invoke() 返回 plain dict,interrupts 位于 "__interrupt__" key。
    • version="v2"(1.1.0 起 opt-in)
      • invoke() 返回 GraphOutput,属性 .value.interrupts
      • stream() 产出 typed StreamPart 字典(带 type 字段)。
      • Pydantic / dataclass 状态 schema 时自动 coerce 输出(result.value 不再是 dict 而是 schema 实例)。
    • 来源:官方能力(GitHub release tag 1.1.0)WebFetch 2026-06-11
  • 迁移策略:默认行为不变 ,可在个别调用点加 version="v2" 增量迁移。最佳实践:新代码直接 v2;旧代码先继续 v1,逐步替换。

A.2.2 checkpointer 选型

Saver 导入 适用场景 关键说明
InMemorySaver from langgraph.checkpoint.memory import InMemorySaver 单元测试 / 单进程内存 官方文档与 ch04-06 正文主推 InMemorySaver。源码保留 MemorySaver = InMemorySaver # Kept for backwards compatibility,未标 deprecated;旧代码可继续 import MemorySaver,新代码建议直接用 InMemorySaver
SqliteSaver / AsyncSqliteSaver 同步:from langgraph.checkpoint.sqlite import SqliteSaver;异步:from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver 本地/单机轻量持久化 langgraph-checkpoint-sqlite 3.1.0(2026-05-12);async 类名为 AsyncSqliteSaver
PostgresSaver / AsyncPostgresSaver 同步:from langgraph.checkpoint.postgres import PostgresSaver;异步:from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver 生产/分布式 langgraph-checkpoint-postgres 3.1.0(2026-05-12)。关键 :首次使用 Postgres 时 await checkpointer.setup() 建表;未用 Alembic;async 类名为 AsyncPostgresSaver
第三方 RedisSaver 社区包 已成熟 不在 langgraph 主仓,由社区维护(如 langgraph-checkpoint-redis 系列),不在本附录权威列表中 ------待补/待验证其与 1.2.x 的兼容矩阵。

checkpointer schema 迁移(来源:官方能力 WebFetch 2026-06-11):

  • 首次使用 Postgres 时 await checkpointer.setup() 即可建表。
  • 自建连接须 autocommit=True + row_factory=dict_row
  • langgraph-checkpoint-postgres 3.0.0(2025-10-20)引入 "Restrict 'json' type deserialization" 安全收紧(WebFetch 2026-06-11),需要复查旧 checkpoint 中 json 字段反序列化行为。
  • 推荐设置 LANGGRAPH_STRICT_MSGPACK=true 或显式传 allowed_msgpack_modules,限制 msgpack 反序列化。

A.2.3 interrupt / Command(resume=...)

  • 入口:from langgraph.types import interrupt, Command
  • 形式:节点内 value = interrupt({"question": ...});恢复时 graph.invoke(Command(resume=value), config)
  • 1.0 → 1.1 兼容性invoke() 返回值的旧式 result["__interrupt__"] 形式在 version="v2" 下已 deprecated ,会发 LangGraphDeprecatedSinceV11,计划 v3.0 移除WebFetch 2026-06-11 GitHub release 1.1.0)。
  • 最佳实践 :新代码用 result.interrupts(v2);混合代码用 version="v1" 维持旧 dict 行为至全量迁移完成。

A.2.4 Send / dynamic parallelism

  • 入口:from langgraph.constants import Send
  • 用法:graph.add_conditional_edges("node_a", lambda s: [Send("node_b", {...}) for x in s["items"]]).
  • 未在 1.0--1.2 公告中出现破坏性变更 (基于 GitHub release 1.0.0/1.1.0/1.2.0 changes-since 列表比对,WebFetch 2026-06-11)。待验证:alpha 阶段(1.0.0a4)前是否还有遗留问题。

A.2.5 Functional API @task / @entrypoint

  • 入口:from langgraph.func import entrypoint, task
  • 用途:把"函数调用"当持久化 task 调度,配合 checkpointer 实现断点续跑。
  • 1.x 期间无破坏性变更(WebFetch 2026-06-11),仍是 task.get() / await task 形式。
  • 最佳实践:复杂图用 StateGraph,线性/简单流程用 Functional API,二者可在同一图内混用。

A.2.6 ToolNode / create_react_agent

  • 入口:from langgraph.prebuilt import ToolNode, create_react_agent
  • create_react_agent(model, tools, ...):官方预置 ReAct 风格 agent 工厂。
  • ToolNode:把 LangChain @tool 列表转成图节点;与 messages_state reducer 配合可自动处理 tool_call / tool_message 状态变更。
  • langgraph-prebuilt 1.1.0 (2026-05-12)满足 langgraph==1.2.4>=1.1.0,<1.2.0 依赖约束,WebFetch 2026-06-11 表明 prebuilt 已进入 1.x 稳定线。
  • 待补/待验证:create_react_agent 1.2.x 与 langchain-core 1.4.0 之间的 model provider 兼容矩阵(需另行核对各 provider 包版本)。

A.2.7 BaseStore / 长期记忆

  • 入口:from langgraph.store.memory import InMemoryStore / from langgraph.store.postgres import PostgresStore
  • 用途:跨 thread、跨用户的键值型长期记忆。
  • 1.0--1.2 期间未观察到破坏性变更WebFetch 2026-06-11 GitHub release 1.0/1.1/1.2 公告未提及 store 破坏变更)。
  • 待验证 :1.2.x set_node_defaults(PR #7747)是否影响 store 的 namespace 解析路径------官方 release notes 未点明,待补

A.3 迁移 checklist(5--10 项)

每条标注"为何要做"和"如何验证"。仅覆盖 Python

1. 0.x → 1.0 重大变化

  • Python 3.9 不再支持 → 升级 Python 到 ≥3.10(WebFetch 2026-06-11 官方 changelog)。
    • 验证:python --version ≥ 3.10;CI 矩阵移除 3.9。
  • 命名空间变更 "LangGraph Platform" 退出 (PR #6281)→ 文档/产品名已不再用 "Platform";不影响 Python API 名称。
    • 验证:grep 内部文档/产品名是否存在 "LangGraph Platform" 字样。
  • interrupts 仍以 "__interrupt__" 形式暴露于 invoke() 返回值 (v1 行为)→ 1.0 阶段未变更。
    • 验证:迁移后跑现有 HITL 集成测试。

2. 1.0 → 1.1 变化

  • version="v2" 可选开启 → 旧代码无需改动;新代码可加 version="v2"GraphOutput / StreamPart
    • 验证:在测试中分别用 v1 / v2 调用同一图,比对 interrupts 读取方式。
  • LangGraphDeprecatedSinceV11 警告启用 → result["key"] / result["__interrupt__"] 仍可用但发警告,v3.0 移除WebFetch 2026-06-11 官方 release notes)。
    • 验证:CI 设 -W error::DeprecationWarning:langgraph.* 抓 1.1 期间误用。

3. 1.1 → 1.2 变化

  • DeltaChannel 与 delta-history APIs 标记为 beta (PR #7732)→ 形态未稳定,不建议生产直接用 delta 通道。
    • 验证:禁用 DeltaChannel 相关自定义逻辑后回归。
  • set_node_defaults() 新增 (PR #7747)→ 可在 StateGraph 层定义节点默认值。
    • 验证:测试新 API 与既有 add_node(..., metadata=...) 行为一致。
  • langchain-core 升到 1.4.0 (PR #7767)→ 可能引入上游破坏,检查 BaseMessage / tool_call / structured output 行为。
    • 验证:跑完整 agent 集成测试。
  • durable error-handler resume across host crashes (PR #7773)→ 错误处理路径在主机崩溃后可恢复。
    • 验证:构造异常路径,验证恢复语义。
  • 1.2.2 修复:id=None 的 BaseMessage 在 DeltaChannel 写入前分配稳定 ID (PR #7913)→ 避免"同消息多次 hash 出不同 ID"问题。
    • 验证:含 id=None 的消息在 checkpoint 持久化时 ID 稳定。

4. Pydantic v1 → v2 兼容

  • langgraph 1.2.4 requires pydantic>=2.7.4WebFetch 2026-06-11 PyPI)→ 1.x 系列只支持 Pydantic v2。
    • 验证:检查依赖锁文件中 pydantic 版本。
  • 项目中若有 Pydantic v1 自定义 model,不能用 BaseModel v1 写法直接喂给 StateGraph ;需用 v2 语法(model_configfield_validator)。
    • 验证:from pydantic import BaseModel 必须是 v2;不混用 from pydantic import v1 兼容包。

5. 升级前必读 changelog

  • 三个权威入口(官方能力 ):
  • yanked 版本警示WebFetch 2026-06-11 PyPI yank reason):
    • 1.1.7:自定义 callback handler bug → 跳到 1.1.8
    • 1.2.3:merging strategy regression → 跳到 1.2.4
    • 0.4.4:Incorrect dependency range。
    • 0.2.29 / 0.2.30 / 0.2.31:Uses internal APIs of upstream package。
    • 0.3.0:Missing dependency on langgraph-prebuilt。
    • 0.6.9:yanked。

6. checkpointer schema 迁移

  • 不走 Alembic ,调用 await checkpointer.setup()WebFetch 2026-06-11 PyPI langgraph-checkpoint-postgres 描述)。
    • 验证:首次启动日志含建表 SQL。
  • Postgres 自建连接 必须 autocommit=True + row_factory=dict_rowWebFetch 2026-06-11)。
    • 验证:手动建连接后跑一次 setup(),不报 SET AUTOCOMMIT 类错误。
  • langgraph-checkpoint-postgres 3.0.0 起收紧 json 反序列化(WebFetch 2026-06-11),旧 checkpoint 读取时需 allowed_msgpack_modules
    • 验证:跨版本读取回放测试。
  • sqlite / postgres 3.1.0 与 langgraph 1.2.x 同步(2026-05-12),升级时一起升。
    • 验证:CI 中固定 (langgraph, langgraph-checkpoint-postgres) >= 3.1.0 配对。

7. 验证消息 reducer 行为

  • add_messages reducer 行为:在 1.x 期间官方未公告破坏(WebFetch 2026-06-11),但 1.2.2 修复了"无 ID 消息在 DeltaChannel 写入前分配稳定 ID",意味着 1.2.2 之前 写出去的 checkpoint 可能在 delta 通道中含不稳定 ID。
    • 验证:含多轮 tool call / tool message 的会话在不同 patch 版本间回放,比对 message ID 集合。

8. 验证 interrupt resume 语义

  • v1 阶段:中断以 "__interrupt__" 暴露;v2 阶段:result.interrupts 暴露。
    • 验证:HITL 集成测试同时跑 v1 / v2,对比 (interrupt_payload, resume_payload) 一致。
  • 跨进程恢复:v1.2 起 durable error-handler resume across host crashes 改变恢复边界(WebFetch 2026-06-11)。
    • 验证:kill -9 拉起后,next invoke 能正确 resume。

9. 锁文件 / 依赖管理

  • langgraph>=1.2.4 隐式要求 langchain-core>=1.4.0,<2pydantic>=2.7.4
    • 验证:pip check 无冲突;uv lock 重新生成。
  • langgraph-prebuilt<1.2.0 :prebuilt 1.2.x 出现时需升级 langgraph 主版本。
    • 验证:CI 中不出现 prebuilt==1.2.0langgraph==1.2.x 混用。

10. 撤回到旧版本的回滚条件

  • 若升 1.2.4 后命中回归,可回滚到 1.2.2 (1.2.3 yanked,建议跳过)。
    • 验证:维护可用的"上一个 stable"作为应急回滚点。

A.4 Deprecated Python API 与替代方案

本节按"已公告 / 源码可见 / 待验证"三档标注。

A.4.1 官方公告的 deprecation(官方能力)

API deprecation 信号 替代方案 来源
result["key"]"key" in resultresult["__interrupt__"] 形式读取 v2 调用结果 LangGraphDeprecatedSinceV11(1.1.0 起) result.valueresult.interrupts(v2 形式) 官方能力(GitHub release 1.1.0)WebFetch 2026-06-11
get_state_version / get_state_history 的旧返回结构 1.x 期间未在 release notes 中显式 deprecate,但 v1.0 起 langgraph.types 公开 typed stream parts,待补/待验证(详见文末「已知限制 / 待补项」W3) 待验证 来源不足------标注待补
NodeStyleSend 旧名 待验证;详见文末「已知限制 / 待补项」W3) langgraph.constants.Send 来源不足------标注待补
ConfigurableCallable 旧名 待验证;详见文末「已知限制 / 待补项」W3) 待补 来源不足------标注待补

A.4.2 源码 / 文档可观察的命名变化

旧 / 别名 当前 / 推荐 备注 来源
langgraph.prebuilt.ToolExecutor langgraph.prebuilt.ToolNode prebuilt 模块拆分,ToolExecutor 在 0.2.x 后被 ToolNode 取代 推测性建议 ------基于代码结构与 0.2.x 重构推断;官方未在本轮 WebFetch 2026-06-11 抓到的 release notes 中明确公告 ------待补/待验证
langgraph.checkpoint.memory.MemorySaver(源码别名) langgraph.checkpoint.memory.InMemorySaver实际公开类 源码 __init__.py 注释 MemorySaver = InMemorySaver # Kept for backwards compatibility,未标 deprecated 官方能力(WebFetch 2026-06-11 官方 persistence 文档明确使用 InMemorySaver;ch04-06 4.1 节定稿已与本表一致)。待验证 1.3+ 是否将 InMemorySaver 提为唯一名。
命名空间 "LangGraph Platform" 1.0.0 起改名为部署/平台相关新名称 PR #6281 官方能力(WebFetch 2026-06-11 GitHub release 1.0.0rc1)

A.4.3 不属于 deprecation 但常见的"易错点"(社区经验 / 推测性建议)

现象 替代做法 来源类型
RedisSaver 不在 langgraph 主仓 langgraph-checkpoint-redis 等社区包;升级前务必在测试环境验证与 1.2.x 的兼容矩阵 推测性建议(WebFetch 2026-06-11 多次抓 langchain-ai.github.io 文档站均被 redirect 到主站,未抓到明确说明)------待补
langgraph.prebuilt.create_react_agent 传入 Anthropic / OpenAI / 其他 provider 时 同时锁 langchain-core<2,>=1.4.0 与 provider 包版本 推测性建议(基于 PyPI 依赖约束 WebFetch 2026-06-11 推断)
pydantic>=2.7.4 显式要求 旧项目若仍用 pydantic==1.x,先升 v2 官方能力(WebFetch 2026-06-11 PyPI requires_dist)

待补/待验证总览 :本节 get_state_version / NodeStyleSend / ConfigurableCallable / RedisSaver 官方推荐替代等条目信息源不足,建议下一步

  1. 直接查 langgraph/__init__.pylanggraph/types.py 源码 grep DeprecationWarning
  2. 抓取 https://langchain-ai.github.io/langgraph/reference/ 各子页(WebFetch 2026-06-11 多次重定向未抓到内容,可能需绕过 SPA 路径)。

A.5 团队代码升级策略

来源类型 :A.5.1 / A.5.2 / A.5.3 主要为 推测性建议 (基于 langgraph API 形态 + 通用发布工程经验),A.5.4 监控项基于社区经验(主流可观测性实践)。

A.5.1 灰度发布(按 assistant_id 路由)

  • 原理 :langgraph 1.x 在多租户/多 agent 场景下常用 assistant_id / graph_id 标识一个图配置。把不同 assistant_id 指向不同版本(如 v1.0.10 stable / v1.2.4 candidate)做蓝绿/金丝雀。
  • 路由实现推测性建议 ):
    • 在网关层按 assistant_id 决定调用的 langgraph runtime 进程 / 容器。
    • 进程内通过 compiled_graph = builder.compile(checkpointer=...) 加载固定版本。
  • 验证指标
    • 同一业务请求分别在 v1.0.10 / v1.2.4 跑一遍,对比 end-to-end 状态快照(thread state 序列化结果)。
    • 监控 interrupt 触发次数、stream 延迟、checkpoint 写入耗时。
  • 来源 :推测性建议(基于 langgraph 编译/部署模式 + WebFetch 2026-06-11 PyPI 元数据)。待验证 :官方是否提供内置 assistant_id 多版本路由(langgraph-sdk 0.4.x 提供 thread/runs/assistants API,待补其多版本路由文档)。

A.5.2 蓝绿部署

  • 原理:v1.0.10 stable 部署为"蓝",v1.2.4 candidate 部署为"绿",网关切换。
  • 共享 checkpointer 注意事项推测性建议 + 社区经验 ):
    • 蓝绿可共用一套 Postgres checkpointer;前提langgraph-checkpoint-postgres 版本一致(建议两边都升到 3.1.0)。
    • 若蓝绿 checkpointer schema 不同,必须走 schema-versioned 命名(如 checkpoints_v3_1_0)并通过 setup() 建表。
  • 回滚条件
    • 命中率(latency P99 / 错误率)超过阈值;
    • interrupt resume 跨进程失败率 > 0;
    • Pydantic 校验错误率上升。
  • 来源:推测性建议(基于 WebFetch 2026-06-11 PyPI 生态包版本一致性要求)。

A.5.3 回滚方案

  • 代码层
    • 锁文件 requirements.txt / pyproject.toml / uv.lock 明确锁定可回滚版本组合。
    • 维护"上一个 stable" tag / Docker 镜像(如 langgraph==1.2.2 为当前 1.2.x 内可回滚点,跳过 1.2.3 yanked)。
    • 官方能力 WebFetch 2026-06-11:1.2.3 已被 yank(merging strategy regression);不要 pin 1.2.3。
  • 数据层
    • checkpoint 升级前做一次全量 snapshot(pg_dump)。
    • 跨版本回放测试:把 v1.2.4 写的 checkpoint 用 v1.0.10 读一遍(反向),验证 schema 兼容矩阵。
    • 来源:推测性建议;待补官方关于"checkpointer 向前 / 向后兼容矩阵"的明确文档。
  • 流量层
    • API 网关侧配置版本开关,可秒级切回。
    • 监控切回时的 thread state 写入成功率。

A.5.4 监控与验证指标

以下指标项是 社区经验 (主流 agent runtime 实践)+ 推测性建议

指标 采集方式 阈值参考
stream 各 mode 延迟(P50 / P99) stream_mode 回调中打点(us 自定义) v1 vs v2 需在测试中建立 baseline
interrupt 触发率 / 恢复率 LangSmith trace / 自定义事件 恢复率应 ≈ 100%(除非业务主动取消)
checkpoint 写入耗时 DB 慢查询日志 / ORM metrics 与历史版本偏差 < 20%
异常恢复成功率 Command(resume=...) 路径 kill -9 后第一次 invoke 成功
__interrupt__result.interrupts 双形态一致性 灰度期 A/B 同时输出 一致率 ≈ 100%
LangGraphDeprecatedSinceV11 警告计数 日志聚合(过滤 DeprecationWarning source) 升级后应为 0(旧调用点已迁移)
pydantic 校验错误率 结构化输出相关 trace 不应因 langchain-core 1.4.0 升级而上升
  • 可观测性依赖
    • LangSmith(官方)--- 通过 LANGSMITH_TRACING=true 开启;
    • OpenTelemetry exporter(社区)--- 通过 langchain-core 内置 callbacks。
  • 来源 :社区经验 + 推测性建议(WebFetch 2026-06-11 多次抓取官方 langchain-ai.github.io 文档站均重定向未抓到内容------待补对官方推荐 metrics 列表的精确引用)。

A.6 关键引用清单(按 URL 归集)

每条标注 "类型(官方/社区/推测) + 资料日期 + 引用范围"。

  1. https://pypi.org/project/langgraph/ --- 官方能力 / 2026-06-11 抓取 / 版本历史、依赖、Python 兼容性。
  2. https://pypi.org/project/langgraph/#history --- 官方能力 / 2026-06-11 抓取 / 0.x--1.2.4 完整时间线。
  3. https://pypi.org/pypi/langgraph/1.2.4/json --- 官方能力 / 2026-06-11 抓取 / 1.2.4 requires_dist
  4. https://pypi.org/project/langgraph-checkpoint/ --- 官方能力 / 2026-06-11 / 4.1.1 + Python ≥3.10。
  5. https://pypi.org/project/langgraph-checkpoint-postgres/ --- 官方能力 / 2026-06-11 / 3.1.0 + setup() 迁移方法。
  6. https://pypi.org/project/langgraph-checkpoint-sqlite/ --- 官方能力 / 2026-06-11 / 3.1.0。
  7. https://pypi.org/project/langgraph-sdk/ --- 官方能力 / 2026-06-11 / 0.4.2。
  8. https://pypi.org/project/langchain-core/ --- 官方能力 / 2026-06-11 / 1.4.5;1.0.0 = 2025-10-17。
  9. https://github.com/langchain-ai/langgraph/releases/tag/1.0.0 --- 官方能力 / 2026-06-11 / 1.0.0 头部与 changes-since 1.0.0rc1。
  10. https://github.com/langchain-ai/langgraph/releases/tag/1.1.0 --- 官方能力 / 2026-06-11 / v2 streaming 格式、GraphOutputStreamPartLangGraphDeprecatedSinceV11
  11. https://github.com/langchain-ai/langgraph/releases/tag/1.2.0 --- 官方能力 / 2026-06-11 / 1.2.0 changes-since 1.2.0a7、DeltaChannel 标 beta、set_node_defaultslangchain-core 1.4.0 bump。
  12. https://github.com/langchain-ai/langgraph/releases --- 官方能力 / 2026-06-11 / 1.2.x 后期小版本变更。
  13. https://www.langchain.com/blog/langgraph-v1 --- 官方能力 / 2026-06-11(redirect 抓取)/ "1.0 with no breaking changes" 公告原文。
  14. https://langchain-ai.github.io/langgraph/ --- 官方能力 / 2026-06-11 多次重定向未抓到内容------待补/待验证


附录 B:代码片段库

0. 版本基线

本附录除特别注明外,默认以下版本基线(截至 2026-06-11):

依赖包 最低版本 备注
langgraph >=1.0 LangGraph 1.0 于 2025-10-22 发布,本附录基线版本
langgraph-checkpoint >=2.0 与 langgraph 1.x 配套
langgraph-supervisor 0.0.31 2025-11-19 发布
langgraph-swarm 0.1.0 2025-12-04 发布
langchain >=1.0 LangChain 1.0 与 LangGraph 1.0 同期发布
langsmith >=0.4.25 OTEL fixes、pytest 集成需 >=0.3.4,metadata 需 >=0.7.13
Python >=3.10 langgraph-supervisor 明确要求 ≥3.10;部分 streaming 能力在 >=3.11 才支持

来源


1. 最小 StateGraph + compile + invoke 🟢

适用场景 :单节点入门、单元测试骨架、教学演示。

依赖langgraph>=1.0

Python>=3.10

python 复制代码
# pip install "langgraph>=1.0"
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END

class State(TypedDict):
    value: str

def node_echo(state: State) -> dict:
    return {"value": state["value"] + " -> echoed"}

workflow = StateGraph(State)
workflow.add_node("echo", node_echo)
workflow.add_edge(START, "echo")
workflow.add_edge("echo", END)

graph = workflow.compile()

result = graph.invoke({"value": "hello"})
print(result)  # {'value': 'hello -> echoed'}

要点

  • 任何图在调用前都必须 .compile();compile 负责结构校验与挂载 runtime 参数(checkpointer、interrupt 列表等)。
  • START / END 是虚拟节点,用于声明入口与出口边。

资料来源


2. TypedDict + Annotated Reducer + add_messages 🟢

适用场景 :多轮对话、消息流合并、并发节点对同一字段的累加。

依赖langgraph>=1.0langchain>=1.0(提供 AnyMessage)。

Python>=3.10

python 复制代码
# pip install "langgraph>=1.0" "langchain>=1.0"
from typing import Annotated
from typing_extensions import TypedDict
from operator import add

from langchain.messages import AnyMessage, HumanMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

class ChatState(TypedDict):
    # add_messages:处理 message id 去重与按序合并,支持工具消息追加
    messages: Annotated[list[AnyMessage], add_messages]
    # operator.add:列表 / 数值的简单累加 reducer
    trace: Annotated[list[str], add]

def step_a(state: ChatState) -> dict:
    return {
        "messages": [HumanMessage(content="hi")],
        "trace": ["a"],
    }

def step_b(state: ChatState) -> dict:
    return {"trace": ["b"]}

graph = (
    StateGraph(ChatState)
    .add_node("a", step_a)
    .add_node("b", step_b)
    .add_edge(START, "a")
    .add_edge("a", "b")
    .add_edge("b", END)
    .compile()
)

print(graph.invoke({"messages": [], "trace": []}))

要点

  • Annotated[X, reducer] 告诉 LangGraph 这个字段不是「整体替换」,而是用 reducer 合并。
  • add_messages 是 message-aware reducer:支持 id 去重、按 id 替换、tool message 追加。
  • 默认(不写 Annotated)行为是「整体覆盖」------并发节点会互相覆盖,调试困难。

资料来源


3. 条件路由(Conditional Edge)🟢

适用场景 :决策树、按状态分发到不同子图、map-reduce fan-out。

依赖langgraph>=1.0

Python>=3.10

python 复制代码
# pip install "langgraph>=1.0"
from typing import Literal
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.types import Send

class State(TypedDict):
    topic: str
    subjects: list[str]

# 路由函数必须是纯函数:只读 state,输出下一节点名(或 Send 列表)
def route_by_topic(state: State) -> Literal["joke", "summary"]:
    return "joke" if state["topic"] == "humor" else "summary"

def joke(state: State) -> dict:
    return {"topic": state["topic"] + ":joke"}

def summary(state: State) -> dict:
    return {"topic": state["topic"] + ":summary"}

# 动态 fan-out:每个 subject 一个 Send 任务
def fan_out(state: State) -> list[Send]:
    return [Send("joke", {"topic": s, "subjects": []}) for s in state["subjects"]]

builder = StateGraph(State)
builder.add_node("joke", joke)
builder.add_node("summary", summary)

# 路由函数 + 显式映射表,便于阅读
builder.add_conditional_edges(
    START,
    route_by_topic,
    {"joke": "joke", "summary": "summary"},
)
builder.add_edge("joke", END)
builder.add_edge("summary", END)

graph = builder.compile()
print(graph.invoke({"topic": "humor", "subjects": []}))

要点

  • 路由函数必须是纯函数:不要在里面调用 LLM、写数据库或修改 state。
  • 同一节点不要混用 普通 edge 与 conditional/Command 路由------两条路径都会执行。
  • 需要「修改 state 的同时跳转」时,优先用 Command(update=..., goto=...) 而非 conditional edge。
  • 真正动态的扇出(不知道目标节点数)用 Send

资料来源


4. create_react_agent vs 自定义 StateGraph 🟢

适用场景

  • create_react_agent(工厂方法):标准 ReAct(LLM + 工具循环)足够时,最小代价上手。
  • 自定义 StateGraph:需要并行节点、复杂 reducer、自定义 HITL 行为、非消息型 state 时。

依赖langgraph>=1.0langchain>=1.0

Python>=3.10

python 复制代码
# pip install "langgraph>=1.0" "langchain>=1.0" "langchain-openai"

# ===== A. 工厂方法路径:create_react_agent =====
from langchain.agents import create_agent  # LangChain 1.0 推荐入口
from langchain_openai import ChatOpenAI

def get_weather(city: str) -> str:
    """Return the weather of the given city."""
    return f"{city}: sunny, 22°C"

agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[get_weather],
    system_prompt="You are a concise weather assistant.",
)
print(agent.invoke({"messages": [("user", "weather of Beijing?")]}))

# ===== B. 自定义图路径:直接控制节点 / reducer / 路由 =====
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition

class State(TypedDict):
    messages: Annotated[list, add_messages]

llm = ChatOpenAI(model="gpt-4o").bind_tools([get_weather])

def chatbot(state: State) -> dict:
    return {"messages": [llm.invoke(state["messages"])]}

builder = StateGraph(State)
builder.add_node("chatbot", chatbot)
builder.add_node("tools", ToolNode([get_weather]))
builder.add_edge(START, "chatbot")
builder.add_conditional_edges("chatbot", tools_condition)
builder.add_edge("tools", "chatbot")
custom_agent = builder.compile()

何时选哪种

维度 create_agent / create_react_agent 自定义 StateGraph
ReAct 循环够用 ✅ 推荐 杀鸡用牛刀
需要并行节点、map-reduce
需要非 messages 的 state schema ❌(Pydantic state schema 不受支持)
需要自定义中断点 / 路由 有限支持
需要多 agent handoff 通过 langgraph-supervisor/swarm ✅ 也可手写

注意 :LangChain 1.0 起,langgraph.prebuilt.create_react_agent 的能力被迁移langchain.agents.create_agent,两者短期共存但官方建议优先使用 create_agent🟠 推测性建议 :新项目直接用 create_agent,老项目逐步迁移。

资料来源


5. Checkpointer + interrupt / resume 🟢

适用场景 :HITL 审批、跨会话恢复、长任务断点续跑。

依赖langgraph>=1.0langgraph-checkpoint>=2.0(内存);生产环境另装 langgraph-checkpoint-sqlitelanggraph-checkpoint-postgres

Python>=3.10

python 复制代码
# pip install "langgraph>=1.0" "langgraph-checkpoint>=2.0"
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command

class State(TypedDict):
    user_input: str
    approved: bool

def collect(state: State) -> dict:
    # interrupt 是「在节点内」抛出可恢复异常的特殊调用
    answer = interrupt({"question": "approve?", "details": state["user_input"]})
    return {"approved": bool(answer)}

def finalize(state: State) -> dict:
    return {"user_input": state["user_input"] + (" OK" if state["approved"] else " NO")}

builder = StateGraph(State)
builder.add_node("collect", collect)
builder.add_node("finalize", finalize)
builder.add_edge(START, "collect")
builder.add_edge("collect", "finalize")
builder.add_edge("finalize", END)

# 必须在 compile 时挂载 checkpointer
graph = builder.compile(checkpointer=InMemorySaver())

config = {"configurable": {"thread_id": "demo-001"}}

# 首轮:会暂停在 interrupt
first = graph.invoke({"user_input": "deploy v2", "approved": False}, config)
print("interrupt payload:", first.get("__interrupt__"))

# 恢复:Command(resume=...) 把值喂回 interrupt 的返回位置
resumed = graph.invoke(Command(resume=True), config)
print(resumed)

要点

  • interrupt() 必须配合 checkpointer + thread_id 才能工作。
  • 恢复时整个节点从头重新执行 ------interrupt 之前的副作用必须幂等(建议:上游入库用 idempotency key、upsert 或 read-before-write)。
  • 同一节点内多个 interrupt()索引匹配 ,不要根据状态条件跳过某个 interrupt
  • 并行多个分支同时挂起时,用 Command(resume={interrupt_id: value, ...}) 一次性恢复。
  • 🟡 deep-research 提示:「严禁在 try/except 中包裹 interrupt」这一说法未被官方文档绝对化背书(投票 1-2 被部分驳回),但实务上仍建议避免------若必须捕获,需把暂停异常重新抛出。

资料来源


6. Time Travel / Checkpoint Fork 🟢

适用场景 :调试错误轨迹、审计某个决策的上下文、A/B 测试同一前序状态的不同分支。

依赖langgraph>=1.0langgraph-checkpoint>=2.0

Python>=3.10

python 复制代码
# pip install "langgraph>=1.0"
from typing_extensions import TypedDict, NotRequired
from langgraph.graph import StateGraph, START
from langgraph.checkpoint.memory import InMemorySaver

class State(TypedDict):
    topic: NotRequired[str]
    joke: NotRequired[str]

def generate_topic(state: State) -> dict:
    return {"topic": "socks in the dryer"}

def write_joke(state: State) -> dict:
    return {"joke": f"Why do {state['topic']} disappear? They elope!"}

checkpointer = InMemorySaver()
graph = (
    StateGraph(State)
    .add_node("generate_topic", generate_topic)
    .add_node("write_joke", write_joke)
    .add_edge(START, "generate_topic")
    .add_edge("generate_topic", "write_joke")
    .compile(checkpointer=checkpointer)
)

config = {"configurable": {"thread_id": "fork-demo"}}
graph.invoke({}, config)

# 1. 列出全部 checkpoint
history = list(graph.get_state_history(config))

# 2. 找到 write_joke 即将执行前的那个 checkpoint
before_joke = next(s for s in history if s.next == ("write_joke",))

# 3. 从该 checkpoint 创建分支:替换 topic
fork_config = graph.update_state(
    before_joke.config,
    values={"topic": "chickens"},
)

# 4. 从分支继续执行(input 传 None 即恢复)
print(graph.invoke(None, fork_config))

要点

  • update_state 不会回滚 原 thread,而是新建一个分支 checkpoint,原 thread 状态保持不变。
  • 受影响节点从分支点的 successor 开始重新执行------LLM 调用、interrupt 都会重跑(成本与幂等性要心里有数)。
  • 「最后一个 checkpoint 没有 next 节点」时 replay 是 no-op,需要 fork 到更早的位置才能产生新输出。

资料来源


7. ToolNode + 结构化输出 🟢

适用场景 :经典 tool-calling 工作流、需要 LLM 返回结构化字段(而非自由文本)。

依赖langgraph>=1.0langchain>=1.0langchain-openai(或其他 chat 提供方)。

Python>=3.10

python 复制代码
# pip install "langgraph>=1.0" "langchain>=1.0" "langchain-openai" "pydantic>=2"
from typing import Annotated
from typing_extensions import TypedDict
from pydantic import BaseModel, Field

from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition

# ===== 7.1 ToolNode + tools_condition =====
class ChatState(TypedDict):
    messages: Annotated[list, add_messages]

@tool
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

llm = ChatOpenAI(model="gpt-4o").bind_tools([add])

def chatbot(state: ChatState) -> dict:
    return {"messages": [llm.invoke(state["messages"])]}

graph = (
    StateGraph(ChatState)
    .add_node("chatbot", chatbot)
    .add_node("tools", ToolNode([add]))
    .add_edge(START, "chatbot")
    .add_conditional_edges("chatbot", tools_condition)
    .add_edge("tools", "chatbot")
    .compile()
)

# ===== 7.2 结构化输出 =====
class WeatherResponse(BaseModel):
    city: str = Field(description="city name")
    temp_c: float = Field(description="temperature in Celsius")
    summary: str

structured_llm = ChatOpenAI(model="gpt-4o").with_structured_output(WeatherResponse)
parsed: WeatherResponse = structured_llm.invoke("Beijing, 22 degrees, sunny")
print(parsed.city, parsed.temp_c, parsed.summary)

要点

  • ToolNodelanggraph.prebuilt 中的成熟工具执行节点:自动从最后一条 AI message 解析 tool_calls,并把结果回灌为 ToolMessage
  • tools_condition 是配套的路由函数:「上一步 AI 是否要求调用工具?是 → tools,否 → END」。
  • with_structured_output(Schema)LangChain ChatModel 通用 API,返回的是「能直接产出 Pydantic / TypedDict 实例的 Runnable」。可在节点内嵌使用。
  • 🟠 推测性建议:当工具数 > 5 个,建议在 system prompt 内显式列出工具职责边界,否则模型经常乱选。

资料来源


8. langgraph-supervisor / langgraph-swarm 🟢

适用场景

  • supervisor:中心化调度多个专家 agent,由一个 supervisor 决定下一步交给谁。
  • swarm:去中心化,agent 之间通过 handoff 工具相互交接,state 中维护「当前活跃 agent」。

依赖langgraph-supervisor==0.0.31(2025-11-19);langgraph-swarm==0.1.0(2025-12-04)。

Python>=3.10

8.1 supervisor 最小示例

python 复制代码
# pip install langgraph-supervisor langchain-openai "langgraph>=1.0"
from langchain_openai import ChatOpenAI
from langgraph_supervisor import create_supervisor
from langgraph.prebuilt import create_react_agent

model = ChatOpenAI(model="gpt-4o")

def add(a: float, b: float) -> float:
    """Add two numbers."""
    return a + b

def web_search(query: str) -> str:
    """Search the web for information."""
    return "FAANG headcount 2024: Meta 67,317; Apple 164,000; Amazon 1,551,000; Netflix 14,000; Google 181,269."

math_agent = create_react_agent(
    model=model, tools=[add], name="math_expert",
    prompt="You are a math expert. Always use one tool at a time.",
)
research_agent = create_react_agent(
    model=model, tools=[web_search], name="research_expert",
    prompt="You are a world class researcher. Do not do any math.",
)

workflow = create_supervisor(
    [research_agent, math_agent],
    model=model,
    prompt=(
        "You are a team supervisor managing a research expert and a math expert. "
        "For current events, use research_agent. For math, use math_agent."
    ),
)
app = workflow.compile()
app.invoke({"messages": [{"role": "user", "content": "combined FAANG headcount 2024?"}]})

8.2 swarm 最小示例

python 复制代码
# pip install langgraph-swarm langchain-openai "langgraph>=1.0"
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
from langgraph_swarm import create_handoff_tool, create_swarm

model = ChatOpenAI(model="gpt-4o")

def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

alice = create_agent(
    model,
    tools=[add, create_handoff_tool(agent_name="Bob", description="Transfer to Bob")],
    system_prompt="You are Alice, an addition expert.",
    name="Alice",
)

bob = create_agent(
    model,
    tools=[create_handoff_tool(agent_name="Alice", description="Transfer to Alice")],
    system_prompt="You are Bob, you speak like a pirate.",
    name="Bob",
)

workflow = create_swarm([alice, bob], default_active_agent="Alice")
app = workflow.compile(checkpointer=InMemorySaver())

注意

  • 🟢 langgraph-supervisor README 在 2026-06-11 抓取时明确写:「We now recommend using the supervisor pattern directly via tools rather than this library for most use cases.」------也就是说官方逐渐倾向于让用户用 handoff 工具自己手写 supervisor,而非依赖 prebuilt 库。
  • langgraph-swarm 0.1.0 README 中的示例使用 langchain.agents.create_agent(LangChain 1.0 入口),而非旧版 create_react_agent
  • supervisor / swarm 在「状态隔离、可观测性、故障恢复」上的取舍 deep-research 标记为「证据不足」,需在实战中专项验证(待补)。

资料来源


9. LangSmith + OpenTelemetry 集成 🟢

适用场景 :上线后链路追踪、Token 成本核算、与已有 OTel 体系(Tempo/Jaeger/Honeycomb 等)打通。

依赖langsmith>=0.4.25(含 OTEL 修复),LangChain/LangGraph 任意 1.x 版本均可。

Python>=3.10

bash 复制代码
# pip install "langsmith[otel]>=0.4.25" langchain langgraph

# === A. 最简:开启 LangSmith 自动追踪 ===
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY="<your-langsmith-api-key>"
# 可选区域:us(默认)/eu/apac/aws
export LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
# OTEL bridge:让 LangSmith trace 同步发送到任意 OTLP collector
export LANGSMITH_OTEL_ENABLED=true
bash 复制代码
# === B. 仅发往外部 OTLP collector(不再写入 LangSmith) ===
export LANGSMITH_OTEL_ENABLED=true
export LANGSMITH_OTEL_ONLY=true
export OTEL_EXPORTER_OTLP_ENDPOINT="https://my-collector.example.com:4318"
export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<token>"
export OTEL_SERVICE_NAME="my-langgraph-app"
python 复制代码
# === C. 任意 Python 入口直接跑,LangChain/LangGraph 自动被埋点 ===
from langgraph.prebuilt import create_react_agent  # 或 langchain.agents.create_agent
from langchain_openai import ChatOpenAI

def get_weather(city: str) -> str:
    """Get weather for a city."""
    return f"{city}: sunny"

agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools=[get_weather])
agent.invoke({"messages": [("user", "weather of Shanghai?")]})
# 启动后到 https://smith.langchain.com 的项目页面即可看到 trace

要点

  • LangSmith 官方推荐的环境变量名是 LANGSMITH_TRACING / LANGSMITH_API_KEY;老变量 LANGCHAIN_TRACING_V2 / LANGCHAIN_API_KEY 仍可工作但不推荐新项目使用
  • 自托管 LangSmith:把 LANGSMITH_ENDPOINT 指向 <your-host>/api/v1,OTel endpoint 走 <your-host>/otel
  • 区域端点:EU eu.api.smith.langchain.com、APAC apac.api.smith.langchain.com、AWS US aws.api.smith.langchain.com
  • 多目的地推荐做法:起一个 OTel Collector,让 LangSmith 与其他后端(Tempo / Honeycomb / Datadog)从 Collector 分流。

资料来源


10. Eval Gate + Replay Testing 🟢

适用场景 :CI 接入「上线前必须跑 N 个评估用例」、「用历史 thread replay 到新版本图」做回归保护。

依赖langsmith>=0.3.4(pytest 集成)、langsmith[pytest](rich output)、openevals(LLM-as-judge 预置 prompt);replay 部分依赖 langgraph>=1.0

Python>=3.10

10.1 LLM-as-Judge + pytest gate

python 复制代码
# pip install -U "langsmith[pytest]>=0.4.25" openevals openai pytest
import pytest
from langsmith import testing as t
from openevals.llm import create_llm_as_judge
from openevals.prompts import CORRECTNESS_PROMPT

def generate_sql(q: str) -> str:
    return "SELECT * FROM customers;"  # 实际换成你的 agent / graph

@pytest.mark.langsmith
def test_sql_select_all() -> None:
    q = "Get all users from the customers table"
    t.log_inputs({"user_query": q})

    expected = "SELECT * FROM customers;"
    t.log_reference_outputs({"sql": expected})

    sql = generate_sql(q)
    t.log_outputs({"sql": sql})

    # LLM-as-Judge:用 openevals 预置 prompt 给一个分数
    judge = create_llm_as_judge(
        prompt=CORRECTNESS_PROMPT,
        model="openai:o3-mini",
        feedback_key="correctness",
    )
    score = judge(inputs={"q": q}, outputs={"sql": sql}, reference_outputs={"sql": expected})
    t.log_feedback(key="correctness", score=score["score"])

    assert sql == expected

运行:

bash 复制代码
pytest --langsmith-output tests/
# 或仅本地试跑、不上报 LangSmith:
LANGSMITH_TEST_TRACKING=false pytest tests/

10.2 历史 thread Replay 到新版本图

🟠 推测性建议(基于官方 time-travel 能力的组合用法):LangGraph 官方没有专门给出一份「跨版本 replay」工程模板,下面是一种社区常见做法。

python 复制代码
# pip install "langgraph>=1.0" "langgraph-checkpoint-postgres"
from langgraph.checkpoint.postgres import PostgresSaver

# 1. 旧 thread 留存于持久 checkpointer(如 Postgres)
checkpointer = PostgresSaver.from_conn_string("postgresql://user:pwd@host/db")
checkpointer.setup()

# 2. 用「新版本」graph 装配,但挂载同一 checkpointer
new_graph = build_graph_v2().compile(checkpointer=checkpointer)

# 3. 遍历线上历史 thread,从某个早期 checkpoint replay
config = {"configurable": {"thread_id": "prod-thread-123"}}
history = list(new_graph.get_state_history(config))
replay_from = next(s for s in history if s.next == ("plan",))

# 4. 用 update_state 创建评估分支,避免污染原 thread
fork_config = new_graph.update_state(replay_from.config, values={})
new_output = new_graph.invoke(None, fork_config)

# 5. 用 LangSmith evaluate 或 pytest mark 评估 new_output

要点

  • @pytest.mark.langsmith 让普通 pytest 用例同时变成 LangSmith experiment------CI 既能 assert 失败阻塞 merge,又能在 LangSmith 后台看见曲线。
  • LANGSMITH_TEST_TRACKING=false 用于本地 dry-run。
  • 跨版本 replay 的本质就是「同一 checkpointer + 新图 + fork checkpoint」组合,没有专用 API。
  • 🟠 推测性建议:CI 中只跑「关键路径用例」+「上线前一日的 N 个生产 thread」,避免回归集失控。

资料来源



附录 C:参考资料与术语表

C.1 官方文档与关键链接

C.1.1 LangGraph 官方文档入口(Python)

主题 URL 抓取日期 适用版本
Overview https://docs.langchain.com/oss/python/langgraph/overview 2026-06-11 >= 1.0
Graph API(概览) https://docs.langchain.com/oss/python/langgraph/graph-api 2026-06-11 >= 1.0
Use Graph API(how-to) https://docs.langchain.com/oss/python/langgraph/use-graph-api 2026-06-11 >= 1.0
Persistence https://docs.langchain.com/oss/python/langgraph/persistence 2026-06-11 >= 0.2
Streaming https://docs.langchain.com/oss/python/langgraph/streaming 2026-06-11 >= 0.2(v3 event 需 >= 1.2)
Event Streaming(v1.2 新) https://docs.langchain.com/oss/python/langgraph/event-streaming 2026-06-11 >= 1.2
Interrupts https://docs.langchain.com/oss/python/langgraph/interrupts 2026-06-11 >= 0.2
Add memory https://docs.langchain.com/oss/python/langgraph/add-memory 2026-06-11 >= 0.2
Use time travel https://docs.langchain.com/oss/python/langgraph/use-time-travel 2026-06-11 >= 0.2
Use subgraphs https://docs.langchain.com/oss/python/langgraph/use-subgraphs 2026-06-11 >= 0.2
Functional API(概览) https://docs.langchain.com/oss/python/langgraph/functional-api 2026-06-11 >= 0.4
Use Functional API(how-to) https://docs.langchain.com/oss/python/langgraph/use-functional-api 2026-06-11 >= 0.4
Choosing between Graph/Functional https://docs.langchain.com/oss/python/langgraph/choosing-apis 2026-06-11 >= 0.4
Test https://docs.langchain.com/oss/python/langgraph/test 2026-06-11 >= 0.2
概念:Memory https://docs.langchain.com/oss/concepts/memory 2026-06-11 ---
API Reference(Python) https://reference.langchain.com/python/langgraph/ 2026-06-11 当前
文档仓库(GitHub 源) https://github.com/langchain-ai/docs/tree/main/src/oss/langgraph 2026-06-11 当前

注:「适用版本」为 官方/推测:官方未在每个页头标注版本要求;此处基于出现该术语的 release 推导。

C.1.2 LangGraph GitHub

资源 URL 抓取日期 备注
主仓库 https://github.com/langchain-ai/langgraph 2026-06-11 34.4k stars / 5.8k forks;MIT 许可;99.6% Python;描述 "Build resilient agents."
Examples(仅存档 https://github.com/langchain-ai/langgraph/tree/main/examples 2026-06-11 README 注明「retained purely for archival purposes and is no longer updated」;新示例请见官方 docs
libs/ 目录 https://github.com/langchain-ai/langgraph/tree/main/libs 2026-06-11 sdk-pyclicheckpointcheckpoint-postgrescheckpoint-sqlitesupervisorswarm 等子包
Releases https://github.com/langchain-ai/langgraph/releases 2026-06-11 共 55 页;最新 1.2.4(2026-06-02)
AGENTS.md(项目 AI 协作规范) https://github.com/langchain-ai/langgraph/blob/main/AGENTS.md 2026-06-11 ---
CLAUDE.md(项目级 AI 规范) https://github.com/langchain-ai/langgraph/blob/main/CLAUDE.md 2026-06-11 ---

C.1.3 PyPI 包

包名 URL 当前版本(2026-06-11 抓取) 关键日期
langgraph https://pypi.org/project/langgraph/ 1.2.4 2026-06-02;1.2.0 = 2026-05-12
langgraph-checkpoint https://pypi.org/project/langgraph-checkpoint/ 4.1.1 2026-05-22;4.1.0 = 2026-05-12;4.0.0 = 2026-01-12
langgraph-checkpoint-postgres https://pypi.org/project/langgraph-checkpoint-postgres/ 3.1.0 2026-05-12;3.0.0 = 2025-10-20
langgraph-checkpoint-sqlite https://pypi.org/project/langgraph-checkpoint-sqlite/ 3.1.0 2026-05-12;3.0.0 = 2025-10-20
langgraph-supervisor https://pypi.org/project/langgraph-supervisor/ 0.0.31 2025-11-19;0.0.1 = 2025-02-07
langgraph-swarm https://pypi.org/project/langgraph-swarm/ 0.1.0 2025-12-04;0.0.1 = 2025-02-25
langgraph-prebuilt(PyPI) https://pypi.org/project/langgraph-prebuilt/ 1.1.0(推测:基于 1.1.0 = 2026-05-12 的 release notes 推导;待精确确认) 2026-05-12
langgraph-cli https://pypi.org/project/langgraph-cli/ 0.4.28 2026-06-02
langgraph-sdk https://pypi.org/project/langgraph-sdk/ 0.4.2 2026-06-02

注:v1.0 GA 的精确发布日期本次未在 GitHub Releases 前 10 页定位到 待补 。从 checkpoint 3.0.0 / postgres 3.0.0 / sqlite 3.0.0 同步在 2025-10-20 发版来看,langgraph 主包 1.0.0 GA 大概率落在 2025-10 中下旬推测 );建议后续通过 git tag 或 CHANGELOG 确认。

C.1.4 LangSmith / LangGraph Platform

主题 URL 抓取日期
Deployment 总览 https://docs.langchain.com/langsmith/deployment 2026-06-11
Deployment Quickstart(LangGraph 路径) https://docs.langchain.com/langsmith/deployment-quickstart 2026-06-11
Agent Server https://docs.langchain.com/langsmith/agent-server 2026-06-11
Control plane https://docs.langchain.com/langsmith/control-plane 2026-06-11
Deploy to Cloud https://docs.langchain.com/langsmith/deploy-to-cloud 2026-06-11
Standalone server(Docker/Compose/K8s) https://docs.langchain.com/langsmith/deploy-standalone-server 2026-06-11
Self-hosted(Enterprise) https://docs.langchain.com/langsmith/self-hosted 2026-06-11
Platform setup(功能对比) https://docs.langchain.com/langsmith/platform-setup 2026-06-11
Studio(交互式开发/调试) https://docs.langchain.com/langsmith/studio 2026-06-11
Custom auth https://docs.langchain.com/langsmith/auth 2026-06-11
Custom routes https://docs.langchain.com/langsmith/custom-routes 2026-06-11
Custom middleware https://docs.langchain.com/langsmith/custom-middleware 2026-06-11
Custom lifespan https://docs.langchain.com/langsmith/custom-lifespan 2026-06-11
Encryption https://docs.langchain.com/langsmith/encryption 2026-06-11
Streaming(Server 视角) https://docs.langchain.com/langsmith/streaming 2026-06-11
Observability(Tracing) https://docs.langchain.com/langsmith/observability 2026-06-11
RemoteGraph https://docs.langchain.com/langsmith/use-remote-graph 2026-06-11
Time travel(Server API) https://docs.langchain.com/langsmith/human-in-the-loop-time-travel 2026-06-11
LangSmith CLI(含 langgraph deploy https://docs.langchain.com/langsmith/cli 2026-06-11
Semantic search https://docs.langchain.com/langsmith/semantic-search 2026-06-11
Configure TTL https://docs.langchain.com/langsmith/configure-ttl 2026-06-11
CI/CD pipeline example https://docs.langchain.com/langsmith/cicd-pipeline-example 2026-06-11
Generative UI in React https://docs.langchain.com/langsmith/generative-ui-react 2026-06-11
LangGraph Python SDK(LangSmith 视角) https://docs.langchain.com/langsmith/langgraph-python-sdk 2026-06-11

C.1.5 官方博客(LangChain Blog)

入口:https://blog.langchain.com/ → 301 跳转至 https://www.langchain.com/blog/ | 抓取日期:2026-06-11

标题 URL 发布日期 标签
Fault Tolerance in LangGraph: Retries, Timeouts, and Error Handlers https://www.langchain.com/blog/fault-tolerance-in-langgraph 2026-06-04 LangGraph;作者 Quanzheng Long、Sydney Runkle
How Lyft Built a Self-Serve AI Agent Platform for Customer Support with LangGraph and LangSmith https://www.langchain.com/blog/lyft-built-a-self-serve-ai-agent-platform-for-customer-support-with-langgraph-and-langsmith 2026-06-(待精确确认)注1 LangGraph / LangSmith
From Token Streams to Agent Streams https://www.langchain.com/blog/token-streams-to-agent-streams 2026-05-21 LangGraph;作者 Christian Bromann、Nick Hollon
  • 注 1:博客首页摘要列示日期为「2026-05-27」附近的相对时间;具体日期以页面为准。
  • v1.0 GA 公告:本次未在 blog 检索到独立公告页(待补)。

C.1.6 LangChain Academy(LangGraph 课程)

课程 URL 抓取日期 备注
Foundation: Introduction to LangGraph - Python https://academy.langchain.com/ 2026-06-11 6 模块 / 55 课 / 约 6 小时;模块:①Introduction ②State and Memory ③UX and Human-in-the-Loop ④Building Your Assistant ⑤Long-Term Memory ⑥Deployment
Project: Deep Agents(with LangGraph) https://academy.langchain.com/courses/deep-agents-with-langgraph 2026-06-11 官方「feature」课程

C.2 关键术语解释(按字母顺序)

每条目的链接均为 官方 原始链接;释义基于官方原文与 API Reference 整合。

BaseStore

  • 中文释义:长时记忆(long-term memory)的底层抽象,向 Graph / Functional API 暴露 put / get / search / list_namespaces 等接口;通过 BaseStore 实现可注入到节点或 entrypoint 中。
  • 官方原文(持久化页):"Memory store ... A memory store in LangGraph is a way to store and retrieve information across threads."
  • 原始链接:https://docs.langchain.com/oss/python/langgraph/persistence#memory-store
  • 适用版本:>= 0.2

Channel

Checkpointer

Command

  • 中文释义:节点返回值类型,用于"组合控制流 + 状态更新"(如 Command(goto="next", update={...})),并支持 Command.PARENT 跨子图跳转、Command(resume=...) 恢复 interrupt。
  • 官方原文(Graph API 概览):"Combine control flow and state updates with Command"
  • 原始链接:https://docs.langchain.com/oss/python/langgraph/graph-api#command
  • 适用版本:>= 0.2

compile

Conditional Edge

DeltaChannel

dispatch_custom_event

durability(exit / async / sync)

  • 中文释义:持久化"耐久度"模式,控制 checkpoint 何时落盘:"exit"(图退出时)、"async"(异步后台)、"sync"(同步立刻)。可在 compile( durability=... ) 或运行时 config 中设置。
  • 原始链接:https://docs.langchain.com/oss/python/langgraph/persistence#durability-modes
  • 适用版本:>= 0.2(确切命名版本 待精确确认

Functional API

get_state_history

Human-in-the-loop(HITL)

InMemorySaver

interrupt

MemorySaver

namespace(ns)

Node

PostgresSaver

persistence

pydantic v1/v2

reducer

recursion_limit

Replay

Resume

Send

SqliteSaver

start / astream / stream

StreamPart

stream_mode

State

StateGraph

StateSnapshot

Subgraph

super-step

Super-step boundary

thread_id

Time Travel

ToolNode

update_state

with_structured_output


C.3 社区资源(按优先级)

C.3.1 高 Star GitHub 项目(与 LangGraph Python 直接相关)

抓取日期:2026-06-11;筛选标准:stars >= 1k 且与 LangGraph 直接相关;以下仅作存在性记录,不背书可用性

项目 URL 抓取日期 备注
langchain-ai/langgraph(官方) https://github.com/langchain-ai/langgraph 2026-06-11 34.4k stars
langchain-ai/langchain(官方) https://github.com/langchain-ai/langchain 2026-06-11 嵌含 langgraph 集成
langchain-ai/langgraphjs(官方) https://github.com/langchain-ai/langgraphjs 2026-06-11 JS/TS 对应实现(非本附录主题
langflow(Low-code UI for LangGraph) https://github.com/logspace-ai/langflow 2026-06-11 社区;stars 量级大,使用时需自查版本
LlamaIndex 与 LangGraph 集成 https://github.com/run-llama/llama_index 2026-06-11 社区;提供 agents/Workflows 跨集成

C.3.2 半年内(2025-12 ~ 2026-06)高质量技术博客

列表来自 LangChain 官方 blog;其它社区文章可参考 LangChain Forum 的 "OSS Product Help" 分类。

标题 URL 发布日期 来源类型
Fault Tolerance in LangGraph: Retries, Timeouts, and Error Handlers https://www.langchain.com/blog/fault-tolerance-in-langgraph 2026-06-04 官方
How Lyft Built a Self-Serve AI Agent Platform for Customer Support with LangGraph and LangSmith https://www.langchain.com/blog/lyft-built-a-self-serve-ai-agent-platform-for-customer-support-with-langgraph-and-langsmith 2026-05-27(待精确确认 官方(客户案例)
From Token Streams to Agent Streams https://www.langchain.com/blog/token-streams-to-agent-streams 2026-05-21 官方

C.3.3 LangChain Interrupt 会议分享

入口:https://www.langchain.com/interrupt | 抓取日期:2026-06-11

实际会议录像与 slide 通过该入口分发;本次未逐项抓取(待补:建议补充 1-2 个 2025/2026 年度演讲中与 LangGraph Python 强相关的链接)。

C.3.4 关键 Discord / Forum 主题

平台 入口 URL 抓取日期 备注
LangChain Forum(Discourse) https://forum.langchain.com/ 2026-06-11 6 个分类:Announcements / OSS Product Help / LangSmith Product Help / LangChain Academy / Talking Shop / Forum Feedback
OSS Product Help(LangGraph & LangChain OSS) https://forum.langchain.com/c/oss-product-help-lc-and-lg/16 2026-06-11 31 主题(首页观察值)
LangChain Academy 答疑分类 https://forum.langchain.com/c/langchain-academy/10 2026-06-11 52 主题
LangChain Discord https://discord.gg/langchain → 301 → https://discord.com/invite/langchain 2026-06-11 官方邀请链接;进入后请在 #langgraph / #help-langgraph 子频道检索

C.4 参考资料引用规范

C.4.1 引用必须包含的字段

字段 说明
标题 文档 / 文章 / 包的展示名
URL 完整可访问链接
发布日期 官方公告、博客、Release Note 应标注;PyPI 抓取时记录版本日期
抓取日期 本次访问 URL 的 UTC 日期(本次统一记 2026-06-11
来源类型 官方 / 社区 / 推测 三选一,必填

C.4.2 来源类型判定规则

  • 官方 :来源域名属于 langchain.com / langchain-ai.github.io / pypi.org / github.com/langchain-ai/* / reference.langchain.com / docs.langchain.com / blog.langchain.com
  • 社区:第三方开发者文章、个人博客、Discord / Forum 用户帖、第三方 GitHub 项目。
  • 推测:由官方资料综合推导、未在官方页面/原文中明示的结论(如"v1.0 GA ≈ 2025-10 中下旬")。

C.4.3 同一事实冲突时的优先级

  1. 官方原文档(docs.langchain.com / reference.langchain.com)> 官方博客 > PyPI / GitHub Release Notes。
  2. 同一事实的版本号,以 当前 PyPI latest 为准;与 release notes 不一致时在文末"待补"中注明。

质量审查说明

本报告所有正文与附录均经过多轮审查闭环。具体节奏:

板块 轮次 结论
第 1--3 章 Round 1 + Round 2 已闭环;Platform 三层、版本锚点、Graph 设计规范与后续章节对齐
第 4--6 章 Round 1 + Round 2 已闭环;checkpointer、HITL、streaming、ToolNode、Agent 构建等口径与 1.2.4 对齐
第 7--9 章 Round 1 + Round 2 已闭环;多智能体、测试与质量保障、可观测性与 deep-research 事实底座对齐
第 10--12 章 Round 1 + Round 2 已闭环;性能 / 成本 / 可靠性、生产部署、安全 / 合规 / 多租户口径统一
附录 A Round 1 + Round 2 已闭环;版本、迁移、deprecated API、InMemorySaver / Async*Saver、prebuilt 1.1.0 等事实修正完成
附录 B Round 1 已闭环;10 个最小代码片段与 LangGraph Python 版本基线对齐
附录 C Round 1 已闭环;官方文档、PyPI、LangSmith 部署、术语表和引用规范完成

已知限制 / 待补项

本节聚合 deep-research 综合结论、附录 A 的 W0--W8 与附录 B / C 的待补条目。

来源冲突与官方公告

  • W0 / v1.0 GA 公告原文链接 :PyPI langgraph==1.0.0 2025-10-17 发布,但官方 blog 检索未在主页命中独立 GA 公告;建议查完整 blog 正文或 langgraph/CHANGELOG.md 确认。
  • 附录 A.1.2 冲突 :「1.0 with no breaking changes」与 1.1.0 显式标记 LangGraphDeprecatedSinceV11、1.0.0rc1 起 drop Python 3.9 同时存在。

文档站抓取限制

  • W1langchain-ai.github.io/langgraph 子页多次重定向至主站空白页,未抓到内容;建议用 curl + 用户代理或读 langgraph 仓内 docs/ 源 md。

历史 / 弃用 API 状态

  • W3get_state_version / NodeStyleSend / ConfigurableCallable 等历史 API 是否仍 deprecate 未在 release notes 中显式公告。

客户端协议版本对应

  • W4langgraph-sdk==0.4.0 引入的 v3 streaming 与 langgraph 1.2.4 core 的对应关系未在本轮抓取中确认。

第三方 checkpointer

  • W5RedisSaver 等第三方 checkpointer 与 1.2.x 兼容矩阵不在主仓;建议查 langgraph-checkpoint-redis 社区包 release notes。

1.2.x 行为细节

  • W7 :1.2.x set_node_defaults(PR #7747)对 store namespace 解析的影响 release notes 未点明。
  • W8:PR #6298 是否对应 Python 3.14 cursory 支持,附录 A 引用但未独立抓取。

多智能体 / 测试 / 可靠性(deep-research 综合结论中证据不足)

  • 多智能体架构(Supervisor / Swarm / Hierarchical / Handoff)在状态边界、隔离、可观测性上的取舍。
  • 不同 checkpointer × durability 配置(sync / async / exit)的精确故障恢复保证。
  • LangSmith 与 LangGraph 闭环:哪些是 OSS 能力、哪些是平台服务。
  • LangGraph Platform / Self-Hosted 部署、Python 服务集成的多租户 / 安全 / 合规 / 成本推荐架构。

部署与定价

  • LangSmith Cloud 自托管 / Cloud 区域列表(EU / US / APAC)、HIPAA BAA 是否在 Cloud 支持。
  • LangGraph Platform 精确计费 / SLA;Self-Hosted Lite / Enterprise 仍需 LangSmith 商业 License,精确价格以 LangChain 销售 / partner portal 为准。
  • runs.create 接受 idempotency_key 的完整 spec;Graph 蓝绿发布 cookbook。

测试 / 可观测性

  • Plan-and-Execute / Reflection / Reflexion 的官方可运行代码细节仍待补。
  • langchain-community 是否含 Llama Guard 集成需自核;PII 脱敏官方 SDK 未明确给出,Presidio / HMAC tokenization 为社区方案。

数据时点

  • 所有 PyPI / GitHub Releases / 官方文档 / 官方博客抓取日期均为 2026-06-11。超过该日期请按附录 C.4 的引用规范复核。

报告正文到此结束。

相关推荐
晨之清风2 分钟前
Codex常用命令
人工智能
hsg775 分钟前
简述:2026年中考一地作文题目 :接纳无解,向阳求索
人工智能·机器学习
北京耐用通信10 分钟前
国产化替代优选!耐达讯自动化NY-HUB6完美兼容替代PB-HUB6\GL
人工智能·科技·网络协议·自动化·信息与通信
LaughingZhu15 分钟前
Product Hunt 每日热榜 | 2026-06-11
人工智能·经验分享·神经网络·html·产品运营
像风一样自由202027 分钟前
17.推理框架横评:vLLM / TGI / TensorRT-LLM / SGLang 全面对比
人工智能·大模型·vllm·sglang
walnut_oyb29 分钟前
CVPR 2026|VisRes Bench:视觉语言模型视觉推理能力评估
人工智能·语言模型·自然语言处理
网教盟人才服务平台35 分钟前
第223期方班学术研讨厅成功举办
人工智能
lauo41 分钟前
ibbot手机:从赛博攻防到Token经济的AI终端革命
人工智能·智能手机
私人珍藏库1 小时前
【Android】BotHub-多模型AI机器人聚合库-内置免费模型
android·人工智能·智能手机·app·工具·多功能
老马聊技术1 小时前
AI对话功能之SpringBoot整合Vue3
vue.js·人工智能·spring boot·后端