Files
ai-kefu-admin/docs/superpowers/specs/2026-10-12-unified-kefu-platform-design.md
浅念 befdd48c63 docs: 统计分析页入 spec(漏斗/趋势/分布/推送健康,逐库聚合无新表)+ 计划修 3红3黄
- Task3 TenantConfig 站点化(Site dataclass,消除与 Task5 的结构矛盾)
- Task5 拆 5a(Registry 单元级)/5b(装配切换+conftest 迁移+默认租户过渡兜底)
- conftest teardown dispose_all(Windows 文件锁)
- 提取脚本改 AST 解析(不 import 源模块);REST session 收 ?site=
- resolve=resolve_site 便捷形式注明

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 15:52:59 +08:00

28 KiB
Raw Permalink Blame History

all-kefu 统一 AI 客服平台设计(spec)

  • 日期:2026-10-12
  • 状态:已与需求方逐节确认,待评审
  • 来源项目:D:\refactor\ai-kefu(Hesu Auto 汽车出口客服,端口 8016)、D:\refactor\ai-kefu-jc(HESUPIPE 管道建材客服,端口 8017)
  • 新项目位置:D:\refactor\all-kefu(本仓库)

1. 背景与问题

两个现有项目是同一套 AI 客服引擎的两份拷贝(2026-09-29 按当时的"方案 A:完整拷贝 + 行业化定制"分叉,约 85% 代码同源)。业务模式相同:AI 只做"需求了解 + 留资获客",价格/交期/MOQ 一律不正面回答、引导留联系方式,线索经邮件 + UniService CRM 推送转人工。技术栈相同:FastAPI + SQLite(aiosqlite) + 智谱 GLM(glm-4-flash)+ 纯静态前端(widget 用 script 标签 + Shadow DOM 嵌入官网)。

复制模式已产生的实际代价(均有实证):

  1. 重复劳动:2026-10-08 同一天,上游实现了 whatsapp-cc-completion,jc 独立实现了更完整的 cc-completion(判定树+状态机,376 行)——同一功能写了两遍且实现分叉。
  2. 人肉同步:上游修 bug 需对照 changelog 手动同步到 jc,规则写在 jc 的 CLAUDE.md 里。
  3. 生产事故:两套 docker-compose 在 1Panel 上项目名撞车(目录都叫 index),起一个容器会删掉另一个(互相 502)。已用 name: 修复,但这是"N 份部署"模式的固有风险。
  4. 同页冲突:两个 widget 源自同一代码,sessionStorage key(widget_invite_dismissed_<path>)与 host id(cc-widget-host)同名互扰;集团官网同页挂两个客服时必然出问题。
  5. 僵尸配置:ai-kefu 的 compose 跑着一个 Redis 容器,代码零引用。

每客户的差异只占约 15%(提示词、词表、品牌、环境配置、部署标识、少量代码点),其余 85% 是应该共享的引擎。每加一个客户复制一套代码不可持续——本设计把差异收敛为"租户配置",引擎单一份。

2. 目标与非目标

目标:

  1. 一套代码(all-kefu)承载所有客户(租户),加客户 = 后台建租户 + 填配置 + 加一条反代,零代码复制、不起新容器。
  2. 统一管理后台:超管管理所有租户、查看全局数据;租户管理员查看并配置自己租户的一切(含提示词与词表,需求方已确认"租户全开")。
  3. 所有配置动态生效(热加载,不重启)。
  4. 存量两个项目的数据与线上行为无感迁移(官网嵌入代码零改动、CRM 线索不重复)。

非目标(明确推迟,YAGNI):

  • 提示词沙箱试聊(用草稿配置开真实对话预览)——需要"草稿态"整套机制,二期;现阶段版本回滚 + 出厂重置够用。
  • 换 PostgreSQL、超管/租户之外更细的角色权限、租户自助注册、计费——量级不到。
  • 引擎行为重构(conversation.py 结构重写)——只做租户化依赖注入,不动结构。
  • 推送失败自动告警——先靠仪表盘推送成功率卡片人工观察,二期视需要再加。

3. 已确认的关键决策

