第 24 章《WebSocket 通道》· nanobot WebSocket 通道源码深度解析:多路复用 + 重连空闲 + 媒体接入

本文回答什么问题:WebSocket 通道和 Telegram 通道有什么不同?为什么 WebUI 用 WebSocket?多路复用怎么实现?断线重连怎么处理?

目标读者 :想深入 WebUI 实现 / 自定义 WebSocket 客户端的开发者

预计阅读时间 :14 分钟

源码版本 :GitHub HKUDS/nanobot main 分支主线代码(仓库相对路径)

WebSocket 通道(nanobot/channels/websocket/)是 nanobot 最特殊的通道------它是 WebUI 与 AgentLoop 的桥梁,所有 WebUI 用户共享同一通道。

1. 整体定位:为什么 WebUI 用 WebSocket

WebUI 是浏览器,与 gateway 长跑进程通信只能用:

  • HTTP 轮询:延迟高 + 服务器负载重
  • WebSocket:双向实时 + 一次连接复用

WebSocket 通道 + FastAPI /ws 端点让浏览器享受毫秒级响应

核心要点速查(建议收藏)

  • 核心文件 :nanobot/channels/websocket/runtime.py(约 250 行)+ nanobot/web/server.py(/ws 端点 ~50 行)
  • WebSocket 端点 ws://host:port/ws(FastAPI WebSocket route)
  • 多路复用 单 gateway 支持 N 个 WebSocket 连接(每个浏览器一个)
  • 断线重连 客户端自动(浏览器内置),服务端不主动断
  • 媒体接入 图片 / 音频作为 base64 inline

2. WebSocket 通道实现

python 复制代码
# nanobot/channels/websocket/runtime.py
class WebSocketChannel(BaseChannel):
    name = "websocket"
    display_name = "WebUI"

    def __init__(self, config: Config, bus: MessageBus):
        super().__init__(config, bus)
        self._connections: dict[str, WebSocket] = {}  # sender_id -> ws

    async def start(self) -> None:
        """WebSocket 通道的 start 由 FastAPI /ws 端点触发,不主动启动。"""
        pass  # 由 gateway 启动 HTTP server

    async def register_connection(self, sender_id: str, ws: WebSocket):
        """新连接注册到 self._connections。"""
        self._connections[sender_id] = ws

    async def handle_message(self, sender_id: str, data: dict):
        """从 WebSocket 收消息,转 InboundMessage。"""
        await self._bus.publish_inbound(InboundMessage(
            channel="websocket",
            sender_id=sender_id,
            chat_id=data["chat_id"],
            content=data["content"],
            media=data.get("media", []),
        ))

    async def send(self, msg: OutboundMessage) -> None:
        """发到指定 sender_id 的 WebSocket。"""
        ws = self._connections.get(msg.sender_id)  # 注意:OutboundMessage 需要 sender_id 字段
        if ws is None:
            return
        await ws.send_json({
            "content": msg.content,
            "event": msg.event.type if msg.event else None,
            "event_data": msg.event.__dict__ if msg.event else {},
        })

    async def stop(self) -> None:
        for ws in self._connections.values():
            await ws.close()

关键差异 vs Telegram:

  • start() 不主动------HTTP server 启动后由 FastAPI 端点触发
  • 多连接 --- self._connections 字典,每个 WebSocket 一个
  • media --- WebUI 直接传 base64,不需要 download

3. FastAPI /ws 端点

python 复制代码
# nanobot/web/server.py
@app.websocket("/ws")
async def ws_endpoint(websocket: WebSocket):
    await websocket.accept()
    sender_id = websocket.headers.get("x-client-id", "anonymous")
    channel = websocket.app.state.ws_channel

    # 注册到通道
    await channel.register_connection(sender_id, websocket)

    try:
        # 处理客户端消息
        async for data in websocket.iter_json():
            await channel.handle_message(sender_id, data)
    except WebSocketDisconnect:
        # 客户端断开
        await channel.unregister_connection(sender_id)

4. 多路复用时序

渲染错误: Mermaid 渲染失败: Parse error on line 14: ...h_inbound Bus->>Loop: consume_inboun ----------------------^ Expecting '+', '-', '()', 'ACTOR', got 'loop'

5. WebSocket 协议(客户端 ↔ 服务端)

客户端 → 服务端

json 复制代码
{
  "type": "message",
  "content": "你好",
  "chat_id": "user1",
  "media": []
}

服务端 → 客户端(文本)

json 复制代码
{
  "content": "你好!",
  "event": null
}

服务端 → 客户端(流式 delta)

json 复制代码
{
  "content": "你好",
  "event": "stream_delta",
  "event_data": {"content": "你好"}
}

服务端 → 客户端(回合结束)

