同一个 AI Agent 如何同时服务 Web、微信和 QQ:PureChat 的渠道架构实践

把模型接进 Web 页面并不算最难。真正接入微信和 QQ 后,问题会变成:谁维护连接、消息如何去重、实例怎样恢复、多个渠道如何复用 Agent,以及 Serverless 能支持到哪一步。

PureChat 最初也经历过把不同能力堆在同一个应用里的阶段。重新设计 PureChatNext 时,我给自己定了一条边界:微信、QQ 和 Web 只能是不同入口,不能成为三套独立业务。

一、先把 UI、BFF 和持续连接拆开

PureChatNext 的生产形态可以概括为"SPA + Next BFF + Channel Gateway":

text 复制代码
┌─────────────────────────────────────────────┐
│                用户入口                     │
│        Web SPA        微信        QQ         │
└──────────┬─────────────┬─────────┬──────────┘
           │             │         │
           ▼             ▼         ▼
┌─────────────────────────────────────────────┐
│ Next.js BFF / API / Auth / Channel Webhook  │
├─────────────────────────────────────────────┤
│ Agent、模型 Provider、搜索、文件与用量逻辑  │
├─────────────────────────────────────────────┤
│ PostgreSQL:用户、配置、事件队列、渠道状态  │
└──────────────────────┬──────────────────────┘
                       │
                       ▼
          Channel Gateway(持久 Node 进程)
             微信 Poller / QQ WebSocket

业务 UI 使用 React 19、Vite 和 React Router。Next.js 16 不负责主要页面路由,而是承担 API、认证、生产 SPA HTML 壳和同域 BFF。这样做的原因很实际:开发时 SPA 可以保持快速反馈,生产环境仍然只有一个域名,认证 Cookie、API 和静态资源不需要跨域拼接。

Channel Gateway 则运行在同一个 Next Node Server 中,负责那些无法依赖短生命周期函数完成的连接。它不是另一个需要单独启动、部署和监控的机器人进程。

二、渠道只保存"入口选择",Agent 能力由核心层复用

如果分别为微信和 QQ 写一套机器人逻辑,短期确实更快,但后面通常会出现几类重复:

  • 每个机器人都维护一份模型和 API Key 配置;
  • 工具调用、联网搜索和文件能力各自实现;
  • 修改 Agent 提示词后,要分别同步到多个渠道;
  • 用量、错误和会话状态无法统一观察。

PureChat 的渠道绑定只关心"这条消息属于谁、应该交给哪个 Agent、使用哪个模型"。真正的模型调用和工具能力仍由核心聊天链路处理。因此,在管理端切换绑定的 Agent 或模型后,微信和 QQ 不需要拥有各自的 Agent 实现。

下面两张图是本地管理端的真实连接状态。浏览器地址显示 localhost,因为完整 Channel Gateway 需要持久 Node.js 运行环境,这里不是线上 Demo 截图。

三、微信链路:不要让 cursor 和业务处理互相打架

微信渠道目前通过 iLink 长轮询接收消息。最初看起来只要"拉消息 → 调模型 → 发回复"即可,但持续运行后至少要处理这些情况:

  • 同一批消息因为网络失败被重复拉取;
  • 模型生成耗时较长,阻塞后续 poll;
  • 实例重启时 cursor 与业务写入不一致;
  • 多个实例同时运行,重复消费同一个绑定;
  • 回复过长,需要分片发送;
  • 会话失效,需要明确进入重新绑定状态。

当前链路把接收和生成解耦:Poller 拉到完整批次后回调内部 Webhook,Webhook 在数据库事务中写入事件队列并推进 cursor,再由 processor 生成和分片发送回复。如果 Webhook 失败,cursor 不会推进,Poller 会重试同一批次。

为了避免多个应用实例同时建立连接,每个绑定会持有 PostgreSQL Gateway lease。租约 TTL 为 90 秒,每 30 秒续租;实例停止后,其他实例可以在租约过期后接管。这里选择数据库租约而不是进程内锁,是因为真正需要解决的是多实例所有权,而不是单进程并发。

这套设计不能消除上游协议变化或会话过期,但能分别观察连接异常和业务消息处理。会话失效时,绑定进入 needs_rebind,由用户重新扫码,而不是继续显示虚假的"已连接"。

四、QQ 为什么同时保留三种模式