决策 结论 备注
运行架构 单进程多租户,一个 FastAPI 进程 = 引擎 + 统一后台 需求方委托推荐后确认
数据隔离 每租户独立 SQLite 文件;平台数据(租户/账号/配置)在 platform.db 文件级隔离,备份=拷文件
租户识别 按 Host 头(域名映射表);官网嵌入代码零改动 现有 widget 即从 script.src origin 推导连接地址
后台形态 统一后台,超管 + 租户管理员两级登录
租户权限 全开:租户可改自己租户的一切配置(含提示词、词表) 配套安全网:版本历史/回滚/试金石/出厂重置
后台前端 art-design-pro(Vue3 + TypeScript + Vite + Element Plus,MIT) 替代原先"单文件原生 JS 后台"的设想
存量数据 导入新平台(写迁移脚本,保留原会话 ID)
worker 数 --workers 1 写死 内存 token/限流/配置缓存/每租户 engine 缓存均假设单进程

4. 总体架构

                        ┌─────────────────────────────────────────┐
                        │        all-kefu 平台(单进程 FastAPI)     │
  chat.agent.hnxinyouhe.com ──┐                    │
  chat2.agent.hnxinyouhe.com ─┼→ 按 Host 识别:     ├─ platform.db(租户/站点/域名/账号/配置)
  ai.hesupipe.com(未来)  ──┘   域名→站点→租户     └─ tenants/auto.db、tenants/jc.db
  多站共用域名时:挂件带 data-site 参数选站点
  admin.xxx.com ────────────→ 统一后台(超管+租户登录)│
                        └─────────────────────────────────────────┘
  • 一个进程:同时承载所有租户的聊天引擎 + 统一管理后台,一个 Docker 容器部署在现有阿里云 ECS(1Panel)上。
  • 租户识别 = Host,站点在中间:数据库存"域名 → 站点 → 租户"映射。站点(site)= 一个租户下的一张门面:一个租户(一个 AI 脑子 + 一套线索管道)可以挂多个官网,每个官网的 brand(颜色/标题/欢迎语/话题/头像/WhatsApp/邮箱/隐私链接)可各配各的。站点接入两种方式:绑自己的域名(Host 自动识别),或多站共用一个挂件域名 + 嵌入代码 data-site="es" 参数选择;未指定或未知 site 回退租户默认门面(向后兼容)。页面 data-* 参数仍可覆盖站点门面(优先级:data-* > 站点 > 租户默认)。加租户 = 后台加记录 + DNS/反代指过来。
  • 数据分层:platform.db = 租户注册表、域名映射、admin_users、配置版本快照、平台级默认(集团 AI key、默认 SMTP);tenants/<tenant_id>.db = 现有 5 张表(conversations/messages/customer_profiles/evaluations/lead_push_logs),一租户一文件,互不可见。
  • 两级登录:超管(tenant_id 为空)看所有租户数据、增删租户、改任何配置;租户管理员只看自己租户数据、改自己配置。数据级隔离由后端强制。

边界规则:

  • 未知 Host 一律 404,不做兜底租户(防 Host 混淆打错租户)。
  • admin 后台走独立域名,不依赖 Host 租户解析。
  • 停用租户(status=disabled):聊天/公开端点返回中性"服务暂停"页(widget 优雅降级);超管仍可只读其数据;Registry 释放其 DB engine。

5. 租户配置模型

5.1 配置 Schema(两个项目 15% 差异的收敛)

配置组 内容 对应现在散落的位置
基础 租户名、启用/停用、站点列表(每站点:短代号、名称、绑定域名、门面覆盖层) compose/openresty 硬编码
品牌(租户默认门面,站点可逐项覆盖) 展示名、主题色、头像、欢迎语、热门话题、输入框占位符、WhatsApp 号、联系邮箱、隐私政策 URL chat.html / widget.js / chat.js 三处硬编码
提示词 4 件套 system.md、company_facts.md(知识库)、extractor.md、evaluator.md(markdown) app/prompts/
安全墙词表 价格墙正则组(多语言)、迂回话术池(EN/ZH/ES)、零号码替换句、触发词正则(边界/预算/索要)、轮中提醒话术(边界/索要/语言跟随/已留资/CC 指引/邮箱轻补六类) boundary_filter.py / conversation.py 内嵌
联系方式策略 区号补全开关、模板句语言集、轻补邮箱开关 conversation.py 内嵌参数
邮件 SMTP(可选"用平台默认")、发件人显示名、通知邮箱列表 .env + email_service.py 硬编码
推送 启用开关、URL、API Key、external_key 前缀(全局唯一约束)、超时/重试 .env
AI 凭据 可选覆盖(不填=平台默认集团 key) .env
引擎杂项 context 种子键映射(product_name vs vehicle_name)、限流参数、国名映射扩展 chat_rest.py / cc_completion.py 代码差异

