---
type: study
created: 2026-08-11
updated: 2026-08-11
sensitivity: private
status: active
tags:
  - study
  - langchain
  - langgraph
  - typescript
  - streaming
  - frontend
  - hitl
topic: LangGraph TypeScript 流式、HITL、子图与前端 SDK
sources:
  - https://docs.langchain.com/oss/javascript/langgraph/event-streaming
  - https://docs.langchain.com/oss/javascript/langgraph/streaming
  - https://docs.langchain.com/oss/javascript/langgraph/interrupts
  - https://docs.langchain.com/oss/javascript/langgraph/use-subgraphs
  - https://docs.langchain.com/oss/javascript/langgraph/frontend/overview
  - https://docs.langchain.com/oss/javascript/langgraph/frontend/graph-execution
  - https://docs.langchain.com/oss/javascript/langgraph/frontend/custom-stream-channels
last_verified: 2026-08-11
---
# TypeScript 流式、HITL、子图与前端 SDK

## 两层 streaming API

LangGraph TypeScript 同时存在两层流式接口：

| 层级 | 入口 | 适用场景 |
|---|---|---|
| Event streaming | streamEvents，v3 protocol | 新应用、类型化消息/State/子图/interrupt 投影 |
| Stream-mode API | stream + streamMode | 低层 graph runtime 事件和特定 mode |

官方建议新应用优先 event streaming。它在 v1.2 引入类型化投影，让不同消费者独立读取同一底层事件流；低层 API 仍适合协议诊断、自定义运行时工具或必须精确处理 raw mode 的应用。

对远程 Agent Server 的流式消费使用 LangSmith/Agent Server Streaming API 或 `@langchain/langgraph-sdk`，不能把进程内 graph 方法等同于网络协议。

## Event streaming

基本形态：

    const stream = await graph.streamEvents(input, { version: "v3" });

    for await (const message of stream.messages) {
      for await (const token of message.text) {
        render(token);
      }
    }

    const finalState = await stream.output;

一个 run stream 提供：

- stream：所有 ProtocolEvent；
- stream.messages：消息与 token；
- stream.values：每步完整 State；
- stream.output：最终输出 Promise；
- stream.subgraphs：嵌套 graph run；
- stream.interrupts：HITL payload；
- stream.interrupted：是否因人工输入暂停；
- stream.extensions：自定义 transformer 投影。

多个 projection 可以并发消费。读取 messages 不会耗掉 values、subgraphs 或 output 的事件；在 JavaScript 中可用 Promise.all 并行启动多个 consumer。

### ProtocolEvent

raw event 的关键结构：

- seq：同一 run 内严格递增，排序应依据它；
- method：channel 名；
- params.namespace：从根图到当前 scope 的路径；
- params.timestamp：墙钟时间，可能漂移，不用于严格排序；
- params.node：可选 node 名；
- params.data：channel-specific payload。

根 namespace 是空数组；子 scope 追加 `name:runtime_id`。稳定逻辑应使用 name/路径结构，不把 runtime_id 当业务主键。

### channels

| channel | 内容 |
|---|---|
| values | 完整 State snapshot |
| updates | node 级 State delta |
| messages | content block 与 token delta |
| tools | 工具 start/progress/finish/error |
| lifecycle | run、subgraph、subagent 状态 |
| checkpoints | time travel 所需轻量 checkpoint 事件 |
| input | HITL 请求与响应 |
| tasks | Pregel task 创建与结果 |
| custom | node/tool 自定义数据 |
| custom:name | transformer 命名扩展 |

messages 以 message-start、content-block-start/delta/finish、message-finish 表达，能统一 text、reasoning、tool-call 与多模态 block。tool 事件用 tool call ID 与消息中的 tool call 关联。

## Stream transformer

Transformer 观察 ProtocolEvent、维护自己的状态，并把派生视图发布到 StreamChannel。接口包含 init、process、可选 finalize/fail。

required stream modes 决定底层 Pregel 实际启用哪些 mode；多个 transformer 的需求取并集。声明某个 mode 只是启用上游事件，process 仍会收到已启用的全部 event，需自行按 method 过滤。

两种 StreamChannel：

- unnamed：只存在于进程内 stream.extensions，可承载 promise、async iterable、class instance 等不可序列化对象；
- named：除 extensions 外，还会作为 `custom:<name>` 进入主协议；payload 必须可序列化。

Transformer 可以在调用 streamEvents 时注册，也可以 compile 时固定到 graph。只有所有 run 都需要的协议扩展才适合 compile-time 注册。

## 低层 streamMode

常用 mode：

