AI 对话接入架构
对话 Bot 的本质是“接收平台事件并回复”,AI 只是其中一种回复生成方式。先完成可预测的普通 API 双向对话,再把业务处理器替换为 LLM,可以明显降低平台配置、事件协议和模型问题相互干扰的概率。
本文是跨平台设计基线,核验日期为 2026-09-08。平台字段、权限和时限仍以各渠道教程及官方资料为准。
| 类型 | 能否接收用户消息 | 本手册中的定位 |
|---|---|---|
| 飞书、钉钉、企业微信、Telegram、Slack、Discord、Teams Bot | 可以,需配置事件、长连接或 Gateway | 可做普通双向 Bot,也可扩展 AI 对话 |
| Incoming Webhook、自定义群机器人通知 URL | 通常不可以 | 只用于单向投递,不能据此实现对话 |
| Server 酱、PushPlus、WxPusher、PushDeer、Gotify、ntfy、Pushover | 本手册覆盖的接口是通知投递 | 保持通知型定位,不虚构对话能力 |
“平台宣传有 AI 能力”不等于必须使用平台自带模型。只要平台能把消息事件交给你的服务,并允许你的服务回复,就可以接入规则引擎、业务 API、自建模型或任意第三方 LLM。
用户 / 群聊 ↓平台事件入口(Webhook / WebSocket / Events API / Gateway) ↓ 验签、解密、快速确认平台接入层(只处理平台协议) ↓ 标准事件去重与路由层 ──→ 权限、配额、审计 ↓会话层(会话键、上下文、取消与并发控制) ↓回复处理器 ──→ 规则 / 业务 API / generateReply(可选 LLM) ↓平台发送适配器(回复原消息 / 原线程 / 原会话)每个平台适配器只承担四件事:验证来源、解析事件、快速确认、发送回复。不要在验签函数或 Webhook 路由中直接堆放提示词、知识库检索和模型调用。
建议把入站消息转换成统一结构:
interface ConversationEvent { platform: 'feishu' | 'dingtalk' | 'wecom' | 'telegram' | 'slack' | 'discord' | 'teams'; eventId: string; conversationId: string; threadId?: string; senderId: string; text: string; mentioned: boolean; replyHandle: unknown; receivedAt: number;}replyHandle 保存平台回复所需的最小上下文,例如消息 ID、线程时间戳、频道 ID、response_url 或 Activity 上下文;它不进入提示词,也不写入面向模型的聊天记录。
普通 API 双向对话(非 AI)
Section titled “普通 API 双向对话(非 AI)”上线 AI 前先完成一个不调用模型的确定性处理器:
async function handleBusinessReply(event) { const text = event.text.trim(); if (text === '/ping') return 'pong'; if (text === '/help') return '可用命令:/ping、/status、/ticket <编号>'; if (text.startsWith('/ticket ')) { return await queryTicket(text.slice('/ticket '.length)); } return '未识别该命令,请输入 /help。';}这条路径已经是完整的 API 双向对话:用户发消息,平台推送事件,你的服务调用业务 API,再把结果回复到同一会话。它具有响应确定、成本低、容易审计的优势,适合查询、审批、运维命令和表单流程。
AI 对话扩展
Section titled “AI 对话扩展”将回复生成约束为厂商无关接口,平台适配器无需知道使用了哪个模型:
type ChatMessage = { role: 'system' | 'user' | 'assistant'; content: string };
interface ReplyGenerator { generateReply(input: { messages: ChatMessage[]; signal: AbortSignal; metadata: { platform: string; senderId: string; conversationKey: string }; }): Promise<string>;}async function processEvent(event, replyGenerator) { if (await dedupe.seen(event.eventId)) return; const conversationKey = buildConversationKey(event); const history = await conversations.load(conversationKey, 20); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 20_000);
try { const reply = await replyGenerator.generateReply({ messages: [...history, { role: 'user', content: event.text }], signal: controller.signal, metadata: { platform: event.platform, senderId: event.senderId, conversationKey }, }); await platform.sendReply(event.replyHandle, reply); await conversations.append(conversationKey, event.text, reply); } finally { clearTimeout(timeout); }}async def process_event(event, reply_generator): if await dedupe.seen(event.event_id): return
conversation_key = build_conversation_key(event) history = await conversations.load(conversation_key, limit=20) reply = await reply_generator.generate_reply( messages=[*history, {"role": "user", "content": event.text}], timeout_seconds=20, metadata={ "platform": event.platform, "sender_id": event.sender_id, "conversation_key": conversation_key, }, ) await platform.send_reply(event.reply_handle, reply) await conversations.append(conversation_key, event.text, reply)会话键决定哪些消息共享上下文。推荐至少包含租户和平台,避免跨企业串话:
私聊:{tenant}:{platform}:dm:{conversationId}:{senderId}群聊:{tenant}:{platform}:group:{conversationId}:{threadId-or-root}群聊默认按线程隔离;平台没有线程时,再按群聊隔离。不要只用 senderId,同一个人在不同企业、群聊或主题中的内容不应自动混合。
- 只保存完成的用户/助手消息,不把原始平台事件整体写入模型上下文。
- 同时设置消息条数、Token 数和时间窗口上限。
- 长会话先摘要再截断,并保留最近几轮原文。
- 提供“清空上下文”命令和明确的数据保留期。
- 多实例部署使用共享存储;内存 Map 只适合本地验证。
事件去重与顺序
Section titled “事件去重与顺序”平台可能因超时或网络错误重试同一事件。以官方事件 ID 作为首选幂等键;没有稳定 ID 时,对“平台 + 会话 + 消息 ID + 事件类型”做哈希。去重记录的过期时间应覆盖平台最大重试窗口。
同一会话的两条消息还可能并行到达。可按会话键加短租约锁,或把事件投递到按会话分区的队列。新的用户消息到达时,可以取消上一轮仍在生成的低优先级回答,避免回复顺序颠倒。
超时与异步响应
Section titled “超时与异步响应”Webhook 的确认时限通常短于一次模型调用。通用处理顺序是:
- 验签、解析并写入队列。
- 在平台规定时限内返回成功或 defer。
- 后台调用业务 API 或 LLM。
- 使用平台允许的回复 API、线程消息或后续 Webhook 返回结果。
只有平台明确支持时才流式更新。否则先回复“已收到,正在处理”可能产生噪声;更稳妥的做法是使用平台的 defer、占位消息或 typing 能力,并设置总超时和失败提示。
权限与触发规则
Section titled “权限与触发规则”- 群聊默认只响应
@机器人、斜杠命令或显式回复,避免监听所有对话。 - 在服务端再次校验租户、群聊、用户和命令权限,不能只依赖客户端按钮是否可见。
- 高风险动作使用二次确认或审批,不让模型直接执行转账、删除、发布等不可逆操作。
- 普通查询与 AI 对话可以共用入口,但命令路由应优先于自由文本模型处理。
AI 安全基线
Section titled “AI 安全基线”把用户消息、检索文档和工具返回值都视为不可信输入。系统指令和工具权限不能由消息正文覆盖;工具调用应通过结构化参数、白名单和服务端鉴权执行。
- 发送到模型前移除 Token、手机号、证件号和业务密钥等敏感数据。
- 明确数据跨境、模型训练、保留期和删除能力是否满足组织要求。
- 日志不记录完整聊天正文和平台凭据;必要时按字段脱敏并限制访问。
- 给用户清晰的 AI 身份提示,重要结论提供来源或要求人工确认。
- 限制单次输入、输出和附件大小,防止成本失控。
- 对 Markdown、HTML、提及和链接按平台规则转义。
- 对工具执行结果做独立验证,不把模型生成的“成功”当成真实执行结果。
- 模型不可用时降级到帮助菜单、工单或人工服务,而不是无限重试。
可观测性与验收
Section titled “可观测性与验收”每次处理至少记录脱敏后的 eventId、会话键哈希、平台、处理阶段、耗时、回复消息 ID、模型用量和错误类型。建议分别统计:入站验签失败、重复事件、队列延迟、生成耗时、平台限流和回复失败。
上线前按顺序验收:
- 确定性
/ping能在私聊或测试群完成一问一答。 - 同一事件重复投递只回复一次。
- 模型超时、平台 429、进程重启都有明确降级。
- 两个租户、两个群聊和两个线程之间不会共享上下文。
- 提示词注入测试不能越过工具权限或泄露系统配置。
- Token 轮换后旧凭据失效,日志中不存在完整密钥。
- 飞书对话机器人
- 钉钉企业机器人对话
- 企业微信智能机器人 API 模式
- Telegram 对话 Bot
- Slack Events API 对话
- Discord Gateway / Interactions 对话
- Microsoft Teams 对话 Agent
跨平台不存在统一的官方对话协议。请以各平台教程末尾列出的官方文档为准;LLM 接口由实际选用的模型服务提供方定义。本页刻意不绑定任何模型厂商或现有通知网关实现。