tokens 聊天助手移植到 xhs-miniapp-saas 设计文档
- 日期:2026-08-01
- 分支:
feat/chat-assistant - 状态:设计已确认,待实施
1. 背景与目标
1.1 背景
tokens/ 目录是一个独立的 Node.js/Express 聊天客服系统(NearLight 智子A),功能包括:
- WebSocket 实时私聊(普通用户 ↔ admin 客服)
- AI 自动流式回复(智谱 GLM / Kimi,原生 web_search 联网搜索)
- 媒体上传分析(Kimi 多模态视觉 + ffmpeg 视频抽帧)
- 知识库(用户级
knowledge_docs+ 全局global_knowledge_docs) - 记忆库(自动分析对话生成记忆切片)
- RSA-OAEP + AES-256-GCM 端到端传输加密
- 独立的 JWT RS256 认证 + HttpOnly Cookie + token_version 失效 + WS 票据
技术栈:Express + sql.js (SQLite) + ws + 原生 HTML/CSS/JS 前端。
1.2 目标
将 tokens 全量功能移植到当前 xhs-miniapp-saas 项目,以现有业务为主:
- 后端:用 Python/FastAPI 全部重写 tokens 的后端逻辑,合并到现有 backend(
/api/admin同一 FastAPI 实例),数据库从 SQLite 迁移到 PostgreSQL(复用现有连接池 + RLS) - 前端:用 React + antd 重写聊天页,作为 web 端新首页(
/),现有 Dashboard 降级到/dashboard - 用户模型:merchant ↔ AI 单角色对话(merchant 登录后直接与 AI 助手对话),admin 在后台查看/管理所有会话、知识库、记忆库
- 认证:复用现有 merchant JWT(access token 内存存储)+ WS 一次性 ticket
- 安全:HTTPS + HMAC-SHA256 请求签名(现有),不保留 RSA+AES 传输加密层
- AI:复用现有
llm_function_configs按功能配置,新增 chat 相关 function_key - 推进:垂直切片渐进式,每个切片端到端可验证,本地测试通过后再考虑上线
1.3 非目标
- 不移植 tokens 的独立用户注册体系(用户名/手机号注册),改用现有 merchant 认证
- 不保留 tokens 的 RSA+AES 端到端加密(用 HTTPS+HMAC 已满足安全约束)
- 不移植 DuckDuckGo 搜索(国内不可用),联网搜索统一走模型原生 web_search
- 不移植 tokens 独立部署(PM2/Nginx 3001 端口),合并到现有部署架构
- 不做 admin 人工介入会话(merchant 优先与 AI 对话,admin 只查看/管理)
2. 架构总览
┌─────────────────────────────────────────────────────────────┐
│ web 端(React + Vite + antd) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 聊天首页 / │ │ Dashboard │ │ 文章/封面等 │ │
│ │ (ChatPage) │ │ /dashboard │ │ /articles... │ │
│ └──────┬───────┘ └──────────────┘ └──────────────┘ │
│ │ useChat hook (WS) + signature.ts (HMAC) │
└─────────┼─────────────────────────────────────────────────────┘
│ wss + ticket
┌─────────┼─────────────────────────────────────────────────────┐
│ backend(FastAPI,同一实例 127.0.0.1:8000) │
│ ┌──────▼───────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ /api/chat/* │ │ /api/admin/ │ │ /api/article │ │
│ │ WS + REST │ │ chat/* │ │ 等现有业务 │ │
│ │ (新增) │ │ (新增) │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │
│ │ │ │
│ ┌──────▼─────────────────▼───────────────────────────┐ │
│ │ LLMClientProxy(复用,新增 function_key) │ │
│ │ chat_reply / chat_memory / chat_media │ │
│ └────────────────────────────────────────────────────┘ │
└─────────┬─────────────────────────────────────────────────────┘
│ asyncpg
┌─────────▼─────────────────────────────────────────────────────┐
│ PostgreSQL(现有) │
│ 现有表 + 新增:chat_conversations / chat_messages / │
│ global_knowledge_docs / chat_memory_docs(带 RLS) │
└──────────────────────────────────────────────────────────────┘3. 数据库设计
3.1 新增表(PostgreSQL,追加到 backend/app/db/schema.sql)
所有带 merchant_id 的表启用 RLS,策略 tenant_isolation 用 current_setting('app.current_merchant_id', true) 隔离。
-- 聊天会话表:每个 merchant 一个与 AI 的会话
CREATE TABLE IF NOT EXISTS chat_conversations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id UUID NOT NULL REFERENCES merchants(id) ON DELETE CASCADE,
last_msg_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (merchant_id) -- 每个 merchant 仅一个会话
);
CREATE INDEX IF NOT EXISTS idx_chat_conversations_merchant ON chat_conversations(merchant_id);
ALTER TABLE chat_conversations ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON chat_conversations
USING (merchant_id::text = current_setting('app.current_merchant_id', true))
WITH CHECK (merchant_id::text = current_setting('app.current_merchant_id', true));
-- 聊天消息表
CREATE TABLE IF NOT EXISTS chat_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
conversation_id UUID NOT NULL REFERENCES chat_conversations(id) ON DELETE CASCADE,
merchant_id UUID NOT NULL REFERENCES merchants(id) ON DELETE CASCADE,
sender_role TEXT NOT NULL CHECK (sender_role IN ('merchant', 'ai', 'system')),
content TEXT NOT NULL,
media_url TEXT,
media_type TEXT,
prompt TEXT,
is_edited BOOLEAN NOT NULL DEFAULT false,
source_doc_ids TEXT,
web_searched BOOLEAN NOT NULL DEFAULT false,
search_sources JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_chat_messages_conv ON chat_messages(conversation_id, created_at);
CREATE INDEX IF NOT EXISTS idx_chat_messages_merchant ON chat_messages(merchant_id, created_at);
ALTER TABLE chat_messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON chat_messages
USING (merchant_id::text = current_setting('app.current_merchant_id', true))
WITH CHECK (merchant_id::text = current_setting('app.current_merchant_id', true));
-- 全局知识库(平台共享,无 RLS,所有 merchant 的 AI 共用)
CREATE TABLE IF NOT EXISTS global_knowledge_docs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 记忆库(按 merchant 隔离)
CREATE TABLE IF NOT EXISTS chat_memory_docs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
merchant_id UUID NOT NULL REFERENCES merchants(id) ON DELETE CASCADE,
title TEXT NOT NULL,
content TEXT NOT NULL,
source_msg_count INTEGER NOT NULL DEFAULT 0,
last_analyzed_msg_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_chat_memory_docs_merchant ON chat_memory_docs(merchant_id, created_at);
ALTER TABLE chat_memory_docs ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON chat_memory_docs
USING (merchant_id::text = current_setting('app.current_merchant_id', true))
WITH CHECK (merchant_id::text = current_setting('app.current_merchant_id', true));3.2 复用现有表
merchants/merchant_profiles:租户主表 + 公司信息merchant_knowledge_chunks:用户级知识库(已有 + pgvector embedding),作为聊天上下文注入llm_function_configs:AI 模型功能级配置(新增 function_key)cover_ai_configs:封面 AI 配置(multimodal_kimi复用于媒体分析)
3.3 RLS 注意事项
global_knowledge_docs不启用 RLS(平台共享),读取时不注入 merchant_id- admin 接口用
AdminDb(无 RLS),可跨租户查看所有会话 - 会话/消息/记忆表写入时由后端自动填充
merchant_id(从 JWT 解析),不信任前端传入
4. 后端 API 设计
4.1 业务端路由 /api/chat(merchant 鉴权 + HMAC 签名)
新增路由文件 backend/app/routers/chat.py:
| 方法 | 路径 | 功能 | 鉴权 |
|---|---|---|---|
| WS | /api/chat/ws?ticket= | WebSocket 连接 | ticket(一次性,TTL 30s) |
| POST | /api/chat/ws-ticket | 获取 WS ticket | CurrentSecureUser |
| GET | /api/chat/messages?before_id=&limit= | 历史消息(分页) | CurrentSecureUser |
| POST | /api/chat/upload | 媒体上传(图片/视频) | CurrentSecureUser |
| GET | /api/chat/suggested-questions | 引导问题 | CurrentSecureUser |
WS ticket 机制:
- merchant 通过
POST /api/chat/ws-ticket(带 JWT + HMAC 签名)获取一次性 ticket - ticket 存内存(TTL 30s,绑定 IP),含
merchant_id(当前项目 JWT 无 token_version 机制,ticket 只需绑定 merchant 身份) - WS 连接时
?ticket=xxx认证,消费后失效 - 复用现有 JWT 校验 merchant 身份
4.2 Admin 路由 /api/admin/chat(admin 鉴权 + 内网隔离)
合并到现有 backend/app/routers/admin.py,新增 chat 子模块:
| 方法 | 路径 | 功能 |
|---|---|---|
| GET | /api/admin/chat/conversations | 所有会话列表(含 merchant 信息、最后消息、消息数) |
| GET | /api/admin/chat/conversations/{merchant_id}/messages | 某 merchant 的消息记录 |
| POST | /api/admin/chat/conversations/{merchant_id}/clear | 清空会话消息 + AI 记忆(保留账号与知识库) |
| GET | /api/admin/chat/global-knowledge/docs | 全局知识库列表 |
| POST | /api/admin/chat/global-knowledge/docs | 新建全局知识文档 |
| PUT | /api/admin/chat/global-knowledge/docs/{id} | 更新 |
| DELETE | /api/admin/chat/global-knowledge/docs/{id} | 删除 |
| POST | /api/admin/chat/global-knowledge/upload | 上传文件 + AI 智能切片 |
| GET | /api/admin/chat/memory/{merchant_id}/docs | 某 merchant 记忆文档 |
| DELETE | /api/admin/chat/memory/docs/{id} | 删除记忆文档 |
4.3 文件组织
backend/app/
├── routers/
│ ├── chat.py # 新增:/api/chat 业务路由
│ └── admin.py # 修改:追加 /api/admin/chat 子路由
├── services/
│ ├── chat_service.py # 新增:会话/消息/记忆 CRUD
│ ├── chat_ws.py # 新增:WebSocket 连接管理 + 消息分发
│ └── chat_ai.py # 新增:AI 流式回复 + 搜索 + 记忆分析
├── models/
│ └── chat.py # 新增:Pydantic 模型
└── db/
└── schema.sql # 修改:追加 4 张表5. WebSocket 协议
5.1 客户端 → 服务端
| type | 字段 | 说明 |
|---|---|---|
dm | {type, content, mediaUrl?, mediaType?} | merchant 发送消息(无需 to,固定发给 AI) |
request-welcome | {type} | 请求欢迎语(首次进入) |
ping | {type} | 心跳 |
5.2 服务端 → 客户端
| type | 字段 | 说明 |
|---|---|---|
dm | {id, senderRole, content, mediaUrl, mediaType, timestamp, aiReplied?, webSearched?, searchSources?} | 消息 |
system | {content} | 系统消息 |
welcome | {content, timestamp} | 欢迎语(不持久化) |
error | {content} | 错误(含 "sign in" 时前端登出) |
pong | - | 心跳响应 |
ai-reply-start | - | AI 开始处理 |
ai-reply-status | {message} | 状态更新("正在搜索...""正在分析图片...") |
ai-reply-chunk | {chunk} | 流式文本块 |
ai-reply-done | {id, content, webSearched, searchSources} | AI 回复完成 |
ai-reply-error | {content} | 生成失败 |
5.3 连接管理
- 单 merchant 单连接(新连接踢旧连接,close 4002)
- 心跳:30s ping/pong,超时 terminate
- 限流:单连接 60 msg/min,单用户 60 msg/min(二级限流)
- 消息大小限制:4096 字节(媒体走 HTTP 上传,WS 只传 URL)
- 优雅关闭:服务端 shutdown 时关闭所有连接
6. AI 集成设计
6.1 复用 LLMClientProxy
新增 function_key 到 llm_function_configs:
| function_key | 用途 | 默认模型 | 说明 |
|---|---|---|---|
chat_reply | 主聊天流式回复 | glm-4.6 或 kimi-k2 | 智谱用原生 web_search 工具,Kimi 用 $web_search |
chat_memory | 对话记忆分析 | glm-4.6-air 或 deepseek-chat | 非流式,分析对话生成记忆 |
chat_media | 媒体分析 | 复用 multimodal_kimi | 不单独建配置,直接复用 |
6.2 流式回复流程(chat_ai.py)
merchant 发消息
↓
chat_ws 接收 → 存 chat_messages → 推送 dm 给 merchant
↓
触发 AI 自动回复(异步):
1. ai-reply-start → 推送
2. 若有媒体: ai-reply-status "正在分析图片..." → 调 chat_media(Kimi 多模态) → 得 mediaContext
3. 智谱/Kimi 原生 web_search: ai-reply-status "已启用联网搜索..."
4. chat_reply 流式生成:
- 构建 system prompt(品牌、知识库上下文、全局知识库上下文、记忆上下文、最近对话)
- 注入 mediaContext
- 逐块 ai-reply-chunk 推送(30ms 节流)
- 完成后存 chat_messages(sender_role=ai) → ai-reply-done 推送
- saveAiReply 记录
5. 断线时 analyzeAndStoreMemory 分析对话生成记忆6.3 System Prompt 模板
基于 tokens ai.js 的 buildMessages,调整:
- 品牌改为与当前项目一致(智米口袋 / 小红书 SaaS 场景)
- 注入:merchant_profiles(公司信息)、merchant_knowledge_chunks(用户知识库)、global_knowledge_docs(全局知识库)、chat_memory_docs(记忆)、最近 20 条对话
- 保留
<followup-questions>["问题1","问题2"]</followup-questions>追问标签机制(前端解析渲染引导按钮) - 输出格式:纯文本,禁止 Markdown/HTML
6.4 联网搜索
- 智谱 GLM:原生
web_search工具(search_result: true),模型自主决定 - Kimi:
$web_search内置工具(builtin_function) - 不移植 DuckDuckGo 和 searchLinks 抓取逻辑
- 不移植
decideSearchPlan(改用模型原生搜索,简化流程)
6.5 媒体分析
- 复用现有
backend/app/services/upload_service.py(图片 base64 / 视频) - 图片:读取为 base64 data URI,调 Kimi 多模态
- 视频:用 ffmpeg 抽帧(每 5s 一帧,最多 4 帧,缩放 512px),转 data URI 数组
- 服务器需预装 ffmpeg(与 tokens 一致)
- 复用
multimodal_kimi功能配置(cover_vision/cover_text 已回退到此)
7. 前端设计
7.1 路由调整(web/src/router/index.tsx)
/ → ChatPage(新首页,聊天)
/dashboard → Dashboard(原首页降级)
/articles → ArticleList(不变)
...其他不变侧边栏导航新增"AI 助手"项(首页),Dashboard 降为子项。
7.2 聊天页组件(web/src/pages/Chat/)
web/src/pages/Chat/
├── index.tsx # 主页面:消息列表 + 输入区 + 引导卡片
├── MessageList.tsx # 消息列表(merchant/ai 气泡 + 媒体 + 流式)
├── MessageInput.tsx # 输入框 + 媒体上传按钮 + 发送
├── WelcomeCard.tsx # 欢迎语 + 引导问题按钮
└── chat.types.ts # 类型定义7.3 Hooks 与状态
web/src/hooks/
├── useChat.ts # 新增:WS 连接管理 + 消息收发 + 重连
└── useMessage.ts # 复用:消息通知
web/src/stores/
└── chat.ts # 新增:Zustand store(消息列表、连接状态、AI 状态)7.4 API client(web/src/api/chat.ts)
- getWsTicket() → POST /api/chat/ws-ticket
- getMessages(beforeId)→ GET /api/chat/messages
- uploadMedia(file) → POST /api/chat/upload
- getSuggestedQs() → GET /api/chat/suggested-questionsWS 连接:复用 signature.ts 不适用(WS 不走 HMAC),用 ticket 认证。
7.5 设计语言
遵循项目"智米口袋"设计语言(project_memory 约定):
- 浅色主题、Fraunces 字体
- 多语义色:珊瑚红(brand)/ 海军蓝(info)/ 翡翠绿(success)/ 琥珀金(warning)/ 紫罗兰(creative/AI)
- AI 消息气泡用紫罗兰(creative)色调区分
- 响应式:移动端汉堡菜单 + 侧边栏抽屉
7.6 Admin 后台新增
admin/src/pages/
├── ChatSessions.tsx # 新增:会话管理(表格 + 详情抽屉)
├── GlobalKnowledge.tsx # 新增:全局知识库 CRUD + 上传
└── MemoryDocs.tsx # 新增:记忆库管理侧边栏新增 3 个导航项。会话管理可点击进入某 merchant 的消息记录详情。
8. 安全设计
8.1 复用现有安全机制
- JWT HS256 + access token(2h,内存存储)+ refresh token(7d,HttpOnly Cookie)
- HMAC-SHA256 请求签名(WebCrypto API,
signature.ts) - 限流:login/register/refresh/API 现有限流 + WS 二级限流
- 内网隔离:admin 接口
AdminIntranetMiddleware+ Nginx IP 白名单
8.2 WS 安全
- WS ticket:一次性,TTL 30s,绑定 IP,含 merchant_id(获取 ticket 时已校验 JWT)
- wss://(HTTPS 升级)
- 消息大小限制 4096 字节
- 不保留 RSA+AES(用 wss + ticket 已足够)
8.3 媒体上传安全
- 复用现有 upload 安全:magic number 校验、大小限制、存储守卫
- 媒体 URL 必须以
/uploads/开头(防路径遍历)
8.4 知识库内容保护
- 全局知识库正文不通过 API 泄露给 merchant(只注入 AI prompt)
/api/chat/suggested-questions只返回通用问题,不暴露知识库标题
9. 垂直切片实施计划
每个切片完成后本地测试,全部通过后再考虑上线。
切片 1:数据库 + 基础聊天
后端:
schema.sql追加 4 张表 + RLSmodels/chat.py:Pydantic 模型services/chat_service.py:会话获取/创建、消息存取services/chat_ws.py:WS 连接管理 + ticket + 基础消息广播(无 AI)routers/chat.py:WS 端点 + ws-ticket + messages 端点main.py注册路由
前端:
pages/Chat/index.tsx:基础聊天 UI(消息列表 + 输入框)hooks/useChat.ts:WS 连接 + 消息收发stores/chat.ts:消息状态api/chat.ts:REST 调用- 路由调整:
/→ Chat,/dashboard→ Dashboard
验证点: merchant 登录后进入首页,发消息能收到系统 echo,消息持久化。
切片 2:AI 流式自动回复
后端:
services/chat_ai.py:流式回复 + system prompt 构建llm_function_configs新增chat_reply配置- 复用
LLMClientProxy.chat_for_function流式 <followup-questions>标签机制- WS 协议:ai-reply-start/status/chunk/done/error
前端:
- 流式 AI 回复气泡渲染(临时气泡 → 正式消息)
- 引导问题按钮组渲染
- AI 状态提示
验证点: merchant 发消息,AI 流式回复,追问按钮可点击。
切片 3:媒体上传分析
后端:
POST /api/chat/upload:图片/视频上传chat_ai.py接入媒体分析:调multimodal_kimi分析图片/视频- ffmpeg 视频抽帧
- ai-reply-status "正在分析图片..."
前端:
- 媒体上传按钮 + 预览
- 媒体消息渲染(图片/视频)
验证点: 发图片,AI 基于图片内容回复。
切片 4:知识库
后端:
- system prompt 注入
merchant_knowledge_chunks(用户级)+global_knowledge_docs(全局) /api/chat/suggested-questions:基于全局知识库标题生成引导问题GET/POST/PUT/DELETE /api/admin/chat/global-knowledge/*- 全局知识库上传 + AI 切片
前端:
- 欢迎卡片 + 引导问题
- admin 全局知识库管理页
验证点: AI 回复包含知识库内容;admin 可管理全局知识库。
切片 5:记忆库
后端:
chat_ai.py新增analyzeAndStoreMemory:merchant 断线时分析对话生成记忆chat_memory_docsCRUD- system prompt 注入记忆上下文
GET/DELETE /api/admin/chat/memory/*
前端:
- admin 记忆库管理页
验证点: 断线重连后 AI 记得之前对话要点;admin 可查看/删除记忆。
切片 6:Admin 后台整合
后端:
GET /api/admin/chat/conversations会话列表GET .../conversations/{merchant_id}/messages消息记录POST .../conversations/{merchant_id}/clear清空
前端(admin):
ChatSessions.tsx:会话管理GlobalKnowledge.tsx:全局知识库MemoryDocs.tsx:记忆库- 侧边栏新增导航
验证点: admin 可查看所有 merchant 会话、管理知识库/记忆库。
10. 部署考虑(上线前)
- Nginx 配置 WS 升级(
Upgrade/Connection头 +proxy_read_timeout) - 服务器预装 ffmpeg
llm_function_configs初始化chat_reply/chat_memory配置- 全局知识库初始内容导入
- 欢迎语模板配置
- 限流参数调优(WS 连接数、消息频率)
- 上线前在本地完整测试所有切片
11. 风险与缓解
| 风险 | 缓解 |
|---|---|
| FastAPI WebSocket 并发性能 | asyncpg 连接池 + 异步处理,单进程足够;必要时加 uvicorn workers |
| AI 流式回复超时 | 30s 超时 + 前端重连 + 错误提示 |
| 记忆分析耗时 | 异步执行,不阻塞 WS |
| ffmpeg 未安装 | 媒体分析降级为仅文字回复 + 通知 |
| 全局知识库泄露 | API 不返回正文,只注入 prompt |
| WS 连接泄漏 | 心跳 + 超时 terminate + 优雅关闭 |