chore: CRM-NEW baseline

This commit is contained in:
wangliang
2026-10-23 10:35:28 +08:00
commit aa499f6dbf
111 changed files with 15126 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
# Chatwoot → LibreDesk 对照表
迁移旧 `bridge` 时按此表逐项替换。字段/事件均已对照 LibreDesk 源码与文档核对(v2.8.0 / v2.9.0-rc)。
## 1. HTTP API
| 用途 | Chatwoot(旧) | LibreDesk(新) |
|---|---|---|
| 鉴权头 | `api_access_token: <token>` | `Authorization: token <key>:<secret>` 或 Basic |
| 发回复 | `POST /api/v1/accounts/:aid/conversations/:id/messages` `{content, message_type:'outgoing'}` | `POST /api/v1/conversations/{uuid}/messages` `{message:"...", sender_type:"agent"}` |
| 私密备注 | 同上 + `private:true` | 同上 + `private:true`(可带 `mentions`) |
| 改会话状态 | `POST .../toggle_status` `{status:'resolved'}` | `PUT /api/v1/conversations/{uuid}/status`(默认状态 Open/Snoozed/Resolved/Closed) |
| 拉历史消息 | `GET .../conversations/:id/messages` | `GET /api/v1/conversations/{uuid}/messages` |
| 会话详情 | `GET .../conversations/:id` | `GET /api/v1/conversations/{uuid}` |
| 联系人查询 | `GET .../contacts/search?q=` | `GET /api/v1/contacts/search` |
| 联系人详情 | `GET .../contacts/:id` | `GET /api/v1/contacts/{id}` |
| 新建联系人 | `POST .../contacts` | **无独立端点**:通过 `POST /api/v1/conversations`(带 `contact_email`)或 widget 创建 |
| 会话 ID | 数字 id | **UUID**(API 路径用 uuid;webhook 同时带 `conversation_id` 与 `conversation_uuid`) |
| 交互式消息 | `content_type: input_select/input_email/form` + `content_attributes.submitted_values` | **不支持**(只有 message/attachments/private/to/cc/bcc) |
## 2. Webhook / 事件
| Chatwoot 事件 | LibreDesk 事件 | 字段差异 |
|---|---|---|
| `message_created`(incoming) | `message.created` | `type: incoming/outgoing/activity`、`sender_type: contact/agent`、`content`/`text_content`、`conversation_uuid`、`private` |
| `message_updated`(OTP 表单提交) | `message.updated` | 无 `content_attributes.submitted_values`;OTP 改由平台内置流程 |
| `conversation_opened` | `conversation.created` | 带 `uuid`/`id`/`contact`/`inbox_channel`/`tags` |
| `webwidget_triggered` | 无直接对应 | 用 `conversation.created` + contact custom attributes 触发欢迎语 |
| (无) | `conversation.status_changed` / `assigned` / `unassigned` / `tags_changed` | 新增能力 |
| 签名头 | `x-chatwoot-signature` + `x-chatwoot-timestamp`,HMAC(ts.body) | `X-Libredesk-Signature: sha256=<hmac(body)>`,**无时间戳**(需自行防重放) |
| 投递 | AgentBot 推送 | 通用 webhook;**失败不重试、5s 超时、队列满丢弃** |
## 3. 身份与 Widget
| 能力 | Chatwoot(旧) | LibreDesk(新) |
|---|---|---|
| 身份锚定 | `window.$chatwoot.setUser(identifier, payload)` + cookie | `window.LibredeskSettings.userJWT`(HS256,服务端用 inbox secret 签) |
| 可信邮箱 | contact `custom_attributes.verified_email`(由 `/identity` 写入) | widget JWT 的 `email` + `external_user_id`;工具侧 `X-Libredesk-Contact-Email` |
| 自定义属性 | `setCustomAttributes({locale, site})` | JWT `contact_custom_attributes` / `PUT /api/v1/contacts/{id}` |
| 语言 | `window.chatwootSettings.locale` + 自研欢迎语 | widget `language` + assistant Languages;欢迎语可用 assistant expectation message 或 automation `send_reply` |
| 验证 | 自研 OTP + 交互组件 + `verified_email` | 平台内置 verified contact OTP;工具请求带 `X-Libredesk-Contact-Verified` |
| 多站点 | contact custom attribute `site` + 多 widget | 多个 livechat inbox,各自 secret/widget;联系人全局共享 |
| 会话连续性 | `identifier` cookie | JWT `external_user_id` |
| 主动消息/帮助中心 | 无 | 有(v2.9 rc 起 widget 内帮助中心 + proactive messages) |
## 4. bridge 代码改造点(旧仓库)
| 文件 | 用途 | 处理 |
|---|---|---|
| `bridge/utils/cwClient.js` | Chatwoot API 封装 | 换成 `libredeskClient`(本仓库未新建,POC 用平台 AI,不需要) |
| `bridge/crm/chatLogger.js` | 发回复/备注/查联系人 | 同上 |
| `bridge/middleware/auth.js` | HMAC + timestamp 验签 | 换成 `X-Libredesk-Signature`,自行补时间窗/重放缓存 |
| `bridge/router.js` `/agent-bot`、`/chatwoot-webhook` | 事件分发 | 换成 `/libredesk-webhook`,事件名/字段按上表映射 |
| `bridge/router.js` 交互组件/OTP | OTP 按钮表单 | 删除;改用平台 verified OTP |
| `bridge/identity/identityManager.js` | 读 `verified_email` | 改读工具身份头 / 平台验证状态 |
| `bridge/mail/mailDigest.js` | 往 Chatwoot 建会话投摘要 | 改发邮件或调报表 API |
| `bridge/register_agent_bot.js` | 注册 AgentBot | 删除;webhook 在 Admin → Integrations 配 |
| `bridge/escalation/manager.js`、`agent/agentRunner.js` | `toggle_status` | 改 `PUT /conversations/{uuid}/status` |
| 6 个测试文件 | mock `/api/v1/accounts` | 重写 mock |
## 5. 数据模型影响
- 旧表 `agent_metrics` / `flywheel_samples` / `trace_steps` 里的 `conversation_id` 是 Chatwoot 数字 id;换平台后新数据用 LibreDesk UUID,**新旧不可直接 join**
- 建议新表统一存 `conversation_uuid`,`conversation_id` 只做历史字段
- 旧 Chatwoot 数据无导入器可用(LibreDesk 只有 CSV 导入 agent);当前旧库仅 2 会话/2 联系人,可直接放弃
## 6. 能力缺口(换平台后必须外部实现或放弃)
| 能力 | 旧实现 | LibreDesk | 结论 |
|---|---|---|---|
| 商机自动创建 | `leadPipeline` + Twenty | 无 | 本仓库 `create_opportunity` 工具 |
| 订单/库存/商品 RPC | `toolExecutor` | 无(仅通用 HTTP tools) | 本仓库 `crm-tools` |
| hybrid 检索 + rerank | `rag/retriever.js` | 纯向量 snippets | 降级或保留旧服务 |
| 引用/幻觉打分 | `citation_checker` | 仅帮助中心 `[[cite]]` | 缺失 |
| 情绪 + 3 级升级 | `escalation/*` | 仅 handoff | 降级 |
| 离线评估 | `eval/*` | 无 | 保留旧服务(可选) |
| 数据飞轮 | `flywheel_*` | `learn from resolved`(弱) | 部分替代 |
| 指标/告警/追踪 | OTel + Prometheus + alerter | 无 | 保留旧服务(可选) |
| A/B 实验 | `config/experiments` | 无 | 缺失 |
| 注入防护 | `security/injectionGuard` | 仅 prompt guardrails | 降级 |
+86
View File
@@ -0,0 +1,86 @@
# 架构说明(新栈 · 方案 B)
> 2026-10-21 决策:完全新建,采用 **LibreDesk 内置 AI** 承接客户对话,bridge 收缩为业务工具/集成服务;Twenty 为全新独立实例。
## 1. 组件与职责
| 组件 | 技术 | 负责 | 不负责 |
|---|---|---|---|
| LibreDesk :9000 | Go 单二进制 + Vue | 渠道(Live chat / Email / WhatsApp*)、会话、坐席工作台、**AI Assistant 回复客户**、snippets RAG、内置 OTP、自动化、SLA/CSAT、报表 | 不访问业务库、不写 CRM |
| bridge :4100 | Node 24,零外部依赖 | 商品/订单/库存查询、Twenty 商机写入、身份头二次校验;后续承载质量旁路(指标/评估/飞轮) | 不保存会话、不生成客户回复 |
| Twenty :3100 | NestJS + React | 公司/联系人/商机/备注/任务 | — |
| 业务 RPC | Hyperf JSON-RPC over TCP | 商品推荐、订单、库存/ETA、会员 | — |
| imap-id-proxy | Node 24 | 给 LibreDesk 的 IMAP 连接注入 ID 命令,兼容 163 | 仅本地 POC |
\* WhatsApp 需 LibreDesk v2.9 RC;当前镜像 `v2.9.0-rc.10`。
## 2. 数据流
```
访客(widget / 邮件 / WhatsApp)
│
▼
LibreDesk 创建会话 → Automation 分配给 AI Assistant
│
├─ snippets 向量检索(知识库)
├─ 需要业务数据 → POST http://bridge:4100/tools/<name>
│ ├─ 平台注入 X-Libredesk-Contact-* / Conversation-UUID / Inbox-Id
│ ├─ 工具配置里带 X-Tools-Key
│ └─ 返回扁平 JSON
└─ 无法处理 / 超 max turns / 超时 → handoff 到兜底团队
```
### 工具调用与验证
- **身份来源**:LibreDesk 服务端注入 `X-Libredesk-Contact-Email`、`X-Libredesk-Contact-Verified`,模型不可见/不可改。
- **平台侧验证**:工具设置 `requires_verification=true`;平台先让客户邮箱收码,通过后才调用。
- **服务端二次校验(fail-closed)**:bridge 仍检查 `verified === true`,否则 403。
- **邮箱作用域**:订单/商机工具的邮箱只取身份头,忽略参数;订单号先按邮箱反查再匹配(防 IDOR)。
### CRM 写入
`create_opportunity` 一次完成:按邮箱找 Person → 查重(已有商机直接返回 `created:false`)→ 公司同名复用/创建 → 建 Opportunity → 备注(公司/联系人/需求/渠道)→ 3 天跟进任务。
## 3. 端口与隔离
| 资源 | 新栈 | 旧栈(crm-ai-demo) |
|---|---|---|
| LibreDesk / Chatwoot | 9000 | 3001 |
| Twenty | 3100 | 3000 |
| bridge | 4100 | 4000 |
| Postgres | 127.0.0.1:5440 | 127.0.0.1:5432 |
| Redis | 127.0.0.1:6390 | 127.0.0.1:6379 |
新栈 Postgres 内含三个库:`libredesk` / `twenty` / `bridge`;Redis 分 db:LibreDesk=2、Twenty=1、bridge=3。
## 4. 为什么选 B(以及代价)
选择理由:代码量最少、维护面最小、直接获得平台渠道/工作台/OTP/CSAT;bridge 只保留无法内建的业务与质量能力。
已知代价(相对旧 bridge 编排):
- RAG:纯向量 snippets,无 hybrid/rerank/翻译兜底
- 缺情绪识别、3 级升级策略(用 max turns + fallback team 近似)
- 缺引用打分/幻觉检测、离线评估、数据飞轮、A/B、注入防护(可后续在 bridge 做 webhook 旁路补齐)
- LibreDesk AI 仍是 beta
## 5. 后续补齐路径(bridge 质量旁路)
LibreDesk webhook(`message.created` / `conversation.*`)可推给 bridge,bridge 做只读分析并回写标签/备注/告警:
1. 指标:延迟、工具调用、升级率 → `bridge` 库
2. 引用/幻觉:比对 assistant 回复与命中 snippets(需 LibreDesk 侧接口或日志)
3. 评估:golden 问题 → assistant preview API
4. 飞轮:低分会话 → 人工标注 → snippets 回流
注意:LibreDesk webhook **无重试、5s 超时**,bridge 必须立即 ACK + 异步处理,并加轮询补偿。
## 6. 环境变量
见 `../.env.example`。要点:
| 变量 | 作用 |
|---|---|
| `TOOLS_SHARED_KEY` | LibreDesk 工具 Header 与 bridge 共享密钥 |
| `TOOLS_PUBLIC_BASE_URL` | compose 内固定 `http://bridge:4100` |
| `TWENTY_*` | Twenty 连接 + API key(首次注册后填) |
| `RECOMMEND_URL` / `ORDER_API_URL` / `INVENTORY_API_URL` | 业务 RPC |
+474
View File
@@ -0,0 +1,474 @@
# CRM-NEW 上线文档
| 项 | 内容 |
|---|---|
| 上线日期 | 2026-10-23 |
| 上线范围 | 方案 A:bridge 接管 Live chat AI;Email/WhatsApp 人工处理 |
| 当前状态 | Live chat 已切 bridge AI;Email/WhatsApp 已切人工团队;Twenty 商机已打通 |
| 变更窗口 | 建议低峰期,预留 30–60 分钟 |
| 负责人 | 待填 |
---
## 1. 上线范围
### 上线内容
- LibreDesk:Live chat / Email / WhatsApp 三个渠道
- bridge AI:Live chat 的 LLM、工具、RAG、OTP、输出护栏、质量指标
- Twenty CRM:联系人、公司、商机、备注、3 天跟进任务
- Email/WhatsApp:不再由 AI 回复,进入 `Wholesale Support` 人工团队
- 旧 bridge KB:Hybrid RAG(parent-child + 向量 + 全文 + RRF)
- 全量商品页 sitemap:www 32,691 / us 22,625
- 质量:逐会话指标、URL/数字审计、质量日报、离线评测
### 不在本次上线范围
- Email / WhatsApp 的 bridge AI 回复
- WhatsApp 模板消息 / 媒体 / 语音
- 邮件每日 digest
- A/B 实验
- 多副本/高可用部署
- PII 自动保留/删除策略
- 真实客户邮件历史 ingest(当前 `kb_mail_docs` 只有测试邮件)
---
## 2. 当前架构
```
Live chat (inbox 1)
LibreDesk widget
→ LibreDesk webhook message.created
→ bridge /libredesk/webhook
→ bridge AI(LLM + 工具 + RAG + OTP + 输出护栏)
→ LibreDesk API 回写 agent 消息
Email (inbox 2) / WhatsApp (inbox 3)
→ LibreDesk 自动化规则
→ assign_team = Wholesale Support
→ 人工团队队列
→ 不调用 AI
```
---
## 3. 访问入口
| 服务 | 地址 | 备注 |
|---|---|---|
| LibreDesk | `http://192.168.31.61:9000` | 管理端 / 工作台 |
| Twenty CRM | `http://192.168.31.61:3100` | CRM |
| bridge | `http://192.168.31.61:4100` | 内部工具/健康检查 |
| Widget 测试页 | `http://192.168.31.61:8088/demo.html` | 本机测试用 |
| 公网入口 | `.env` 的 `PUBLIC_BASE_URL` | Cloudflare Quick Tunnel,**URL 会变** |
| Postgres | `127.0.0.1:5440` | 仅本机 |
| Redis | `127.0.0.1:6390` | 仅本机 |
| IMAP proxy | `127.0.0.1:1993` | 仅本机 |
---
## 4. 当前切流配置
### Live chat
| 项 | 状态 |
|---|---|
| bridge webhook | 启用 |
| LibreDesk 规则 1“分配新会话给 AI Assistant” | 关闭 |
| bridge inbox 过滤 | `LIBREDESK_WEBHOOK_INBOX_IDS=1` |
### Email / WhatsApp
| 项 | 状态 |
|---|---|
| 团队 | `Wholesale Support`(id=1) |
| 团队类型 | `Round robin` |
| 成员 | agent1 (26)、agent2 (27) |
| 规则 4 | Email inbox 2 → assign_team 1 |
| 规则 5 | WhatsApp inbox 3 → assign_team 1 |
| AI Assistant 分配 | 已移除 |
> agent1/agent2 当前是 `offline`。Round robin 只有坐席在线/可用时才会进一步分到个人;离线时停留在团队队列。若希望永远只进团队队列,可把团队类型改为 `Manual`。
---
## 5. 上线前检查
- [ ] `.env` 已备份,且权限为 `600`
- [ ] `docker compose config -q` 通过
- [ ] RPC 隧道可用(商品/订单/库存)
- [ ] LibreDesk webhook `Bridge AI` 为启用状态
- [ ] LibreDesk 规则 1 关闭
- [ ] 规则 4/5 指向 `Wholesale Support`
- [ ] agent1/agent2 可登录,必要时设为在线
- [ ] WhatsApp access token 未过期(当前 token 有到期时间)
- [ ] 公网 URL 与 Meta webhook 已同步
- [ ] 163 SMTP / IMAP 可用
- [ ] 已做数据库备份
- [ ] bridge 测试通过
- [ ] 质量告警 cron 已配置(或本次上线后立即配置)
---
## 6. 部署/启动步骤
### 6.1 当前机器上启动
```bash
cd /home/dev/code/crm-new
# 1) 校验 compose
docker compose config -q
# 2) 启动/更新
docker compose up -d
# 3) 重建 bridge(代码变更时必须)
docker compose up -d --build --force-recreate bridge
# 4) 查看状态
docker compose ps
```
### 6.2 首次部署/新环境
```bash
cd /home/dev/code/crm-new
bash scripts/bootstrap.sh
```
首次部署后还需要:
```bash
node scripts/setup-defaults.js
node scripts/twenty-bootstrap.js
node scripts/register-tools.js
node scripts/configure-email.js
```
> 已有环境不要直接重跑 `bootstrap.sh`,会覆盖 `.env`/配置。
### 6.3 数据准备
```bash
# 全量商品页站点地图(每周/商品大更新后)
node scripts/build-site-map.js www us
# 重建 KB parent-child 索引(KB 更新后;会先备份旧表)
bash scripts/rebuild-kb-index.sh
# 旧 KB 补 text_tsv/parent_id(已执行过,幂等)
node scripts/upgrade-kb-schema.js
```
### 6.4 RPC 隧道
商品/订单/库存依赖本机到内网的 RPC 隧道:
```bash
bash scripts/rpc-tunnel.sh
```
隧道断了会导致 `search_products/search_orders/search_inventory` 失败。
### 6.5 公网 HTTPS / WhatsApp webhook
```bash
bash scripts/public-https.sh
```
Cloudflare Quick Tunnel URL 每次重启可能变化,需要同步:
- LibreDesk `app.root_url`
- Meta WhatsApp callback URL
---
## 7. 验收清单
### 7.1 基础健康
```bash
curl -s http://127.0.0.1:4100/healthz
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3100/healthz
cd bridge && npm test
```
预期:
- bridge `{"ok":true,...}`
- Twenty `200`
- tests **75/75**
### 7.2 Live chat JWT
```bash
node scripts/e2e-widget-jwt.js "What is the minimum order value?"
```
预期:
- AI 回复 €30
- 调用 `search_kb`
- 回复为 HTML
- 无未验证 URL
### 7.3 匿名 OTP
```bash
node scripts/e2e-anon-otp.js
```
预期:
- AI 先要求验证
- 收到验证码邮件
- 输入后 verified
- 能查订单(或明确无订单)
> 该脚本会读取 163 邮箱并创建测试会话,生产环境谨慎执行。
### 7.4 商机
```bash
node scripts/e2e-widget-jwt.js "I want to buy 500 pieces of stainless steel rings"
```
然后发:
```
Our company is Test Buyer BV, contact name Jan Jansen.
```
到 Twenty 检查:
- Company / Person / Opportunity
- Opportunity 名称为 `<company> — <need>`
- Note 已关联
- Task 3 天后到期
### 7.5 离线评测
```bash
node scripts/eval-offline.js
node scripts/eval-live.js
```
预期:
- 确定性评测 **18/18**
- 真实模型 smoke **4/4**
### 7.6 质量指标
```bash
node scripts/quality-report.js 24
node scripts/quality-alert.js 1 --dry-run
```
预期:
- 无错误/无未验证 URL 告警
- 当前健康时输出 `{"ok":true,...}`
---
## 8. 监控与告警
### 质量告警 cron
```cron
*/30 * * * * cd /home/dev/code/crm-new && node scripts/quality-alert.js 1 >> logs/quality-alert.log 2>&1
```
阈值:
- AI 错误率 > 10%
- 工具错误率 > 10%
- 未验证 URL 占比 > 15%
- 平均延迟 > 8000ms
### 必看日志
```bash
docker logs -f crmnew-bridge
docker logs -f crmnew-libredesk
docker logs -f crmnew-twenty-server
docker logs -f crmnew-cloudflared
```
bridge 关键日志:
- `webhook: message.created`
- `agent: tool`
- `agent: output_guard`
- `agent: replied`
- `crm: stage_advanced`
### 外部依赖巡检
- RPC 隧道进程
- 163 SMTP/IMAP
- WhatsApp token 剩余有效期
- Cloudflare tunnel 状态
- 未分配/团队队列积压
---
## 9. 日常运维
```bash
# 重启 bridge
docker compose restart bridge
# 重建 bridge
docker compose up -d --build --force-recreate bridge
# 查看资源
docker stats
# 数据库备份
docker exec crmnew-postgres pg_dump -U crmnew -d libredesk > backup-libredesk-$(date +%F).sql
docker exec crmnew-postgres pg_dump -U crmnew -d bridge > backup-bridge-$(date +%F).sql
docker exec crm-postgres pg_dump -U postgres -d products > backup-products-$(date +%F).sql
```
详细命令见 [runbook.md](runbook.md)。
---
## 10. 回滚方案
### 10.1 Live chat 回滚(最常用)
```bash
# 1) 停用 bridge webhook
# LibreDesk 后台 Webhooks → Bridge AI → 停用
# 2) 重新开启规则 1
# Automations → Assign new conversations to AI Assistant → 启用
# 3) 可选:停止 bridge
docker compose stop bridge
```
效果:Live chat 回到 LibreDesk 自带 AI。
### 10.2 Email / WhatsApp 回滚
```bash
# 规则 4/5 改回 assign_user = AI Assistant(id=3)
```
或直接停用规则 4/5,由人工在未分配队列处理。
### 10.3 数据回滚
- KB 旧表:`kb_docs_v1`
- rebuild 前备份:`kb_docs_backup_20261022`
- 恢复前先确认表结构/索引
- Twenty 测试数据删除需按 ID 精确操作,不要批量删
### 10.4 代码回滚
- 保留上一版 bridge 镜像/commit
- `docker compose up -d --build --force-recreate bridge`
- 回滚后必须重跑:
- `npm test`
- `scripts/eval-offline.js`
- 一条 Live chat smoke
---
## 11. 已知限制与风险
### 高
1. **单点**:bridge 只有一个实例;bridge 挂了 Live chat 无 AI 回复。
2. **RPC 隧道**:依赖本机 SSH 隧道,断了商品/订单/库存工具不可用。
3. **公网 URL**:Cloudflare Quick Tunnel URL 不固定,重启可能失效。
4. **WhatsApp token**:临时 token 有到期时间,需要提前续期。
5. **Email 历史数据为空**:`kb_mail_docs` 只有测试邮件,真实客户历史未 ingest。
### 中
6. **商机币种**:`USD 1000` 目前存为 EUR。
7. **productNeed**:可能把整句需求写入商机名。
8. **输出护栏是规则型**:可能漏掉未覆盖措辞,或重写后语句略生硬。
9. **告警未落 cron/看板**:需要上线后配置。
10. **PII**:日志/指标可能包含客户消息片段;无自动保留/删除策略。
11. **agent1/agent2 offline**:Email/WhatsApp 会停在团队队列,需要人工上线/认领。
12. **bridge 4100 暴露在局域网**:建议防火墙限制,仅允许 LibreDesk/内网访问。
### 低
13. KB/索引/sitemap 仍以手动脚本刷新为主。
14. 无 A/B、无真实邮件 digest、无 WhatsApp 模板/媒体。
15. 无多副本/跨机高可用。
---
## 12. 上线后待办
| 优先级 | 事项 |
|---|---|
| P0 | 配置质量告警 cron + 确认邮件送达 |
| P0 | 确认 agent1/agent2 上线流程和团队队列 SLA |
| P0 | 续期 WhatsApp token、固定公网域名 |
| P1 | Email/WhatsApp bridge AI 切流 |
| P1 | 真实邮件 ingest + 索引 |
| P1 | 商机币种 + productNeed 归一 |
| P1 | 数据备份自动化 |
| P2 | 告警看板、离线评测接 CI |
| P2 | PII 保留/删除策略 |
| P3 | A/B 实验、WhatsApp 模板/媒体 |
---
## 13. 附录:关键配置
### bridge 关键环境变量
```
LIBREDESK_BASE_URL
LIBREDESK_API_KEY
LIBREDESK_API_SECRET
LIBREDESK_WEBHOOK_SECRET
LIBREDESK_WEBHOOK_INBOX_IDS=1
LLM_BASE_URL / LLM_API_KEY / LLM_MODEL
RAG_PG_URL
EMB_BASE_URL / EMB_API_KEY / EMB_MODEL / EMB_DIM
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM
TWENTY_BASE_URL / TWENTY_API_KEY
```
### 关键数据表
- `libredesk`:conversations、messages、automation_rules、webhooks
- `bridge`:bridge_ai_metrics、bridge_llm_cache、bridge_site_map_urls
- `products`(旧 bridge 库):kb_docs、kb_mail_docs
### 关键脚本
```
scripts/bootstrap.sh
scripts/rpc-tunnel.sh
scripts/public-https.sh
scripts/build-site-map.js
scripts/rebuild-kb-index.sh
scripts/e2e-widget-jwt.js
scripts/e2e-anon-otp.js
scripts/eval-offline.js
scripts/eval-live.js
scripts/quality-report.js
scripts/quality-alert.js
```
---
## 14. 变更记录
| 日期 | 变更 | 操作人 |
|---|---|---|
| 2026-10-23 | 初版上线文档 | 待填 |
+34
View File
@@ -0,0 +1,34 @@
# Email / WhatsApp 人工分配
## 当前配置
- 团队:`Wholesale Support`
- id: 1
- 分配方式:`Round robin`
- 时区:`Asia/Shanghai`
- 最大自动分配会话:10
- 成员:`agent1@yehwang.com` (26)、`agent2@yehwang.com` (27)
- 自动化规则:
- 规则 4:Email inbox 2 → assign_team 1
- 规则 5:WhatsApp inbox 3 → assign_team 1
- 规则 1:Live chat 仍关闭(bridge AI 接管)
## 行为
新 Email/WhatsApp 会话:
```
conversation.assigned_team_id = 1
conversation.assigned_user_id = 空(团队队列)
```
- 不再分配给 AI Assistant(id=3)
- 不会触发 LibreDesk AI 回复
- 坐席登录后在团队 Inbox 中领取/处理
Round robin 只有在坐席在线/可用时才会进一步分配到个人;当前 agent1/agent2 为 offline,所以会话停在团队队列。
## 切换方式
- 想让消息只进团队队列:团队分配方式改为 `Manual`
- 想自动轮询到在线坐席:保持 `Round robin`,坐席上线
+236
View File
@@ -0,0 +1,236 @@
# LibreDesk 后台配置步骤
> 面向 `crm-new` 全栈(方案 B:LibreDesk 内置 AI 回复客户)。
> 本栈已通过 `scripts/setup-defaults.js` 自动建好 Inbox / Assistant / Automation;本文用于手工核对或重建。
> 登录:用户 `System`,密码见 `.env` 的 `LIBREDESK_SYSTEM_USER_PASSWORD`。
## 0. 前置检查
```bash
bash scripts/bootstrap.sh # LibreDesk 已启动
bash scripts/start-tools.sh # 另开终端,tools 服务 :4010
curl -s http://127.0.0.1:4100/healthz
```
## 1. AI Provider(必须先配)
`Admin → AI → Providers`,两个 provider 都要配(LibreDesk 要求 OpenAI 兼容端点):
| 项 | Completion | Embedding |
|---|---|---|
| Base URL | `https://dashscope.aliyuncs.com/compatible-mode/v1` | 同左 |
| API Key | DashScope key(与旧项目 `kb.config.json` 同一个) | 同左 |
| Model | `qwen-plus-latest` | `text-embedding-v4` |
| Dimensions | — | **1024**(必须与模型输出一致,否则报维度不匹配) |
| Temperature | 0.3 | — |
| Max tokens | 800 | — |
点 **Test connection** 两个都通过再继续。改 embedding 模型/维度会自动重新嵌入全部 snippets。
成本上限在 `config/config.toml` 的 `[ai_agent]`:`max_steps`、`max_history_messages`、`worker_count`。
## 2. 知识库(帮助中心文章)
本栈已把旧 `customer-service-kb` 导入为**帮助中心文章**(而不是 snippets),原因:只有文章能被 AI 引用(`[[cite:id]]`),并且同时进入帮助中心站内搜索。
公开地址:`http://192.168.31.61:9000/hc/yehwang-help/en`(1 个帮助中心 / 9 个 collection / 108 篇文章,全部 `published + ai_enabled`)。
重新导入(幂等):
```bash
cd /home/dev/code/crm-new
node scripts/import-kb-helpcenter.js --dry-run # 只解析
node scripts/import-kb-helpcenter.js # 登录 System 并导入
```
以下为 LibreDesk 原生 snippets 的用法(内部/敏感内容才用):
1. **手工**:一条 Q&A 一个 snippet,正文写完整答案(不要写片段)
2. **Import from URL**:抓取官网 FAQ 页面
3. **API 批量导入**(迁移旧 KB 时用):
```bash
curl -X POST http://192.168.31.61:9000/api/v1/ai/snippets \
-H "Authorization: token ${LIBREDESK_API_KEY}:${LIBREDESK_API_SECRET}" \
-H 'Content-Type: application/json' \
-d '{"title":"Minimum order","content":"The minimum order value is €30.","enabled":true}'
```
迁移建议:旧 `customer-service-kb/*.md` 按 `### Q:` 切块,标题用问题,正文用答案,逐条 POST。Import from URL 走 `POST /api/v1/ai/snippets/import-url`。
> RAG 差异:LibreDesk 是 **纯向量 top-k**,没有旧 bridge 的「向量+全文+RRF+rerank+翻译兜底」。EU 语种召回需要用真实问题抽检。
## 3. 注册业务工具
### 方式 A:自动(推荐)
1. `Admin → Teammates → System → API Key` 生成 key/secret,填进 `.env`:
```
LIBREDESK_API_KEY=...
LIBREDESK_API_SECRET=...
```
2. 执行:
```bash
node scripts/register-tools.js
```
### 方式 B:手工
`Admin → AI → Tools → New tool`,4 个工具按下面填:
| 字段 | search_products | search_orders | search_inventory | create_opportunity |
|---|---|---|---|---|
| URL | `http://bridge:4100/tools/search_products` | `.../search_orders` | `.../search_inventory` | `.../create_opportunity` |
| Method | POST | POST | POST | POST |
| Header | `X-Tools-Key: <TOOLS_SHARED_KEY>` | 同 | 同 | 同 |
| Parameters | 见 `GET /tools/manifest` | 同 | 同 | 同 |
| Require verified contact | 否 | **是** | 否 | **是** |
| Copilot / Generate reply | 开 | 开 | 开 | 开 |
| Require agent approval | 否 | 否 | 否 | 否 |
`GET http://127.0.0.1:4100/tools/manifest`(带 `X-Tools-Key`)会输出每个工具的完整 JSON Schema,可直接复制。
## 4. 创建 AI Assistant
`Admin → AI → Assistants → New assistant`:
| 项 | 建议值 |
|---|---|
| Name | `Yehwang Assistant` |
| Tone / Length | Friendly / Concise |
| Languages | English, Dutch, German, French, Spanish, Italian(留空=跟随客户) |
| Max turns | 8–10 |
| Fallback team | `Support`(先建团队) |
| Offer handoff | 开 |
| Tools | 勾选上一步的 4 个工具 |
**Instructions 草稿(可直接改)**:
```
You are the Yehwang B2B customer assistant for stainless-steel jewelry wholesale.
- Always reply in the customer's language.
- For product questions call search_products; never invent catalog items.
- For order questions call search_orders. If the result says verification is required,
ask the customer to verify their email with the one-time code, then retry.
- For stock questions call search_inventory with the SKU digits.
- When you have company + contact name + product/need, call create_opportunity once.
- Never quote prices, discounts or stock that did not come from a tool result.
- If you cannot help or the customer asks for a human, hand off to the Support team.
```
**Guardrails**:
```
Never reveal another customer's data. Never promise discounts above the published tiers.
Never create an opportunity before the contact is verified.
```
> AI Assistant 目前是 **beta**,官方建议先在低流量 inbox 验证。
## 5. 自动路由(关键)
Assistant **只有被分配给会话才会回复**。配置:
`Admin → Automations → New rule`
- 触发:`new_conversation`
- 条件:`inbox = Live chat`(如需只处理工作时间,再加 business hours 条件)
- 动作:`assign_user` → `Yehwang Assistant`
可选第二条规则:`conversation_update` + `status = Resolved` → 发送 CSAT。
人工一旦在会话里回复,assistant 会自动 handoff 并取消分配。
## 6. Live Chat Inbox 与 Widget
1. `Admin → Inboxes → New inbox → Live chat`
- Name:`Website`
- Brand / Website URL:`https://www.yehwang.com`
2. **Security** 标签:生成 widget **Secret key**(用于签 JWT,绝不能放前端)
3. **Installation** 标签复制 snippet:
```html
<script>
window.LibredeskSettings = {
baseURL: 'http://192.168.31.61:9000',
inboxID: 'YOUR_INBOX_UUID'
};
</script>
<script async src="http://192.168.31.61:9000/widget.js"></script>
```
### 登录用户身份锚定(已实现)
bridge 提供 `POST /identity`:站点 JWT → member RPC 取邮箱 → 用 inbox secret 签 LibreDesk widget JWT。
本地 POC 也可用 `IDENTITY_DEV_MODE=true` 直接传 email 生成。前端拿到 `userJWT` 后传给 widget。
### 原始说明(LibreDesk 侧)
已在网站登录的客户,服务端用 inbox Secret 签 HS256 JWT 传给 `userJWT`:
```js
// 服务端(Node 示例,密钥只在服务端)
const payload = {
external_user_id: String(user.id), // 与旧栈 yw_uid 对齐
email: user.email, // 这才是可信邮箱
first_name: user.firstName,
last_name: user.lastName,
contact_custom_attributes: { site: 'www', locale: user.locale },
exp: Math.floor(Date.now() / 1000) + 3600
};
// HS256(payload, inboxSecret) → window.LibredeskSettings.userJWT
```
多站点(www / US)建两个 livechat inbox,各自 widget + secret;联系人按 email 全局共享,注意隐私口径。
参考 `us-site/ai-snippet.js`(旧栈 Chatwoot 版)需要改写的点:`$chatwoot.setUser` → `userJWT`,`setCustomAttributes` → `contact_custom_attributes`。
## 7. Email Inbox(可选)
`Admin → Inboxes → New inbox → Other provider`:
| 项 | 值 |
|---|---|
| IMAP | `imap.163.com:993`,SSL/TLS |
| SMTP | `smtp.163.com:465`,SSL |
| 用户名/密码 | 163 邮箱 + 授权码 |
| Scan interval | `5m`(默认) |
接完后旧桥的 `bridge/mail/`(IMAP 轮询 + 自动回复)可以退役;邮件摘要改为定时调 LibreDesk 报表 API。
## 8. WhatsApp(当前 v2.9.0-rc.10 已可用)
> **版本现状**:本套已运行 `v2.9.0-rc.10`,`channels` 枚举为 `email / livechat / whatsapp`,`whatsapp_templates` 表已就绪。
> RC 属于预发布版本,生产使用前评估稳定性;回滚见 README「回滚到稳定版」。
1. Meta 侧准备:Business Account、WABA ID、Phone Number ID、Permanent Access Token、App Secret、Webhook Verify Token
2. `Admin → Inboxes → New inbox → WhatsApp`,填上述凭据
3. Meta App 的 Webhook 指向 `https://你的域名/webhooks/whatsapp`(以实际路由为准),订阅 `messages`
4. 模板:`whatsapp_templates` 表 + 后台模板管理;24 小时窗口外的主动消息必须用已审核模板
5. **AI 对 WhatsApp 的支持文档未覆盖**(文档只写 email + live chat),需实测 assistant 是否在 WhatsApp 会话触发
切换镜像:`.env` 里改 `LIBREDESK_IMAGE=libredesk/libredesk:v2.9.0-rc.9`(若 Docker Hub 无该 tag,则按官方文档从 `v2.9.0-rc.9` 源码构建)。
## 9. 验收清单
- [ ] `Admin → AI → Providers` 两个 Test connection 通过
- [ ] Snippets 至少 10 条,问一个政策问题能引用
- [ ] Tools 4 个,`crm-tools` 日志能看到调用
- [ ] Assistant 已建 + automation 已分配
- [ ] widget 能开聊,收到 AI 回复
- [ ] 未验证时问订单 → AI 引导验证;验证后能查单
- [ ] 说“我要人工” → handoff 到 Support 团队
- [ ] `curl -s http://127.0.0.1:4100/healthz` 正常
## 10. 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 登录后立刻掉登录 | 用 http 访问但 cookie 是 Secure | `config.toml` 里 `disable_secure_cookies = true` |
| 容器反复重启,日志提示 `system user password is not strong` | System 密码不满足规则(10–72 位,含大小写+数字+特殊字符) | 改 `.env` 的 `LIBREDESK_SYSTEM_USER_PASSWORD`,再 `docker compose up -d --force-recreate app` |
| 工具 401 | `X-Tools-Key` 与 `TOOLS_SHARED_KEY` 不一致 | 核对 `.env` 和工具 Header |
| 工具 403 verification_required | 未验证联系人 / 工具未开验证 | 先走 OTP 验证,或该工具不需要验证则关掉 `requires_verification` |
| 工具 503 tools_key_not_configured | tools 服务未配置共享密钥 | 重启 `start-tools.sh` |
| AI 不回复 | 会话未分配给 assistant / 会话不是 open | 检查 automation 规则与状态 |
| embedding 维度报错 | dimensions 与模型不符 | `text-embedding-v4` 用 1024 |
| 容器访问不了 tools | `host.docker.internal` 未解析 | compose 已配 `extra_hosts`;Linux 需 Docker 20.10+ |
+111
View File
@@ -0,0 +1,111 @@
# 迁移方案与取舍(2026-10-21 决策记录)
## 结论
**不能把整套实现完全搬进 LibreDesk。** 可以做到的是:
> LibreDesk 承担「渠道 + 客服工作台 + 大部分对话 AI」,原 bridge 从 ~11.7k 行压缩到 ~2–3k 行的业务工具 / 质量服务。
原因是旧系统分 6 层,其中 L5(业务集成)和 L6(质量工程)不在 LibreDesk 的产品边界内;L3/L4 可以迁但会降级。
## 1. 六层拆分
| 层 | 旧实现 | 代码量* | LibreDesk 原生 | 判定 |
|---|---|---|---|---|
| L1 渠道 | Chatwoot | — | Live chat / Email / WhatsApp(rc) | 完全替代 |
| L2 工作台 | Chatwoot | — | SLA/CSAT/宏/自动化/角色/自动分配/报表/审计 | 完全替代 |
| L3 知识库/RAG | pgvector + 向量+全文+RRF+rerank+翻译 | ~1.9k | snippets/帮助文章纯向量检索 | 降级替代 |
| L4 对话编排 | agentRunner/意图/情绪/升级/上下文/引用打分/A-B | ~3.2k | AI assistant + HTTP tools + handoff | 替代 ~70% |
| L5 业务集成 | Twenty + 4 个 RPC + 槽位采集 | ~1.1k | 无(仅通用 HTTP tools) | 必须外挂 |
| L6 质量工程 | eval/飞轮/metrics/告警/注入防护 | ~2.4k | 仅 learn-from-resolved + 报表 | 必须外挂或放弃 |
\* 按旧仓库模块行数估算,非精确切分。
## 2. 三种终态
| 方案 | 架构 | 保留能力 | 工作量 | 风险 |
|---|---|---|---|---|
| **A. 平台换、AI 留 bridge** | LibreDesk L1+L2;bridge 保留 L3–L6,webhook/API 对接 | 全部保留,白得 email/WhatsApp/SLA/CSAT/报表 | 6–8 人日 | 低;webhook 无重试需轮询兜底 |
| **B. 平台 + AI 都换**(本 POC 验证目标) | LibreDesk AI + tools 承担 L3/L4;bridge 缩成 L5(+可选 L6) | 失去 hybrid RAG、情绪/3 级升级、引用审计、A-B、注入防护、评估/飞轮/告警 | 10–14 人日 | 中;AI beta、RAG 降级、质量不可观测 |
| **C. 全内建(fork Go)** | 把 L5/L6 写进 LibreDesk | 理论全保留 | 15–20+ 人日 | 高;AGPL、升级维护 |
**推荐路径:先用本 POC 验证 B 的 L3/L4 是否够用;生产切换走 A,再按数据决定是否把 L4 也迁走。**
## 3. 方案 B 代码存废清单
保留(~3k 行):
- `crm/twentyClient.js` → 已提炼为 `services/tools/src/twenty.js`
- `agent/toolExecutor.js` 的 RPC 部分 → 已提炼为 `services/tools/src/tools.js`
- 可选:`eval/`、`middleware/alerter`、`faq`/`sitemap` 同步脚本
退役(~8k 行):
- `agent/agentRunner`、`intentClassifier`、`contextManager`、`llmCache`
- `rag/*`、`prompts/*`、`escalation/*`
- `mail/*`(交 LibreDesk 邮箱)
- `identity/otp*`、router 的交互组件/欢迎语/两个 webhook
- `crm/chatLogger`、`utils/cwClient`、`middleware/auth`(改 LibreDesk 验签)
- `config/experiments`、`security/injectionGuard`(除非保留)
## 4. 分阶段计划
### Phase 0 — POC(本仓库,1–2 天)
- [x] 独立目录 + compose(LibreDesk + 独立 pg/redis)
- [x] crm-tools 服务(4 工具 + 鉴权 + 验证门控 + 单测)
- [x] 配置/注册/冒烟脚本 + 文档
- [ ] 按 `docs/libredesk-setup.md` 配 AI/工具/助手/自动化
- [ ] 20–30 轮真实问题对比:bridge vs LibreDesk AI(质量/工具成功率/延迟/成本)
### Phase 1 — 平台切换(方案 A,4–6 天)
- LibreDesk 接 Live chat + Email(旧 mail 模块退役)
- bridge 改 `libredeskClient`(发消息/备注/状态/历史)
- webhook 改 `X-Libredesk-Signature` + 轮询补偿
- widget JWT 签发替换 `us-site/ai-snippet.js`
- 灰度:一个 inbox 走 LibreDesk,其余保持 Chatwoot
### Phase 2 — AI 迁移(视 Phase 0 结果,4–6 天)
- KB → snippets 批量导入 + 抽检
- 助手 instructions/guardrails/tools 调优
- 情绪/升级/引用/评估:接受降级或保留为独立服务
### Phase 3 — 质量服务(可选)
- 保留 bridge 的 `eval/` + `flywheel_*` + alerter,改成读 LibreDesk 报表/UUID
- 或直接放弃,用平台报表 + `learn from resolved` 代替
## 5. 必须提前接受的损失(方案 B)
1. RAG:hybrid + rerank + 翻译兜底 → 纯向量 top-k
2. 引用/幻觉:citation_score / hallucination_flag / 告警全部缺失
3. 情绪与 3 级升级:只能 max turns + fallback team 近似
4. 逐会话质量指标、A/B 实验、注入防护:无
5. 历史 `agent_metrics.conversation_id` 与新 UUID 不可 join
## 6. 风险清单
| 风险 | 影响 | 缓解 |
|---|---|---|
| AI Assistant 处于 beta | 回复质量不稳定 | 先低流量 inbox,保留人工兜底 |
| WhatsApp 仅 v2.9-rc | 不能直接上生产 | 等 stable 或用方案 A 保留旧桥 |
| webhook 无重试 | 丢事件 | 轮询补偿 + 本地队列 |
| AGPL-3.0 | 修改后对外提供需开源 | 不改源码,工具外置(本方案) |
| 无 Chatwoot 导入器 | 历史数据丢失 | 当前数据量小,接受 |
| RAG 降级 | EU 语种召回下降 | 迁移后用 golden 问题抽检,必要时保留旧 RAG 服务 |
| 单工作区 | 多站点只能多 inbox,联系人共享 | 用 JWT 的 site 属性 + 隐私口径评审 |
## 7. 版本核实(2026-10-21)
- LibreDesk 稳定版:**v2.8.0**(2026-08-22),渠道 `email/livechat`
- 预发布:**v2.9.0-rc.10**(2026-10-21),新增 WhatsApp(Meta Cloud API + 模板)
- 本套环境已切到 rc.10;稳定版 v2.8.0 仍无 WhatsApp;官方文档暂无 WhatsApp 配置页(仅 release notes / 权限表提及)
- AI Assistant / AI tools / verified contact 为 v2.8 已有能力,标注 beta
---
## 8. 2026-10-21 决策记录
- **选择方案 B**:LibreDesk 内置 AI 回复客户;bridge 收缩为业务工具/集成;Twenty 起全新实例。
- **完全新建**:不迁旧数据、不保留 Chatwoot 兼容层、不复制旧 bridge 代码;旧栈保持只读并存。
- **渠道范围**:Live chat + Email + WhatsApp(后者依赖 v2.9 RC)。
- **业务内容复用**:`customer-service-kb` 知识库与销售规则沿用,迁移为 LibreDesk snippets。
- 已落地:全栈 compose(LibreDesk :9000 / Twenty :3100 / bridge :4100 / PG :5440 / Redis :6390)、4 个 AI tools、注册/冒烟脚本。
- 待补齐:AI Provider、inbox/assistant/automation 配置、Twenty API key、KB 导入、质量旁路。
+75
View File
@@ -0,0 +1,75 @@
# 方案 A:bridge 接管 AI 编排(Live chat 优先)
## 决策
- 使用新 bridge 重写 AI 编排(不复用旧 bridge 代码)
- 先接 Live chat
- 保留 LibreDesk 的 widget JWT + OTP
## 架构
```
LibreDesk(渠道/工作台)
├─ 关闭该 inbox 的自带 AI 自动分配
├─ webhook message.created → bridge /libredesk/webhook
└─ bridge 通过 Agent API Key 回写消息
Bridge(AI 编排)
├─ LLM 调用 + 工具循环
├─ RAG / 上下文 / 情绪 / 质量闭环(后续)
└─ 业务工具(商品/订单/库存/商机/槽位)
```
## Phase 0(已完成)
- [x] 生成 LibreDesk Agent API Key(System 用户)
- [x] 创建 webhook:`message.created` → `http://bridge:4100/libredesk/webhook`
- [x] bridge webhook 端点:HMAC 验签、去重、防循环、inbox 过滤
- [x] bridge 通过 API Key 回写 agent 消息
- [x] Live chat 测试:LibreDesk 收到消息 → bridge 占位回复 → 工作台可见
- [x] 测试后恢复 LibreDesk 原有 AI 自动分配
### Phase 0 环境变量
```
LIBREDESK_API_KEY=...
LIBREDESK_API_SECRET=...
LIBREDESK_WEBHOOK_SECRET=...
LIBREDESK_WEBHOOK_INBOX_IDS=1
LIBREDESK_WEBHOOK_ID=1
```
## Phase 1 核心(已完成)
- [x] LLM 客户端(复用旧 DashScope qwen-plus-latest)
- [x] 系统提示词 + 历史消息上下文
- [x] 工具调用循环(5 个工具)
- [x] 工具调用审计日志
- [x] Live chat 实测:LLM 调 search_products,返回真实价格/库存/链接
- [x] 匿名 OTP 模块(SMTP 163 + Redis,发送/校验实测通过)
- [x] 旧 bridge pgvector RAG(vector-only;旧库无 text_tsv/parent_id)
- [x] AI 已实测:search_kb 回答最低起订量 €30 + 引用 URL
- [x] 匿名 OTP 端到端(AI 问邮箱 → 发码 → 客户回码 → verified → 查订单)
- [x] 切流:inbox 1 关闭 LibreDesk AI automation,启用 bridge webhook
- [ ] Email / WhatsApp 切 bridge(目前仍由 LibreDesk AI 处理)
- [x] 质量闭环第一步:逐会话指标 + URL/工具审计(bridge_ai_metrics)
- [x] LLM 语义缓存(政策 FAQ,pgvector 余弦 >0.85)
- [x] 上下文摘要/压缩(>24 条,保留最近 16 条)
- [x] Prompt 注入防护(消息检测 + 工具结果信任边界)
- [x] 全量商品页站点地图(www 32,691 / us 22,625,商品页 32,051/22,305)
- [x] Hybrid RAG:text_tsv/parent_id/chunk_type,292 parent + 69 child,向量+全文+RRF+父块扩展
- [x] 购买意向 post-LLM 追问兜底(产品+数量触发,缺公司/联系方式时多语言硬追问)
- [x] 商机补齐:内容指纹去重、跨渠道复用/同步、阶段推进、CRM 故障 fail-closed
- [x] `search_email_history`:已验证客户邮件历史,强制按 verified email scope
- [x] 发送前输出护栏:句子级自动重写 + URL 白名单 + 价格/库存数字必须有工具证据
- [x] Email/WhatsApp 人工接管:Wholesale Support 团队 + assign_team 规则(AI 不再分配)
- [x] 指标告警(错误率/工具错误/未验证 URL/延迟 + 邮件)
- [x] 离线评测(18 条确定性黄金集 + 4 条真实模型 smoke)
- [ ] 质量闭环后续:告警、离线评测、飞轮
> 目前为避免 LibreDesk AI 与 bridge AI 双回复,bridge webhook 已临时停用;等 OTP/RAG 完成后切流(关闭 LibreDesk AI automation + 启用 webhook)。
## Phase 2(待做)
- RAG:混合检索 / rerank / 翻译回退
- 情绪检测 / 升级策略
- 质量闭环:引用审计、指标、评测、飞轮
- Email / WhatsApp 接入
## 开放问题
1. LLM 是否复用旧 DashScope `qwen-plus-latest` 配置?
2. Phase 1 先只处理已登录 JWT 用户,匿名 OTP 用户后置?
3. RAG 用旧 bridge 的 pgvector 还是 LibreDesk 帮助中心?
+173
View File
@@ -0,0 +1,173 @@
# 运维手册(crm-new)
## 启动 / 停止
```bash
cd /home/dev/code/crm-new
# 启动(生成 .env / 渲染配置 / 构建 bridge / 等健康)
bash scripts/bootstrap.sh
# 只启动,不重新生成
docker compose up -d
# 停止(保留数据)
docker compose down
# 停止并删除数据(危险:会清空 LibreDesk/Twenty/业务库)
# docker compose down -v
```
## 状态与日志
```bash
docker compose ps
docker compose logs -f --tail=100 libredesk
docker compose logs -f --tail=100 twenty-server
docker compose logs -f --tail=100 bridge
# 健康
curl -s http://192.168.31.61:9000/health
curl -s http://192.168.31.61:3100/healthz
curl -s http://192.168.31.61:4100/healthz
```
## 内容与 AI 配置脚本
```bash
# 导入/更新知识库(帮助中心文章,幂等)
node scripts/import-kb-helpcenter.js --dry-run
node scripts/import-kb-helpcenter.js
# 配置 DashScope(Completion + Embedding,自动触发重建向量)
node scripts/configure-ai.js
# 验证 RAG + 引用(创建临时助手跑 preview 后自动删除)
node scripts/verify-rag.js "What is the minimum order value?"
# 注册 4 个业务工具
node scripts/register-tools.js
# 创建/更新默认 Inbox + Assistant + Automation(幂等)
node scripts/setup-defaults.js
# Twenty:创建初始 workspace + 在库内生成 API key(不走 UI)
node scripts/twenty-bootstrap.js
# 重建 KB parent-child 索引(旧 bridge indexer + 回填 text_tsv,自动备份)
bash scripts/rebuild-kb-index.sh
# 升级旧 kb_docs 支持 Hybrid RAG(text_tsv/parent_id/chunk_type)
node scripts/upgrade-kb-schema.js
# 全量商品页站点地图(官网 sitemap → bridge 库)
node scripts/build-site-map.js www us
# AI 质量日报(最近 24 小时;可传小时数)
node scripts/quality-report.js 24
# 质量告警(默认最近 1 小时;--dry-run 只打印不发邮件)
node scripts/quality-alert.js 1 --dry-run
# 建议 cron:
# */30 * * * * cd /home/dev/code/crm-new && node scripts/quality-alert.js 1 >> logs/quality-alert.log 2>&1
# 离线回归评测(确定性逻辑,不调 LLM)
node scripts/eval-offline.js
# 真实模型离线评测(fake LibreDesk,不写真实会话;会调 LLM/RPC)
node scripts/eval-live.js
# 公网 HTTPS(WhatsApp webhook 需要):Cloudflare Quick Tunnel + 路径白名单网关
bash scripts/public-https.sh
# 测试服 RPC 端口外网被封:建 SSH 隧道并让 bridge 指向隧道(网关 IP)
bash scripts/rpc-tunnel.sh --apply
# 配置 163 SMTP(自动重启 LibreDesk)
node scripts/configure-email.js
# 配置 Email 渠道(163 收件走 imap-proxy + 默认外发模板 + AI 自动分配)
node scripts/add-email-inbox.js
# 本地静态测试页(widget 接入演示)
bash scripts/serve-web.sh # http://192.168.31.61:8088/demo.html
# 端到端验证 widget JWT(登录用户可查订单)
node scripts/e2e-widget-jwt.js "Can you check the status of my order SO-1?"
# 端到端模拟 widget 访客对话(等待 AI 回复)
node scripts/e2e-widget.js "What is the minimum order value?"
```
## 冒烟
```bash
bash scripts/smoke.sh
```
## 常用运维操作
```bash
# 修改 bridge 代码后
docker compose up -d --build bridge
# 修改 .env 后(如 Twenty API key)
docker compose up -d --force-recreate bridge
# 修改 LibreDesk 配置模板后
node scripts/render-config.js && docker compose up -d --force-recreate libredesk
# 备份数据库
docker exec crmnew-postgres pg_dump -U crmnew -d libredesk > backups/libredesk-$(date +%F).sql
docker exec crmnew-postgres pg_dump -U crmnew -d twenty > backups/twenty-$(date +%F).sql
# 备份 Twenty 文件存储
docker run --rm -v crm-new_twenty-data:/data -v "$PWD/backups":/backup alpine \
tar czf /backup/twenty-data-$(date +%F).tar.gz -C /data .
```
## 镜像升级(LibreDesk)
```bash
# 1. 备份
docker exec crmnew-postgres pg_dump -U crmnew -d libredesk > backups/libredesk-before-upgrade.sql
# 2. 修改 .env 的 LIBREDESK_IMAGE 后重建
docker compose up -d --force-recreate libredesk
# 3. 容器启动命令会自动执行 --upgrade;确认日志
docker compose logs --tail=50 libredesk
```
## 回滚
```bash
# 切回稳定版
sed -i 's#^LIBREDESK_IMAGE=.*#LIBREDESK_IMAGE=docker.1panel.live/libredesk/libredesk:latest#' .env
docker compose up -d --force-recreate libredesk
# 跨大版本回滚数据库需恢复备份(升级前 pg_dump)
```
## 端口冲突排查
```bash
ss -ltnp | grep -E ':(9000|3100|4100|5440|6390)\s'
```
## 已知坑
| 现象 | 原因 | 处理 |
|---|---|---|
| 容器启动报 `database "xxx" does not exist` | 首次初始化脚本未执行 | `docker exec crmnew-postgres psql -U crmnew -d postgres -c 'CREATE DATABASE xxx;'` |
| bridge 健康检查 `connection refused` | 容器内 `localhost` 解析到 IPv6 | 健康检查已改用 `127.0.0.1` |
| LibreDesk 反复重启 | System 密码不满足强度(10–72 位、大小写+数字+特殊字符) | 改 `.env` 的 `LIBREDESK_SYSTEM_USER_PASSWORD` 后重建 |
| 工具 401 | `X-Tools-Key` 与 `TOOLS_SHARED_KEY` 不一致 | 核对 `.env` 与 LibreDesk 工具 Header |
| 工具 403 verification_required | 联系人未验证 | 先走平台 OTP 验证,或该工具不需要验证时关闭 `requires_verification` |
| Twenty API 调用失败 | `TWENTY_API_KEY` / workspace 未配置 | 在 Twenty 后台创建 API key 填 `.env` 后重建 bridge |
| 163 邮箱报 `Unsafe Login` | LibreDesk 的 go-imap 不发 IMAP ID | 已加 `imap-id-proxy`,email inbox 的 IMAP host 指向 `imap-proxy:1993`,`tls_type=none` |
| 管理员后台实时更新不工作 | 通过 http + IP 访问时 agent websocket 拒绝非 HTTPS 来源 | POC 可忽略;生产用 HTTPS 或经 localhost 访问 |
| collect_info 槽位丢失 | Redis 不可用,退回了进程内存 | 检查 `REDIS_URL=redis://redis:6379/3` 和 `docker logs crmnew-bridge` 的 lead store 告警 |
| 多站点商品推荐串站 | `bridge/config/sites.json` 的 inbox 映射没配 | 把对应 inbox 数字 ID 指到 us/eu/www profile,重建 bridge |
| WhatsApp webhook 收不到 | 没有公网 HTTPS / Meta 回调地址不对 | 跑 `bash scripts/public-https.sh`,把输出的 `.../webhooks/whatsapp/<inbox_id>` 配到 Meta;quick tunnel URL 每次重启会变 |
| 公网访问到管理后台 | 网关白名单配置 | `services/public-gateway` 只放行 `/webhooks/whatsapp/*`、`/hc/*`、`/widget*`、`/static/public/*`,其余 404 |
| AI 回复"系统暂时无法查询订单" | 测试服 24503/24505/24506/24509 外网 TCP 被封,bridge 8s 超时 | 跑 `bash scripts/rpc-tunnel.sh --apply`,bridge RPC 指向 docker 网关(隧道只绑网关,不暴露 LAN) |