Files

226 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# crm-new
**完全新建**的一套 CRM 客服栈:LibreDesk(平台 + 内置 AI)+ Twenty(CRM)+ bridge(业务工具/集成)。
与旧项目 `/home/dev/code/crm/crm-ai-demo` 完全隔离:独立目录、独立 Postgres/Redis、独立端口,不迁旧数据。
## 当前状态(2026-10-26)
| 组件 | 状态 |
|---|---|
| LibreDesk v2.9.0 | ✅ 运行中 :9000(System 密码见 `.env`) |
| Twenty v2.30.1(全新实例) | ✅ 运行中 :3100,数据库迁移完成(71 张表) |
| bridge | ✅ 运行中 :4100,4 个工具,单测 18/18 通过 |
| Postgres / Redis | ✅ :5440 / :6390 healthy |
| AI Provider | ✅ 已配置(DashScope `qwen-plus-latest` + `text-embedding-v4` 1024 维),测试通过 |
| 知识库向量 | ✅ 108 篇文章已索引(embeddings=108) |
| AI Tools | ✅ 4 个工具已注册(search_products / search_orders / search_inventory / create_opportunity) |
| RAG/引用验证 | ✅ 已验证:问题命中文章并返回编号引用 |
| Live chat Inbox | ✅ `Website`(uuid `10fd5207-d235-4c49-beaa-a65eed0ddd0e`) |
| AI Assistant | ✅ `Yehwang Assistant`(4 个工具、引用开启、handoff 开启、max_turns=8) |
| Automation | ✅ 新会话自动分配给 Assistant(仅 Website inbox) |
| 端到端验证 | ✅ widget 发起 → 自动分配 → AI 回复(带文章引用);商品问题已触发 bridge `search_products` |
| Twenty Workspace | ✅ 已创建(admin@yehwang.com,workspace 已 ACTIVE) |
| Twenty API Key | ✅ 已在库内生成并写入 `.env`,GraphQL 验证通过 |
| 商机写入 | ✅ `create_opportunity` 实测创建 company/person/opportunity/note/task |
| 业务 RPC | ✅ 本地开发用 SSH 隧道(`scripts/rpc-tunnel.sh`);测试/生产应直连内网 RPC,不需要隧道 |
| 邮件通知(OTP) | ✅ 163 SMTP 已配置,实测验证码邮件已送达 163 收件箱 |
| Email 渠道 | ✅ 163 收件(imap-proxy 绕过 Unsafe Login)+ AI 回复邮件(模板)已实测 |
| IMAP 代理 | ✅ `services/imap-id-proxy`(LibreDesk 不发 IMAP ID,163 会拒绝 SELECT) |
| widget JWT | ✅ bridge `/identity` 签发;登录用户端到端验证通过(`search_orders` verified=true) |
| 知识库 | ✅ 帮助中心 `yehwang-help`:9 个 collection / 108 篇文章(公开)→ http://localhost:9000/hc/yehwang-help/en |
旧栈 `crm/crm-ai-demo` 不受影响,两套可并行对照。
## 架构
```
访客(Web widget / Email / WhatsApp)
│
▼
LibreDesk :9000 ── 渠道、会话、坐席工作台、内置 AI Assistant、snippets RAG、OTP 验证
│ AI 调用自定义 HTTP tools
▼
bridge :4100 ── 商品/订单/库存查询 + Twenty 商机写入(身份校验在服务端二次执行)
│
▼
Twenty :3100 ── 公司/联系人/商机/备注/任务
```
与旧栈的区别:**AI 编排交给 LibreDesk 内置 assistant**,bridge 收缩为业务工具/集成服务。
知道这样会丢失旧桥的评估/飞轮/情绪升级等能力,后续按需在 bridge 里补"质量旁路"(webhook 消费)。
## 组件与端口
| 组件 | 地址 | 镜像/实现 |
|---|---|---|
| LibreDesk | http://localhost:9000 | `libredesk:v2.9.0`(含 WhatsApp) |
| Twenty | http://localhost:3100 | `twentycrm/twenty:v2.30.1`(全新实例) |
| bridge | http://localhost:4100 | Node 24,自研 |
| Postgres | localhost:5440 | pgvector/pg18,库 `libredesk` / `twenty` / `bridge` |
| Redis | localhost:6390 | redis:8.8 |
## 目录
```
crm-new/
├── docker-compose.yml # 全栈编排
├── .env / .env.example # 全栈配置(.env 自动生成密钥)
├── infra/init-databases.sql # 首次初始化三个库
├── libredesk/ # LibreDesk 配置模板/渲染结果/uploads(legacy/ 为旧单机模式归档)
├── twenty/ # Twenty 部署说明(不需要源码)
├── bridge/
│ └── kb/ # KB 源 Markdown(在线 FAQ 同步会更新这里) # 业务工具服务(原 tools)+ 单测
│ └── config/sites.json # inbox ID → 站点 profile(tenant/语言/类目地图)
├── services/imap-id-proxy/ # IMAP ID 注入代理(163 专用)
├── scripts/
│ ├── bootstrap.sh # 生成 .env + 渲染配置 + 启动 + 健康等待
│ ├── render-config.js
│ ├── register-tools.js # 把 4 个工具注册进 LibreDesk(支持 session/API key)
│ ├── configure-ai.js # 配置 DashScope Completion + Embedding
│ ├── twenty-bootstrap.js # 库里创建 Twenty workspace + API key(不走 UI)
│ ├── configure-email.js # 配置 163 SMTP(通知/OTP 邮件)
│ ├── add-email-inbox.js # 配置 Email Inbox + 默认外发模板 + Automation
│ ├── e2e-widget-jwt.js # 端到端验证 widget JWT + 订单工具
│ ├── rpc-tunnel.sh # 仅本地开发:测试服 RPC 端口 SSH 隧道
│ ├── public-https.sh # 仅本地联调:Cloudflare Quick Tunnel + 白名单网关
│ ├── set-public-url.js # 更新 LibreDesk app.root_url
│ ├── import-kb-helpcenter.js # 旧 KB -> 帮助中心文章(幂等)
│ ├── verify-rag.js # 临时助手 + preview,验证检索/引用
│ ├── export-kb-snapshot.sh # 导出 kb_docs 快照
│ ├── import-kb-snapshot.sh # 无旧 bridge 时导入 KB 快照
│ ├── sync-kb-online.js # 在线 FAQ 同步 + 重建 KB(不依赖旧 bridge)
│ ├── build-site-kb.js # sitemap → kb/11-site-map.md
│ ├── kb-version.js # KB 备份列表/回滚
│ ├── eval-retrieval.js # RAG 检索评测
│ ├── kb-audit.js # KB 数量/重复/空答案审计
│ ├── smoke.sh
│ └── serve-web.sh # 仅本地预览 demo 页面
├── web/ # 站点接入:ai-snippet.js + demo.html(widget JWT 演示)
├── docs/ # 架构、LibreDesk 配置、环境差异、上线/运维手册
│ ├── go-live.md # 上线文档(范围/切流/验收/监控/回滚)
│ ├── environments.md # 本地开发 vs 测试/生产:RPC/Cloudflare 隧道差异
│ ├── human-routing.md # Email/WhatsApp 人工团队分配
│ └── plan-a.md # 方案 A:bridge 接管 AI 编排进度
├── deploy/
│ ├── nginx.conf.example
│ └── seeds/kb_docs.sql.gz # KB 快照(594 chunks:523 parent + 71 child)
services/public-gateway/ # 公网路径白名单反向代理(仅 webhook/帮助中心)
```
## 快速开始
```bash
cd /home/dev/code/crm-new
# 1) 启动全栈(首次 Twenty 迁移 1-3 分钟)
bash scripts/bootstrap.sh
# 2) 冒烟
bash scripts/smoke.sh
```
> `scripts/rpc-tunnel.sh`、`scripts/public-https.sh`、`scripts/serve-web.sh` 仅本地开发/联调使用;测试/生产环境不需要,见 [docs/environments.md](docs/environments.md)。
启动完成后:
1. **LibreDesk**(`System`,密码见 `.env` 的 `LIBREDESK_SYSTEM_USER_PASSWORD`)
- `Admin → AI → Providers`:Completion 用 DashScope `qwen-plus-latest`,Embedding 用 `text-embedding-v4`(1024 维)
- 建 Live chat / Email / WhatsApp inbox
- 建 AI Assistant + Automation(new_conversation → assign assistant)
- 生成 API key 填 `.env`,然后 `node scripts/register-tools.js`
2. **Twenty**(首次访问需注册 workspace)
- 注册后在 `Settings → Developers → API Keys` 创建 key,连同 workspaceId 填进 `.env`,再 `docker compose up -d --force-recreate bridge`
3. 回到 LibreDesk 的 Assistant 里勾选 4 个工具,验证一条会话能查单 + 建商机
## Widget 安装
```html
<script>
window.LibredeskSettings = {
baseURL: 'http://localhost:9000',
inboxID: '10fd5207-d235-4c49-beaa-a65eed0ddd0e'
};
</script>
<script async src="http://localhost:9000/widget.js"></script>
```
登录用户用服务端签的 `userJWT` 绑定身份(见 `docs/libredesk-setup.md` 第 6 节)。
## 站点接入(widget)
```bash
bash scripts/serve-web.sh # http://localhost:8088/demo.html
```
- `web/ai-snippet.js`:设置 `window.YehwangChatConfig` 后引入;有 `siteJWT` 时先调 bridge `/identity` 换 widget JWT
- `web/demo.html`:匿名 / 登录身份两种模式的联调页
- 生产:`IDENTITY_DEV_MODE=false`,由站点注入 `siteJWT`;CORS 白名单加真实域名
## 多站点路由(bridge)
`bridge/config/sites.json` 把 LibreDesk inbox 映射到站点 profile:
```json
{
"defaultSite": "www",
"languageIds": { "en": 1, "nl": 2, "de": 3, "es": 4, "fr": 5, "it": 6, "tr": 7, "cn": 10 },
"profiles": {
"www": { "site": "www", "tenantId": 2, "language": "en", "searchUrl": "https://www.yehwang.com/search.html?q=", "siteMap": "site-map.json" },
"us": { "site": "us", "tenantId": 2, "language": "en", "searchUrl": "https://us.yehwang.com/search.html?q=", "siteMap": "site-map-us.json" }
},
"inboxes": { "1": "www", "2": "www", "3": "www" }
}
```
- 工具调用时按 `X-Libredesk-Inbox-ID` 选 profile
- RPC 请求会带 `reqContext`:当前默认 `tenantId=2`;不强制币种,金额按 RPC 返回原样展示,`languageId` 按 profile 的 `language` code 映射(`DEFAULT_TENANT_ID` / `DEFAULT_LANGUAGE_ID` 可覆盖)
- 商品搜索走真实 RPC(带 `_class` 的 `ReqContext`/`DeviceInfoDto`),RPC 不返回 URL 时退化到官网搜索链接
- 商品兜底用对应站点的 site-map 和搜索地址
- 商机备注会记录 `Site / Tenant / inbox`
- 可用 `SITES_JSON` 环境变量整体覆盖
## 工具清单(bridge)
| 工具 | 需要验证 | 作用 |
|---|---|---|
| `search_kb` | 否 | 旧 pgvector 知识库检索(政策/FAQ),vector-only |
| `search_products` | 否 | 商品搜索:价格/库存/图片/product_id/URL;中文词自动映射英文类目 |
| `search_orders` | **是** | 订单列表/详情(状态、商品、金额、地址、物流) |
| `search_inventory` | 否 | 库存/ETA(优先用 `product_id`) |
| `search_email_history` | **是** | 已验证客户自己的邮件历史(旧 `kb_mail_docs`,向量检索,邮箱强制 scope) |
| `collect_info` | 否 | 跨轮采集 lead 槽位(company/contact/need/quantity/budget/closeDate) |
| `handoff_to_human` | 否 | 私聊备注 + 分配人工用户 |
| `send_verification_code` | 否 | 匿名客户邮箱验证码(163 SMTP) |
| `verify_email_code` | 否 | 校验验证码,成功后 30 分钟内可查私有数据 |
| `create_opportunity` | **是** | 合并槽位后写 Twenty 商机 + Note + 3 天 Task |
> `collect_info` 槽位存 Redis DB 3(key `bridge:lead:<conversation-uuid>`,TTL 24h);Redis 不可用时退回进程内存并告警。
## 运维
环境差异见 [docs/environments.md](docs/environments.md)(RPC 隧道/Cloudflare 隧道仅本地开发用)。
上线清单见 [docs/go-live.md](docs/go-live.md),常用命令见 [docs/runbook.md](docs/runbook.md)(启动/停止/日志/备份/升级/回滚/排错)。
## 测试
```bash
cd bridge && npm test
```
## 发送前输出护栏
- 禁止声称已下单/已发邮件/已生成订单号/PDF/已通知团队(没有对应工具)
- 回复中的 URL 只允许来自本轮工具结果 + 静态白名单(`/cart`、`/order.html`)
- 价格/库存数量必须出现在工具结果里,否则替换为“无法确认”提示
- 命中后自动重写:删除虚假动作句子、替换未验证价格/库存、移除未验证 URL,保留商品/订单等有效内容
- 只有重写后没有有效内容时才回退到固定安全话术
- 命中记录到 `audit.output_guard`(`blocked` + `rewritten`),并打 `agent: output_guard` 日志
## 关键约束
- **镜像直接使用 Docker Hub**:`libredesk/libredesk`、`pgvector/pgvector`、`redis`、`twentycrm/twenty`;如网络受限可在 Docker daemon 配自己的 registry mirror
- **WhatsApp 自 v2.9.0 起支持**,需要 Meta Cloud API 凭据;当前运行 v2.9.0 正式版
- **LibreDesk AI 仍是 beta**,建议低流量验证
- 旧栈(Chatwoot/bridge/Twenty :3000)保持运行,可并行对照,互不影响