Files
crm-ai/docs/deploy-wangliang.md

23 KiB
Raw Permalink Blame History

crm-ai 在 wangliang 工作区的部署记录

部署时间:2026-10-23 | 部署位置:/data/dev/wangliang/crm-ai | 代码版本:main @ 1ae25f1

本文件记录这套栈实际跑成什么样、和仓库默认配置差在哪、怎么运维、以及还缺哪些凭据。 环境相关的临时信息写在这里,通用约定见 /data/dev/_shared/NOTES.md。

1. 交付摘要

一套自包含的客服 + CRM 栈(与仓库 docker-compose.yml 一致,Compose 项目名 crm-new):

组件 容器 宿主端口 状态
LibreDesk v2.9.0(渠道/工作台) crmnew-libredesk 9000 ✅ healthy
Twenty CRM v2.30.1(server) crmnew-twenty-server 3100 ✅ healthy
Twenty worker crmnew-twenty-worker — ✅ running
bridge(业务工具 / RPC 集成) crmnew-bridge 4100 ✅ healthy
PostgreSQL 18 + pgvector crmnew-postgres 127.0.0.1:5440 ✅ healthy
Redis 8.8 crmnew-redis 127.0.0.1:6390 ✅ healthy
IMAP ID 注入代理(163 专用) crmnew-imap-proxy 127.0.0.1:1993 ✅ running

数据卷:crm-new_pg-data、crm-new_redis-data、crm-new_twenty-data(docker compose down 不会删)。

2. 访问地址与账号

入口 地址 账号
LibreDesk(工作台 / 帮助中心 / widget) https://wangliang-crm.dev.gaia888.com System /密码见下方命令
Twenty CRM https://wangliang-twenty.dev.gaia888.com admin@yehwang.com /密码见下方命令
bridge(调试/集成) https://wangliang-crmbridge.dev.gaia888.com 请求头 X-Tools-Key,值见下方命令
cd /data/dev/wangliang/crm-ai
grep '^LIBREDESK_SYSTEM_USER_PASSWORD=' .env   # LibreDesk System 密码
grep '^TWENTY_ADMIN_PASSWORD='           .env   # Twenty 管理员密码
grep '^TOOLS_SHARED_KEY='                .env   # bridge 工具密钥

密钥只存在 .env(600)与 libredesk/config/config.toml(600),两者都已被 .gitignore 排除,不要提交。

3. 与仓库默认配置的差异(重要)

项 仓库默认 本环境 原因
LIBREDESK_IMAGE libredesk/libredesk:v2.9.0 libredesk/libredesk:v2.9.0-**arm64** 宿主是 ARM64;v2.9.0-arm64 的 digest 与多架构 v2.9.0 的 arm64 子镜像一致,显式 arm64 tag 沿用原部署习惯
LIBREDESK_BASE_URL http://localhost:9000 https://wangliang-crm.dev.gaia888.com 走宿主 nginx 的 HTTPS
TWENTY_SERVER_URL http://192.168.31.61:3100 https://wangliang-twenty.dev.gaia888.com 前端 REACT_APP_SERVER_BASE_URL 用的就是它(已核对页面内联配置)
RECOMMEND_URL 测试服地址 jsonrpc://wangliang-dev:9503 用 本工作区 的 product-service
ORDER_API_URL 测试服地址 jsonrpc://wangliang-dev:9505 trading-service
INVENTORY_API_URL 测试服地址 jsonrpc://wangliang-dev:9509 eta-service
MEMBER_RPC_URL 测试服地址 jsonrpc://wangliang-dev:9506 member-service
IDENTITY_DEV_MODE false true dev 环境方便 widget 联调;上生产前改回 false
PRODUCT_TIMEOUT_MS 未透传(bridge 默认 15s) 30000(compose 增加透传) 冷门长查询会偶发 >15s,超时会降级成无价格的站点地图结果,模型易编价
public-gateway / cloudflared compose 里默认启动 未启动 已有宿主 nginx 直出 HTTPS,不需要 Cloudflare 临时隧道
bridge 网络 只挂 crm-new_default 额外接入 spug-net 直连 wangliang-dev:9503/9505/9506/9509,去掉对宿主 socat 转发的依赖