5.2 存储(platform.db)

tenants(id, slug, name, status, created_at)
sites(id, tenant_id, slug, name, status, brand_overlay JSON)  -- 站点门面覆盖层(缺省项继承租户 brand)
site_domains(domain UNIQUE, site_id)                            -- 域名→站点(站点→租户随外键)
tenant_config_versions(tenant_id, version, config_json, updated_by, comment, created_at)
  -- 当前生效版 = MAX(version);每次保存存整份快照(几十 KB,不做增量)
admin_users(id, tenant_id NULL=超管, username, password_hash, status)
platform_settings(key, value)   -- 平台级默认:集团 AI 凭据、默认 SMTP

回滚 = 把旧 version 复制成新 version(历史不可变)。提示词与词表在同一快照内,回滚原子。

5.3 生效机制(热加载)

进程内 TenantRegistry:tenant_id → { config, db_engine, session_factory, manager }。后台保存 → 校验 → 写 platform.db → 原子替换 Registry 条目。进行中的会话下一条消息即用新配置(提示词逐轮拼装,天然支持)。不重启、无分发协议——单进程架构红利。并发编辑策略:同一租户被两人同时保存时后保存者覆盖(last-write-wins)——单人维护场景接受,不做乐观锁。

5.4 保存校验 + 试金石(租户可编辑正则的安全网)

  1. 编译校验:每条正则 re.compile 试跑,非法整单拒收。
  2. 试金石面板:编辑页内嵌测试框,贴样例句("What's your price for DN800?"、"预算5000美元")实时显示拦/放/替换判定,发布前眼见为实。
  3. 回滚:版本列表任意旧版一键恢复;每个配置组另有"重置为出厂默认"。

5.5 密钥处理

  • 后台读取一律掩码;仅主动修改时明文输入;"用平台默认"状态下租户侧不存密钥。
  • platform.db 备份按敏感件对待(含 SMTP/push/AI 密钥),写入部署文档。

5.6 关键设计规则

  • 话术单一事实源(R1):迂回话术池只存于墙配置;ai_service 组装 system prompt 时以模板变量($DEFLECTION_POOL 等)注入提示词。改一处,提示词与墙同时变——消除"提示词教的说法"与"墙的执法标准"分叉。
  • 配置演进(E4):Registry 加载 = 快照深合并出厂默认值,引擎升级新增字段不炸旧租户。
  • external_key 前缀全局唯一(E1):数据库约束 + 保存校验,防 CRM 线索空间串号。
  • tenant slug 创建后不可变:客户端存储 key 全部以 slug 命名,改动 slug = 重置所有客户页面的记忆(邀请卡状态、会话令牌)。site slug 同理不可变。
  • 站点只覆盖门面:brand 跟站点走;AI 脑子(提示词/词表/行为开关/AI 凭据)与线索管道(邮件/推送)留租户级——一个租户 = 一个客户经理团队收所有站点的线索,脑子分叉就乱。需要"两套完全不同的 AI"时应建两个租户而非两个站点。会话记录带站点标(后台可按官网筛线索、看各站转化)。
  • 出厂档案:seed_profiles/auto/、seed_profiles/jiancai/(提示词 md + 词表 json + 品牌 json),由提取脚本从两个老项目生成;新租户创建时选出厂档案或空白。两个概念分清:重置出厂 = 代码库当前模板(随平台升级演进);租户创建 = 当时选定档案的快照(不随升级变化)。

