以编程方式使用文档

LangGraph 智能体流式传输的内容不仅仅是消息和工具调用。服务端 **流转换器** 可以在协议流向 客户端时对其进行检查或重写,并在指定的 **自定义通道**上发布其自己的结构化数据。 前端使用两个选择器读取该通道:useExtension 获取最新 ],以及 useChannel 作为原始事件的逃生通道。

以下示例是一个客户服务智能体,其转换器会删除 PII (电子邮件、电话号码、社会安全号码、银行卡号、IP地址),在事件 到达浏览器之前,并在一个 redaction-stats 通道上发布实时的脱敏计数。侧边栏会实时渲染这些计数。

自定义通道的工作原理

自定义通道有两端。在服务端,一个 StreamTransformer 打开一个 命名的 StreamChannel 并向其推送负载。在客户端,一个选择器 订阅匹配的 custom:<name> 通道,并将负载作为 响应式状态暴露。

转换器的 process 方法为每个协议事件运行。它可以原地变更 事件(在此处,从 messages, toolsvalues 数据中清除 PII),并在有内容需要报告时推送侧通道更新。

客户端选择器(useExtension, useChannel)随 v1 前端 SDK 包一起提供(@langchain/react, @langchain/vue, @langchain/svelte, @langchain/angular).

  redactionStats: StreamChannel;
}> => {
  // Open a remote channel named "redaction-stats".
  const redactionStats = StreamChannel.remote("redaction-stats");
  const counts = emptyCounts();

  return {
    init: () => ({ redactionStats }),

    process(event: ProtocolEvent): boolean {
      // Redact event.params.data in place and tally what was found.
      const delta = redactInPlace(event, counts);
      if (Object.keys(delta).length > 0) {
        // Publish a payload on the channel.
        redactionStats.push({
          kind: "update",
          at: Date.now(),
          delta,
          counts: { ...counts },
          total: totalRedactions(counts),
        });
      }
      return true; // Keep the (now-redacted) event in the stream.
    },
  };
};

在构建智能体时附加转换器:

const agent = createAgent({
  model: "anthropic:claude-haiku-4-5",
  tools: [...],
  streamTransformers: [createRedactionStatsTransformer],
});

负载类型由转换器推送的内容决定。下面的客户端示例 读取此结构:

type PiiType = "email" | "phone" | "ssn" | "credit_card" | "ip_address";

type RedactionStatsEvent = {
  kind: "update";
  at: number;
  delta: Partial<Record<PiiType, number>>;
  counts: Record<PiiType, number>;
  total: number;
};

设置 useStream

像往常一样连接 @[useStream。自定义通道选择器采用相同的 stream 句柄返回到这里。

const AGENT_URL = "http://localhost:2024";

  const stream = useStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "custom_stream_channel",
  });

  return ;
}
<script setup lang="ts">


const AGENT_URL = "http://localhost:2024";

const stream = useStream<typeof myAgent>({
  apiUrl: AGENT_URL,
  assistantId: "custom_stream_channel",
});
</script>

<template>

</template>
<script lang="ts">


  const AGENT_URL = "http://localhost:2024";

  const stream = useStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "custom_stream_channel",
  });
</script>
const AGENT_URL = "http://localhost:2024";

@Component({
  selector: "app-redaction-chat",
  template: `<app-redaction-stats-panel [stream]="stream" />`,
})

  stream = injectStream<typeof myAgent>({
    apiUrl: AGENT_URL,
    assistantId: "custom_stream_channel",
  });
}

使用以下方式读取最新负载 useExtension

useExtension 订阅一个 custom:<name> 通道并返回最 近一次转换器推送的负载,已解包并类型化。这是 当 UI 只需要当前值时的最佳选择,例如实时 计数器、进度百分比或状态徽章。

传递裸通道名称("redaction-stats"),而不是 custom: prefix:

const latest = useExtension(stream, "redaction-stats");
// latest?.total, latest?.counts.email, latest?.delta
const latest = useExtension(stream, "redaction-stats");
// latest.value?.total
const latest = useExtension(stream, "redaction-stats");
// latest?.total
const latest = injectExtension(stream, "redaction-stats");
// latest()?.total

返回值遵循各框架的响应式模型:React 和 Svelte 中为普通值, Vue 中为 Reflatest.value),Angular 中为 signal (latest())。该值 undefined 直到收到第一个 payload。

可选的第三个 target 参数将订阅限定到一个命名空间, 与 useMessages(stream, node) 将消息限定到已发现的图形节点的方式相同。 参见 图形执行 了解命名空间 targeting.

使用 useChannel

useChannel 是原始事件的应急方案。它订阅一个或多个 通道,返回底层协议事件的有限缓冲区,而非 单个未包装的值。当您需要历史记录而不是 最新值时(如事件日志或审计跟踪),或当您需要一个 没有更高级别选择器覆盖的通道时,请使用它。

传递完整的通道 id("custom:redaction-stats"):

const rawEvents = useChannel(stream, ["custom:redaction-stats"]);
const rawEvents = useChannel(stream, ["custom:redaction-stats"]);
// rawEvents.value
const rawEvents = useChannel(stream, ["custom:redaction-stats"]);
const rawEvents = injectChannel(stream, ["custom:redaction-stats"]);
// rawEvents()

每个条目都是原始协议事件,因此 payload 位于 event.params.data。请自行解包:

function parseRedactionStatsEvents(rawEvents: Event[]): RedactionStatsEvent[] {
  const out: RedactionStatsEvent[] = [];
  for (const event of rawEvents) {
    const data = event.params?.data;
    const payload = data?.payload ?? data;
    if (payload?.kind === "update") out.push(payload);
  }
  return out;
}

使用选项参数控制缓冲区:

const rawEvents = useChannel(
  stream,
  ["custom:redaction-stats"],
  undefined, // target namespace
  { bufferSize: 200, replay: true },
);
选项默认值效果
bufferSize"default"缓冲区事件的最大数量。超过上限后,较早的事件会被丢弃。
replaytrue在选择器挂载时重放通道上已看过的事件,而非仅处理实时事件。

useExtensionuseChannel

之间选择。两者都读取同一个自定义通道,但返回内容不同:

useExtensionuseChannel
**返回**最新 payload(`T \undefined`)
**形状**解包后的类型化 payload原始协议事件;解包 event.params.data 自行
**订阅方式**通道名称("redaction-stats"完整通道 id(["custom:redaction-stats"])
**使用场景**需要当前值需要历史记录、日志或多通道
**选项**bufferSize, replay

一种常见的模式是在同一通道上同时使用两者: useExtension 驱动 实时摘要(当前总数),而 useChannel 支持可滚动的事件日志,记录 整个线程中的每次更新。

用例

自定义通道适用于任何无法干净映射到 消息、工具调用或图状态的服务器端信号:

- **合规性和编辑统计**:已清除的PII、被阻止的内容 或策略命中次数,如上面的示例所示。 - **进度报告**:长时间运行的工具发出的 完成百分比或步骤标签。 - **实时指标**:运行期间累积的令牌使用量、延迟或成本。 - **来源和引用**:代理在 提供答案时推送到侧边栏的检索文档。 - **领域事件**:后端想要呈现的任何结构化更新 而无需更改消息记录。

相关资源

- 概述 — LangGraph 前端流 API 和架构。 - 图执行 — 命名空间作用域 用于多节点管道的选择器。