feat(kb): VikingDB纯检索+重排+Redis上下文+全库搜索+别名扩展+KB保护窗口+RAG语气引导

- 新增 kbRetriever.js: VikingDB search_knowledge 纯检索替代 Ark chat/completions, doubao-seed-rerank 重排, RAG payload 语气引导缓解音色差异

- 新增 redisClient.js: Redis 连接管理 + 5轮对话历史 + KB缓存双写

- toolExecutor.js: 产品别名扩展25条, 全库检索topK=25, 检索阈值0.01, 精简 buildDeterministicKnowledgeQuery

- nativeVoiceGateway.js: isPureChitchat扩展, KB保护窗口60s, prequery参数调优

- realtimeDialogRouting.js: resolveReply感知KB保护窗口, fast-path适配raw模式

- app.js: 健康检查新增 redis/reranker/kbRetrievalMode

- 新增测试: alias A/B测试, KB retriever测试, Redis客户端测试, raw模式集成测试
This commit is contained in:
User
2026-03-26 14:30:32 +08:00
parent 9567eb7358
commit 56940676f6
15 changed files with 2096 additions and 170 deletions

View File

@@ -0,0 +1,43 @@
/**
* A/B 测试:原始查询 vs 别名扩展后查询的检索效果对比
* 直接调用 VikingDB + reranker比较 topScore 和 hit 状态
*/
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
const kbRetriever = require('../services/kbRetriever');
const TEST_QUERIES = [
// 中文俗名 → 中文全名(语义接近)
{ raw: '牙膏怎么用', alias: '草本护理牙膏 Med Dental+怎么用', label: '牙膏(俗名→全名)' },
{ raw: '喷雾功效', alias: 'IB5 口腔免疫喷雾功效', label: '喷雾(俗名→全名)' },
{ raw: '乳酪怎么喝', alias: '乳酪煲 乳酪饮品怎么喝', label: '乳酪(俗名→全名)' },
// 中文昵称 → 英文产品名(语义无关联)
{ raw: '小红怎么吃', alias: '小红产品 Activize Oxyplus怎么吃', label: '小红(昵称→英文名)' },
{ raw: '大白功效', alias: '大白产品 Basics功效', label: '大白(昵称→英文名)' },
{ raw: '小绿排毒', alias: 'D-Drink 小绿 排毒饮排毒', label: '小绿(昵称→英文名)' },
{ raw: '小黑适合谁', alias: 'MEN+ 倍力健 小黑适合谁', label: '小黑(昵称→英文名)' },
// 通用词 → 特定产品
{ raw: '氨基酸', alias: 'ProShape Amino 氨基酸', label: '氨基酸(通用→产品)' },
{ raw: '胶原蛋白', alias: '胶原蛋白肽', label: '胶原蛋白(通用→产品)' },
{ raw: '细胞抗氧素功效', alias: 'Zellschutz 细胞抗氧素功效', label: '细胞抗氧素(中→英)' },
];
async function runTest() {
console.log('=== A/B 测试:原始查询 vs 别名扩展 ===\n');
console.log('| 场景 | 原始 topScore | 扩展 topScore | 差值 | 原始hit | 扩展hit |');
console.log('|------|-------------|-------------|------|---------|---------|');
for (const t of TEST_QUERIES) {
const rawRes = await kbRetriever.searchAndRerank(t.raw, {});
await new Promise(r => setTimeout(r, 1500));
const aliasRes = await kbRetriever.searchAndRerank(t.alias, {});
await new Promise(r => setTimeout(r, 1500));
const rawScore = rawRes.topScore?.toFixed(3) || '0.000';
const aliasScore = aliasRes.topScore?.toFixed(3) || '0.000';
const diff = ((aliasRes.topScore || 0) - (rawRes.topScore || 0)).toFixed(3);
const diffStr = diff > 0 ? `+${diff}` : diff;
console.log(`| ${t.label} | ${rawScore} | ${aliasScore} | ${diffStr} | ${rawRes.hit ? 'HIT' : 'MISS'} | ${aliasRes.hit ? 'HIT' : 'MISS'} |`);
}
console.log('\n阈值: reranker hit >= 0.1');
}
runTest().catch(e => { console.error('FAILED:', e.message); process.exit(1); });