5.7 内容与文件上限

  • 头像等上传文件存挂载卷 data/uploads/:服务端 uuid 重命名(防路径穿越)+ 类型校验 + 大小 ≤2MB,经接口动态读取——绝不写入镜像静态目录(容器重建即丢,同类教训:改 static 必须 build)。头像按 (租户[, 站点]) 维度解析:站点覆盖 > 租户默认 > 平台兜底图。
  • 提示词/知识库保存时校验大小:知识库软上限 50KB,超限拒收并提示精炼——company_facts 每轮整段拼入 system prompt,过大会线性放大每轮 LLM 成本与时延,租户自己无感。

6. 引擎改造与代码合并

6.1 TenantContext——重构主轴

现状服务层是"进程级全局"(import 时读提示词、全局 Settings、模块级正则、全局 engine)。改造为"随租户走":

@dataclass
class TenantRuntime:
    tenant_id: str
    config: TenantConfig        # 快照 ⊕ 出厂默认 深合并;词表已编译校验
    engine: AsyncEngine         # 该租户 SQLite(懒创建,停用时关闭)
    session_factory: async_sessionmaker
    manager: ConversationManager

class TenantRegistry:
    resolve(host) -> TenantRuntime   # 域名表缓存,后台改域名即失效
    reload(tenant_id)                # 配置保存后原子换条目

请求流:中间件按 Host 解析租户 → request.state → 路由把 runtime 传进服务。服务层从"读全局"变"接参数"。

逐文件改造点:

文件 现状 改造后
config.py 全局 Settings 读 .env 拆 ProcessSettings(.env:平台库路径/端口/超管初始密码)+ TenantConfig(platform.db);顺带丢弃 Redis 死配置(REDIS_URL/REDIS_PASSWORD)
ai_service.py import 时读 4 提示词 PromptProvider 挂 TenantRuntime;组装 = system.md + company_facts.md + $DEFLECTION_POOL 注入 + 当轮 context reminder
conversation.py(约 1250 行) 3 组模块级正则 + 逐字话术 正则/话术从 config.wall 编译;Manager 每租户实例化;只做依赖注入,不重写结构
boundary_filter.py 正则话术焊死 纯函数化:接收已编译词表,机制与数据分离
email/lead_push .env 全局配置 读租户配置段;启动 sweep_stale_pending 逐租户跑
rate_limit.py key=ip+device key=(tenant_id, ip, device)
admin 路由 内存 token + 单密码 platform.db 认证 + 会话超时 + 登录失败限流

表外三条硬纪律:

  • 每租户库打开即 PRAGMA journal_mode=WAL(读写不互斥,优于现在靠 timeout=30s 硬扛);platform.db 同样开启。
  • 每租户 engine 连接池收紧至 1-2(SQLite 单文件用不上默认 5 连接;20 租户 × 5 = 100 个句柄纯属浪费)。
  • 事件循环零同步 IO:智谱 SDK 同步流式必须走线程 executor(沿用上游 glm.py 做法);推送 HTTP 改用 httpx.AsyncClient(后台重试的几秒不能卡住全平台);SMTP 已是 aiosmtplib。合并时逐点审计。

6.2 合并清单(jc 为主线)

取自 jc(10-08 两条分叉线中更完整一方):

  • cc_completion.py 完整判定树 + 状态机 + extractor cc_reply + e164 出站渲染
  • 多语言墙(€/£、西/葡/俄/阿词表、ES 迂回池、建材防误杀白名单)
  • dual-contact 分步双要(先 WhatsApp 带国家码→轻补邮箱)+ _email_asked
  • CC 强制墙 + 零号码墙回显豁免收窄
  • XFF 服务端优先、阿语数字归一、resolve_region_code

从上游吸收:input-placeholder 可配置化(落进品牌配置组);上游独有测试的断言价值(test_channel_hygiene、test_chat_rest_ip、test_lead_push_cc、test_cc_integration、test_ip_service_region、test_prompt_static 等)。

不带走:blog.html(模板残留)、Redis 配置(死代码)、上游旧版 whatsapp-cc-completion(被 jc 版取代,但其测试断言移植过来验证 jc 版行为)。

