fix(bridge): KB 检索尊重 limit/去重并接入 reranker;记录 quality/KB 批次运行结果
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# 密钥与运行时(不入库)
|
# 密钥与运行时(不入库)
|
||||||
.env
|
.env
|
||||||
|
.env.bak-*
|
||||||
libredesk/config/config.toml
|
libredesk/config/config.toml
|
||||||
libredesk/uploads/*
|
libredesk/uploads/*
|
||||||
!libredesk/uploads/.gitkeep
|
!libredesk/uploads/.gitkeep
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const { createAgent } = require('./agent');
|
|||||||
const { createVerification } = require('./verification');
|
const { createVerification } = require('./verification');
|
||||||
const { createLibreDeskClient } = require('./libredesk');
|
const { createLibreDeskClient } = require('./libredesk');
|
||||||
const { createRetriever } = require('./rag');
|
const { createRetriever } = require('./rag');
|
||||||
|
const { createLLM } = require('./llm');
|
||||||
|
const { createReranker } = require('./reranker');
|
||||||
const prom = require('./prom');
|
const prom = require('./prom');
|
||||||
const { ToolError } = require('./errors');
|
const { ToolError } = require('./errors');
|
||||||
|
|
||||||
@@ -91,9 +93,11 @@ function createServer(options = {}) {
|
|||||||
const verification = options.verification || createVerification(cfg);
|
const verification = options.verification || createVerification(cfg);
|
||||||
const client = options.client || createLibreDeskClient(cfg);
|
const client = options.client || createLibreDeskClient(cfg);
|
||||||
const retriever = options.retriever || (cfg.ragPgUrl ? createRetriever(cfg) : null);
|
const retriever = options.retriever || (cfg.ragPgUrl ? createRetriever(cfg) : null);
|
||||||
const tools = options.tools || createTools(cfg, { verification, retriever, client });
|
const llm = options.llm || createLLM(cfg);
|
||||||
|
const reranker = options.reranker || createReranker(llm);
|
||||||
|
const tools = options.tools || createTools(cfg, { verification, retriever, client, reranker });
|
||||||
const identity = options.identity || createIdentity(cfg);
|
const identity = options.identity || createIdentity(cfg);
|
||||||
const agent = options.agent || createAgent(cfg, { verification, client, retriever });
|
const agent = options.agent || createAgent(cfg, { verification, client, retriever, reranker, llm });
|
||||||
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();
|
||||||
|
|||||||
+11
-1
@@ -377,7 +377,17 @@ function createTools(cfg, deps = {}) {
|
|||||||
const query = String(args.query || '').trim();
|
const query = String(args.query || '').trim();
|
||||||
if (!query) throw new ToolError(400, 'invalid_arguments', 'query is required');
|
if (!query) throw new ToolError(400, 'invalid_arguments', 'query is required');
|
||||||
const limit = Math.min(Number(args.limit) || 5, 8);
|
const limit = Math.min(Number(args.limit) || 5, 8);
|
||||||
let results = await retriever.searchKB(query, Math.max(limit, 8));
|
// 有 reranker 时多取一些候选再重排;没有 reranker 时必须尊重调用方 limit。
|
||||||
|
const fetchLimit = reranker ? Math.max(limit, 8) : limit;
|
||||||
|
let results = await retriever.searchKB(query, fetchLimit);
|
||||||
|
// parent/child 可能命中同一篇文章:按 question+answer 去重后再重排
|
||||||
|
const seen = new Set();
|
||||||
|
results = results.filter((r) => {
|
||||||
|
const key = `${r.question || ''}\u0000${r.answer || ''}`;
|
||||||
|
if (seen.has(key)) return false;
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
if (reranker) results = await reranker.rerank(query, results, limit);
|
if (reranker) results = await reranker.rerank(query, results, limit);
|
||||||
return {
|
return {
|
||||||
results,
|
results,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ 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`),
|
- **知识库已接入**:`bridge` 库 `kb_docs` 594 行(523 parent / 71 child,来自 `deploy/seeds/kb_docs.sql.gz`),
|
||||||
`RAG_PG_URL` 已配置,`search_kb` 实测命中最低起订量/退换货等 FAQ,AI 能据此回答。
|
`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 /
|
||||||
@@ -153,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)~~ | ✅ **已配置**:快照导入 `bridge` 库(367 行),`RAG_PG_URL` 已指向,`search_kb` 可用 | 更新:`bash scripts/import-kb-snapshot.sh` 或 `node scripts/sync-kb-online.js --local-only --force` |
|
| ~~知识库 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 渠道** | 无 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` |
|
||||||
|
|
||||||
@@ -248,6 +248,7 @@ table_exists = bridge_ai_metrics;当前 rows=0
|
|||||||
|
|
||||||
1. 导入仓库内置快照:`bash scripts/import-kb-snapshot.sh`
|
1. 导入仓库内置快照:`bash scripts/import-kb-snapshot.sh`
|
||||||
→ 导入到 `crmnew-postgres/bridge`,实测 **367 行(296 parent / 71 child)**,与文档预期一致;
|
→ 导入到 `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`;
|
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`,
|
3. 修了一个上游接线 bug:`server.js` 调 `createTools(cfg)` 时没传 `retriever/verification/client`,
|
||||||
导致 HTTP 工具入口的 `search_kb` / `send_verification_code` / `handoff_to_human` 永远返回 `not_configured`。
|
导致 HTTP 工具入口的 `search_kb` / `send_verification_code` / `handoff_to_human` 永远返回 `not_configured`。
|
||||||
@@ -270,7 +271,7 @@ RAG_PG_URL=postgres://crmnew:<password>@127.0.0.1:5440/bridge \
|
|||||||
node scripts/sync-kb-online.js
|
node scripts/sync-kb-online.js
|
||||||
```
|
```
|
||||||
|
|
||||||
## 11. 最新代码(origin/main merge `a1d1de0`)怎么运行
|
## 11. 最新代码(origin/main merge `a1d1de0` + `589e5f9`)怎么运行
|
||||||
|
|
||||||
最新 `main` 新增了 KB 快照/在线同步、环境差异文档,并去掉了 5.1 对旧栈的依赖。本机已合并并落地。
|
最新 `main` 新增了 KB 快照/在线同步、环境差异文档,并去掉了 5.1 对旧栈的依赖。本机已合并并落地。
|
||||||
|
|
||||||
@@ -291,7 +292,7 @@ docker compose ps
|
|||||||
|
|
||||||
| 任务 | 命令 | 本机结果 |
|
| 任务 | 命令 | 本机结果 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| KB 快照导入 | `bash scripts/import-kb-snapshot.sh` | ✅ 367 行(296/71) |
|
| 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=...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`) |
|
| 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) |
|
| 全量 sitemap | `node scripts/build-site-map.js www us` | ✅ 55,354 行(见 5.2) |
|
||||||
@@ -303,3 +304,41 @@ docker compose ps
|
|||||||
本机现状正好符合:
|
本机现状正好符合:
|
||||||
- RPC 走 `spug-net` 直连 `wangliang-dev:950x`,**没有**运行 `rpc-tunnel.sh`;
|
- RPC 走 `spug-net` 直连 `wangliang-dev:950x`,**没有**运行 `rpc-tunnel.sh`;
|
||||||
- HTTPS 由宿主 nginx + `*.dev.gaia888.com` 证书提供,**没有**用 Cloudflare Quick Tunnel。
|
- 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。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 重导新版 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` 也能享受重排。
|
||||||
|
|||||||
Reference in New Issue
Block a user