json 复制代码
{
  "content": "",
  "event": "turn_end",
  "event_data": {"latency_ms": 1234}
}

6. 实战:WebSocket 客户端示例

javascript 复制代码
// webui/src/connection.ts
const ws = new WebSocket(`ws://${host}:${port}/ws`);

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: "message",
    content: "你好",
    chat_id: "user1",
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.event === "stream_delta") {
    // 流式:编辑原消息
    updateMessage(data.event_data.content);
  } else {
    // 最终回复
    showMessage(data.content);
  }
};

7. 实战:断线重连

javascript 复制代码
// webui/src/connection.ts
function connect() {
  ws = new WebSocket(url);
  ws.onclose = () => {
    // 自动重连(指数退避)
    setTimeout(connect, Math.min(30000, 1000 * 2 ** reconnectCount));
    reconnectCount++;
  };
}

服务端侧 :channel._connections[sender_id]WebSocketDisconnect 时自动移除------下次客户端重连会重新注册。

8. 4 个核心决策

决策 1 · 为什么多路复用(每客户端一连接)而不是广播?

WebUI 是个人化 UI------每用户独立 session / 历史 / 上下文。广播会让"用户 A 看到用户 B 的回复"。

决策 2 · 为什么 WebSocket 通道不 start()?

HTTP server 启动后,FastAPI 端点 accept() 才算"通道启动"------start() 等待 accept() 而非主动 start。

决策 3 · 为什么 media 直接传 base64?

浏览器访问 WebUI 的图像无需 download 服务器------base64 inline 直接渲染。

决策 4 · 为什么 WebSocket 通道不用 pairing?

WebUI 在 localhost,只有本机用户能访问------pairing 机制对 WebUI 多余。

9. 4 个常见误区

误区 1 · WebSocket 断线会丢消息?

A :(未投递的 outbound 找不到对应 sender_id)。建议 WebUI 重连时拉 get_recent 历史补齐。

误区 2 · WebSocket 性能瓶颈?

A:单 gateway 100+ 并发 WebSocket 没问题;FastAPI + uvicorn 单进程 ~500 连接。

误区 3 · 流式 delta 顺序乱?

A :send_json 按 publish 顺序;但不同 sender_id 并发可能交错------前端按 chat_id 过滤。

误区 4 · WebSocket 通道支持跨域?

A :。WebUI 在 localhost OK;远程需 app.add_middleware(CORSMiddleware, ...)

10. 小结

  • WebSocket 通道 WebUI 与 gateway 的桥梁
  • 多路复用 每客户端独立连接
  • 流式 StreamDeltaEvent 实时编辑
  • 断线重连 客户端自动

本文要点速查

  1. WebSocket 通道 见 §2
  2. FastAPI /ws 端点 见 §3
  3. 协议 见 §5
  4. 下一步:第 25 章《Feishu / Weixin 复杂通道解析》------ 复杂通道的 5 个挑战

按角色推荐
  • 聊天通道开发者:必读(WebSocket 通道模式必读)
  • 前端开发者:必读(WebUI 怎么连 gateway)
  • 系统架构师:必读
  • LLM Agent 开发者:选读
  • Tool / MCP 工具开发者:选读
下一步
  • 第 25 章《Feishu / Weixin 复杂通道解析》 ------ 复杂通道的 5 个挑战(主题群"聊天通道",第 4 周)

tags :#nanobot #AI Agent #LLM #Python #源码解析 #WebSocket #WebUI

相关推荐
sugar__salt2 天前
从跨域到 WebSocket:一篇讲透浏览器的通信边界
网络·websocket·网络协议
ocean21032 天前
2025-2026年计算机网络大厂面试高频问题示例
websocket·计算机网络·秋招·tcp·后端面试·大厂面经·面试真题
Darling噜啦啦2 天前
WebSocket 双工通信实战:从协议握手到跨域原理,彻底搞懂实时通信的"另一条路"
websocket
艾莉丝努力练剑2 天前
【AI大模型接入SDK】ChatGPT API
网络·c++·人工智能·websocket·网络协议·学习·chatgpt
wjcroom3 天前
一个可以在线多人玩的五子棋开发与布署方法-WebRTC和WebSocket的测试方法
websocket·网络协议·webrtc
orient.lu3 天前
第 21 章《图像生成与音频转录》· nanobot 多模态 Provider 源码解析:11 图像 + 6 转录注册表
音视频·nanobot
Sylvia33.3 天前
LOL实时数据接入深度解析:从WebSocket到完整数据模型
java·网络·python·websocket·网络协议
鲨鱼辣钊5 天前
FastAPI进阶_Day21_WebSocket实时通讯实战
websocket·网络协议·fastapi
秋田君5 天前
Qt_webSocket协议编程实战
开发语言·qt·websocket