- updates：每个 node 的 State 更新；同一步多个更新分别发出；
- values：每步完整 State；
- messages：`[messageChunk, metadata]`，即使模型用 invoke 调用也可产生 token；
- custom：node/tool 通过 writer 发出的任意数据；
- tools：工具生命周期事件；
- debug：最大量运行时信息。

一次传多个 mode 时，chunk 形态是 `[mode, data]`。启用 `subgraphs: true` 后，会再带 namespace；解析代码必须按所选 mode 和 subgraphs 选项处理实际 tuple 形态。

### messages 过滤

- 给模型调用加 tags，可按 metadata.tags 过滤；
- `nostream` tag 可让一次模型调用运行但不进入 messages；
- 按 metadata.langgraph_node 过滤具体 node；
- 不支持 LangChain chat interface 的模型可通过 custom mode 转发自己的 token。

内部结构化模型输出、审查模型等通常应加 nostream，避免把内部推理或重复内容发给用户。

### tools mode

普通 Promise 工具自动产生 start/end；async generator 工具的每次 yield 产生 progress event，return 成为最终 ToolMessage 内容。事件包括：

- on_tool_start；
- on_tool_event；
- on_tool_end；
- on_tool_error。

前端 useStream 在包含 tools mode 时可维护 toolProgress，记录 starting/running/completed/error、input、data、result 和 error。

tools mode 适合结构化工具生命周期；custom mode 适合不属于工具生命周期的自由业务事件。

## interrupt 的执行语义

interrupt 在 node 中抛出运行时控制异常，checkpointer 保存当前状态，run 返回 `__interrupt__` payload，并无限等待应用用同一 thread_id 恢复。

恢复方式：

    await graph.invoke(
      new Command({ resume: humanValue }),
      sameConfig,
    );

恢复值成为 interrupt 调用的返回值。但 node 不是从那一行继续，而是从函数开头重新执行。

使用 interrupt 必须具备：

1. checkpointer；
2. 稳定 thread_id；
3. JSON 可序列化 payload；
4. 对 node replay 和副作用的明确设计。

## interrupt 的硬规则

### 不用裸 try/catch 包 interrupt

interrupt 通过特殊异常暂停。裸 catch 会吞掉控制异常。需要捕获其他错误时，把 interrupt 移出 try/catch，或只处理明确业务异常并重新抛出未知异常。

### 不改变同一 node 内 interrupt 顺序

resume value 按任务内调用位置匹配。不能在旧 run 未完成时：

- 插入、删除或重排之前的 interrupt；
- 根据非确定性数据跳过某个 interrupt；
- 用长度会变化的循环动态调用 interrupt。

输入校验应让 node 返回 State，再通过 conditional edge 回到同一个单 interrupt 节点，而不是在 node 内用动态 while 循环不断 interrupt。

### payload 保持可序列化

只传 string、number、boolean、array 和普通 object。不要传函数、数据库连接、class instance 或 framework 对象。

### interrupt 前的副作用必须幂等

恢复会重跑 node 开头。优先：

- interrupt 后再执行真实写操作；
- 把审批与动作拆成两个 node；
- 前置操作采用 upsert 和稳定幂等键；
- 不在 interrupt 前 append、创建无唯一约束记录或发送通知。

### 多个并行 interrupt

并行分支可能一次返回多个 interrupt。应用要读取每个 interrupt ID，构造 `{ [interruptId]: resumeValue }` 映射，一次 Command.resume 正确配对；不能只按数组顺序猜测。

## 流式 HITL 循环

交互式客户端应循环执行：

1. streamEvents(input/Command, same config)；
2. 消费 messages、values 或 subgraphs；
3. 检查 stream.interrupted；
4. 若暂停，渲染 payload 并收集用户输入；
5. 以 new Command({ resume }) 再次 streamEvents；
6. 直到 interrupted 为 false，再 await output。

resume 是 invoke/stream 输入中唯一常规 Command 模式。update/goto/graph 主要由 node 返回，不用于普通前端 turn。

静态 interruptBefore/interruptAfter 是调试 breakpoint，不是生产 HITL 替代品。

## 子图的两种通信方式

### 父子 State 不同

在父 node 函数内调用 subgraph.invoke，由 wrapper 显式完成：

- parent State -> subgraph input；
- subgraph output -> parent update。

这适合独立 agent 私有消息历史或不同团队组件，但 wrapper 本身是边界，需要校验与错误映射。

### 父子共享 State key

把 compiled subgraph 直接 addNode。共享 key 自动作为输入输出 channel；父图不需要知道子图内部节点。

若子图写父图未声明或 output schema 隐藏的字段，不能假设父图一定可观察；接口应明确共享 key、reducer 和 ownership。

## 子图持久化、并发与可观察性