RPC 走 容器网络直连:compose 把 bridge 同时接入外部网络 spug-net(与 wangliang-dev 同网络), 直接按容器名解析到 wangliang-dev,不再依赖宿主 socat 24503-24509 转发。容器重建后 DNS 名不变, 比 socat 需要按 IP 重建更稳。宿主侧的 conf/dev/rpc-forward.sh 仍保留(供宿主/不在 spug-net 的容器使用), 但本栈已不依赖它。

4. 已完成初始化

LibreDesk

  • System 账号可登录(浏览器实测落到 /inboxes/assigned)。
  • AI Provider 已配置:DashScope qwen-plus-latest(completion)+ text-embedding-v4 1024 维(embedding), configure-ai.js 的两项连通性测试均通过。密钥只存在 LibreDesk 库内与 .env(600),未写入文档。
  • 助理指令已加固(见第 9 节):search_products 必须用 2-5 个词的关键词、不得编造价格/商品,price=null 时明确说“暂不可用”。
  • 知识库已接入:bridge 库 kb_docs 594 行(523 parent / 71 child,来自 deploy/seeds/kb_docs.sql.gz), RAG_PG_URL 已配置,search_kb 实测命中最低起订量/退换货等 FAQ,AI 能据此回答。
  • app.root_url = https://wangliang-crm.dev.gaia888.com(scripts/set-public-url.js)。
  • 10 个 bridge 工具已注册(scripts/register-tools.js):search_products / search_orders / search_inventory / search_kb / search_email_history / collect_info / send_verification_code / verify_email_code / handoff_to_human / create_opportunity。
  • 默认助理 Yehwang Assistant(#1,10 个工具、引用与 handoff 开启)。
  • Live chat Inbox Website(#1,uuid ad25d7a4-722d-4561-aebf-9d8f60952d09)。
  • Automation:Live chat 规则 1 已关闭(AI Assistant);新会话由 bridge webhook 接管。
  • 2026-10-23 切流完成:
    • bridge webhook Bridge AI 已创建并启用(message.created → http://bridge:4100/libredesk/webhook)
    • LibreDesk API key 已生成并写入 .env
    • Wholesale Support 团队(id=1,Round robin,System 为成员)
    • WhatsApp 规则改为 assign_team=Wholesale Support,启用
    • IDENTITY_CORS_ORIGIN 改为 https://wangliang-crm.dev.gaia888.com
    • bridge AI 端到端已验证,bridge_ai_metrics 开始有数据

Twenty

  • Workspace Yehwang:2ff5a56b-95d8-40e1-85cf-529ad3a70ed7(ACTIVE)。
  • API key 已生成并写入 .env 的 TWENTY_API_KEY;GraphQL companies 查询验证通过。
  • 浏览器实测可登录,首次进入 /install-apps(Twenty 的新工作区引导页,正常现象)。

bridge

  • /healthz 正常,10 个工具就绪。
  • 实测 search_products 直接命中本工作区真实商品数据(如 Stainless Steel Y-Chain Necklace / $6.73 / product_id=11079),说明 wangliang-dev:9503 的 JSONRPC 直连链路是通的(工具调用 925ms,source=catalog)。
  • search_orders 未验证身份时按设计返回 verification_required。

5. 运维命令

cd /data/dev/wangliang/crm-ai

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

docker compose start                   # 启动已有容器
docker compose stop                    # 停止(保留数据)
docker compose restart libredesk       # 重启单个服务

# 改 .env 后(bridge 必做;libredesk 也要重建才会读到新 env)
docker compose up -d --force-recreate bridge
docker compose up -d --force-recreate libredesk

# 改 LibreDesk 配置模板后
node scripts/render-config.js && docker compose up -d --force-recreate libredesk

# 备份(DB + Twenty 自带数据都在 PG 里;卷里是上传文件)
mkdir -p backups
docker exec crmnew-postgres pg_dump -U crmnew -Fc libredesk > backups/libredesk-$(date +%F).dump
docker exec crmnew-postgres pg_dump -U crmnew -Fc twenty    > backups/twenty-$(date +%F).dump
docker exec crmnew-postgres pg_dump -U crmnew -Fc bridge    > backups/bridge-$(date +%F).dump

# 恢复
docker exec -i crmnew-postgres pg_restore -U crmnew -d libredesk --clean --if-exists < backups/libredesk-YYYY-MM-DD.dump

所有容器都是 restart: unless-stopped,Docker 守护进程重启后会自己拉起来。

6. 域名反向代理

宿主 nginx 新增了独立配置文件 /etc/nginx/conf.d/zz-wangliang-crm.conf:

  • wangliang-crm.dev.gaia888.com → 127.0.0.1:9000
  • wangliang-twenty.dev.gaia888.com → 127.0.0.1:3100
  • wangliang-crmbridge.dev.gaia888.com → 127.0.0.1:4100
  • 80 → 308 跳 HTTPS;证书复用 *.dev.gaia888.com(通配一级域名,三个域名都被覆盖)。

它没有改 devstack.conf 的 map/正则,因此不影响其它工作区。

改完后的自检 / 重载(query 用户没有 root,用容器代跑):

docker run --rm --privileged --pid=host redis:7-alpine \
  sh -c 'nsenter -t 1 -m -u -i -n -p /usr/sbin/nginx -t'
docker run --rm --privileged --pid=host redis:7-alpine \
  sh -c 'nsenter -t 1 -m -u -i -n -p /usr/sbin/nginx -s reload'

回滚(只需删掉这个文件并 reload,其它站点不受影响):

docker run --rm -v /etc/nginx/conf.d:/c redis:7-alpine rm -f /c/zz-wangliang-crm.conf
# 再执行上面的 nginx -t / -s reload

7. 尚未配置(需要真实凭据,不是代码问题)

项 现状 补法
AI Provider(DashScope) ✅ 已配置(qwen-plus-latest / text-embedding-v4),端到端已验证 换 key:DASHSCOPE_API_KEY=sk-xxx node scripts/configure-ai.js
SMTP / 163 邮件 SMTP_* 为空,OTP 与邮件渠道不可用 填 .env 后 node scripts/configure-email.js + node scripts/add-email-inbox.js
知识库 RAG(search_kb) ✅ 已配置:快照导入 bridge 库(594 行),RAG_PG_URL 已指向,search_kb 可用 更新:FORCE=1 bash scripts/import-kb-snapshot.sh 或 node scripts/sync-kb-online.js --local-only --force
WhatsApp 渠道 WA_* 已存在,inbox 已存在;规则已指向人工团队 仍需确认 Meta callback 是否指向测试域名,并补测试号/模板
public-gateway / cloudflared 未启动 需要 Cloudflare 临时隧道时再 docker compose up -d public-gateway cloudflared

IDENTITY_DEV_MODE=true 只适合 dev;上生产前改回 false。

8. 验证记录(2026-10-23)

$ curl -o /dev/null -w '%{http_code}' https://wangliang-crm.dev.gaia888.com/health        # 200
$ curl -o /dev/null -w '%{http_code}' https://wangliang-twenty.dev.gaia888.com/healthz    # 200
$ curl -o /dev/null -w '%{http_code}' https://wangliang-crmbridge.dev.gaia888.com/healthz # 200
$ curl -s https://wangliang-crmbridge.dev.gaia888.com/healthz
  {"ok":true,"service":"crm-tools","tools":[10 个],"sites":["www","us","eu"]}
$ curl -s -X POST http://127.0.0.1:4100/tools/search_products -H 'X-Tools-Key: …' -d '{"query":"stainless steel necklace"}'
  {"ok":true,"products":[{"name":"Stainless Steel Y-Chain Necklace","price":"$6.73","product_id":11079}]}

AI 端到端(node scripts/e2e-widget.js "Show me 3 stainless steel necklaces with prices and stock."): 助手调用 search_products(612ms,source=catalog)并返回真实数据 —— Y-Chain $6.73/237、Necklace $6.85/186、 Necklace Star $5.70/110,与 RPC 直查一致(首次调试时的编价问题见第 9 节)。

浏览器(Playwright 真 Chrome,1440×900):

  • LibreDesk 登录成功 → /inboxes/assigned(截图 docs/screenshots/wangliang-libredesk-inbox.png)
  • Twenty 登录成功 → /install-apps 引导页(截图 docs/screenshots/wangliang-twenty-onboarding.png)
  • 回归:wangliang-www 仍 200,wangliang-agent 仍 302(宿主 nginx 未被改坏)

资源占用:约 1.9 GB 内存(Twenty worker 约 1.0 GB + server 0.6 GB + PG/Redis/bridge/LibreDesk 约 0.3 GB)。

9. 上线当天修的一个 AI 质量问题(编造价格)

首次用 e2e-widget.js 问“3 条不锈钢项链的价格和库存”时,助手返回了三款不存在的商品和虚高价格。 排查(临时把 LibreDesk log_level 调到 debug)定位到:

  1. 模型把用户整句(含 "under 10 dollars" 等)直接当成 search_products 的 query;
  2. 这类长查询偶发 >15s(bridge 的 PRODUCT_TIMEOUT_MS 默认 15s),超时后走降级:只返回名字、price=null;
  3. 模型面对 price=null 时自行编了价格(LibreDesk 的 guardrails 只是提示词,不是硬拦截)。

修复(已落地并验证):

  • docker-compose.yml 给 bridge 透传 PRODUCT_TIMEOUT_MS,.env 设为 30000;
  • 助理的 instructions 追加严格规则(scripts/setup-defaults.js 同步):短关键词查询、工具输出是唯一事实来源、 price=null/stock=null 时必须说“暂不可用”并建议转人工,禁止估算/换算币种。
  • 复测:同一条问题,工具调用 612ms 返回真实商品,回复价格/库存与 RPC 完全一致。

已改为 bridge agent 回复,输出护栏、Citation、飞轮、质量指标都生效;LibreDesk 内置 AI 仅保留作回滚。

验证完已把 log_level 恢复为 info。

10. 数据面初始化(5.1 KB / 5.2 sitemap / 5.3 质量表)

5.3 质量表 ✅

node scripts/quality-report.js 1:自动建表成功。

table_exists = bridge_ai_metrics;切流后已有数据
{"summary":{"runs":0,"errors":0,...},"tools":[],"hours":1}

✅ 切流后 bridge_ai_metrics 由 bridge agent 写入;当前已有 Live chat AI 运行记录。

5.2 全量 sitemap ✅

官网 sitemap.xml 受阿里云 WAF 的 JS 挑战保护(直接 fetch 返回 4KB 挑战 HTML、0 个 <loc>)。 处理:用真 Chrome(Playwright)过挑战后抓取,缓存到 var/sitemap-{www,us}.xml(已 gitignore), 并给 scripts/build-site-map.js 增加本地 XML 回退(var/sitemap-<site>.xml 或 SITEMAP_XML_<SITE>)。

[site-map] www: total=32710, products=32070, nav=640
[site-map] us:  total=22644, products=22324, nav=320

表 bridge_site_map_urls 已核对:

 site | total  | products
 us   | 22644  | 22324
 www  | 32710  | 32070

刷新方式(sitemap 会变):先用浏览器重新抓 var/sitemap-{www,us}.xml,再 node scripts/build-site-map.js www us。 直接跑脚本在 WAF 下会明确报错「返回 0 个 」,不会静默写空表。

5.1 旧 KB(products.kb_docs)✅ 已用最新代码解决

拉取 origin/main 之前,本机确实没有旧 products.kb_docs:upgrade-kb-schema.js 报 password authentication failed for user "postgres",rebuild-kb-index.sh 报 No such container: crm-postgres (这两个脚本是旧栈专用,新版仓库已不依赖它们)。

最新代码(f526696 / 81b49c9)提供了「无旧 bridge」的标准路径,已按 docs/go-live.md 6.3 执行:

  1. 导入仓库内置快照:bash scripts/import-kb-snapshot.sh → 导入到 crmnew-postgres/bridge,实测 367 行(296 parent / 71 child),与文档预期一致; 后续第二批上游把快照升级到 594 chunks(523 parent / 71 child),用 FORCE=1 bash scripts/import-kb-snapshot.sh 重导;
  2. .env 增加 RAG_PG_URL=postgres://crmnew:***@postgres:5432/bridge,docker compose up -d --build --force-recreate bridge;
  3. 修了一个上游接线 bug:server.js 调 createTools(cfg) 时没传 retriever/verification/client, 导致 HTTP 工具入口的 search_kb / send_verification_code / handoff_to_human 永远返回 not_configured。 现已按 agent.js 的方式创建并传入(本地提交 fix(bridge): HTTP 工具入口注入 retriever/verification/client)。
  4. 验证:
    • search_kb "minimum order value" → 命中 What is the minimum order value?(score 0.733,source_urls 含 order.html);
    • AI 端到端 → 回答 The minimum order value is €30,内容与 kb_docs parent 记录一致;
  5. 新索引器实测:RAG_PG_URL=postgres://...@127.0.0.1:5440/bridge node scripts/sync-kb-online.js --local-only --force → parents=296 children=71,自动备份 kb_docs_v20261023033455,search_kb 仍正常。

维护方式:

# 从 bridge/kb/*.md 重建(宿主机执行,RAG 用 127.0.0.1:5440)
RAG_PG_URL=postgres://crmnew:<password>@127.0.0.1:5440/bridge \
  node scripts/sync-kb-online.js --local-only --force

# 在线同步官网 FAQ(建议每周一次;官网有 WAF,失败就退回 --local-only)
RAG_PG_URL=postgres://crmnew:<password>@127.0.0.1:5440/bridge \
  node scripts/sync-kb-online.js

11. 最新代码(origin/main merge a1d1de0 + 589e5f9)怎么运行

最新 main 新增了 KB 快照/在线同步、环境差异文档,并去掉了 5.1 对旧栈的依赖。本机已合并并落地。

日常更新

cd /data/dev/wangliang/crm-ai
git pull --ff-only                 # 或 merge origin/main
docker compose config -q
docker compose up -d
docker compose up -d --build --force-recreate bridge   # bridge 代码变更时必须
docker compose ps

bootstrap.sh 只用于全新环境;已有环境重跑会覆盖 .env/配置,不要跑。

数据准备(对应 docs/go-live.md 6.3)

任务 命令 本机结果
KB 快照导入 FORCE=1 bash scripts/import-kb-snapshot.sh ✅ 594 行(523/71)
KB 本地重建 RAG_PG_URL=...127.0.0.1:5440/bridge node scripts/sync-kb-online.js --local-only --force ✅ 296/71 + 备份
KB 在线同步 RAG_PG_URL=... node scripts/sync-kb-online.js 可选(官网 WAF,失败用 --local-only)
全量 sitemap node scripts/build-site-map.js www us ✅ 55,354 行(见 5.2)
质量报表 node scripts/quality-report.js 1 ✅ 表已建(0 行,原因见 5.3)

环境差异(docs/environments.md)

最新文档明确:rpc-tunnel.sh / public-https.sh 只用于本地联调;测试/生产直接用内网 RPC 地址 + 正式域名 HTTPS。 本机现状正好符合:

  • RPC 走 spug-net 直连 wangliang-dev:950x,没有运行 rpc-tunnel.sh;
  • HTTPS 由宿主 nginx + *.dev.gaia888.com 证书提供,没有用 Cloudflare Quick Tunnel。

第二批(merge 589e5f9):质量/观测/KB 版本

新增:Prometheus /metrics、RAG reranker、citation checker、三级升级、A/B 实验、agent_trace_steps(TRACE_ENABLED)、 flywheel_samples、chat-log,以及 kb-audit.js / kb-version.js / eval-retrieval.js / build-site-kb.js。 KB 快照同步升级到 594 chunks。

# 重导新版 KB 快照
FORCE=1 bash scripts/import-kb-snapshot.sh

# 观测与质量
curl -s http://127.0.0.1:4100/metrics | head
PW=$(grep '^POSTGRES_PASSWORD=' .env | cut -d= -f2)
RAG_PG_URL="postgres://crmnew:${PW}@127.0.0.1:5440/bridge" node scripts/kb-audit.js
RAG_PG_URL="postgres://crmnew:${PW}@127.0.0.1:5440/bridge" node scripts/eval-retrieval.js
RAG_PG_URL="postgres://crmnew:${PW}@127.0.0.1:5440/bridge" node scripts/kb-version.js list

本机实测:

  • kb-audit:594 行(523/71)、empty_answers=0、missing_tsv=0;
  • eval-retrieval:9/9 PASS;
  • kb-version list:能看到 kb_docs_v20261023033455 备份与 kb_sync_runs 记录;
  • AI 端到端:退款/VAT 问题均按 KB 内容回答(如 VAT refund 给出 helpdesk@yehwang.com 与 proof of export 流程)。

⚠️ 两点范围说明:

  1. /metrics 的 bridge_ai_* 计数只在 bridge agent 路径(webhook 编排)里累加;LibreDesk 内置 AI 不走这条路径, 所以当前 /metrics 主要只有 uptime/RSS,bridge_ai_metrics 也仍是 0 行。
  2. build-site-kb.js 直接 fetch 官网 sitemap,和 build-site-map.js 一样会被 WAF 挑战页挡住; 如需从本地 sitemap 重新生成 bridge/kb/11-site-map.md,要先给脚本加本地 XML 回退(build-site-map.js 已有同款逻辑)。

本地在这一批上补了两个小修复(未推送):

  • search_kb 在没有 reranker 时会忽略 limit(上游改为先取 8 条给 reranker)→ 现在无 reranker 时尊重 limit;
  • parent/child 可能命中同一篇 → 按 question+answer 去重后再重排;
  • 同时把 createLLM + createReranker 接进 HTTP 工具入口,让 LibreDesk 直接调 search_kb 也能享受重排。

11. 2026-10-23 后续配置

Live chat / bridge AI

  • bridge webhook Bridge AI:message.created → http://bridge:4100/libredesk/webhook(启用)
  • LibreDesk 规则 1(AI Assistant):关闭
  • bridge API key:已生成并写入 .env
  • bridge AI e2e:最低起订量问题回复正常,bridge_ai_metrics 已有数据
  • IDENTITY_CORS_ORIGIN:已从 * 改为 https://wangliang-crm.dev.gaia888.com

人工团队

  • Wholesale Support(id=1,Round robin)
  • 成员:
  • 两个 agent 的临时密码写在测试服 .agent-credentials(600,已在 .gitignore 忽略)
  • 登录验证:两个 agent 均 200

Email

  • SMTP 已使用本地 163 凭据配置
  • Email Inbox Email 163(id=3,IMAP 走 imap-proxy:1993)
  • Automation #3:email → assign_team=Wholesale Support
  • ⚠️ 本机测试服和本地共用同一个 163 邮箱,两个 LibreDesk 会同时轮询,可能重复/抢占邮件;长期测试建议用独立测试邮箱

WhatsApp

  • WA_INBOX_ID=2
  • PUBLIC_BASE_URL=https://wangliang-crm.dev.gaia888.com
  • 规则 #2:WhatsApp → assign_team=Wholesale Support
  • Meta callback 已改到测试域名:https://wangliang-crm.dev.gaia888.com/webhooks/whatsapp/2,GET 验证 challenge 200。
    • 因为一个 Meta App 只能有一个 callback,本机 WhatsApp 已暂停接收;
    • 如需本地和测试同时接收,需要独立 Meta App / 测试号。

坐席邮件通知

  • agent1/agent2 的邮件通知已关闭(assignment/new_reply/SLA 等 email 渠道全部 false),仅保留站内通知。
  • 原因:agent1@yehwang.com / agent2@yehwang.com 在 yehwang.com 邮件服务器上被 550 拒绝,导致“New conversation assigned to you”退信。
  • OTP 邮件不受影响,bridge OTP 已实测可发送并送达 163 收件箱。

11. 2026-10-26 LibreDesk 升级记录(rc.10 → v2.9.0)

  • 目标:libredesk/libredesk:v2.9.0(2026-10-22 正式版);本环境用 libredesk/libredesk:v2.9.0-arm64(digest 与多架构 v2.9.0 的 arm64 子镜像一致)。
  • 升级前:pg_dump -Fc libredesk → backups/libredesk-before-v2.9.0-2026-10-26.dump(已验证可 pg_restore -l);记录旧镜像 image ID sha256:03df145b…(rc.10-arm64)。
  • 变更范围:rc.10 → v2.9.0 无 DB migration、无 config 变更(仅 UI / 邮件模板 / widget / campaign 修复),数据库 schema 保持 57 张表。
  • 操作:改 .env 的 LIBREDESK_IMAGE → docker compose up -d --force-recreate libredesk(启动链自动 --install --upgrade,预期打印 no upgrades to run)。
  • 回滚:切回 libredesk/libredesk:v2.9.0-rc.10-arm64 并重建;无 schema 变更,不需要恢复数据库。