- 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文档、系统提示词目录
492 lines
18 KiB
JavaScript
492 lines
18 KiB
JavaScript
/**
|
||
* KB场景深度扩展测试
|
||
* 覆盖:更多产品×维度交叉、更多确定性改写、更多追问变体、更多多轮切换、更多热答案、更多边界
|
||
*
|
||
* 运行方式: node --test tests/test_kb_scenarios_extended.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('产品×维度全交叉 —— 大白', () => {
|
||
const dims = ['怎么吃', '功效是什么', '成分有哪些', '多少钱', '适合谁', '副作用', '哪里买', '保质期'];
|
||
for (const d of dims) {
|
||
it(`"大白${d}" → 应走KB`, () => { assertKbRoute(`大白${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— 小白', () => {
|
||
const dims = ['怎么吃', '功效是什么', '成分有哪些', '多少钱', '适合谁', '副作用', '哪里买', '怎么服用'];
|
||
for (const d of dims) {
|
||
it(`"小白${d}" → 应走KB`, () => { assertKbRoute(`小白${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— CC套装', () => {
|
||
const dims = ['怎么用', '功效是什么', '成分有哪些', '多少钱', '适合谁', '副作用', '区别', '包含什么'];
|
||
for (const d of dims) {
|
||
it(`"CC套装${d}" → 应走KB`, () => { assertKbRoute(`CC套装${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— Q10', () => {
|
||
const dims = ['怎么吃', '功效是什么', '成分', '多少钱', '适合谁', '副作用', '怎么买', '适合什么人'];
|
||
for (const d of dims) {
|
||
it(`"Q10${d}" → 应走KB`, () => { assertKbRoute(`Q10${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— IB5', () => {
|
||
const dims = ['怎么用', '功效是什么', '成分', '多少钱', '适合谁', '副作用', '什么时候用', '适合什么人'];
|
||
for (const d of dims) {
|
||
it(`"IB5${d}" → 应走KB`, () => { assertKbRoute(`IB5${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— D-Drink', () => {
|
||
const dims = ['怎么用', '功效是什么', '成分', '多少钱', '适合谁', '副作用', '排毒原理', '喝法'];
|
||
for (const d of dims) {
|
||
it(`"D-Drink${d}" → 应走KB`, () => { assertKbRoute(`D-Drink${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— Hair+', () => {
|
||
const dims = ['怎么用', '功效是什么', '成分', '多少钱', '适合谁', '副作用'];
|
||
for (const d of dims) {
|
||
it(`"Hair+${d}" → 应走KB`, () => { assertKbRoute(`Hair+${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— 儿童倍适', () => {
|
||
const dims = ['怎么吃', '功效是什么', '成分', '多少钱', '适合几岁', '副作用', '适合什么人', '口味'];
|
||
for (const d of dims) {
|
||
it(`"儿童倍适${d}" → 应走KB`, () => { assertKbRoute(`儿童倍适${d}`); });
|
||
}
|
||
});
|
||
|
||
describe('产品×维度全交叉 —— 关节套装', () => {
|
||
const dims = ['怎么用', '功效是什么', '成分', '多少钱', '适合谁', '副作用'];
|
||
for (const d of dims) {
|
||
it(`"关节套装${d}" → 应走KB`, () => { assertKbRoute(`关节套装${d}`); });
|
||
}
|
||
});
|
||
|
||
// ================================================================
|
||
// 2. 确定性改写深度覆盖 —— 更多产品规则
|
||
// ================================================================
|
||
describe('确定性改写深度 —— 更多产品规则', () => {
|
||
|
||
describe('一成系统及子话题', () => {
|
||
const cases = [
|
||
['一成系统是什么', '一成系统'],
|
||
['一成系统怎么用', '一成系统'],
|
||
['一成系统三大平台', '一成系统'],
|
||
['一成系统行动圈', '一成系统'],
|
||
['身未动梦已成', '一成系统'],
|
||
['一部手机做天下', '一成系统'],
|
||
['如何发展PM事业', '一成系统'],
|
||
];
|
||
for (const [q, expect] of cases) {
|
||
it(`"${q}" → 改写含"${expect}"`, () => {
|
||
const r = ToolExecutor.buildDeterministicKnowledgeQuery(q, []);
|
||
assert.ok(r && r.includes(expect), `"${q}" → got "${r}"`);
|
||
});
|
||
}
|
||
});
|
||
|
||
describe('PM公司相关', () => {
|
||
const cases = [
|
||
['德国PM公司介绍', 'PM'],
|
||
['PM公司背景', 'PM'],
|
||
['PM是不是传销', 'PM'],
|
||
['PM公司合法吗', 'PM'],
|
||
];
|
||
for (const [q, expect] of cases) {
|
||
it(`"${q}" → 改写含"${expect}"`, () => {
|
||
const r = ToolExecutor.buildDeterministicKnowledgeQuery(q, []);
|
||
assert.ok(r && r.includes(expect), `"${q}" → got "${r}"`);
|
||
});
|
||
}
|
||
});
|
||
|
||
describe('NTC/火炉原理/阿育吠陀', () => {
|
||
const cases = [
|
||
['NTC营养保送系统是什么', 'NTC'],
|
||
['NTC核心优势', 'NTC'],
|
||
['火炉原理', '火炉原理'],
|
||
['阿育吠陀是什么', '阿育吠陀'],
|
||
];
|
||
for (const [q, expect] of cases) {
|
||
it(`"${q}" → 改写含"${expect}"`, () => {
|
||
const r = ToolExecutor.buildDeterministicKnowledgeQuery(q, []);
|
||
assert.ok(r && r.includes(expect), `"${q}" → got "${r}"`);
|
||
});
|
||
}
|
||
});
|
||
|
||
describe('上下文追问改写 —— 各产品', () => {
|
||
const ctxCases = [
|
||
[{ role: 'assistant', content: '小红Activize是...' }, '成分是什么', 'Activize'],
|
||
[{ role: 'assistant', content: '小白Restorate帮助修复...' }, '适合谁', 'Restorate'],
|
||
[{ role: 'assistant', content: 'CC套装含有CC-Cell葡萄籽精华胶囊和乳霜' }, '怎么吃', 'CC'],
|
||
[{ role: 'assistant', content: 'Hair+发宝防脱发口服发宝外用发健' }, '功效', 'Hair'],
|
||
[{ role: 'assistant', content: '儿童倍适PowerCocktail Junior适合小朋友' }, '怎么用', '儿童倍适'],
|
||
[{ role: 'assistant', content: 'D-Drink小绿排毒饮是14天排毒方案' }, '功效是什么', 'D-Drink'],
|
||
[{ role: 'assistant', content: 'Apple Antioxy Zellschutz细胞抗氧素是独立小袋包装' }, '他的规格是什么', 'Apple Antioxy'],
|
||
[{ role: 'assistant', content: '小白Restorate建议睡前空腹服用' }, '它一天几次', 'Restorate'],
|
||
];
|
||
for (const [ctxMsg, query, expect] of ctxCases) {
|
||
it(`上下文"${ctxMsg.content.slice(0, 10)}..."追问"${query}" → 含"${expect}"`, () => {
|
||
const r = ToolExecutor.buildDeterministicKnowledgeQuery(query, [ctxMsg]);
|
||
assert.ok(r && r.includes(expect), `Got "${r}"`);
|
||
});
|
||
}
|
||
});
|
||
|
||
describe('无匹配场景', () => {
|
||
const noMatch = ['你好', '天气怎么样', '讲个故事', '几点了', '我要听音乐'];
|
||
for (const q of noMatch) {
|
||
it(`"${q}" → 无改写`, () => {
|
||
const r = ToolExecutor.buildDeterministicKnowledgeQuery(q, []);
|
||
assert.equal(r, '', `"${q}" should not rewrite, got "${r}"`);
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
// ================================================================
|
||
// 3. 追问变体全覆盖 —— 各种追问句式 × 不同产品上下文
|
||
// ================================================================
|
||
describe('追问变体全覆盖 —— 不同追问句式', () => {
|
||
const followUpPatterns = [
|
||
'怎么吃', '怎么用', '功效是什么', '成分是什么', '多少钱',
|
||
'适合谁', '哪里买', '什么意思', '有什么好处', '怎么服用',
|
||
'详细说说', '介绍一下', '继续说', '展开说说', '配方',
|
||
'原理是什么', '适合什么人', '怎么买', '具体内容',
|
||
];
|
||
|
||
const ctxProducts = [
|
||
buildCtx([['user', '大白产品'], ['assistant', '大白Basics...']]),
|
||
buildCtx([['user', '小红功效'], ['assistant', '小红Activize...']]),
|
||
buildCtx([['user', '一成系统'], ['assistant', '一成系统是...']]),
|
||
];
|
||
|
||
for (let pi = 0; pi < ctxProducts.length; pi++) {
|
||
for (const fup of followUpPatterns) {
|
||
it(`ctx${pi + 1} + "${fup}" → 应走KB`, () => {
|
||
assertKbRoute(fup, ctxProducts[pi]);
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
// ================================================================
|
||
// 4. 多KB切换 —— 更多组合
|
||
// ================================================================
|
||
describe('多KB切换 —— 更多组合模式', () => {
|
||
|
||
it('产品→FAQ→科学: 小红→副作用→NTC原理', () => {
|
||
assertKbRoute('小红有什么副作用');
|
||
assertKbRoute('PM产品有副作用吗');
|
||
assertKbRoute('NTC营养保送系统原理');
|
||
});
|
||
|
||
it('事业→公司→产品→FAQ: 招商→PM背景→大白→见效时间', () => {
|
||
assertKbRoute('招商代理政策');
|
||
assertKbRoute('德国PM公司背景');
|
||
assertKbRoute('大白怎么吃');
|
||
assertKbRoute('多久见效');
|
||
});
|
||
|
||
it('科学→产品→产品→产品: NTC→小红→小白→CC', () => {
|
||
assertKbRoute('NTC是什么');
|
||
assertKbRoute('小红功效');
|
||
assertKbRoute('小白成分');
|
||
assertKbRoute('CC套装怎么用');
|
||
});
|
||
|
||
it('FAQ→FAQ→FAQ: 传销→副作用→见效→全套搭配', () => {
|
||
assertKbRoute('PM是传销吗');
|
||
assertKbRoute('PM产品有副作用吗');
|
||
assertKbRoute('多久能见效');
|
||
assertKbRoute('为什么要全套搭配');
|
||
});
|
||
|
||
it('系统→事业→认证: 一成系统→做PM→邓白氏', () => {
|
||
assertKbRoute('一成系统介绍');
|
||
assertKbRoute('怎么做PM事业');
|
||
assertKbRoute('邓白氏AAA+认证');
|
||
});
|
||
|
||
it('产品→追问→切换→追问: 大白→怎么吃→小红→功效', () => {
|
||
assertKbRoute('大白是什么');
|
||
const ctx1 = buildCtx([['user', '大白'], ['assistant', '大白Basics...']]);
|
||
assertKbRoute('怎么吃', ctx1);
|
||
assertKbRoute('小红产品介绍');
|
||
const ctx2 = buildCtx([['user', '小红'], ['assistant', '小红Activize...']]);
|
||
assertKbRoute('功效是什么', ctx2);
|
||
});
|
||
});
|
||
|
||
// ================================================================
|
||
// 5. 质疑+追问混合 —— 更多组合场景
|
||
// ================================================================
|
||
describe('质疑+追问混合 —— 更多组合', () => {
|
||
|
||
it('质疑后继续追问详情', () => {
|
||
assertKbRoute('大白功效');
|
||
const ctx = buildCtx([['user', '大白功效'], ['assistant', '大白Basics帮助...']]);
|
||
assertKbRoute('你搞错了吧', ctx);
|
||
assertKbRoute('那正确的功效到底是什么', ctx);
|
||
});
|
||
|
||
it('追问后发现错误再质疑', () => {
|
||
assertKbRoute('CC套装包含什么');
|
||
const ctx = buildCtx([['user', 'CC套装'], ['assistant', 'CC套装含有...']]);
|
||
assertKbRoute('具体成分是什么', ctx);
|
||
assertKbRoute('说的有问题,我记得不是这个成分', ctx);
|
||
});
|
||
|
||
it('质疑→纠正→再追问另一个维度', () => {
|
||
const ctx = buildCtx([['user', '小红怎么吃'], ['assistant', '小红冲服...']]);
|
||
assertKbRoute('不对,不是冲着喝的', ctx);
|
||
assertKbRoute('小红到底是什么剂型', ctx);
|
||
assertKbRoute('小红适合什么人群', ctx);
|
||
});
|
||
|
||
it('连续切换产品并质疑', () => {
|
||
assertKbRoute('大白功效');
|
||
assertKbRoute('你说的不对');
|
||
assertKbRoute('小红功效');
|
||
assertKbRoute('也不对吧');
|
||
assertKbRoute('小白功效');
|
||
assertKbRoute('说的不准确');
|
||
});
|
||
|
||
it('先闲聊再切到KB质疑', () => {
|
||
assertNotKbRoute('今天心情不错');
|
||
assertKbRoute('对了基础三合一怎么吃');
|
||
const ctx = buildCtx([['user', '基础三合一'], ['assistant', '大白小红小白...']]);
|
||
assertKbRoute('不是这样吧', ctx);
|
||
});
|
||
});
|
||
|
||
// ================================================================
|
||
// 6. 热答案可达性 —— 更多热门问题变体
|
||
// ================================================================
|
||
describe('热答案可达性 —— 问题变体', () => {
|
||
const hotVariants = [
|
||
// 基础三合一吃法的各种问法
|
||
'基础三合一怎么吃', '基础三合一的吃法', '基础套装怎么服用',
|
||
'大白小红小白怎么吃',
|
||
// 传销/合法性的各种问法
|
||
'PM是不是传销', 'PM合法吗', 'PM是传销吗', 'PM正规吗',
|
||
// NTC
|
||
'NTC是什么', 'NTC核心优势是什么', 'NTC营养保送系统',
|
||
// 见效时间
|
||
'多久见效', '多久能见效', '吃多久有效果',
|
||
// 好转反应
|
||
'好转反应', '好转反应是什么', '吃了不舒服正常吗',
|
||
// 公司
|
||
'德国PM公司介绍', 'PM公司背景', 'PM公司怎么样',
|
||
// 价格
|
||
'多少钱', '产品价格', '产品贵不贵',
|
||
];
|
||
|
||
for (const q of hotVariants) {
|
||
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
|
||
}
|
||
});
|
||
|
||
// ================================================================
|
||
// 7. 口语化查询 —— 更多自然说法
|
||
// ================================================================
|
||
describe('口语化查询 —— 真实用户的自然说法', () => {
|
||
const colloquial = [
|
||
'你们公司是做什么的',
|
||
'你们那个产品怎么吃',
|
||
'咱们这个东西多少钱',
|
||
'那个什么三合一是什么',
|
||
'帮我介绍一下你们产品',
|
||
'我想了解PM产品',
|
||
'说说你们公司',
|
||
'讲讲那个什么系统',
|
||
'查查那个产品',
|
||
'帮我查一下基础三合一',
|
||
'帮我问一下价格',
|
||
'你们产品正规吗',
|
||
'你们那个东西靠谱吗',
|
||
'说说那个什么功效',
|
||
'我想知道怎么加入',
|
||
'你们卖的是什么东西',
|
||
'健康产品有哪些',
|
||
'帮我看看成分',
|
||
'你们的东西有什么用',
|
||
'咱吃这个有好处吗',
|
||
];
|
||
|
||
for (const q of colloquial) {
|
||
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
|
||
}
|
||
});
|
||
|
||
// ================================================================
|
||
// 8. 负面边界 —— 更多不应走KB的场景
|
||
// ================================================================
|
||
describe('负面边界 —— 更多不应走KB的场景', () => {
|
||
const negatives = [
|
||
'',
|
||
' ',
|
||
'?',
|
||
'!!!',
|
||
'。。。',
|
||
'好',
|
||
'嗯',
|
||
'哦',
|
||
'行',
|
||
'啊',
|
||
'对',
|
||
'是的',
|
||
'好的好的',
|
||
'知道了知道了',
|
||
'哈哈哈哈',
|
||
'嘻嘻',
|
||
'666',
|
||
'999',
|
||
'111',
|
||
'早',
|
||
'晚安',
|
||
'ok',
|
||
'OK',
|
||
'hello',
|
||
'hi',
|
||
'bye',
|
||
'good',
|
||
'今天天气真好',
|
||
'我要睡觉了',
|
||
'你是AI吗',
|
||
'你能做什么',
|
||
];
|
||
|
||
for (const text of negatives) {
|
||
it(`"${text}" → 不应走KB`, () => { assertNotKbRoute(text); });
|
||
}
|
||
});
|
||
|
||
// ================================================================
|
||
// 9. 关键词嵌入长句 —— 确保关键词在句中也能命中
|
||
// ================================================================
|
||
describe('关键词嵌入长句 —— 子串匹配验证', () => {
|
||
const embedded = [
|
||
['我想了解一下基础三合一的功效', '基础三合一嵌入'],
|
||
['请问德国PM公司在哪个城市', '公司嵌入'],
|
||
['你能帮我查查一成系统怎么用吗', '一成系统嵌入'],
|
||
['我朋友推荐我吃小红你能介绍下吗', '小红嵌入'],
|
||
['听说有个叫NTC的营养保送系统', 'NTC嵌入'],
|
||
['好转反应的话应该怎么处理', '好转反应嵌入'],
|
||
['孕妇是不是不能吃PM的产品', '孕妇嵌入'],
|
||
['为什么说要全套搭配使用呢', '全套搭配嵌入'],
|
||
['听人家说邓白氏AAA+评级很厉害', '邓白氏嵌入'],
|
||
['想问一下儿童倍适几岁能吃', '儿童倍适嵌入'],
|
||
['我对火炉原理很感兴趣', '火炉原理嵌入'],
|
||
['请介绍一下CC套装的功效', 'CC套装嵌入'],
|
||
['Q10辅酵素是做什么用的', 'Q10嵌入'],
|
||
['Hair+发宝真的能防脱吗', 'Hair+嵌入'],
|
||
['想知道D-Drink排毒的原理', 'D-Drink嵌入'],
|
||
];
|
||
|
||
for (const [text, label] of embedded) {
|
||
it(`${label}: "${text}" → 应走KB`, () => { assertKbRoute(text); });
|
||
}
|
||
});
|
||
|
||
// ================================================================
|
||
// 10. 同义问法覆盖 —— 相同意图不同表达
|
||
// ================================================================
|
||
describe('同义问法 —— 相同意图不同表达', () => {
|
||
|
||
describe('询问功效的N种方式', () => {
|
||
const efficacyCases = [
|
||
'大白有什么功效', '大白的作用是什么', '大白有什么用',
|
||
'大白好在哪', '吃大白有什么好处',
|
||
];
|
||
for (const q of efficacyCases) {
|
||
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
|
||
}
|
||
});
|
||
|
||
describe('询问用法的N种方式', () => {
|
||
const usageCases = [
|
||
'小红怎么吃', '小红怎么服用', '小红怎么用',
|
||
'小红的吃法', '小红用法',
|
||
];
|
||
for (const q of usageCases) {
|
||
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
|
||
}
|
||
});
|
||
|
||
describe('询问价格的N种方式', () => {
|
||
const priceCases = [
|
||
'基础三合一多少钱', '基础三合一价格', '基础三合一贵不贵',
|
||
'产品多少钱', '产品价格表',
|
||
];
|
||
for (const q of priceCases) {
|
||
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
|
||
}
|
||
});
|
||
|
||
describe('询问合法性的N种方式', () => {
|
||
const legalCases = [
|
||
'PM是不是传销', 'PM正规吗', 'PM合法吗',
|
||
'PM是传销吗', 'PM是直销还是传销', 'PM靠谱吗',
|
||
];
|
||
for (const q of legalCases) {
|
||
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
|
||
}
|
||
});
|
||
|
||
describe('询问公司的N种方式', () => {
|
||
const companyCases = [
|
||
'PM公司介绍', '德国PM公司怎么样',
|
||
'PM公司成立多久了', 'PM公司实力如何',
|
||
];
|
||
for (const q of companyCases) {
|
||
it(`"${q}" → 应走KB`, () => { assertKbRoute(q); });
|
||
}
|
||
});
|
||
});
|
||
|
||
// ================================================================
|
||
// 11. 特殊人群×产品交叉
|
||
// ================================================================
|
||
describe('特殊人群×产品 —— 能不能吃的场景', () => {
|
||
const groups = ['孕妇', '小孩', '老人', '糖尿病人', '高血压'];
|
||
const products = ['大白', '小红', '基础三合一', 'PM产品'];
|
||
|
||
for (const g of groups) {
|
||
for (const p of products) {
|
||
it(`"${g}能吃${p}吗" → 应走KB`, () => {
|
||
assertKbRoute(`${g}能吃${p}吗`);
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log('\n=== KB场景扩展测试加载完成 ===\n');
|