Files
bigwo/test2/server/tests/test_kb_scenarios.js
User 9567eb7358 feat(server): KB prompt优化、字幕修复、S2S重连、助手配置API
- assistantProfileConfig: KB answer prompt改为分层策略(严格产品信息+灵活常识补充)
- nativeVoiceGateway: S2S upstream自动重连(最多50次)、event 351字幕debounce(800ms取最长文本)
- toolExecutor: 确定性query改写增强、KB查询传递session上下文
- contextKeywordTracker: 支持KB话题记忆优先enrichment
- contentSafeGuard: 新增品牌安全内容过滤服务
- assistantProfileService: 新增助手配置CRUD服务
- routes/assistantProfile: 新增助手配置API路由
- knowledgeKeywords: 扩展KB关键词词典
- fastAsrCorrector: ASR纠错规则更新
- tests/: KB prompt测试、保护窗口测试、Viking性能测试
- docs/: 助手配置API文档、系统提示词目录
2026-03-24 17:19:36 +08:00

594 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 基于知识库实际内容的功能性测试
* 覆盖单KB查询、多KB查询话题切换、追问+质疑混合、确定性改写、热答案匹配
*
* 运行方式: node --test tests/test_kb_scenarios.js
*/
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const { shouldForceKnowledgeRoute } = require('../services/realtimeDialogRouting');
const { hasKnowledgeRouteKeyword } = require('../services/knowledgeKeywords');
const ToolExecutor = require('../services/toolExecutor');
// ================================================================
// 辅助函数
// ================================================================
function assertKbRoute(text, ctx, msg) {
assert.equal(shouldForceKnowledgeRoute(text, ctx), true, msg || `"${text}" should route to KB`);
}
function assertNotKbRoute(text, ctx, msg) {
assert.equal(shouldForceKnowledgeRoute(text, ctx), false, msg || `"${text}" should NOT route to KB`);
}
function buildCtx(pairs) {
return pairs.map(([role, content]) => ({ role, content }));
}
// ================================================================
// 1. 单知识库查询 —— 每个产品/话题独立首轮查询
// ================================================================
describe('单KB查询 —— 产品类', () => {
const productQueries = [
['基础三合一怎么吃', '基础三合一'],
['大白产品有什么功效', '大白Basics'],
['小红Activize的作用是什么', '小红Activize'],
['小白Restorate怎么服用', '小白Restorate'],
['儿童倍适适合几岁的孩子', '儿童倍适'],
['CC套装怎么用', 'CC套装'],
['Q10辅酵素有什么功效', 'Q10辅酵素'],
['IB5口腔喷雾怎么用', 'IB5口腔喷雾'],
['D-Drink小绿排毒饮怎么用', 'D-Drink'],
['Hair+发宝怎么用', 'Hair+发宝'],
['运动饮料Fitness-Drink是什么', 'Fitness-Drink'],
['TopShape纤萃减肥产品', 'TopShape'],
['Generation 50+乐活产品', 'Generation 50+'],
['Apple Antioxy细胞抗氧素功效', 'Apple Antioxy'],
['ProShape氨基酸BCAA是什么', 'ProShape'],
['Herbal Tea草本茶功效', 'Herbal Tea'],
['Med Dental+草本护理牙膏', 'Med Dental+'],
['Men Face男士护肤乳霜', 'Men Face'],
['叶黄素产品怎么吃', '叶黄素'],
['关节套装关节舒缓怎么用', '关节套装'],
['乳清蛋白粉适合谁', '乳清蛋白'],
['乐活奶昔怎么喝', '乐活奶昔'],
];
for (const [query, label] of productQueries) {
it(`${label}: "${query}" → 应走KB路由`, () => {
assertKbRoute(query);
});
}
});
describe('单KB查询 —— 系统/平台类', () => {
const systemQueries = [
['一成系统是什么', '一成系统'],
['三大平台介绍一下', '三大平台'],
['四大AI生态是什么', '四大AI生态'],
['行动圈怎么用', '行动圈'],
['盟主社区是什么', '盟主社区'],
['AI众享是什么', 'AI众享'],
['数字化工作室怎么用', '数字化工作室'],
['盛咖学愿培训平台', '盛咖学愿'],
];
for (const [query, label] of systemQueries) {
it(`${label}: "${query}" → 应走KB路由`, () => {
assertKbRoute(query);
});
}
});
describe('单KB查询 —— 科学原理类', () => {
const scienceQueries = [
['NTC营养保送系统是什么原理', 'NTC'],
['火炉原理是什么意思', '火炉原理'],
['阿育吠陀是什么', '阿育吠陀'],
];
for (const [query, label] of scienceQueries) {
it(`${label}: "${query}" → 应走KB路由`, () => {
assertKbRoute(query);
});
}
});
describe('单KB查询 —— 公司/认证类', () => {
const companyQueries = [
['德国PM公司介绍', '德国PM'],
['PM公司地址和电话', '地址电话'],
['邓白氏认证是什么', '邓白氏'],
['DSN全球100强', 'DSN'],
['ELAB科隆名单认证', 'ELAB'],
['PM是不是传销', '合法性'],
['Rolf Sorg是谁', '创始人'],
['宣明会慈善合作', '宣明会'],
['培安烟台工厂', '培安'],
];
for (const [query, label] of companyQueries) {
it(`${label}: "${query}" → 应走KB路由`, () => {
assertKbRoute(query);
});
}
});
describe('单KB查询 —— FAQ常见问题类', () => {
const faqQueries = [
['PM产品多久见效', '见效时间'],
['好转反应是什么', '好转反应'],
['为什么要全套搭配使用', '全套搭配'],
['和其他保健品有什么区别', '保健品区别'],
['孕妇能吃PM产品吗', '特殊人群'],
['产品多少钱', '价格'],
['怎么加入PM', '加入方式'],
['PM产品能治病吗', '治病声明'],
];
for (const [query, label] of faqQueries) {
it(`${label}: "${query}" → 应走KB路由`, () => {
assertKbRoute(query);
});
}
});
describe('单KB查询 —— 事业发展类', () => {
const businessQueries = [
['如何发展PM事业', '事业发展'],
['线上拓客怎么做', '线上拓客'],
['招商代理政策', '招商'],
['新人起步三关是什么', '新人培训'],
['为什么选择德国PM', '选择理由'],
['陌生客户怎么沟通PM事业', '陌生沟通'],
];
for (const [query, label] of businessQueries) {
it(`${label}: "${query}" → 应走KB路由`, () => {
assertKbRoute(query);
});
}
});
// ================================================================
// 2. 多KB查询 —— 话题切换场景
// ================================================================
describe('多KB查询 —— 话题切换(同一会话中切换不同产品/话题)', () => {
it('场景1: 大白→小红→小白 三个产品连续查询', () => {
assertKbRoute('大白产品功效是什么');
const ctx1 = buildCtx([['user', '大白产品功效是什么'], ['assistant', '德国PM大白Basics是基础营养素...']]);
assertKbRoute('那小红呢', ctx1);
const ctx2 = buildCtx([
['user', '大白产品功效是什么'], ['assistant', '德国PM大白Basics...'],
['user', '那小红呢'], ['assistant', 'FitLine小红Activize...'],
]);
assertKbRoute('小白怎么吃', ctx2);
});
it('场景2: 产品→公司→再回到产品', () => {
assertKbRoute('基础三合一介绍一下');
assertKbRoute('德国PM公司是什么时候成立的');
const ctx = buildCtx([
['user', '德国PM公司介绍'], ['assistant', '德国PM-International是1993年创立的...'],
]);
assertKbRoute('那他们的产品有哪些', ctx);
});
it('场景3: 产品→一成系统→培训', () => {
assertKbRoute('CC套装怎么用');
assertKbRoute('一成系统是什么');
assertKbRoute('新人起步三关怎么做');
});
it('场景4: 科学原理→产品→FAQ', () => {
assertKbRoute('NTC营养保送系统原理');
assertKbRoute('大白Basics功效');
assertKbRoute('多久能见效');
});
it('场景5: 合法性→公司→产品→事业', () => {
assertKbRoute('PM是不是传销');
assertKbRoute('邓白氏AAA+是什么');
const ctx5 = buildCtx([['user', '邓白氏AAA+'], ['assistant', '邓白氏是全球最权威的商业信用评估机构...']]);
assertKbRoute('那产品有哪些', ctx5);
assertKbRoute('怎么加入PM事业');
});
it('场景6: 快速切换5个不同产品', () => {
const products = [
'D-Drink排毒饮怎么用',
'Q10辅酵素功效',
'IB5口腔喷雾是什么',
'叶黄素怎么吃',
'关节套装适合谁',
];
for (const q of products) {
assertKbRoute(q);
}
});
});
// ================================================================
// 3. 追问场景(有上下文)
// ================================================================
describe('追问场景 —— 基于上下文的知识库追问', () => {
it('聊基础三合一后追问"怎么吃"', () => {
const ctx = buildCtx([['user', '基础三合一介绍'], ['assistant', '基础三合一包含大白小红小白...']]);
assertKbRoute('怎么吃', ctx);
});
it('聊小红后追问"功效是什么"', () => {
const ctx = buildCtx([['user', '小红产品'], ['assistant', 'FitLine小红Activize Oxyplus...']]);
assertKbRoute('功效是什么', ctx);
});
it('聊一成系统后追问"怎么用"', () => {
const ctx = buildCtx([['user', '一成系统介绍'], ['assistant', '一成系统是德国PM事业...']]);
assertKbRoute('怎么用', ctx);
});
it('聊CC套装后追问"适合谁"', () => {
const ctx = buildCtx([['user', 'CC套装功效'], ['assistant', 'CC套装含有葡萄籽提取物...']]);
assertKbRoute('适合谁', ctx);
});
it('聊火炉原理后追问"什么意思"', () => {
const ctx = buildCtx([['user', '火炉原理'], ['assistant', '火炉原理是PM产品的核心理念...']]);
assertKbRoute('什么意思', ctx);
});
it('聊D-Drink后追问"多少钱"', () => {
const ctx = buildCtx([['user', 'D-Drink小绿怎么用'], ['assistant', 'D-Drink小绿是14天排毒饮料...']]);
assertKbRoute('多少钱', ctx);
});
it('聊邓白氏后追问"什么意思"', () => {
const ctx = buildCtx([['user', '邓白氏AAA+认证'], ['assistant', '邓白氏是全球最权威的...']]);
assertKbRoute('什么意思', ctx);
});
it('聊NTC后追问"有什么好处"', () => {
const ctx = buildCtx([['user', 'NTC营养保送系统'], ['assistant', 'NTC营养保送系统是PM的核心技术...']]);
assertKbRoute('有什么好处', ctx);
});
it('聊Hair+后追问"成分是什么"', () => {
const ctx = buildCtx([['user', 'Hair+发宝怎么用'], ['assistant', 'Hair+包含口服发宝和外用发健...']]);
assertKbRoute('成分是什么', ctx);
});
it('用代词追问"这个产品怎么吃"', () => {
const ctx = buildCtx([['user', '小白Restorate功效'], ['assistant', '德国PM小白Restorate的核心功效是夜间修复...']]);
assertKbRoute('这个产品怎么吃', ctx);
});
});
// ================================================================
// 4. KB查询 + 质疑混合场景
// ================================================================
describe('KB查询+质疑混合 —— 用户查询后对回答产生质疑', () => {
it('场景1: 问基础三合一→AI说冲剂→用户纠正"不对,是胶囊"', () => {
assertKbRoute('基础三合一怎么吃');
const ctx = buildCtx([['user', '基础三合一怎么吃'], ['assistant', '基础三合一这样吃...']]);
assertKbRoute('不对,是胶囊不是冲剂', ctx);
});
it('场景2: 问小红功效→用户质疑"你搞错了吧"', () => {
assertKbRoute('小红产品功效');
const ctx = buildCtx([['user', '小红产品功效'], ['assistant', '小红Activize...']]);
assertKbRoute('你搞错了吧', ctx);
});
it('场景3: 问NTC原理→用户说"我听说不是这样的"', () => {
assertKbRoute('NTC营养保送系统原理');
const ctx = buildCtx([['user', 'NTC原理'], ['assistant', 'NTC营养保送系统...']]);
assertKbRoute('我听说不是这样的', ctx);
});
it('场景4: 问传销→用户要求"再查一下"', () => {
assertKbRoute('PM是不是传销');
const ctx = buildCtx([['user', 'PM是不是传销'], ['assistant', '德国PM不是传销...']]);
assertKbRoute('你再查查,我看网上说法不一样', ctx);
});
it('场景5: 问价格→用户质疑"太贵了,你确定吗"', () => {
assertKbRoute('产品多少钱');
const ctx = buildCtx([['user', '产品多少钱'], ['assistant', '产品价格因国家和地区有所不同...']]);
assertKbRoute('你确定吗,怎么那么贵', ctx);
});
it('场景6: 问CC套装→用户说"明明是乳霜不是胶囊"', () => {
assertKbRoute('CC套装是什么');
const ctx = buildCtx([['user', 'CC套装'], ['assistant', 'CC套装包含CC-Cell胶囊和乳霜...']]);
assertKbRoute('明明是乳霜不是胶囊', ctx);
});
it('场景7: 问好转反应→用户说"骗人的吧"', () => {
assertKbRoute('好转反应是怎么回事');
const ctx = buildCtx([['user', '好转反应'], ['assistant', '这是正常的好转反应...']]);
assertKbRoute('骗人的吧,有科学依据吗', ctx);
});
it('场景8: 问一成系统→用户说"跟我了解的不一样"', () => {
assertKbRoute('一成系统介绍');
const ctx = buildCtx([['user', '一成系统'], ['assistant', '一成系统是德国PM事业发展的智能赋能工具...']]);
assertKbRoute('跟我了解的不一样啊', ctx);
});
it('场景9: 问D-Drink→用户说"这个是泡着喝的吧"', () => {
assertKbRoute('D-Drink怎么用');
const ctx = buildCtx([['user', 'D-Drink怎么用'], ['assistant', 'D-Drink小绿是14天排毒饮料...']]);
assertKbRoute('这个是泡着喝的吧', ctx);
});
it('场景10: 问邓白氏→用户说"谁说的?有什么根据"', () => {
assertKbRoute('邓白氏评级是什么');
const ctx = buildCtx([['user', '邓白氏'], ['assistant', '邓白氏是全球最权威的商业信用评估机构...']]);
assertKbRoute('谁说的?有什么根据', ctx);
});
});
// ================================================================
// 5. 多轮复杂对话场景(查询→追问→质疑→再查→切换话题)
// ================================================================
describe('多轮复杂对话 —— 模拟真实用户完整会话', () => {
it('5轮对话: 产品查询→追问→质疑→纠正→切换话题', () => {
// 轮1: 直接问产品
assertKbRoute('基础三合一是什么');
// 轮2: 追问怎么吃
const ctx2 = buildCtx([['user', '基础三合一是什么'], ['assistant', '基础三合一包含大白小红小白...']]);
assertKbRoute('怎么吃', ctx2);
// 轮3: 质疑回答
const ctx3 = buildCtx([
['user', '基础三合一是什么'], ['assistant', '基础三合一包含大白小红小白...'],
['user', '怎么吃'], ['assistant', '大白早上空腹1平勺...'],
]);
assertKbRoute('你说的温度不对吧', ctx3);
// 轮4: 用户纠正
assertKbRoute('应该是40度以下的水', ctx3);
// 轮5: 切换到完全不同的话题
assertKbRoute('PM是不是传销');
});
it('6轮对话: 系统→子功能→质疑→公司→产品→FAQ', () => {
assertKbRoute('一成系统介绍');
const ctx2 = buildCtx([['user', '一成系统'], ['assistant', '一成系统是德国PM事业发展的智能赋能工具...']]);
assertKbRoute('行动圈是什么', ctx2);
const ctx3 = buildCtx([['user', '一成系统'], ['assistant', '一成系统是德国PM事业发展的智能赋能工具...'], ['user', '行动圈是什么'], ['assistant', '行动圈是数字化工作室里的团队管理功能...']]);
assertKbRoute('好像不是这样吧', ctx3);
assertKbRoute('德国PM公司背景');
assertKbRoute('大白产品功效');
assertKbRoute('孕妇能吃吗');
});
it('4轮对话: 连续质疑同一个话题', () => {
assertKbRoute('火炉原理是什么');
const ctx1 = buildCtx([['user', '火炉原理'], ['assistant', '火炉原理是PM产品的核心理念比喻...']]);
assertKbRoute('不对,我记得不是这么说的', ctx1);
assertKbRoute('你再查查,应该是另一种说法', ctx1);
assertKbRoute('算了,到底是什么意思', ctx1);
});
});
// ================================================================
// 6. 确定性改写验证
// ================================================================
describe('确定性改写 —— buildDeterministicKnowledgeQuery', () => {
describe('产品直接改写', () => {
const cases = [
['基础三合一怎么吃', '基础套装'],
['大白产品', 'Basics'],
['小红Activize功效', 'Activize'],
['小白Restorate成分', 'Restorate'],
['儿童倍适怎么吃', '儿童倍适'],
['CC套装功效', 'CC'],
['Q10辅酵素作用', 'Q10'],
['IB5口腔喷雾', 'IB5'],
['D-Drink排毒', 'D-Drink'],
];
for (const [query, expectContain] of cases) {
it(`"${query}" → 改写应含"${expectContain}"`, () => {
const result = ToolExecutor.buildDeterministicKnowledgeQuery(query, []);
assert.ok(result, `Should have deterministic rewrite for "${query}"`);
assert.ok(result.includes(expectContain),
`Rewrite should contain "${expectContain}", got "${result}"`);
});
}
});
describe('上下文追问改写', () => {
it('上下文有"大白"时追问"怎么吃" → 改写含Basics', () => {
const ctx = [{ role: 'assistant', content: '大白Basics是基础营养素...' }];
const result = ToolExecutor.buildDeterministicKnowledgeQuery('怎么吃', ctx);
assert.ok(result, 'Should rewrite');
assert.ok(result.includes('Basics'), `Should contain Basics, got "${result}"`);
});
it('上下文有"一成系统"时追问"怎么用" → 改写含一成系统', () => {
const ctx = [{ role: 'assistant', content: '一成系统是德国PM事业发展...' }];
const result = ToolExecutor.buildDeterministicKnowledgeQuery('怎么用', ctx);
assert.ok(result, 'Should rewrite');
assert.ok(result.includes('一成系统'), `Should contain 一成系统, got "${result}"`);
});
it('上下文有"火炉原理"时追问"什么意思" → 改写含火炉原理', () => {
const ctx = [{ role: 'assistant', content: '火炉原理是PM产品的核心理念...' }];
const result = ToolExecutor.buildDeterministicKnowledgeQuery('什么意思', ctx);
assert.ok(result === '火炉原理', `Should rewrite to 火炉原理, got "${result}"`);
});
});
describe('无匹配时返回空', () => {
it('"今天天气好" → 无确定性改写', () => {
const result = ToolExecutor.buildDeterministicKnowledgeQuery('今天天气好', []);
assert.equal(result, '', 'Chitchat should not have deterministic rewrite');
});
});
});
// ================================================================
// 7. 热答案匹配测试
// ================================================================
describe('热答案匹配 —— matchHotAnswer 验证', () => {
// matchHotAnswer是模块内部函数通过ToolExecutor.execute间接调用
// 这里直接测试确定性改写+KB路由来验证热答案可达性
const hotTopics = [
['基础三合一怎么吃', '基础三合一吃法'],
['PM是不是传销', '合法性'],
['NTC核心优势是什么', 'NTC核心优势'],
['多久见效', '见效时间'],
['为什么要全套搭配', '全套搭配原因'],
['好转反应是什么', '好转反应'],
['德国PM公司介绍', '公司介绍'],
['小红功效', '小红功效'],
['大白功效', '大白功效'],
['小白功效', '小白功效'],
['和其他保健品有什么区别', '保健品区别'],
['CC套装怎么用', 'CC套装'],
['Q10功效', 'Q10功效'],
['IB5怎么用', 'IB5'],
['邓白氏AAA+是什么', '邓白氏'],
['一成系统是什么', '一成系统'],
['火炉原理是什么', '火炉原理'],
['D-Drink排毒饮怎么用', 'D-Drink'],
['怎么加入PM', '加入方式'],
['孕妇能吃PM产品吗', '特殊人群'],
['多少钱', '价格'],
];
for (const [query, label] of hotTopics) {
it(`${label}: "${query}" → 应路由到KB热答案可达`, () => {
assertKbRoute(query);
});
}
});
// ================================================================
// 8. 问题维度交叉测试(同一产品不同问法)
// ================================================================
describe('问题维度交叉 —— 同一产品的不同问法', () => {
describe('基础三合一 × 多维度', () => {
const dimensions = [
'基础三合一怎么吃',
'基础三合一功效',
'基础三合一成分',
'基础三合一多少钱',
'基础三合一适合谁',
'为什么要全套搭配三合一',
];
for (const q of dimensions) {
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
}
});
describe('小红 × 多维度', () => {
const dimensions = [
'小红怎么吃',
'小红功效是什么',
'小红成分有哪些',
'小红副作用',
'小红多少钱',
'小红适合什么人',
];
for (const q of dimensions) {
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
}
});
describe('一成系统 × 多维度', () => {
const dimensions = [
'一成系统是什么',
'一成系统核心竞争力',
'一成系统怎么用',
'一成系统三大平台',
'一成系统AI智能生产力',
'一成系统线上拓客',
'一成系统邀约话术',
'一成系统文化',
];
for (const q of dimensions) {
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
}
});
});
// ================================================================
// 9. 口语/ASR变体测试语音识别的常见错误变体
// ================================================================
describe('口语/ASR变体 —— 语音识别常见变体', () => {
const asrVariants = [
['移程系统', '一成系统ASR变体'],
['PM细胞营养素', 'PM产品'],
['暖炉原理', '火炉原理ASR变体'],
['产品有哪些', '产品列表'],
['你们公司产品', '口语化'],
['这个东西怎么吃', '口语化追问'],
['咱们公司介绍一下', '口语化公司'],
];
for (const [query, label] of asrVariants) {
it(`${label}: "${query}" → 应走KB路由`, () => {
assertKbRoute(query);
});
}
});
// ================================================================
// 10. 边界测试
// ================================================================
describe('边界测试', () => {
it('空字符串 → 不走KB', () => {
assertNotKbRoute('');
});
it('纯标点 → 不走KB', () => {
assertNotKbRoute('');
});
it('单字"好" → 不走KB', () => {
assertNotKbRoute('好');
});
it('纯数字 → 不走KB', () => {
assertNotKbRoute('123456');
});
it('超长无意义文本 → 不走KB', () => {
assertNotKbRoute('啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊');
});
it('纯英文闲聊 → 不走KB', () => {
assertNotKbRoute('hello how are you');
});
it('含KB关键词但实际是闲聊的边界', () => {
// "不对"是质疑词会被检测为KB follow-up
// 需要context才会真正路由到KB
// 无context时: hasKnowledgeRouteKeyword('不对') → true (因为加了质疑词)
// 这是预期行为宁可多查一次KB也不要漏掉用户质疑
const result = shouldForceKnowledgeRoute('不对');
assert.equal(typeof result, 'boolean', 'Should return boolean');
});
});
console.log('\n=== KB场景测试加载完成 ===\n');