6.3 前端

  1. widget.js:启动时 fetch /api/public/brand(Host 已识别租户,免鉴权返回品牌 JSON,响应携带 tenant slug;script 标签有 data-site 时带 ?site= 选站点)→ 与 data-* 合并(data-* 优先);失败时 data-*/中性默认兜底;no-cache。
  2. 客户端 key 全部租户化(<tenant> 取 brand 响应里的 tenant slug):cc-widget-host-<tenant>、widget_invite_dismissed_<tenant>_<path>、chat 页 localStorage 会话 key 同理——同页双挂件根治。
  3. widget.js 与 chat.html 保持零构建原生 JS(嵌入官网资产必须轻量零依赖);主题派生单元机制不变,默认值来自品牌配置。
  4. WS 通道补限流:现有限流只覆盖 REST 消息接口;平台化后单 IP 可对所有租户域名各开一圈 WS 耗尽单进程资源——加每 IP 并发 WS 连接上限(如 10,内存计数,超限拒绝)。

6.4 目标目录结构

all-kefu/
  admin/                # art-design-pro SPA(Vue3+Vite+TS+Element Plus)
  app/
    main.py             # 平台入口:租户中间件 + 路由挂载,--workers 1
    platform/           # registry、配置服务、校验+试金石、admin 认证、platform.db 模型
    engine/             # 合并后的客服引擎(jc+上游),全部租户感知
      routes/           #   ws/chat、rest/chat(租户作用域)
      services/         #   conversation、boundary_filter、cc_completion、contact_pipeline、
                        #   email、lead_push、ip、rate_limit、ai_service(PromptProvider)
      providers/        #   glm
    models/             # 租户库 5 张表 + 逐租户 _migrate
  static/               # widget.js、chat.html、theme(零构建)
  seed_profiles/        # auto/、jiancai/
  tenants/              # 运行期:每租户一个 .db
  platform.db
  scripts/              # extract_config.py、import_legacy.py、mock_uniservice
  tests/
  docs/

platform/ 与 engine/ 是隔离边界:引擎不感知平台存在,只认 TenantRuntime——将来某租户需独立部署时 engine 包可整体拎走。