QQ 目前支持扫码绑定、WebSocket 和 Webhook:

  1. 扫码绑定:适合本地和单实例 Docker。手机确认后获取机器人凭证,再建立 WebSocket;
  2. 手动 WebSocket:适合已经有 QQ 开放平台 App ID / Secret 的自托管环境;
  3. Webhook:由 QQ 开放平台调用无状态 Route,适合 Vercel 等不能维护持久连接的环境。

三种模式并存是因为运行环境存在差异。WebSocket 状态必须由真实心跳判断,不能因为数据库里有绑定记录就显示"在线"。当前实现以 90 秒内的心跳作为依据,同时保存运行状态、最近心跳和脱敏错误摘要。

扫码还有一个需要特别说明的限制:扫码会话和机器人 Secret 在成功绑定前只存在于当前 Node.js 进程中。因此首版扫码只适合单实例本地或 Docker,不适合多副本路由漂移、Serverless 或跨进程续接。相比为了"看起来支持云部署"而隐藏限制,我更倾向于让 UI 在运行环境不支持时直接禁用扫码和 WebSocket。

五、为什么把 Gateway 放回 Next Node Server

早期最自然的做法是为微信和 QQ 分别启动 gateway 脚本,但这会拆散环境变量、健康检查、内部鉴权和重试策略,也会增加 Docker 用户需要理解的进程与启动顺序。

现在 Gateway 与 Next Node Server 同进程运行,生产 Compose 只需要一个 app 容器。健康检查可以汇总连接数量,单绑定异常标记为 degraded,核心启动失败则返回 503 unhealthy。这只是当前规模下对部署和可观测性的取舍;绑定数量继续增长后,Gateway 仍可能重新独立扩容。

六、部署边界必须成为产品的一部分

很多渠道问题并不是代码 Bug,而是把不可能的运行方式包装成了"支持"。PureChat 当前明确区分:

环境 微信 QQ 扫码 / WebSocket QQ Webhook Web 工作台
本地 Node.js 支持 支持 支持 支持
Docker 支持 支持,扫码首版建议单实例 支持 支持
Vercel 不支持持久连接 不支持 支持 支持

微信和 QQ WebSocket 的最小配置大致如下:

dotenv 复制代码
CHANNEL_GATEWAY_ENABLED=1
CHANNEL_GATEWAY_INTERNAL_SECRET=replace-with-a-random-secret
DATABASE_URL=postgresql://...
KEY_VAULTS_SECRET=replace-with-a-random-secret
OPENAI_API_KEY=... # 或其他受支持的服务端模型密钥

实际部署还需要配置认证、正式域名和 CORS。密钥不应该写入仓库,也不应该通过聊天、Issue 或截图提供。

七、这套架构换来了什么,也付出了什么

同一个 Agent 现在可以服务 Web、微信和 QQ,搜索、文件、模型切换和用量逻辑也能从核心层复用。代价是 PostgreSQL 还要承担队列与租约协调,自托管配置和测试矩阵都会变大,并且必须明确区分 Serverless 与持久连接。

这套结构不适合只想快速完成个人聊天页面的项目。只有当多个入口确实需要复用同一套 Agent、工具和数据时,这些复杂度才有意义。

八、体验与讨论

我接下来最想继续验证三个问题:数据库队列是否足够支撑当前规模、扫码流程怎样安全支持多实例,以及用户是否真的需要在不同渠道切换同一个 Agent。如果你也在做消息渠道、Bot 或自托管 AI,欢迎分享你在连接恢复、消息幂等和部署边界上的做法。


相关推荐
lovingsoft1 小时前
AI Agent 不是复读机:一个“计划-观察-执行“闭环,把大模型从聊天框变成能闭环干活的实习生
人工智能
码路漫漫1 小时前
人类程序员还有用,记一次 GPT 把 Figma 两个接口搞反的事
人工智能·程序员
我的AI队友1 小时前
DeepSeek Harness 接钉钉通知,踩了两个坑:签名不匹配 + 纯对话刷屏
后端·deepseek
程序员鱼皮1 小时前
16 个超火的 DeepSeek Harness 插件,大肥鱼已经落后 N 个版本了。。。
前端·后端·ai编程
云存储小精灵1 小时前
DeepSeek Harness COS 插件上线:让 Agent 管理文件更简单
人工智能·产品
步行cgn1 小时前
Spring Cache 详解:Spring 框架的缓存抽象
后端
eralong1 小时前
Java IO 与 NIO
java·后端
量化小c1 小时前
从数据到策略:QuantDash + DuckDB 搭建 5 分钟 K 线本地量化数据仓库
后端·github