加入和重新加入让你可以从正在运行的代理流断开连接,而不停止代理,之后再重新连接。客户端离开期间,代理会继续在服务器端执行,你可以从离开的位置准确接回流。
This feature requires the LangGraph Agent Server. Run your agent locally with langgraph dev or deploy it to LangSmith to use this pattern.

为什么使用加入和重新加入?

传统流式 API 将客户端和服务器紧密耦合:如果客户端断开连接,流就会丢失。加入和重新加入打破了这种耦合,支持几个重要模式:
  • 网络中断:移动用户在基站或 Wi-Fi 网络之间切换时可以无缝恢复
  • 页面导航:用户离开聊天页面后稍后返回,不会丢失进度
  • 移动端后台运行:被操作系统挂起的应用回到前台后可以重新加入流
  • 长时间运行的任务:代理执行持续数分钟的操作(研究、代码生成、数据分析)时,用户不需要一直保持页面打开
  • 多设备交接:在手机上开始对话,在桌面端重新加入

核心概念

加入/重新加入模式包含三个关键机制:
方法 / 选项用途
threadId将流绑定到你想观察的 LangGraph 线程
onThreadId持久化新创建的线程 ID,让重新挂载可以重新连接
stream.disconnect()在客户端离开流,同时让代理继续在服务器端运行
使用相同的 threadId 重新挂载重新附加到该线程正在进行的工作
加入/重新加入使用 stream.disconnect(),不是 stream.stop() 默认情况下,stream.stop()取消活动运行:它会断开客户端,并取消服务器上的运行。对于加入/重新加入,请调用 stream.disconnect()stop({ cancel: false }) 的别名),这样你离开时代理仍会继续处理。要从应用代码显式取消执行,请使用 stream.stop()client.runs.cancel

设置 useStream

关键设置步骤是持久化 threadId。当组件用相同线程 ID 重新挂载时,流会附加到线程的当前状态和任何正在进行的运行。
The code examples use useStream<typeof myAgent> for type-safe stream state. See Type inference for Python or JavaScript backends.
import { useStream } from "@langchain/react";
import { useCallback, useState } from "react";

function Chat() {
  const [connected, setConnected] = useState(true);
  const [mountKey, setMountKey] = useState(0);
  const [threadId, setThreadId] = useState<string | null>(
    () => sessionStorage.getItem("activeThreadId"),
  );

  const stream = useStream<typeof myAgent>({
    apiUrl: "http://localhost:2024",
    assistantId: "join_rejoin",
    threadId,
    onThreadId(id) {
      setThreadId(id);
      if (id) sessionStorage.setItem("activeThreadId", id);
    },
  });

  const disconnect = useCallback(() => {
    void stream.disconnect();
    setConnected(false);
  }, [stream]);

  const rejoin = useCallback(() => {
    setMountKey((key) => key + 1);
    setConnected(true);
  }, []);

  return (
    <div key={mountKey}>
      <ConnectionStatus connected={connected} />
      <MessageList messages={stream.messages} />
      <ChatControls
        stream={stream}
        threadId={threadId}
        connected={connected}
        onDisconnect={disconnect}
        onRejoin={rejoin}
      />
    </div>
  );
}

提交消息

正常提交消息。线程 ID 绑定让之后的重新挂载可以重新连接到同一对话:
stream.submit({ messages: [{ type: "human", content: text }] });

从流断开连接

调用 stream.disconnect() 离开流且不取消运行。代理会继续在服务器端处理。
await stream.disconnect();
// equivalent to: await stream.stop({ cancel: false })
这里不要使用 stream.stop(),默认情况下它会取消服务器上的运行。 调用 disconnect() 后:
  • stream.isLoading 变为 false
  • 你自己的 connected 标记也应变为 false
  • 消息列表保留断开连接前收到的所有消息
  • 代理继续在服务器上运行
  • 在你重新加入之前不会收到新消息

重新加入流

使用保存的线程 ID 重新挂载流消费者以重新连接。在 React 中,演示会递增 mountKey;在其他框架中,使用等效的重新挂载或条件渲染模式:
setMountKey((key) => key + 1);
setConnected(true);
重新加入后:
  • connected 变为 true
  • 断开连接期间生成的任何消息都会送达
  • 新的流式消息会实时恢复
  • 如果代理仍在运行,stream.isLoading 变为 true;如果它已经完成,你会立即收到最终状态

最佳实践

  • 使用 disconnect() 加入/重新加入,使用 stop() 取消:离开页面或应用进入后台时应调用 stream.disconnect()。面向用户的“Stop”或“Cancel”按钮应调用 stream.stop()(或 client.runs.cancel)。
  • 始终保存线程 ID:没有线程 ID,就无法重新加入。使用组件状态和持久存储提高韧性。
  • 显示清晰的连接状态:用户应始终知道自己是在接收实时更新,还是在查看快照。
  • 在可见性变化时自动重新加入:使用 Page Visibility API,在用户返回标签页时自动重新加入。
  • 设置合理超时:如果重新加入尝试耗时太久,回退到获取线程历史。
  • 清理过期线程:当用户重新开始,或后端报告线程不可用时,移除持久化的线程 ID。