运行期产物绝不进 git(首次提交前建好 .gitignore):tenants/*.db、platform.db(含密钥,进了 git 历史就洗不干净)、data/uploads/、admin/node_modules/、admin/dist/。

7. 统一管理后台(art-design-pro)

7.1 工程与构建

  • admin/ 独立 SPA;开发时 vite dev proxy 到 FastAPI;生产两阶段 Docker(node 阶段 pnpm build → 产物拷入 Python 镜像),服务器零 node 依赖。
  • admin 静态文件打进镜像(延续"改前端必须 build"的既有教训);由 FastAPI 在管理域名下同源服务,无 CORS。
  • 只有 admin 走前端工程化;widget/chat 保持原生 JS。
  • admin SPA 用 history 路由;FastAPI 仅在管理域名下做 index.html 回退,不影响租户域名路径。

7.2 认证与权限

  • admin_users 表(超管 tenant_id 为空);token + 会话超时 + 登录失败限流 + 超管强制改初始密码。
  • art-design-pro 路由守卫/权限菜单映射两级角色:菜单级权限(超管可见租户管理/平台配置)+ 后端强制的数据级隔离(租户查询自动 scope)。
  • admin_users 密码用 bcrypt/argon2 哈希;SPA token 走 Authorization 头而非 Cookie(规避 CSRF 整类问题)。

7.3 页面清单

页面 超管 租户管理员
仪表盘 租户卡片墙(今日会话/线索/推送成功率,逐库查询 + 应用层合并——不用 SQLite ATTACH,其默认上限 10 个附加库,租户一多就炸)+ 平台总览 只看自己指标
租户管理 创建向导(名称→域名→选出厂档案 auto/jiancai/空白→生成租户管理员)、停用/启用、域名绑定 ✗
会话与线索 顶部租户切换器,看所有租户会话/画像/推送日志 只看自己的
配置中心 编辑任何租户全部配置组 编辑自己全部配置组(全开)
站点管理 租户下站点增删/停用、域名绑定、门面编辑(覆盖层+继承项提示+嵌入代码含 data-site) 同左(限自己租户)
统计分析 全租户对比 + 单租户下钻 只看自己租户
版本历史 快照列表、两版对比、一键回滚(回滚生成新版本) 同
平台配置 集团默认 AI 凭据、默认 SMTP、超管改密 ✗

7.4 配置编辑形态

  • 品牌:表单(颜色选择器、头像上传、文案)+ 挂件实时预览 + 嵌入代码复制块(显示本租户 <script> 标签与域名,复制即用,替代人肉查嵌入文档)。
  • 提示词:markdown 编辑器(语法高亮),侧栏显示 $DEFLECTION_POOL 等注入变量当前值,保存必须留 comment;预览用安全渲染(sanitize,防 markdown 内嵌 HTML 注入)。
  • 词表:结构化表格(每条正则一行:语言组/表达式/备注)+ 编译校验 + 内嵌试金石。
  • 邮件:SMTP 表单 + 发测试邮件;推送:目标配置 + 连通性探测(只验鉴权,不发真数据)+ 前缀唯一校验。

7.5 API 契约

后台 SPA 走 /api/platform/*(新增)与租户化 admin 数据端点;聊天引擎端点(/ws/chat、/api/chat/*、/api/public/brand)契约不变——widget 嵌入方完全无感。

/api/public/brand 用显式字段白名单序列化(只吐品牌字段,绝不序列化含密钥的整个配置 JSON);支持 ?site=<slug> 返回"站点覆盖层 ⊕ 租户默认门面"的合并结果,未带或未知 site 回退租户默认;该端点被客户官网跨域 fetch,响应需带 Access-Control-Allow-Origin: *(与 widget.js 现行 CORS 策略一致)。

7.6 统计分析页(数据全部来自现有五张表,无新表)

仪表盘是速览卡片,统计页是下钻分析。内容:

  • 核心卡片:会话数、线索数(已留联系方式的会话)、高意向数、推送成功率
  • 转化漏斗:会话 → 留资 → 完成评估 → 推送成功(每级流失可见)
  • 趋势图:按日会话量/线索量(近 7/30/90 天 + 自定义区间)
  • 分布:意向三级占比、来源(widget/app)、站点(哪个官网带来的量)、国家 Top10(IP 归国)
  • 推送健康:success / failed / skipped / duplicated 计数
  • 筛选:时间范围 + 站点;超管可跨租户对比与单租户下钻

实现:/api/platform/stats 聚合端点(逐库 SQL 聚合 + 应用层合并,沿用"不用 ATTACH"的决定);前端用 ECharts(Element Plus 生态标配)。统计 API 归 Phase 2 平台层、页面归 Phase 3 后台——写那两份计划时据此展开任务。

二期不做:墙拦截率统计(需在消息层给墙命中做埋点,现阶段继续用 scripts/breach_rate.py 离线观察)。

8. 数据迁移

scripts/extract_config.py:从两个老项目提取首版配置快照,生成 seed_profiles/auto/ 与 seed_profiles/jiancai/(提示词拷 md;词表从 boundary_filter.py/conversation.py 提取正则话术转 JSON;品牌从 chat.html/widget.js/chat.js 三处硬编码 + .env 归一)。此后两个老项目只读。

scripts/import_legacy.py:老库 5 张表原结构导入对应租户库;保留原 conversation_id(external_key 连续,UniService 不产生重复线索);session_id/page_token/status 等字段全部原样保留(切换时刻客户凭页面令牌无缝续聊);老库 status=pending 的推送日志导入即标 failed 并加注释(切换前未完成态,新平台不重试);导入后对账报告(两会话数/消息数/推送日志数 + 最近 10 条会话抽查)。脚本幂等:按目标库已有数据跳过,重复执行不产生重复行。

切换顺序:新平台部署验证(不切域名)→ 导存量 → 租户域名反代从老容器切到新平台 → 观察一周(沿用 breach_rate 双向偏差观察法)→ 老项目归档下线。

9. 部署

chat.agent.hnxinyouhe.com  ─┐
chat2.agent.hnxinyouhe.com ─┼→ 1Panel 各站点反代 → 127.0.0.1:8018 → all-kefu 容器
admin.xxx.com(新,具体值部署前由需求方确定)─┘
  • 一个容器一个 upstream;compose 固定 name: all-kefu(互删根因根治——只剩一个 compose 项目);WS 升级头单套(426 教训);/static/widget.js no-cache(缓存教训);--workers 1;容器 TZ=Asia/Shanghai(仪表盘"今日"边界与业务时区一致);mem_limit 768m(单进程承载 admin SPA 服务 + 全部租户引擎,较现在单租户 512m 上调)。
  • 日志:compose logging 设 json-file + max-size: 10m + max-file: 3 封顶(Docker 默认不限大小,会涨满磁盘);应用日志只进 stdout,且不打聊天内容/联系方式等 PII(库内数据有 admin 权限保护,进日志等于裸奔磁盘)。
  • 备份:platform.db(敏感件)+ tenants/*.db 用 SQLite backup API(或 VACUUM INTO)做一致性快照,每日执行;禁止运行时直接拷 .db 文件(可能拷到写一半状态)。每季度做一次恢复演练(没验证过的备份等于没有备份)。写入部署文档。
  • 数据保留:lead_push_logs.request_payload 存完整对话 JSON、只涨不掉;提供 scripts/archive.py(会话超 12 个月导出归档 + 清理 + VACUUM 回收空间),后台手动触发(不做自动任务,YAGNI)。
  • /health 分级:进程状态 + 各租户 engine 状态 + 域名表版本;单租户库异常时报告"哪个租户不健康",不整体 500(防 1Panel 误判容器死亡反复重启)。
  • 发版回滚:镜像打版本 tag,compose 可 30 秒切回上一版,流程写入部署文档;部署文档整体继承 jc 项目 deploy-production.md 踩坑手册(1Panel include 行、面板位置联动、no-cache 等)。
  • 加租户 = 后台建租户 + 1Panel 加一条反代,不起容器。

10. 测试策略

  1. 机制测试全保留:jc 的全部机制测试(约 320 个用例,实测计数;jc 文档旧称 230 项)+ 上游独有测试,移植到统一 conftest——租户夹具(临时 platform.db + auto/jc 两个种子租户 + 各自租户库),断言基本不动;LLM 全 mock 原则不变。
  2. 多租户专项(改造的回归安全网):Host 路由(A 域名进 A、未知 Host 404);跨租户数据隔离(A 的 admin/API 摸不到 B 数据);配置热切换(保存新提示词→下一条消息生效;改词表→墙行为即时变);前缀唯一约束;快照深合并默认值;试金石判定;widget 客户端 key 租户前缀(静态断言);停用租户行为。
  3. 真实 LLM 验收:真实智谱 Key + scripts/mock_uniservice.py 联调流程照旧(沿用 test-checklist 体系)。

11. 风险与对策

风险 对策
conversation.py(1250 行核心编排器)租户化回归 230 项机制测试安全网 + 只做依赖注入不重写
admin 引入 node 构建链 两阶段 Docker 隔离,服务器零 node;widget/chat 零构建
租户改坏提示词/词表 版本回滚 + 出厂重置 + 编译校验 + 试金石;话术池单一事实源消除双改遗漏
迁移导致 CRM 重复线索 保留原 conversation_id + external_key 前缀不变
单进程升级影响全部租户 秒级重启可接受;engine/ 与 platform/ 隔离保留拆分路径
密钥入库 掩码显示、platform.db 按敏感件备份
后台暴露面变大 登录失败限流、超管强制改初始密码、会话超时、admin 独立域名
事件循环被同步 IO 阻塞(单进程生死线:一个租户的慢调用卡住所有租户) 零同步 IO 纪律:智谱流式走 executor、推送 HTTP 异步化、SMTP 已异步;合并时逐点审计

12. 附录:两项目差异实证(配置 Schema 的来源)

差异点 ai-kefu(auto) ai-kefu-jc(建材)
提示词 4 件套 汽车语义 管道建材语义(多语言墙、规格防误杀)
端口 / DB / 容器 8016 / cc_ai_kefu.db / cc-ai-kefu 8017 / hesupipe_ai_kefu.db / hs-ai-kefu
品牌色 / 发件人 橙 #f08a31 / 【XYH】 蓝 #066fc0 / 【HESUPIPE】
context 种子键 vehicle_name/vehicle_id product_name/product_id
推送键前缀 ai-kefu ai-kefu-jc
cc 实现 whatsapp-cc-completion(旧) cc_completion 判定树(新,主线)
独有功能 input-placeholder 配置、blog.html(弃) dual-contact、CC 强制墙、阿语数字归一
Redis 有容器但代码零引用(弃) 分叉时已移除