- 默认 per-invocation：继承父 checkpointer，每次调用独立，支持当前调用 interrupt；
- `checkpointer: true`：per-thread，跨调用积累 State，但同一子图不能并行多次调用；
- `checkpointer: false`：stateless，无 interrupt、恢复和 state inspection。

多个不同 per-thread 子图要使用唯一稳定 node 名生成 namespace。同一 per-thread 子图作为工具时，应禁用模型对该工具的 parallel tool calling，或用调用限制中间件序列化。

getState(config, { subgraphs: true }) 只能检查运行时可静态发现的子图：直接加为 node，或在已知 node 内调用。隐藏在任意 tool/其他间接层中的子图通常无法这样发现，但 interrupt 仍会向顶层传播。

观察嵌套输出优先 event streaming 的 stream.subgraphs；低层 stream 也可 `subgraphs: true`，但调用者要解析 namespace tuple。

## 前端架构

LangGraph 前端不是只能显示一个聊天气泡。named node、State key、checkpoint、interrupt、subgraph 和 stream metadata 都可以映射为 UI：

- node -> 卡片、时间线步骤、状态 badge；
- State key -> typed result panel、table、chart；
- interrupt -> 审批/编辑表单；
- checkpoint -> 历史和 fork；
- subgraph -> 可折叠的嵌套执行；
- tool lifecycle -> 进度卡和错误状态。

v1 前端包覆盖 React、Vue、Svelte、Angular：

- React：useStream；
- Vue/Svelte：同名 useStream，按各自响应式模型读取；
- Angular：injectStream；
- 当前包名使用 `@langchain/react`、`@langchain/vue`、`@langchain/svelte`、`@langchain/angular`。

不要混用旧示例中的 `@langchain/langgraph-sdk/react` 与 v1 包名；升级时按各框架 migration guide 和当前 reference 核对。

### 图执行卡

useStream 的 `stream.subgraphs` 会发现实际执行的 node，而不是要求前端硬编码全部步骤。SubgraphDiscoverySnapshot 提供 nodeName 与 pending/running/complete/error 状态。

推荐：

- 从当前 run 的 discovery map 渲染动态 pipeline；
- 用 `useMessages(stream, node)` 读取 node-scoped token 与最终消息；
- 只有确实需要 State 字段时才读 stream.values；
- 完成节点自动折叠，错误按 node 展示；
- partial Markdown 使用能容忍未闭合语法的 renderer；
- 条件分支未执行的节点不会自动出现在 discovery map。

node 名和稳定 State key 是前后端合同。重命名它们既影响 checkpoint，也影响 UI。

## Custom stream channel 到浏览器

服务端 transformer 可用命名 StreamChannel 发布 `custom:<name>`。前端有两个读取层次：

| selector | 入参 | 返回 | 适用 |
|---|---|---|---|
| useExtension/injectExtension | 裸 name | 最新 typed payload | 进度、状态、累计值 |
| useChannel/injectChannel | 完整 `custom:name` | 有界 raw event buffer | 日志、审计、历史 |

useChannel 的 bufferSize 控制容量，replay 控制 selector mount 时是否回放已看到事件。raw payload 位于 event.params.data，需要应用自行解包。

服务端 StreamTransformer 与 StreamChannel 需要 `@langchain/langgraph >= 1.3.1`。远程 channel 应使用可序列化 payload。

custom channel 可用于进度、token 成本、引用、域事件与合规统计。原文示例还展示 transformer 在事件抵达浏览器前原地清理 PII，再发布脱敏计数；这是有用的 defense-in-depth，但不能替代服务端数据最小化、访问控制与日志脱敏。

## Node 与浏览器安全边界

- 模型 API key、LangSmith secret、数据库凭据只放服务端。
- 浏览器连接 Agent Server 时必须有用户级认证与租户授权。
- 不把 thread_id 当授权凭据；服务端仍需确认该用户可访问该 thread。
- custom channel 也属于对外 API，需做 schema、大小限制和敏感字段审查。
- raw values/debug stream 可能泄露内部 State，生产客户端只订阅必要 projection。
- 断线重连要使用 SDK 的 resumable stream/thread 能力，不自行拼接不完整 token。
- UI 发送 resume 前要校验 interrupt ID、当前 thread 状态与用户权限，防止重复审批。

## 文档实现边界

部分 TypeScript 页面仍出现 Python 风格的 `stream_events`、`stream_mode`、`create_agent`、`checkpointer=True` 或 snake_case reference 链接；一些长示例还有重复 import/code fence。它们说明概念，但不能作为可编译代码的唯一依据。

落地时以三层证据为准：

1. 当前 TypeScript reference 和导出；
2. 本项目锁定版本的类型检查；
3. 本地 Agent Server 与目标浏览器的真实端到端测试。
