ffc9574a45c0aed2e2641683766de9e920b8c08d
crm-new
完全新建的一套 CRM 客服栈:LibreDesk(平台 + 内置 AI)+ Twenty(CRM)+ bridge(业务工具/集成)。
与旧项目 /home/dev/code/crm/crm-ai-demo 完全隔离:独立目录、独立 Postgres/Redis、独立端口,不迁旧数据。
当前状态(2026-10-21)
| 组件 | 状态 |
|---|---|
| LibreDesk v2.9.0-rc.10 | ✅ 运行中 :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-rc.10(含 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/帮助中心)
快速开始
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。
启动完成后:
- LibreDesk(
System,密码见.env的LIBREDESK_SYSTEM_USER_PASSWORD)Admin → AI → Providers:Completion 用 DashScopeqwen-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
- Twenty(首次访问需注册 workspace)
- 注册后在
Settings → Developers → API Keys创建 key,连同 workspaceId 填进.env,再docker compose up -d --force-recreate bridge
- 注册后在
- 回到 LibreDesk 的 Assistant 里勾选 4 个工具,验证一条会话能查单 + 建商机
Widget 安装
<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 scripts/serve-web.sh # http://localhost:8088/demo.html
web/ai-snippet.js:设置window.YehwangChatConfig后引入;有siteJWT时先调 bridge/identity换 widget JWTweb/demo.html:匿名 / 登录身份两种模式的联调页- 生产:
IDENTITY_DEV_MODE=false,由站点注入siteJWT;CORS 白名单加真实域名
多站点路由(bridge)
bridge/config/sites.json 把 LibreDesk inbox 映射到站点 profile:
{
"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 的languagecode 映射(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(keybridge:lead:<conversation-uuid>,TTL 24h);Redis 不可用时退回进程内存并告警。
运维
环境差异见 docs/environments.md(RPC 隧道/Cloudflare 隧道仅本地开发用)。
上线清单见 docs/go-live.md,常用命令见 docs/runbook.md(启动/停止/日志/备份/升级/回滚/排错)。
测试
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 RC 支持,需要 Meta Cloud API 凭据;RC 版供联调,不直接上生产
- LibreDesk AI 仍是 beta,建议低流量验证
- 旧栈(Chatwoot/bridge/Twenty :3000)保持运行,可并行对照,互不影响
Languages
JavaScript
95.2%
Shell
3.8%
HTML
0.9%