fix(bridge): HTTP 工具入口注入 retriever/verification/client(search_kb 等不再 not_configured)+ 记录最新代码运行方式

This commit is contained in:
wangliang
2026-10-23 11:35:28 +08:00
parent a1d1de03c3
commit d1bd344b96
2 changed files with 75 additions and 23 deletions
+14 -3
View File
@@ -7,6 +7,9 @@ const { createTools, resolveSiteProfile } = require('./tools');
const { createIdentity } = require('./identity'); const { createIdentity } = require('./identity');
const { createLibreDeskWebhook } = require('./libredesk-webhook'); const { createLibreDeskWebhook } = require('./libredesk-webhook');
const { createAgent } = require('./agent'); const { createAgent } = require('./agent');
const { createVerification } = require('./verification');
const { createLibreDeskClient } = require('./libredesk');
const { createRetriever } = require('./rag');
const { ToolError } = require('./errors'); const { ToolError } = require('./errors');
const MAX_BODY_BYTES = 256 * 1024; const MAX_BODY_BYTES = 256 * 1024;
@@ -82,9 +85,14 @@ function contextFromHeaders(headers = {}) {
function createServer(options = {}) { function createServer(options = {}) {
const cfg = options.config || loadConfig(); const cfg = options.config || loadConfig();
const auther = createAuther(cfg.toolsSharedKey); const auther = createAuther(cfg.toolsSharedKey);
const tools = options.tools || createTools(cfg); // HTTP 工具入口(LibreDesk 直接调用)也必须带上 retriever/verification/client,
// 否则 search_kb / send_verification_code / handoff_to_human 会一直是 not_configured。
const verification = options.verification || createVerification(cfg);
const client = options.client || createLibreDeskClient(cfg);
const retriever = options.retriever || (cfg.ragPgUrl ? createRetriever(cfg) : null);
const tools = options.tools || createTools(cfg, { verification, retriever, client });
const identity = options.identity || createIdentity(cfg); const identity = options.identity || createIdentity(cfg);
const agent = options.agent || createAgent(cfg); const agent = options.agent || createAgent(cfg, { verification, client, retriever });
const libredeskWebhook = options.libredeskWebhook || createLibreDeskWebhook(cfg, { agent }); const libredeskWebhook = options.libredeskWebhook || createLibreDeskWebhook(cfg, { agent });
const byName = new Map(tools.map((t) => [t.name, t])); const byName = new Map(tools.map((t) => [t.name, t]));
const startedAt = Date.now(); const startedAt = Date.now();
@@ -252,7 +260,10 @@ if (require.main === module) {
if (!cfg.toolsSharedKey) console.warn('[crm-tools] TOOLS_SHARED_KEY is not set — all /tools/* calls will return 503'); if (!cfg.toolsSharedKey) console.warn('[crm-tools] TOOLS_SHARED_KEY is not set — all /tools/* calls will return 503');
if (cfg.devBypassVerification) console.warn('[crm-tools] TOOLS_DEV_BYPASS=true — verification checks are BYPASSED (local development only)'); if (cfg.devBypassVerification) console.warn('[crm-tools] TOOLS_DEV_BYPASS=true — verification checks are BYPASSED (local development only)');
}); });
const shutdown = () => server.close(() => process.exit(0)); const shutdown = () => server.close(() => {
if (retriever && retriever.pool) retriever.pool.end().catch(() => {});
process.exit(0);
});
process.on('SIGINT', shutdown); process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown); process.on('SIGTERM', shutdown);
} }
+61 -20
View File
@@ -66,6 +66,8 @@ RPC 走 **容器网络直连**:compose 把 `bridge` 同时接入外部网络 `
- **AI Provider 已配置**:DashScope `qwen-plus-latest`(completion)+ `text-embedding-v4` 1024 维(embedding), - **AI Provider 已配置**:DashScope `qwen-plus-latest`(completion)+ `text-embedding-v4` 1024 维(embedding),
`configure-ai.js` 的两项连通性测试均通过。密钥只存在 LibreDesk 库内与 `.env`(600),未写入文档。 `configure-ai.js` 的两项连通性测试均通过。密钥只存在 LibreDesk 库内与 `.env`(600),未写入文档。
- 助理指令已加固(见第 9 节):`search_products` 必须用 2-5 个词的关键词、不得编造价格/商品,`price=null` 时明确说“暂不可用”。 - 助理指令已加固(见第 9 节):`search_products` 必须用 2-5 个词的关键词、不得编造价格/商品,`price=null` 时明确说“暂不可用”。
- **知识库已接入**:`bridge` 库 `kb_docs` 367 行(296 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`)。 - `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 / - 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 / search_kb / search_email_history / collect_info / send_verification_code / verify_email_code / handoff_to_human /
@@ -151,7 +153,7 @@ docker run --rm -v /etc/nginx/conf.d:/c redis:7-alpine rm -f /c/zz-wangliang-crm
|---|---|---| |---|---|---|
| ~~AI Provider(DashScope)~~ | ✅ **已配置**(qwen-plus-latest / text-embedding-v4),端到端已验证 | 换 key:`DASHSCOPE_API_KEY=sk-xxx node scripts/configure-ai.js` | | ~~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` | | **SMTP / 163 邮件** | `SMTP_*` 为空,OTP 与邮件渠道不可用 | 填 `.env` 后 `node scripts/configure-email.js` + `node scripts/add-email-inbox.js` |
| **知识库 RAG(search_kb)** | 无 `RAG_PG_URL`,工具返回 503;旧 KB 数据源本机不存在(见第 10 节) | 提供旧 `products.kb_docs` dump 或 `customer-service-kb/*.md` 后导入 | | ~~知识库 RAG(search_kb)~~ | ✅ **已配置**:快照导入 `bridge` 库(367 行),`RAG_PG_URL` 已指向,`search_kb` 可用 | 更新:`bash scripts/import-kb-snapshot.sh` 或 `node scripts/sync-kb-online.js --local-only --force` |
| **WhatsApp 渠道** | 无 Meta 凭据 | 填 `WA_*` 后 `scripts/add-whatsapp-inbox.js`;需要公网回调(本栈已有 HTTPS,可不用 cloudflared) | | **WhatsApp 渠道** | 无 Meta 凭据 | 填 `WA_*` 后 `scripts/add-whatsapp-inbox.js`;需要公网回调(本栈已有 HTTPS,可不用 cloudflared) |
| **public-gateway / cloudflared** | 未启动 | 需要 Cloudflare 临时隧道时再 `docker compose up -d public-gateway cloudflared` | | **public-gateway / cloudflared** | 未启动 | 需要 Cloudflare 临时隧道时再 `docker compose up -d public-gateway cloudflared` |
@@ -236,29 +238,68 @@ table_exists = bridge_ai_metrics;当前 rows=0
刷新方式(sitemap 会变):先用浏览器重新抓 `var/sitemap-{www,us}.xml`,再 `node scripts/build-site-map.js www us`。 刷新方式(sitemap 会变):先用浏览器重新抓 `var/sitemap-{www,us}.xml`,再 `node scripts/build-site-map.js www us`。
直接跑脚本在 WAF 下会明确报错「返回 0 个 <loc>」,不会静默写空表。 直接跑脚本在 WAF 下会明确报错「返回 0 个 <loc>」,不会静默写空表。
### 5.1 旧 KB(products.kb_docs)❌ 本机无数据源 ### 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`
(这两个脚本是旧栈专用,新版仓库已不依赖它们)。
```text 最新代码(`f526696` / `81b49c9`)提供了「无旧 bridge」的标准路径,已按 `docs/go-live.md` 6.3 执行:
$ node scripts/upgrade-kb-schema.js
[upgrade-kb-schema] password authentication failed for user "postgres" 1. 导入仓库内置快照:`bash scripts/import-kb-snapshot.sh`
$ bash scripts/rebuild-kb-index.sh → 导入到 `crmnew-postgres/bridge`,实测 **367 行(296 parent / 71 child)**,与文档预期一致;
Error response from daemon: No such container: crm-postgres 2. `.env` 增加 `RAG_PG_URL=postgres://crmnew:***@postgres:5432/bridge`,`docker compose up -d --build --force-recreate bridge`;
$ ls /home/dev/code/crm/crm-ai-demo/customer-service-kb 3. 修了一个上游接线 bug:`server.js` 调 `createTools(cfg)` 时没传 `retriever/verification/client`,
No such file or directory 导致 HTTP 工具入口的 `search_kb` / `send_verification_code` / `handoff_to_human` 永远返回 `not_configured`。
$ docker ps -a | grep -E '^crm-(postgres|bridge)$' 现已按 `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` 仍正常。
维护方式:
```bash
# 从 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
``` ```
原因:这两个脚本是为**旧栈**写的 —— `products.kb_docs` 在旧 `crm-postgres` 里, ## 11. 最新代码(origin/main merge `a1d1de0`)怎么运行
`rebuild-kb-index.sh` 还会 `docker exec crm-bridge` 调旧 bridge 的 `faq/kbIndexer`(新仓库已没有这段代码)。
本工作区是全新栈:宿主 PG 5432 只有 `postgres/storefront` 两个库,没有 `products`;也没有旧容器的数据卷。
要补齐需要**数据源**,二选一: 最新 `main` 新增了 KB 快照/在线同步、环境差异文档,并去掉了 5.1 对旧栈的依赖。本机已合并并落地。
1. 旧测试服导出 `products.kb_docs`:`pg_dump -t kb_docs products > kb_docs.sql`; ### 日常更新
2. 或直接给 `customer-service-kb/*.md`(9 个文件,108 篇文章的来源)。
拿到后我按新栈方式接入(推荐导入 LibreDesk 帮助中心,AI 原生 RAG + 引用;旧的 `search_kb` 可保留为兜底)。 ```bash
在此之前,商品/库存/订单/商机等 9 个工具不受影响,只有 `search_kb` 返回 503。 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 快照导入 | `bash scripts/import-kb-snapshot.sh` | ✅ 367 行(296/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。