feat(kb-routing): expand 5-way keyword routing coverage

This commit is contained in:
User
2026-03-20 10:56:29 +08:00
parent d13084cc0f
commit 93b8135d51
4 changed files with 1195 additions and 171 deletions

View File

@@ -3,18 +3,14 @@
* 记忆最近的产品/主题关键词,用于追问理解 * 记忆最近的产品/主题关键词,用于追问理解
*/ */
const { TRACKER_KEYWORD_GROUPS, buildKeywordRegex } = require('./knowledgeKeywords');
class ContextKeywordTracker { class ContextKeywordTracker {
constructor() { constructor() {
this.sessionKeywords = new Map(); this.sessionKeywords = new Map();
this.TTL = 30 * 60 * 1000; this.TTL = 30 * 60 * 1000;
this.MAX_KEYWORDS = 8; this.MAX_KEYWORDS = 8;
this.keywordPatterns = [ this.keywordPatterns = TRACKER_KEYWORD_GROUPS.map((group) => buildKeywordRegex(group, 'gi'));
/(一成系统|Ai众享|AI众享|数字化工作室|盛咖学愿|数字化运营|数字化经营|数字化营销|数字化创业|数字化事业)/gi,
/(PM-FitLine|PM细胞营养素|细胞营养素|德国PM|PM公司)/gi,
/(小红产品|大白产品|小白产品|Activize Oxyplus|Activize|Basics|Restorate|儿童倍适|Basic Power|CitrusCare|NutriSunny|Omega)/gi,
/(肽美|艾特维|德丽|德维|宝丽|美固健|葡萄籽|白藜芦醇|益生菌|胶原蛋白肽|Q10)/gi,
/(NTC营养保送系统|火炉原理|阿育吠陀|招商|加盟|代理|事业机会|招商加盟|合作加盟|事业合作)/gi,
];
this.cleanupTimer = setInterval(() => this.cleanup(), this.TTL); this.cleanupTimer = setInterval(() => this.cleanup(), this.TTL);
if (typeof this.cleanupTimer.unref === 'function') { if (typeof this.cleanupTimer.unref === 'function') {
this.cleanupTimer.unref(); this.cleanupTimer.unref();

View File

@@ -0,0 +1,948 @@
const COMPANY_ENTITY_KEYWORDS = [
'德国PM',
'德国PM公司',
'PM公司',
'PM国际',
'PM-International',
'PM-International AG',
'Rolf Sorg',
'邓白氏',
'AAA+',
'DSN',
'BFH',
'ELAB',
'科隆名单',
'GMP',
'Halal',
'宣明会',
'世界宣明会',
'斯派尔',
'Speyer',
'卢森堡',
'培安(烟台)日用品有限责任公司',
'培安烟台',
'烟台',
];
const SYSTEM_ENTITY_KEYWORDS = [
'一成系统',
'一成AI',
'一成Ai',
'Ai众享',
'AI众享',
'数字化工作室',
'盛咖学愿',
'三大平台',
'四大AI生态',
'四大Ai生态',
'四大生态',
'盟主社区',
'AI智能生产力',
'AI生产力',
'智能生产力',
'行动圈',
'批发式晋级',
'身未动,梦已成',
'身未动梦已成',
'零成本高效率',
'零成本高效率运行',
'赋能团队',
'团队赋能',
'团队发展',
'文化解析',
'故事分享',
'自我介绍',
'邀约话术',
'线上拓客',
'线上成交',
'陌生客户',
'陌生人沟通',
];
const PRODUCT_ENTITY_KEYWORDS = [
'PM产品',
'PM-FitLine',
'FitLine',
'PM细胞营养素',
'细胞营养素',
'基础套装',
'基础三合一',
'三合一',
'基础二合一',
'二合一',
'小红产品',
'小红',
'艾特维',
'Activize Oxyplus',
'Activize Serum',
'Activize',
'小红精华液',
'大白产品',
'大白',
'倍适',
'Basics',
'Basic Power',
'PowerCocktail',
'小白产品',
'小白',
'维适多',
'Restorate',
'儿童倍适',
'PowerCocktail Junior',
'NTC营养保送系统',
'NTC',
'Nutrient Transport Concept',
'火炉原理',
'暖炉原理',
'阿育吠陀',
'Ayurveda',
'Med Dental+',
'草本护理牙膏',
'Men Face',
'全效男士护肤抗衰乳霜',
'CC-Cell',
'CC-Cell胶囊',
'CC-Cell乳霜',
'CC套装',
'CC胶囊',
'D-Drink',
'小绿排毒饮',
'14天排毒D饮料Plus',
'ProShape Amino',
'ProShape® Amino',
'氨基酸',
'支链氨基酸',
'BCAA',
'MEN+',
'Men+',
'倍力健 MEN+',
'倍力健',
'小黑',
'Herbal Tea',
'草本茶',
'Hair+',
'med Hair+',
'口服发宝',
'外用发健',
'发宝',
'Fitness-Drink',
'运动饮料健康饮品',
'运动饮料',
'健康饮品',
'TopShape',
'孅萃TopShape纤萃减肥',
'纤萃减肥',
'乐活50+',
'Generation 50+',
'Apple Antioxy',
'苹果细胞抗氧素',
'Antioxy',
'Zellschutz',
'细胞抗氧素',
'胶原蛋白肽',
'胶原蛋白',
'Women+',
'乐活奶昔',
'乐活',
'乳清蛋白',
'蛋白粉',
'乳酪煲',
'乳酪饮品',
'乳酪',
'IB5',
'口腔免疫喷雾',
'Q10',
'辅酵素',
'Q10辅酵素氧修护',
'关节套装',
'关节舒缓',
'男士乳霜',
'男士护肤',
'去角质',
'面膜',
'叶黄素',
'葡萄籽',
'白藜芦醇',
'益生菌',
'肽美',
'德丽',
'德维',
'宝丽',
'美固健',
'CitrusCare',
'NutriSunny',
'Omega',
'Young Care',
];
const BUSINESS_ENTITY_KEYWORDS = [
'PM事业',
'做PM',
'加入PM',
'招商合作',
'招商与代理',
'招商稿',
'招商',
'招募',
'代理',
'代理商',
'代理政策',
'加盟',
'招商加盟',
'合作加盟',
'事业合作',
'事业机会',
'创业',
'培训新人起步三关',
'起步三关',
'培训打造精品会议具体如下',
'精品会议',
'会议组织',
'培训成长上总裁',
'成长上总裁',
];
const ROUTE_TOPIC_KEYWORDS = [
// 公司相关
'公司介绍',
'公司背景',
'公司实力',
'公司地址',
'公司电话',
'联系方式',
'总部',
'分公司',
'公司成立',
'公司历史',
'公司规模',
'全球布局',
'信用评级',
'行业排名',
'获奖',
'慈善',
'慈善事业',
'社会责任',
'不上市',
'汽车奖励',
'退休金',
'旅行',
'福利',
'企业性质',
'发展历程',
// 产品相关
'产品介绍',
'产品说明',
'产品推荐',
'产品有哪些',
'产品列表',
'产品图片',
'产品外观',
// 语音口语化触发词
'你们公司',
'你们的公司',
'你们产品',
'你们的产品',
'你们那个',
'咱们公司',
'咱们产品',
'咱们的',
'你们这个',
'你们卖的',
'你们的东西',
'这个东西',
'这东西',
'那玩意',
// 口语化动词
'说说',
'讲讲',
'介绍介绍',
'查查',
'帮我查',
'帮我问',
'帮我看看',
'有啥用',
'咱吃',
'咱用',
'咱回事',
'咱样',
'啥意思',
'有啥',
'咱办',
// 营养品/保健品通用词
'营养素',
'营养品',
'保健品',
'保健食品',
'营养补充',
'营养剂',
'膏营养',
// 直销事业
'直销',
'直销公司',
'直销事业',
// 健康/症状/功效相关触发KB总入口
'排毒',
'排毒产品',
'减肥',
'减肥产品',
'瘦身',
'护肤',
'护肤品',
'护发',
'脱发',
'掉发',
'头发',
'牙膏',
'喷雾',
'关节痛',
'关节',
'眼睛',
'视力',
'叶黄素',
'抗氧化',
'抗衰',
'抗衰老',
'胶原蛋白',
'运动饮料',
'免疫力',
'能量',
'抗疲劳',
'疲劳',
'睡眠',
'失眠',
'消化',
'肠胃',
'便秘',
'皮肤',
'美容',
'美白',
'祠斑',
'祠痘',
'痘痘',
'补钹',
'补铁',
'补血',
'骨密度',
'补钙',
// 特定人群触发FAQ或产品库
'孕妇',
'哺乳期',
'怀孕',
'孕期',
'儿童',
'小孩',
'孩子',
'老人',
'老年人',
'过敏',
'过敏体质',
// 事业/招商
'事业',
'做这个事业',
'怎么做这个',
'怎么赚钱',
'能赚钱吗',
'收入',
'奖金',
'奖金制度',
'纪念品',
'服务商',
'合作伙伴',
'为什么选择德国PM',
// 合法性/传销相关
'正规性',
'合法性',
'传销',
'骗局',
'骗子',
'是不是传销',
'直销还是传销',
'合不合法',
'正不正规',
'层级分销',
'非法集资',
'拉人头',
'发展下线',
'报单',
'人头费',
'安全吗',
'合规吗',
'有许可证吗',
// 好转反应/副作用
'好转反应',
'整应反应',
'排毒反应',
'副作用',
'不良反应',
'皮肤发痒',
'皮肤微痒',
// 促销活动
'促销活动',
'活动分数',
'5+1活动分数',
'5+1',
// 产品使用方法
'怎么吃',
'怎么用',
'怎么服用',
'服用方法',
'吃法',
'用法',
'用量',
'搭配',
'空腹吃',
'饭前吃',
'饭后吃',
'温水冲',
'冷水冲',
'一天吃几次',
'一次吃多少',
// 产品属性
'功效',
'作用',
'成分',
'原料',
'配方',
'多少钱',
'价格',
'适合谁',
'适用人群',
'区别',
'哪个好',
'多久见效',
'哪里买',
'怎么买',
'保质期',
'储存',
// 效果/评价相关
'效果怎么样',
'有效果吗',
'有没有用',
'好不好',
'靠谱吗',
'值得买吗',
'值得做吗',
'真的有用吗',
'真的假的',
'有科学依据吗',
// 加入/参与
'怎么加入',
'如何加入',
'怎么报名',
'怎么参与',
'怎么做',
'如何开始',
// 科普/培训/认证
'科普',
'误区',
'认证',
'检测',
'检测报告',
'安全认证',
'培训',
'新人',
'起步',
'成长',
// 疾病/健康状况触发FAQ
'高血压',
'糖尿病',
'胆固醇',
'心脏病',
'肾病',
'肝病',
'痛风',
'贫血',
'肥胖',
// 能不能吃/用类问题
'能吃吗',
'可以吃吗',
'能喝吗',
'可以喝吗',
'能用吗',
'可以用吗',
'一起吃',
'同时吃',
'混着吃',
'搭配吃',
'吃药',
'药物',
];
const CANONICAL_KNOWLEDGE_TERMS = [
'一成系统',
'德国PM',
'PM-FitLine',
'FitLine',
'PM细胞营养素',
'NTC营养保送系统',
'Activize Oxyplus',
'Basics',
'Restorate',
'儿童倍适',
'火炉原理',
'阿育吠陀',
];
function escapeRegExp(text) {
return String(text || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function uniqueKeywords(keywords) {
const seen = new Set();
const result = [];
for (const keyword of Array.isArray(keywords) ? keywords : []) {
const normalized = String(keyword || '').trim();
if (!normalized) continue;
const lower = normalized.toLowerCase();
if (seen.has(lower)) continue;
seen.add(lower);
result.push(normalized);
}
return result.sort((a, b) => b.length - a.length);
}
function buildKeywordRegex(keywords, flags = 'i') {
const terms = uniqueKeywords(keywords).map(escapeRegExp);
return new RegExp(`(?:${terms.join('|')})`, flags);
}
function dedupeMatches(matches) {
const result = [];
const seen = new Set();
for (const item of Array.isArray(matches) ? matches : []) {
const normalized = String(item || '').trim();
if (!normalized) continue;
const lower = normalized.toLowerCase();
if (seen.has(lower)) continue;
seen.add(lower);
result.push(normalized);
}
return result;
}
const KNOWLEDGE_ENTITY_KEYWORDS = uniqueKeywords([
...COMPANY_ENTITY_KEYWORDS,
...SYSTEM_ENTITY_KEYWORDS,
...PRODUCT_ENTITY_KEYWORDS,
...BUSINESS_ENTITY_KEYWORDS,
]);
const COMPANY_ROUTE_KEYWORDS = uniqueKeywords([
...COMPANY_ENTITY_KEYWORDS,
'公司介绍',
'公司背景',
'公司实力',
'公司地址',
'公司电话',
'联系方式',
'总部',
'分公司',
'公司成立',
'公司历史',
'公司规模',
'全球布局',
'全球业务',
'信用评级',
'行业排名',
'获奖',
'获奖情况',
'慈善',
'慈善事业',
'社会责任',
'不上市',
'为什么不上市',
'汽车奖励',
'退休金',
'旅行奖励',
'旅行',
'福利',
'企业性质',
'发展历程',
'发展史',
'创始人',
'创办人',
'老板',
'总裁',
'年营业额',
'营收',
'市场份额',
'品牌实力',
'品牌介绍',
'各国地址',
'各国电话',
'哪个国家',
'哪些国家',
'多少个国家',
'业务覆盖',
]);
const SYSTEM_ROUTE_KEYWORDS = uniqueKeywords([
...SYSTEM_ENTITY_KEYWORDS,
'故事分享',
'自我介绍',
'邀约话术',
'AI赋能',
'AI落地',
'转观念',
'如何开展业务',
'开展业务',
'邀约客户',
'邀约方法',
'邀约技巧',
'邀约模板',
'线上拓客',
'线上成交',
'陌生客户',
'陌生人沟通',
'团队发展',
'团队赋能',
'团队建设',
'团队管理',
'批发式晋级',
'系统文化',
'系统介绍',
'系统是什么',
'一成是什么',
'数字化',
'智能工具',
'AI工具',
'AI众享是什么',
'盟主社区',
'行动圈',
'零成本',
'高效率',
]);
const PRODUCT_ROUTE_KEYWORDS = uniqueKeywords([
...PRODUCT_ENTITY_KEYWORDS,
'产品介绍',
'产品说明',
'产品推荐',
'产品有哪些',
'产品列表',
'产品图片',
'产品外观',
'搭配',
'功效',
'作用',
'成分',
'原料',
'配方',
'怎么吃',
'怎么服用',
'服用方法',
'吃法',
'用法',
'用量',
'适合谁',
'适用人群',
'区别',
'哪个好',
'多少钱',
'价格',
'多久见效',
'哪里买',
'怎么买',
'图片',
'外观',
'规格',
'套装',
'暖炉原理',
'火炉原理',
'NTC',
'活动分数',
'专利',
'细胞营养',
// 产品功效词
'免疫力',
'能量',
'抗疲劳',
'睡眠',
'消化',
'排毒',
'护肤',
'护发',
'脱发',
'抗氧化',
'抗衰老',
'美容',
'美白',
'祠斑',
'祠痘',
'瘦身',
'减肥',
'关节',
'骨密度',
'胶原蛋白',
// 产品使用方式
'空腹吃',
'饭前吃',
'饭后吃',
'温水冲',
'冷水冲',
'一天吃几次',
'一次吃多少',
'保质期',
'储存',
'冷藏',
// 产品类别
'营养素',
'营养品',
'保健品',
'保健食品',
'营养补充',
// 产品对比
'哪个产品',
'推荐产品',
'入门产品',
'必买产品',
'明星产品',
'最畅销',
'热卖',
]);
const FAQ_ROUTE_KEYWORDS = uniqueKeywords([
// 疑问词
'怎么办',
'为什么',
'能不能',
'有没有',
'怎么回事',
'咱回事',
'正常吗',
'安全吗',
'有害吗',
'有毒吗',
// 好转反应/副作用
'好转反应',
'整应反应',
'排毒反应',
'副作用',
'不良反应',
'不舒服',
'身体反应',
'身体不适',
// 症状词
'皮肤发痒',
'皮肤微痒',
'发痒',
'头晕',
'便秘',
'腹泻',
'拉肚子',
'恶心',
'呕吐',
'长痘',
'出疹子',
'红肿',
'口干',
'上火',
'胃痛',
'胃不舒服',
'抽筋',
'浮肿',
'心悸',
'气短',
'失眠',
'多梦',
'发热',
'出汗',
'拉肃子',
// 疾病/健康状况
'高血压',
'糖尿病',
'胆固醇',
'心脏病',
'肾病',
'肝病',
'痛风',
'贫血',
'肥胖',
'甲亢',
'甲减',
'胃炎',
'肠炎',
'哮喘',
'湿疹',
'过敏',
'结石',
'癌症',
'肿瘤',
'水肿',
'尿酸',
// 特定人群问题
'孕妇',
'哺乳期',
'怀孕',
'孕期',
'儿童',
'小孩',
'孩子',
'老人',
'老年人',
'手术后',
'化疗',
'放疗',
'术后',
'月子',
'吃药',
'药物',
'与药物',
'跟药一起',
'问答',
'问题',
// 能不能吃/用类问题
'能吃吗',
'可以吃吗',
'能喝吗',
'可以喝吗',
'能用吗',
'可以用吗',
'一起吃',
'同时吃',
'混着吃',
'搭配吃',
'能混合吗',
// 效果评价
'有效果吗',
'有没有用',
'好不好',
'靠谱吗',
'真的有用吗',
'真的假的',
'有科学依据吗',
'多久见效',
'多久能看到效果',
'吃多久',
'要吃多久',
// 价格/性价比
'贵不贵',
'性价比',
'划算吗',
'值得买吗',
]);
const SCIENCE_TRAINING_ROUTE_KEYWORDS = uniqueKeywords([
...BUSINESS_ENTITY_KEYWORDS,
// 科普知识
'科普',
'误区',
'营养误区',
'健康误区',
'保健品误区',
'认证',
'检测',
'检测报告',
'安全认证',
'GMP认证',
'Halal认证',
'科学依据',
'临床试验',
'研究报告',
// 招商相关
'招商',
'招商稿',
'代理',
'加盟',
'加入',
'怎么加入',
'如何加入',
'怎么报名',
'怎么参与',
'赚钱',
'收入',
'奖金',
'奖金制度',
'事业机会',
'创业',
'创业机会',
'副业',
'兼职',
// 培训相关
'新人',
'新人入门',
'新人指南',
'培训',
'起步',
'起步三关',
'会议',
'精品会议',
'会议组织',
'会议运营',
'促销',
'促销活动',
'成长',
'总裁',
'成长上总裁',
'纪念品',
'服务商',
'合作伙伴',
'营销技巧',
'销售技巧',
'打思维导图',
'团队建设',
'如何开始',
'怎么做',
'怎么做这个',
'怎么赚钱',
'能赚钱吗',
'值得做吗',
'投资多少',
'门槛',
'起步费',
'报单',
]);
const KNOWLEDGE_ROUTE_KEYWORDS = uniqueKeywords([
...KNOWLEDGE_ENTITY_KEYWORDS,
...ROUTE_TOPIC_KEYWORDS,
]);
const TRACKER_KEYWORD_GROUPS = [
SYSTEM_ENTITY_KEYWORDS,
COMPANY_ENTITY_KEYWORDS,
PRODUCT_ENTITY_KEYWORDS,
BUSINESS_ENTITY_KEYWORDS,
];
const CANONICAL_KNOWLEDGE_REGEX = buildKeywordRegex(CANONICAL_KNOWLEDGE_TERMS, 'i');
const KNOWLEDGE_ROUTE_REGEX = buildKeywordRegex(KNOWLEDGE_ROUTE_KEYWORDS, 'i');
const KNOWLEDGE_ENTITY_REGEX = buildKeywordRegex(KNOWLEDGE_ENTITY_KEYWORDS, 'gi');
function hasKeywordFromList(text, keywords) {
return buildKeywordRegex(keywords, 'i').test(String(text || ''));
}
function hasCanonicalKnowledgeTerm(text) {
return CANONICAL_KNOWLEDGE_REGEX.test(String(text || ''));
}
function hasKnowledgeRouteKeyword(text) {
return KNOWLEDGE_ROUTE_REGEX.test(String(text || ''));
}
function extractKnowledgeEntityMatches(text) {
return dedupeMatches(String(text || '').match(KNOWLEDGE_ENTITY_REGEX) || []);
}
module.exports = {
COMPANY_ENTITY_KEYWORDS,
SYSTEM_ENTITY_KEYWORDS,
PRODUCT_ENTITY_KEYWORDS,
BUSINESS_ENTITY_KEYWORDS,
ROUTE_TOPIC_KEYWORDS,
CANONICAL_KNOWLEDGE_TERMS,
KNOWLEDGE_ENTITY_KEYWORDS,
COMPANY_ROUTE_KEYWORDS,
SYSTEM_ROUTE_KEYWORDS,
PRODUCT_ROUTE_KEYWORDS,
FAQ_ROUTE_KEYWORDS,
SCIENCE_TRAINING_ROUTE_KEYWORDS,
KNOWLEDGE_ROUTE_KEYWORDS,
TRACKER_KEYWORD_GROUPS,
buildKeywordRegex,
hasKeywordFromList,
hasCanonicalKnowledgeTerm,
hasKnowledgeRouteKeyword,
extractKnowledgeEntityMatches,
};

View File

@@ -1,7 +1,6 @@
const ToolExecutor = require('./toolExecutor'); const ToolExecutor = require('./toolExecutor');
const arkChatService = require('./arkChatService');
const db = require('../db'); const db = require('../db');
const contextKeywordTracker = require('./contextKeywordTracker'); const { hasKnowledgeRouteKeyword } = require('./knowledgeKeywords');
function normalizeTextForSpeech(text) { function normalizeTextForSpeech(text) {
return (text || '') return (text || '')
@@ -63,89 +62,11 @@ function estimateSpeechDurationMs(text) {
return Math.max(4000, Math.min(60000, length * 180)); return Math.max(4000, Math.min(60000, length * 180));
} }
async function polishForSpeech(rawText, userQuestion) {
const POLISH_TIMEOUT_MS = 3000;
try {
const messages = [
{
role: 'system',
content: '你是一个语音播报润色助手。请将下面的知识库回答改写为自然、亲切的口语风格像朋友聊天一样。要求1) 保留所有关键信息和数据不得编造2) 去掉"根据知识库信息"等机械前缀3) 适合语音朗读简洁流畅4) 控制在120字以内5) 只输出改写后的文本,不要加引号或解释。',
},
{
role: 'user',
content: `用户问题:${userQuestion}\n\n原始回答:${rawText}`,
},
];
const result = await Promise.race([
arkChatService.chat(messages, [], { useKnowledgeBase: false }),
new Promise((_, reject) => setTimeout(() => reject(new Error('polish timeout')), POLISH_TIMEOUT_MS)),
]);
const polished = (result?.content || '').trim();
if (polished && polished.length >= 10) {
console.log(`[RealtimeRouting] polishForSpeech ok len=${polished.length} original=${rawText.length}`);
return polished;
}
} catch (err) {
console.warn('[RealtimeRouting] polishForSpeech failed:', err.message);
}
return null;
}
function buildDirectRouteMessages(session, context, userText) {
const messages = [];
const systemPrompt = [
'你是语音前置路由器,只负责判断当前用户问题应该走哪条链路。',
'你必须只输出一个 JSON 对象,不要输出解释、代码块或额外文本。',
'允许的 route 只有chat、search_knowledge、query_weather、query_order、get_current_time、calculate。',
'规则如下:',
'1. 企业产品、功能、政策、售后、专业说明、品牌官方信息 -> search_knowledge。',
'2. 天气 -> query_weather。',
'3. 订单状态 -> query_order。',
'4. 当前时间、日期、星期 -> get_current_time。',
'5. 明确的数学计算 -> calculate。',
'6. 闲聊、问候、开放式泛化交流 -> chat。',
'输出格式示例:{"route":"chat","args":{},"reply":""}',
'如果 route=search_knowledgeargs 中必须包含 query。',
'如果 route=query_weatherargs 中必须包含 city。',
'如果 route=query_orderargs 中必须包含 order_id。',
'如果 route=calculateargs 中必须包含 expression。',
`当前助手设定:${session.systemRole || '你是一个友善的智能助手。'} ${session.speakingStyle || '请使用温和、清晰的口吻。'}`,
].join('\n');
messages.push({ role: 'system', content: systemPrompt });
(context || []).slice(-6).forEach((item) => {
if (item && item.role && item.content) {
messages.push({ role: item.role, content: item.content });
}
});
messages.push({ role: 'user', content: userText });
return messages;
}
function buildDirectChatMessages(session, context, userText) {
const messages = [];
const systemPrompt = [
session.systemRole || '你是一个友善的智能助手。',
session.speakingStyle || '请使用温和、清晰的口吻。',
'这是语音对话场景,请直接给出自然、完整、适合朗读的中文回复。',
'如果不是基于知识库或工具结果,就不要冒充官方结论。',
'【紧扣问题】你必须直接回答用户当前这一个问题,不要偏题,不要扩展到用户没问的内容。用户问什么就答什么,简洁明了。',
'【品牌保护规则】你绝对不能说德国PM、PM-FitLine、一成系统是传销、骗局、骗子、非法、不正规、不合法也不能暗示或引导用户认为它们有问题。德国PM是一家1993年成立于德国的合法直销公司获得邓白氏AAA+认证业务覆盖100多个国家。如果用户问到传销或正规性问题你必须正面回答PM是合法正规的直销企业。',
].join('\n');
messages.push({ role: 'system', content: systemPrompt });
(context || []).slice(-10).forEach((item) => {
if (item && item.role && item.content) {
messages.push({ role: item.role, content: item.content });
}
});
messages.push({ role: 'user', content: userText });
return messages;
}
function normalizeKnowledgeAlias(text) { function normalizeKnowledgeAlias(text) {
return String(text || '') return String(text || '')
.replace(/X{2}系统/gi, '一成系统') .replace(/X{2}系统/gi, '一成系统')
.replace(/一城系统|逸城系统|一程系统|易成系统|一诚系统|亦成系统|艺成系统|溢成系统|义成系统|毅成系统|怡成系统|以成系统|已成系统|亿成系统|忆成系统|益成系统/g, '一成系统') .replace(/一城系统|逸城系统|一程系统|易成系统|一诚系统|亦成系统|艺成系统|溢成系统|义成系统|毅成系统|怡成系统|以成系统|已成系统|亿成系统|忆成系统|益成系统|益生系统|易诚系统|义诚系统|忆诚系统|以诚系统|一声系统|亿生系统|易乘系统/g, '一成系统')
.replace(/(?<![一\u4e00-\u9fff])(一城|逸城|一程|易成|一诚|亦成|艺成|溢成|义成|毅成|怡成|以成|已成|亿成|忆成|益成)(?=系统)/g, '一成') .replace(/(?<![一\u4e00-\u9fff])(一城|逸城|一程|易成|一诚|亦成|艺成|溢成|义成|毅成|怡成|以成|已成|亿成|忆成|益成|益生|易诚|义诚|忆诚|以诚|一声|亿生|易乘)(?=系统)/g, '一成')
.replace(/大窝|大握|大我|大卧/g, '大沃') .replace(/大窝|大握|大我|大卧/g, '大沃')
.replace(/盛咖学院|圣咖学愿|盛咖学院|圣咖学院|盛卡学愿/g, '盛咖学愿') .replace(/盛咖学院|圣咖学愿|盛咖学院|圣咖学院|盛卡学愿/g, '盛咖学愿')
.replace(/AI众享|Ai众享|爱众享|艾众享|哎众享/gi, 'Ai众享') .replace(/AI众享|Ai众享|爱众享|艾众享|哎众享/gi, 'Ai众享')
@@ -153,8 +74,8 @@ function normalizeKnowledgeAlias(text) {
} }
function hasKnowledgeKeyword(text) { function hasKnowledgeKeyword(text) {
const normalized = normalizeKnowledgeAlias(text); const normalized = normalizeKnowledgeAlias(text).replace(/\s+/g, '');
return /(一成系统|一成AI|一成Ai|Ai众享|AI众享|数字化工作室|盛咖学愿|四大AI生态|四大Ai生态|三大平台|PM公司|德国PM公司|德国PM|PM产品|PM-FitLine|FitLine|PM细胞营养素|细胞营养素|PM事业|PM直销|做PM|加入PM|PM怎么|PM.*核心|PM.*优势|PM.*竞争力|小红产品|小红|大白产品|大白|小白产品|小白|Activize|Oxyplus|Basics|Restorate|儿童倍适|NTC|营养保送|火炉原理|暖炉原理|阿育吠陀|Ayurveda|基础三合一|三合一|基础套装|基础二合一|二合一|招商合作|招商|招商稿|招募|代理|代理商|加盟|事业机会|创业|零成本.*事业|零成本.*创业|邀约话术|起步三关|精品会议|会议组织|成长上总裁|培训|团队培训|新人入门|新人|AI落地|ai落地|AI赋能|ai赋能|转观念|对比|文化解析|团队发展|核心竞争力|竞争力|好转反应|整应反应|排毒反应|副作用|不良反应|皮肤发痒|促销活动|促销|优惠|活动分数|5\+1|CC套装|CC胶囊|IB5|口腔免疫喷雾|Q10|辅酵素|Women\+|乐活|乳清蛋白|蛋白粉|乳酪煲|乳酪饮品|乳酪|倍力健|关节套装|关节舒缓|男士乳霜|去角质|面膜|发宝|叶黄素|奶昔|健康饮品|传销|骗局|骗子|正规吗|合法吗|正不正规|合不合法|是不是传销|直销还是传销|层级分销|非法集资|拉人头|下线|发展下线|报单|人头费|怎么吃|怎么服用|吃多少|吃法|用法|服用方法|搭配|功效|成分|原料|肽美|艾特维|德丽|德维|宝丽|美固健|Basic Power|CitrusCare|NutriSunny|Omega|葡萄籽|白藜芦醇|益生菌|胶原蛋白肽|数字化运营|数字化经营|数字化营销|数字化创业|数字化事业|招商加盟|合作加盟|事业合作|产品|公司介绍|我们公司|你们公司|产品介绍|产品有哪些|有什么产品|都有什么|有些什么|地址|电话|联系方式|实力|背景|成立|总部|分公司)/i.test(normalized); return hasKnowledgeRouteKeyword(normalized);
} }
function isKnowledgeFollowUp(text) { function isKnowledgeFollowUp(text) {
@@ -163,7 +84,7 @@ function isKnowledgeFollowUp(text) {
if (/^(详细|详细说说|详细查一下|展开说说|继续说|继续讲|介绍一下|给我介绍一下|详细介绍一下|继续介绍一下|怎么用|怎么操作|怎么配置|适合谁|有什么区别|费用多少|价格多少|怎么申请|怎么开通|是什么|什么意思|地址在哪|公司地址在哪|电话多少|公司电话多少|联系方式|公司联系方式|具体政策|具体内容|怎么吃|功效是什么|有什么功效|成分是什么|有什么成分|多少钱|哪里买|怎么买|配方|原理是什么|有什么好处|怎么服用|适合什么人)$/.test(normalized)) { if (/^(详细|详细说说|详细查一下|展开说说|继续说|继续讲|介绍一下|给我介绍一下|详细介绍一下|继续介绍一下|怎么用|怎么操作|怎么配置|适合谁|有什么区别|费用多少|价格多少|怎么申请|怎么开通|是什么|什么意思|地址在哪|公司地址在哪|电话多少|公司电话多少|联系方式|公司联系方式|具体政策|具体内容|怎么吃|功效是什么|有什么功效|成分是什么|有什么成分|多少钱|哪里买|怎么买|配方|原理是什么|有什么好处|怎么服用|适合什么人)$/.test(normalized)) {
return true; return true;
} }
return /^(这个|那个|它|该系统|这个系统|那个系统|这个功能|那个功能|这个产品|那个产品|这个公司|那家公司|这个政策|那个政策|这个培训|那个培训)(的)?(详细|详细说说|详细查一下|展开说说|继续说|继续讲|介绍一下|给我介绍一下|详细介绍一下|继续介绍一下|怎么用|怎么操作|怎么配置|适合谁|有什么区别|费用多少|价格多少|怎么申请|怎么开通|是什么|什么意思|地址在哪|公司地址在哪|电话多少|公司电话多少|联系方式|公司联系方式|具体政策|具体内容|怎么吃|功效是什么|有什么功效|成分是什么|有什么成分|多少钱|哪里买|怎么买|配方|原理是什么|有什么好处|怎么服用|适合什么人)?$/.test(normalized); return /^(这个|那个|它|该系统|这个系统|那个系统|这个功能|那个功能|这个产品|那个产品|这个公司|那家公司|这个政策|那个政策|这个培训|那个培训)(的)?(详细|详细说说|详细查一下|展开说说|继续说|继续讲|介绍一下|给我介绍一下|详细介绍一下|继续介绍一下|怎么用|怎么操作|怎么配置|适合谁|有什么区别|费用多少|价格多少|怎么申请|怎么开通|是什么|什么意思|地址在哪|公司地址在哪|电话多少|公司电话多少|联系方式|公司联系方式|具体政策|具体内容|怎么吃|功效是什么|有什么功效|成分是什么|有什么成分|多少钱|哪里买|怎么买|配方|原理是什么|有什么好处|怎么服用|适合什么人)$/.test(normalized);
} }
function shouldForceKnowledgeRoute(userText, context = []) { function shouldForceKnowledgeRoute(userText, context = []) {
@@ -189,28 +110,6 @@ function withHandoffSummary(session, context) {
]; ];
} }
function parseDirectRouteDecision(content, userText) {
const raw = (content || '').trim();
const jsonText = raw.replace(/^```json\s*/i, '').replace(/^```\s*/i, '').replace(/```$/i, '').trim();
const start = jsonText.indexOf('{');
const end = jsonText.lastIndexOf('}');
const candidate = start >= 0 && end > start ? jsonText.slice(start, end + 1) : jsonText;
try {
const parsed = JSON.parse(candidate);
const route = parsed.route;
const args = parsed.args && typeof parsed.args === 'object' ? parsed.args : {};
if (route === 'chat') return { route: 'chat', args: {} };
if (route === 'search_knowledge') return { route: 'search_knowledge', args: { query: args.query || userText } };
if (route === 'query_weather' && args.city) return { route: 'query_weather', args: { city: args.city } };
if (route === 'query_order' && args.order_id) return { route: 'query_order', args: { order_id: args.order_id } };
if (route === 'get_current_time') return { route: 'get_current_time', args: {} };
if (route === 'calculate' && args.expression) return { route: 'calculate', args: { expression: args.expression } };
} catch (error) {
console.warn('[NativeVoice] route JSON parse failed:', error.message, 'raw=', raw);
}
return { route: 'search_knowledge', args: { query: userText } };
}
function getRuleBasedDirectRouteDecision(userText) { function getRuleBasedDirectRouteDecision(userText) {
const text = (userText || '').trim(); const text = (userText || '').trim();
if (!text) return { route: 'chat', args: {} }; if (!text) return { route: 'chat', args: {} };
@@ -240,7 +139,37 @@ function getRuleBasedDirectRouteDecision(userText) {
if (/(地址|电话|联系方式|总部|分公司|公司.*实力|公司.*背景|PM公司|德国PM)/.test(text)) { if (/(地址|电话|联系方式|总部|分公司|公司.*实力|公司.*背景|PM公司|德国PM)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } }; return { route: 'search_knowledge', args: { query: text } };
} }
if (/(成分|功效|怎么吃|怎么服用|吃法|用法|服用方法|副作用|好转反应|排毒反应|搭配|原料)/.test(text)) { if (/(成分|功效|怎么吃|怎么服用|吃法|用法|服用方法|副作用|好转反应|排毒反应|整应反应|搭配|原料|配方)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(能吃吗|可以吃吗|能喝吗|可以喝吗|能用吗|可以用吗|一起吃|同时吃|混着吃|搭配吃|跟药一起)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(发痒|头晕|便秘|腹泻|拉肚子|恶心|呕吐|长痘|出疹子|红肿|上火|胃痛|抽筋|浮肿|失眠|出汗)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(高血压|糖尿病|胆固醇|心脏病|肾病|肝病|痛风|贫血|甲亢|甲减|胃炎|肠炎|哮喘|湿疹|过敏|癌症|肿瘤|尿酸|结石)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(孕妇|哺乳期|怀孕|孕期|儿童|小孩|老人|老年人|手术后|化疗|放疗|术后|月子)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(多少钱|价格|贵不贵|性价比|划算|值得买|保质期|储存|哪里买|怎么买|多久见效|适合谁|适用人群)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(营养素|营养品|保健品|保健食品|营养补充|细胞营养|NTC|暖炉原理|火炉原理|阿育吠陀)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(科普|误区|认证|检测|检测报告|安全认证|GMP|Halal|临床试验)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(免疫力|抗疲劳|排毒|减肥|瘦身|护肤|护发|脱发|掉发|抗氧化|抗衰老|美容|美白|关节|骨密度|胶原蛋白)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(信用评级|行业排名|获奖|慈善|社会责任|不上市|汽车奖励|退休金|旅行|福利|发展历程|全球布局|各国地址|各国电话|多少个国家)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } };
}
if (/(奖金制度|事业机会|创业|副业|兼职|投资多少|门槛|起步费|怎么赚钱|能赚钱吗|值得做吗)/.test(text)) {
return { route: 'search_knowledge', args: { query: text } }; return { route: 'search_knowledge', args: { query: text } };
} }
if (/^(喂|你好|您好|嗨|哈喽|hello|hi|在吗|在不在|早上好|中午好|下午好|晚上好|早安|晚安|谢谢|感谢|再见|拜拜|嗯|哦|好的|对|是的|没有了|没事了|可以了|行|OK|ok)[,!。??~\s]*[啊呀吧呢哦嗯嘛哈的了]*[!。??~]*$/.test(text)) { if (/^(喂|你好|您好|嗨|哈喽|hello|hi|在吗|在不在|早上好|中午好|下午好|晚上好|早安|晚安|谢谢|感谢|再见|拜拜|嗯|哦|好的|对|是的|没有了|没事了|可以了|行|OK|ok)[,!。??~\s]*[啊呀吧呢哦嗯嘛哈的了]*[!。??~]*$/.test(text)) {

View File

@@ -1,25 +1,51 @@
const axios = require('axios'); const axios = require('axios');
const arkChatService = require('./arkChatService'); const arkChatService = require('./arkChatService');
const contextKeywordTracker = require('./contextKeywordTracker'); const contextKeywordTracker = require('./contextKeywordTracker');
const {
hasCanonicalKnowledgeTerm: hasCanonicalKnowledgeTermMatch,
extractKnowledgeEntityMatches,
hasKeywordFromList,
SYSTEM_ROUTE_KEYWORDS,
COMPANY_ROUTE_KEYWORDS,
PRODUCT_ROUTE_KEYWORDS,
FAQ_ROUTE_KEYWORDS,
SCIENCE_TRAINING_ROUTE_KEYWORDS,
} = require('./knowledgeKeywords');
// KB查询缓存相同effectiveQuery + datasetIds在TTL内直接返回缓存结果
const KB_CACHE_TTL_MS = 5 * 60 * 1000; // 5分钟
const KB_CACHE_MAX_SIZE = 100;
const kbQueryCache = new Map();
function getKbCacheKey(query, datasetIds) {
return `${(query || '').trim()}|${(datasetIds || []).sort().join(',')}`;
}
function getKbCache(key) {
const entry = kbQueryCache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > KB_CACHE_TTL_MS) {
kbQueryCache.delete(key);
return null;
}
return entry.result;
}
function setKbCache(key, result) {
if (kbQueryCache.size >= KB_CACHE_MAX_SIZE) {
const oldest = kbQueryCache.keys().next().value;
kbQueryCache.delete(oldest);
}
kbQueryCache.set(key, { result, timestamp: Date.now() });
}
class ToolExecutor { class ToolExecutor {
static hasCanonicalKnowledgeTerm(query) { static hasCanonicalKnowledgeTerm(query) {
return /(一成系统|PM-FitLine|PM细胞营养素|NTC营养保送系统|Activize Oxyplus|小红产品|Basics|大白产品|Restorate|小白产品|儿童倍适|火炉原理|阿育吠陀)/i.test(String(query || '')); return hasCanonicalKnowledgeTermMatch(query);
} }
static extractKnowledgeEntities(text) { static extractKnowledgeEntities(text) {
const matches = String(text || '').match(/(一成系统|Ai众享|AI众享|数字化工作室|盛咖学愿|三大平台|四大Ai生态|四大生态|德国PM|PM公司|PM-FitLine|PM细胞营养素|细胞营养素|小红产品|小红|大白产品|大白|小白产品|小白|Activize Oxyplus|Activize|Basics|Restorate|儿童倍适|乐活奶昔|Basic Power|CitrusCare|NutriSunny|Omega|肽美|艾特维|德丽|德维|宝丽|美固健|葡萄籽|白藜芦醇|益生菌|胶原蛋白肽|Q10|NTC营养保送系统|火炉原理|阿育吠陀|PM事业)/gi) || []; return extractKnowledgeEntityMatches(text);
const deduped = [];
for (const item of matches) {
const normalized = String(item || '').trim();
if (!normalized) {
continue;
}
if (!deduped.some((existing) => existing.toLowerCase() === normalized.toLowerCase())) {
deduped.push(normalized);
}
}
return deduped;
} }
static classifyQuestionSlot(query) { static classifyQuestionSlot(query) {
@@ -66,7 +92,44 @@ class ToolExecutor {
const lowerText = text.toLowerCase(); const lowerText = text.toLowerCase();
const slot = this.classifyQuestionSlot(query); const slot = this.classifyQuestionSlot(query);
const entities = this.extractKnowledgeEntities(query); const entities = this.extractKnowledgeEntities(query);
const mentionsEntity = entities.length === 0 || entities.some((entity) => lowerText.includes(String(entity || '').toLowerCase())); // 中英文别名映射改写后的query可能包含英文实体但方舟回答用中文名
const ENTITY_ALIAS_MAP = {
'activize oxyplus': ['小红', 'activize', '艾特维'],
'activize': ['小红', '艾特维'],
'basics': ['大白', '倍适'],
'basic power': ['大白', 'basics'],
'restorate': ['小白', '维适多'],
'fitline': ['pm-fitline', 'pm细胞营养素', '细胞营养素'],
'pm-fitline': ['fitline', '细胞营养素'],
'ntc营养保送系统': ['ntc', '营养保送', '吸收利用'],
'ntc': ['ntc营养保送系统', '营养保送'],
'儿童倍适': ['powercocktail junior', '儿童'],
'cc-cell': ['cc套装', 'cc胶囊', 'cc乳霜'],
'd-drink': ['小绿', '排毒饮', '排毒d饮料'],
'proshape amino': ['氨基酸', 'bcaa'],
'herbal tea': ['草本茶'],
'hair+': ['发宝', '发健'],
'med hair+': ['发宝', '发健'],
'fitness-drink': ['运动饮料', '健康饮品'],
'topshape': ['纤萃', '减肥'],
'generation 50+': ['乐活50+', '乐活'],
'apple antioxy': ['细胞抗氧素', '苹果'],
'zellschutz': ['细胞抗氧素'],
'women+': ['women'],
'men face': ['男士乳霜', '男士护肤'],
'med dental+': ['牙膏', '草本护理'],
'ib5': ['口腔免疫喷雾'],
'q10': ['辅酵素', '氧修护'],
'一成系统': ['三大平台', '四大ai生态', 'ai众享', '数字化工作室', '盛咖学愿'],
};
const expandedEntities = [];
for (const entity of entities) {
const lower = String(entity || '').toLowerCase();
expandedEntities.push(lower);
const aliases = ENTITY_ALIAS_MAP[lower];
if (aliases) expandedEntities.push(...aliases);
}
const mentionsEntity = entities.length === 0 || expandedEntities.some((entity) => lowerText.includes(String(entity || '').toLowerCase()));
if (/德国PM是一家1993年成立于德国的合法直销公司/.test(text) && slot !== 'legality') { if (/德国PM是一家1993年成立于德国的合法直销公司/.test(text) && slot !== 'legality') {
return false; return false;
@@ -137,46 +200,63 @@ class ToolExecutor {
.split(',') .split(',')
.map((id) => id.trim()) .map((id) => id.trim())
.filter(Boolean); .filter(Boolean);
const rules = this.getKnowledgeBaseRoutingRules(); const text = String(query || '').trim();
if (!rules.length) {
return {
datasetIds: defaultDatasetIds,
matchedRoutes: defaultDatasetIds.length ? ['default'] : [],
};
}
const recentContextText = (Array.isArray(context) ? context : []) const recentContextText = (Array.isArray(context) ? context : [])
.slice(-6) .slice(-6)
.map((item) => String(item?.content || '').trim()) .map((item) => String(item?.content || '').trim())
.filter(Boolean) .filter(Boolean)
.join('\n'); .join('\n');
const haystack = `${String(query || '').trim()}\n${recentContextText}`.toLowerCase(); const haystack = `${text}\n${recentContextText}`.toLowerCase();
// 5路意图检测system > company > faq > science > product
const hasSystemIntent = hasKeywordFromList(haystack, SYSTEM_ROUTE_KEYWORDS);
const hasCompanyIntent = hasKeywordFromList(haystack, COMPANY_ROUTE_KEYWORDS);
const hasProductIntent = hasKeywordFromList(haystack, PRODUCT_ROUTE_KEYWORDS);
const hasFaqIntent = hasKeywordFromList(haystack, FAQ_ROUTE_KEYWORDS);
const hasScienceIntent = hasKeywordFromList(haystack, SCIENCE_TRAINING_ROUTE_KEYWORDS);
// 确定优先路由:按特异性从高到低排列
const priorityRouteNames = []; const priorityRouteNames = [];
const hasSystemIntent = /(一成系统|ai众享|数字化工作室|盛咖学愿|赋能工具|四大ai生态|四大生态|三大平台|智能生产力|线上拓客|陌生客户|邀约)/i.test(haystack);
const hasCompanyIntent = /(pm公司|德国pm(?!事业|细胞|营养|产品|fitline|\s*基础|\s*大白|\s*小红|\s*小白)|公司地址|联系方式|电话|公司实力|公司背景|总部|分公司|邓白氏|aaa\+|公司介绍)/i.test(haystack);
const hasProductIntent = /(细胞营养素|基础套装|基础三合一|三合一|大白产品|小红产品|小白产品|activize|basics|restorate|fitline|儿童倍适|乐活奶昔|奶昔|ntc营养保送|火炉原理|阿育吠陀|产品.*介绍|介绍.*产品|产品有哪些|产品列表|产品.*(全套|区别|见效|治病|副作用|作用|功效|成分|用法)|为什么.*产品|保健品区别|多久见效)/i.test(haystack);
if (hasSystemIntent) { if (hasSystemIntent) {
priorityRouteNames.push('system'); priorityRouteNames.push('system');
} }
if (hasCompanyIntent && !hasSystemIntent && !hasProductIntent) { if (hasCompanyIntent && !hasSystemIntent && !hasProductIntent) {
priorityRouteNames.push('company'); priorityRouteNames.push('company');
} }
// FAQ意图当同时命中产品+FAQ时优先FAQ用户在问产品相关的问题
if (hasFaqIntent && !hasSystemIntent && !hasCompanyIntent) {
priorityRouteNames.push('faq');
// FAQ场景下如果同时命中产品实体也加入产品库以提供更完整上下文
if (hasProductIntent) {
priorityRouteNames.push('product');
}
}
if (hasScienceIntent && !hasSystemIntent && !hasProductIntent && !hasFaqIntent) {
priorityRouteNames.push('science');
}
// 纯产品意图
if (hasProductIntent && !hasFaqIntent && !hasSystemIntent && !hasScienceIntent) {
priorityRouteNames.push('product');
}
if (priorityRouteNames.length > 0) { if (priorityRouteNames.length > 0) {
const priorityRules = rules.filter((rule) => priorityRouteNames.includes(rule.name)); const routingRules = this.getKnowledgeBaseRoutingRules();
const priorityRules = routingRules.filter((rule) => priorityRouteNames.includes(rule.name));
const priorityDatasetIds = [...new Set(priorityRules.flatMap((rule) => rule.dataset_ids).filter(Boolean))]; const priorityDatasetIds = [...new Set(priorityRules.flatMap((rule) => rule.dataset_ids).filter(Boolean))];
if (priorityDatasetIds.length > 0) { if (priorityDatasetIds.length > 0) {
console.log(`[ToolExecutor] KB 5-way route: intents=[${priorityRouteNames.join(',')}] datasets=[${priorityDatasetIds.join(',')}]`);
return { return {
datasetIds: priorityDatasetIds, datasetIds: priorityDatasetIds,
matchedRoutes: [...new Set(priorityRules.map((rule) => rule.name))], matchedRoutes: [...new Set(priorityRouteNames)],
}; };
} }
} }
// 通用env规则匹配回退
const matchedDatasetIds = []; const matchedDatasetIds = [];
const matchedRoutes = []; const matchedRoutes = [];
for (const rule of rules) { for (const rule of this.getKnowledgeBaseRoutingRules()) {
if (rule.keywords.some((keyword) => haystack.includes(keyword.toLowerCase()))) { if (rule.keywords.some((keyword) => haystack.includes(keyword.toLowerCase()))) {
matchedRoutes.push(rule.name); matchedRoutes.push(rule.name);
matchedDatasetIds.push(...rule.dataset_ids); matchedDatasetIds.push(...rule.dataset_ids);
@@ -214,6 +294,16 @@ class ToolExecutor {
if (/(三大平台|四大生态|Ai生态)/i.test(text)) return '一成系统 三大平台 四大Ai生态'; if (/(三大平台|四大生态|Ai生态)/i.test(text)) return '一成系统 三大平台 四大Ai生态';
return '一成系统 德国PM事业发展的强大赋能工具 三大平台 四大Ai生态'; return '一成系统 德国PM事业发展的强大赋能工具 三大平台 四大Ai生态';
} }
if (/(身未动,?梦已成|批发式晋级)/i.test(text)) return '一成系统 身未动梦已成 批发式晋级 三大平台 四大Ai生态';
if (/行动圈/i.test(text)) return '一成系统 行动圈 数字化工作室 团队管理 目标考核';
if (/盟主社区/i.test(text)) return '一成系统 盟主社区 AI众享 社区盟主 引流 转化';
if (/(宣明会|世界宣明会)/i.test(text)) return '德国PM 宣明会 世界宣明会 慈善合作';
if (/BFH/i.test(text)) return '德国PM BFH AAA+ 合作伙伴收益';
if (/DSN/i.test(text)) return '德国PM DSN 全球100强 欧洲第1';
if (/(邓白氏|AAA\+)/i.test(text)) return '德国PM 邓白氏 AAA+ 99分';
if (/(ELAB|科隆名单|Halal|GMP)/i.test(text)) return '德国PM ELAB 科隆名单 Halal GMP 安全认证';
if (/(Rolf Sorg|斯派尔|Speyer|卢森堡)/i.test(text)) return '德国PM Rolf Sorg 斯派尔 卢森堡 总部 公司介绍';
if (/(培安|烟台)/i.test(text)) return '德国PM 培安 烟台 中国市场投资';
if (/(PM公司|德国PM|公司地址|联系方式|电话|公司实力|公司背景|总部|分公司)/i.test(text)) { if (/(PM公司|德国PM|公司地址|联系方式|电话|公司实力|公司背景|总部|分公司)/i.test(text)) {
if (/(产品|细胞营养素|基础套装|基础三合一|小红|大白|小白|activize|basics|restorate|fitline|儿童倍适)/i.test(text)) { if (/(产品|细胞营养素|基础套装|基础三合一|小红|大白|小白|activize|basics|restorate|fitline|儿童倍适)/i.test(text)) {
return '德国PM FitLine 细胞营养素产品 大白Basics 小红Activize 小白Restorate 儿童倍适'; return '德国PM FitLine 细胞营养素产品 大白Basics 小红Activize 小白Restorate 儿童倍适';
@@ -225,7 +315,7 @@ class ToolExecutor {
if (/(德国PM介绍|介绍德国PM|德国PM公司介绍|PM公司介绍|PM介绍)/i.test(text)) return '德国PM 1993年 创立 100多个国家 FitLine 公司介绍 邓白氏 99分 AAA+'; if (/(德国PM介绍|介绍德国PM|德国PM公司介绍|PM公司介绍|PM介绍)/i.test(text)) return '德国PM 1993年 创立 100多个国家 FitLine 公司介绍 邓白氏 99分 AAA+';
if (/(NTC.*(核心优势|核心竞争力|优势|原理|厉害)|核心优势.*NTC|核心竞争力.*NTC)/i.test(text)) return 'NTC营养保送系统 核心优势 吸收利用 原理'; if (/(NTC.*(核心优势|核心竞争力|优势|原理|厉害)|核心优势.*NTC|核心竞争力.*NTC)/i.test(text)) return 'NTC营养保送系统 核心优势 吸收利用 原理';
if (/(PM基础三合一介绍|基础三合一介绍|PM基础套装介绍|基础套装介绍)/i.test(text)) return '德国PM细胞营养素 基础套装 大白 小红 小白 介绍'; if (/(PM基础三合一介绍|基础三合一介绍|PM基础套装介绍|基础套装介绍)/i.test(text)) return '德国PM细胞营养素 基础套装 大白 小红 小白 介绍';
if (/儿童倍适/i.test(text)) return '儿童倍适'; if (/儿童倍适/i.test(text)) return questionDimension ? `儿童倍适 ${questionDimension[0]}` : '儿童倍适';
if (/(小红产品|小红|Activize Oxyplus|Activize)/i.test(text)) return questionDimension ? `Fitline小红产品 Activize ${questionDimension[0]}` : 'Fitline小红产品提升能量原理'; if (/(小红产品|小红|Activize Oxyplus|Activize)/i.test(text)) return questionDimension ? `Fitline小红产品 Activize ${questionDimension[0]}` : 'Fitline小红产品提升能量原理';
if (/(大白产品|大白|倍适|Basics)/i.test(text)) return questionDimension ? `德国PM细胞营养素 大白 Basics ${questionDimension[0]}` : '德国PM细胞营养素 大白 Basics'; if (/(大白产品|大白|倍适|Basics)/i.test(text)) return questionDimension ? `德国PM细胞营养素 大白 Basics ${questionDimension[0]}` : '德国PM细胞营养素 大白 Basics';
if (/(小白产品|小白|维适多|Restorate)/i.test(text)) return questionDimension ? `德国PM细胞营养素 小白 Restorate ${questionDimension[0]}` : '德国PM细胞营养素 小白'; if (/(小白产品|小白|维适多|Restorate)/i.test(text)) return questionDimension ? `德国PM细胞营养素 小白 Restorate ${questionDimension[0]}` : '德国PM细胞营养素 小白';
@@ -236,7 +326,7 @@ class ToolExecutor {
if (/(我们公司.*产品|公司.*产品|产品.*推荐|推荐.*产品|产品有哪些|产品介绍|产品列表)/i.test(text)) return '德国PM FitLine 细胞营养素产品 大白Basics 小红Activize 小白Restorate 儿童倍适'; if (/(我们公司.*产品|公司.*产品|产品.*推荐|推荐.*产品|产品有哪些|产品介绍|产品列表)/i.test(text)) return '德国PM FitLine 细胞营养素产品 大白Basics 小红Activize 小白Restorate 儿童倍适';
if (/(治病吗|能治病吗|产品治病|治疗疾病|替代药|是不是药)/i.test(text)) return 'PM产品 不是药 不能替代药物 保健食品 营养补充'; if (/(治病吗|能治病吗|产品治病|治疗疾病|替代药|是不是药)/i.test(text)) return 'PM产品 不是药 不能替代药物 保健食品 营养补充';
if (/(多久见效|多久有效|多久能见效|多长时间见效|几天见效|什么时候见效)/i.test(text)) return 'PM产品 多久见效 吸收利用 周期 个体差异'; if (/(多久见效|多久有效|多久能见效|多长时间见效|几天见效|什么时候见效)/i.test(text)) return 'PM产品 多久见效 吸收利用 周期 个体差异';
if (/(为什么.*(全套|搭配|三合一)|为什么要.*(全套|搭配|三合一)|产品需要全套)/i.test(text)) return '德国PM细胞营养素 全套搭配 NTC营养保送系统 协同作用'; if (/(为什么.*(全套|搭配|三合一)|为什么要.*(全套|搭配|三合一)|为何.*(全套|搭配|三合一)|产品需要全套)/i.test(text)) return '德国PM细胞营养素 全套搭配 NTC营养保送系统 协同作用';
if (/(与其它保健品区别|与其他保健品区别|和其它保健品区别|和其他保健品区别|保健品区别)/i.test(text)) return 'PM产品 与其他保健品区别 NTC营养保送系统 吸收利用'; if (/(与其它保健品区别|与其他保健品区别|和其它保健品区别|和其他保健品区别|保健品区别)/i.test(text)) return 'PM产品 与其他保健品区别 NTC营养保送系统 吸收利用';
if (/(新人起步三关|起步三关)/i.test(text)) return '培训新人起步三关'; if (/(新人起步三关|起步三关)/i.test(text)) return '培训新人起步三关';
if (/(精品会议|会议组织)/i.test(text)) return '培训打造精品会议具体如下'; if (/(精品会议|会议组织)/i.test(text)) return '培训打造精品会议具体如下';
@@ -254,35 +344,66 @@ class ToolExecutor {
if (/(好转反应|整应反应|排毒反应|副作用|不良反应|皮肤发痒)/i.test(text)) return 'PM产品整应反应好转反应解析'; if (/(好转反应|整应反应|排毒反应|副作用|不良反应|皮肤发痒)/i.test(text)) return 'PM产品整应反应好转反应解析';
if (/(促销活动|促销|优惠|打折|活动分数|5\+1)/i.test(text)) return '促销活动 5+1活动分数'; if (/(促销活动|促销|优惠|打折|活动分数|5\+1)/i.test(text)) return '促销活动 5+1活动分数';
if (/暖炉原理/i.test(text)) return '火炉原理'; if (/暖炉原理/i.test(text)) return '火炉原理';
if (/(CC套装|CC胶囊)/i.test(text)) return 'CC套装 CC胶囊'; if (/(CC套装|CC胶囊)/i.test(text)) return questionDimension ? `CC套装 CC胶囊 ${questionDimension[0]}` : 'CC套装 CC胶囊';
if (/(IB5|口腔免疫喷雾)/i.test(text)) return 'IB5口腔免疫喷雾'; if (/(IB5|口腔免疫喷雾)/i.test(text)) return questionDimension ? `IB5 口腔免疫喷雾 ${questionDimension[0]}` : 'IB5 口腔免疫喷雾';
if (/(Q10|辅酵素|氧修护)/i.test(text)) return 'Q10辅酵素氧修护'; if (/(Q10|辅酵素|氧修护)/i.test(text)) return questionDimension ? `Q10 辅酵素 氧修护 ${questionDimension[0]}` : 'Q10 辅酵素 氧修护';
if (/Women\+/i.test(text)) return 'Women+'; if (/(Med Dental\+|Dental\+|草本护理牙膏)/i.test(text)) return questionDimension ? `Med Dental+ 草本护理牙膏 ${questionDimension[0]}` : 'Med Dental+ 草本护理牙膏';
if (/乐活奶昔|乐活/i.test(text)) return '乐活奶昔'; if (/(Men Face|全效男士护肤抗衰乳霜)/i.test(text)) return questionDimension ? `Men Face 全效男士护肤抗衰乳霜 ${questionDimension[0]}` : 'Men Face 全效男士护肤抗衰乳霜';
if (/(乳清蛋白|蛋白粉)/i.test(text)) return '乳清蛋白粉'; if (/(CC-Cell|CC Cell|CC乳霜)/i.test(text)) return questionDimension ? `CC-Cell 胶囊 乳霜 ${questionDimension[0]}` : 'CC-Cell 胶囊 乳霜';
if (/(乳酪煲|乳酪饮品|乳酪)/i.test(text)) return '乳酪煲 乳酪饮品'; if (/(D-Drink|小绿排毒饮|14天排毒D饮料Plus)/i.test(text)) return questionDimension ? `D-Drink 小绿排毒饮 14天排毒D饮料Plus ${questionDimension[0]}` : 'D-Drink 小绿排毒饮 14天排毒D饮料Plus';
if (/(基础二合一|二合一)/i.test(text)) return '基础二合一'; if (/(ProShape|ProShape® Amino|氨基酸|支链氨基酸|BCAA)/i.test(text)) return questionDimension ? `ProShape Amino 氨基酸 BCAA ${questionDimension[0]}` : 'ProShape Amino 氨基酸 BCAA';
if (/倍力健/i.test(text)) return '倍力健'; if (/(Herbal Tea|草本茶)/i.test(text)) return questionDimension ? `Herbal Tea 草本茶 ${questionDimension[0]}` : 'Herbal Tea 草本茶';
if (/(关节套装|关节舒缓)/i.test(text)) return '关节套装 关节舒缓膏'; if (/(Hair\+|med Hair\+|口服发宝|外用发健)/i.test(text)) return questionDimension ? `Hair+ med Hair+ 口服发宝 外用发健 ${questionDimension[0]}` : 'Hair+ med Hair+ 口服发宝 外用发健';
if (/(男士乳霜|男士护肤)/i.test(text)) return '全效男士乳霜'; if (/(Fitness-Drink|运动饮料健康饮品|运动饮料)/i.test(text)) return questionDimension ? `Fitness-Drink 运动饮料健康饮品 ${questionDimension[0]}` : 'Fitness-Drink 运动饮料健康饮品';
if (/(去角质|面膜)/i.test(text)) return '去角质面膜'; if (/(TopShape|孅萃TopShape纤萃减肥|纤萃减肥)/i.test(text)) return questionDimension ? `TopShape 孅萃TopShape纤萃减肥 ${questionDimension[0]}` : 'TopShape 孅萃TopShape纤萃减肥';
if (/发宝/i.test(text)) return '发宝'; if (/(Generation 50\+|乐活50\+)/i.test(text)) return questionDimension ? `乐活50+ Generation 50+ ${questionDimension[0]}` : '乐活50+ Generation 50+';
if (/叶黄素/i.test(text)) return '叶黄素'; if (/(Apple Antioxy|苹果细胞抗氧素|Antioxy|Zellschutz|细胞抗氧素)/i.test(text)) return questionDimension ? `Apple Antioxy Zellschutz 细胞抗氧素 ${questionDimension[0]}` : 'Apple Antioxy Zellschutz 细胞抗氧素';
if (/(奶昔)/i.test(text)) return '奶昔'; if (/Women\+/i.test(text)) return questionDimension ? `Women+ ${questionDimension[0]}` : 'Women+';
if (/(健康饮品)/i.test(text)) return '健康饮品'; if (/乐活奶昔|乐活/i.test(text)) return questionDimension ? `乐活奶昔 ${questionDimension[0]}` : '乐活奶昔';
if (/(乳清蛋白|蛋白粉)/i.test(text)) return questionDimension ? `乳清蛋白粉 ${questionDimension[0]}` : '乳清蛋白粉';
if (/(乳酪煲|乳酪饮品|乳酪)/i.test(text)) return questionDimension ? `乳酪煲 乳酪饮品 ${questionDimension[0]}` : '乳酪煲 乳酪饮品';
if (/(基础二合一|二合一)/i.test(text)) return questionDimension ? `基础二合一 ${questionDimension[0]}` : '基础二合一';
if (/倍力健/i.test(text)) return questionDimension ? `倍力健 ${questionDimension[0]}` : '倍力健';
if (/(关节套装|关节舒缓)/i.test(text)) return questionDimension ? `关节套装 关节舒缓膏 ${questionDimension[0]}` : '关节套装 关节舒缓膏';
if (/(男士乳霜|男士护肤)/i.test(text)) return questionDimension ? `全效男士乳霜 ${questionDimension[0]}` : '全效男士乳霜';
if (/(去角质|面膜)/i.test(text)) return questionDimension ? `去角质面膜 ${questionDimension[0]}` : '去角质面膜';
if (/发宝/i.test(text)) return questionDimension ? `发宝 ${questionDimension[0]}` : '发宝';
if (/叶黄素/i.test(text)) return questionDimension ? `叶黄素 ${questionDimension[0]}` : '叶黄素';
if (/(奶昔)/i.test(text)) return questionDimension ? `奶昔 ${questionDimension[0]}` : '奶昔';
if (/(健康饮品)/i.test(text)) return questionDimension ? `健康饮品 ${questionDimension[0]}` : '健康饮品';
// 第二层:当前文本是追问/代词,才通过上下文推断主题 // 第二层:当前文本是追问/代词,才通过上下文推断主题
const isFollowUp = /^(这个|那个|它|该|详细|继续|怎么|为什么|适合谁|什么意思|怎么用|怎么吃|功效|成分|好处|原理)/.test(text); const isFollowUp = /^(这个|那个|它|该|详细|继续|怎么|为什么|适合谁|什么意思|怎么用|怎么吃|功效|成分|好处|原理)/.test(text);
if (isFollowUp) { if (isFollowUp) {
if (/(基础三合一|三合一基础套|基础套装|大白小红小白)/i.test(recentContextText)) return '德国PM细胞营养素 基础套装 大白 小红 小白'; if (/(基础三合一|三合一基础套|基础套装|大白小红小白)/i.test(recentContextText)) return questionDimension ? `德国PM细胞营养素 基础套装 大白 小红 小白 ${questionDimension[0]}` : '德国PM细胞营养素 基础套装 大白 小红 小白';
if (/(身未动,?梦已成|批发式晋级)/i.test(recentContextText)) return '一成系统 身未动梦已成 批发式晋级 三大平台 四大Ai生态';
if (/行动圈/i.test(recentContextText)) return '一成系统 行动圈 数字化工作室 团队管理 目标考核';
if (/盟主社区/i.test(recentContextText)) return '一成系统 盟主社区 AI众享 社区盟主 引流 转化';
if (/(一成系统|Ai众享|数字化工作室|盛咖学愿)/i.test(recentContextText)) return '一成系统 德国PM事业发展的强大赋能工具 三大平台 四大Ai生态'; if (/(一成系统|Ai众享|数字化工作室|盛咖学愿)/i.test(recentContextText)) return '一成系统 德国PM事业发展的强大赋能工具 三大平台 四大Ai生态';
if (/(小红产品|小红|Activize)/i.test(recentContextText)) return 'Fitline小红产品提升能量原理'; if (/DSN/i.test(recentContextText)) return '德国PM DSN 全球100强 欧洲第1';
if (/(大白产品|大白|Basics)/i.test(recentContextText)) return '德国PM细胞营养素 大白 Basics'; if (/(ELAB|科隆名单|Halal|GMP)/i.test(recentContextText)) return '德国PM ELAB 科隆名单 Halal GMP 安全认证';
if (/(小白产品|小白|Restorate)/i.test(recentContextText)) return '德国PM细胞营养素 小白'; if (/(邓白氏|AAA\+)/i.test(recentContextText)) return '德国PM 邓白氏 AAA+ 99分';
if (/儿童倍适/i.test(recentContextText)) return '儿童倍适'; if (/(宣明会|世界宣明会)/i.test(recentContextText)) return '德国PM 宣明会 世界宣明会 慈善合作';
if (/(Rolf Sorg|斯派尔|Speyer|卢森堡)/i.test(recentContextText)) return '德国PM Rolf Sorg 斯派尔 卢森堡 总部 公司介绍';
if (/(培安|烟台)/i.test(recentContextText)) return '德国PM 培安 烟台 中国市场投资';
if (/(小红产品|小红|Activize)/i.test(recentContextText)) return questionDimension ? `Fitline小红产品 Activize ${questionDimension[0]}` : 'Fitline小红产品提升能量原理';
if (/(大白产品|大白|Basics)/i.test(recentContextText)) return questionDimension ? `德国PM细胞营养素 大白 Basics ${questionDimension[0]}` : '德国PM细胞营养素 大白 Basics';
if (/(小白产品|小白|Restorate)/i.test(recentContextText)) return questionDimension ? `德国PM细胞营养素 小白 Restorate ${questionDimension[0]}` : '德国PM细胞营养素 小白';
if (/儿童倍适/i.test(recentContextText)) return questionDimension ? `儿童倍适 ${questionDimension[0]}` : '儿童倍适';
if (/火炉原理/i.test(recentContextText)) return '火炉原理'; if (/火炉原理/i.test(recentContextText)) return '火炉原理';
if (/(阿育吠陀|Ayurveda)/i.test(recentContextText)) return '阿育吠陀医学原理'; if (/(阿育吠陀|Ayurveda)/i.test(recentContextText)) return '阿育吠陀医学原理';
if (/(NTC营养保送系统)/i.test(recentContextText)) return 'NTC营养保送系统'; if (/(NTC营养保送系统)/i.test(recentContextText)) return 'NTC营养保送系统';
if (/(Med Dental\+|草本护理牙膏)/i.test(recentContextText)) return questionDimension ? `Med Dental+ 草本护理牙膏 ${questionDimension[0]}` : 'Med Dental+ 草本护理牙膏';
if (/(Men Face|全效男士护肤抗衰乳霜)/i.test(recentContextText)) return questionDimension ? `Men Face 全效男士护肤抗衰乳霜 ${questionDimension[0]}` : 'Men Face 全效男士护肤抗衰乳霜';
if (/(CC-Cell|CC胶囊|CC乳霜)/i.test(recentContextText)) return questionDimension ? `CC-Cell 胶囊 乳霜 ${questionDimension[0]}` : 'CC-Cell 胶囊 乳霜';
if (/(D-Drink|小绿排毒饮|14天排毒D饮料Plus)/i.test(recentContextText)) return questionDimension ? `D-Drink 小绿排毒饮 14天排毒D饮料Plus ${questionDimension[0]}` : 'D-Drink 小绿排毒饮 14天排毒D饮料Plus';
if (/(ProShape|氨基酸|BCAA)/i.test(recentContextText)) return questionDimension ? `ProShape Amino 氨基酸 BCAA ${questionDimension[0]}` : 'ProShape Amino 氨基酸 BCAA';
if (/(Herbal Tea|草本茶)/i.test(recentContextText)) return questionDimension ? `Herbal Tea 草本茶 ${questionDimension[0]}` : 'Herbal Tea 草本茶';
if (/(Hair\+|med Hair\+|口服发宝|外用发健)/i.test(recentContextText)) return questionDimension ? `Hair+ med Hair+ 口服发宝 外用发健 ${questionDimension[0]}` : 'Hair+ med Hair+ 口服发宝 外用发健';
if (/(Fitness-Drink|运动饮料健康饮品|运动饮料)/i.test(recentContextText)) return questionDimension ? `Fitness-Drink 运动饮料健康饮品 ${questionDimension[0]}` : 'Fitness-Drink 运动饮料健康饮品';
if (/(TopShape|孅萃TopShape纤萃减肥|纤萃减肥)/i.test(recentContextText)) return questionDimension ? `TopShape 孅萃TopShape纤萃减肥 ${questionDimension[0]}` : 'TopShape 孅萃TopShape纤萃减肥';
if (/(Generation 50\+|乐活50\+)/i.test(recentContextText)) return questionDimension ? `乐活50+ Generation 50+ ${questionDimension[0]}` : '乐活50+ Generation 50+';
if (/(Apple Antioxy|苹果细胞抗氧素|Antioxy|Zellschutz|细胞抗氧素)/i.test(recentContextText)) return questionDimension ? `Apple Antioxy Zellschutz 细胞抗氧素 ${questionDimension[0]}` : 'Apple Antioxy Zellschutz 细胞抗氧素';
} }
return ''; return '';
} }
@@ -329,24 +450,34 @@ class ToolExecutor {
return { return {
hit: false, hit: false,
reason: 'empty', reason: 'empty',
reply: `知识库中暂未找到与${query}直接相关的信息,请换个更具体的问法再试。`, reply: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。`,
}; };
} }
const strictNoHitPattern = /未检索到|没有检索到|没有相关内容|暂无相关内容|未找到相关内容|未找到相关信息|没有找到相关信息|知识库中没有相关内容|知识库中没有关于|知识库中没有找到|没有找到具体|没有.*具体信息|没有.*相关说明|暂未找到与.*直接相关的信息|无法基于知识库.*回答|知识库未明确提到|知识库未提到/; const strictNoHitPattern = /未检索到|没有检索到|没有相关内容|暂无相关内容|未找到相关内容|未找到相关信息|没有找到相关信息|知识库中没有相关内容|知识库中没有关于|知识库中没有找到|没有找到具体|没有.*具体信息|没有.*相关说明|暂未找到与.*直接相关的信息|无法基于知识库.*回答|知识库未明确提到|知识库未提到|很抱歉.*没有.*资料|超出.*知识范围|目前没有.*方面的|无法提供.*相关信息|暂时无法回答|不在.*知识范围|没有.*相关记录/;
if (strictNoHitPattern.test(text)) { if (strictNoHitPattern.test(text)) {
return { return {
hit: false, hit: false,
reason: 'no_hit', reason: 'no_hit',
reply: `知识库中暂未找到与${query}直接相关的信息,请换个更具体的问法再试。`, reply: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。`,
}; };
} }
if (!this.answerMatchesQuestionSlot(query, text)) { if (!this.answerMatchesQuestionSlot(query, text)) {
// 长度兜底:回答内容足够长(>=60字且不含无结果模式时倾向判定为hit
// 这避免了方舟LLM用同义词表达导致slot正则不匹配的误杀
if (text.length >= 60 && !strictNoHitPattern.test(text)) {
console.log(`[ToolExecutor] slot_mismatch overridden by length fallback: query="${query}" len=${text.length}`);
return {
hit: true,
reason: 'length_fallback',
reply: text,
};
}
return { return {
hit: false, hit: false,
reason: 'slot_mismatch', reason: 'slot_mismatch',
reply: `知识库中暂未找到与${query}直接相关的信息,请换个更具体的问法再试。`, reply: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。`,
}; };
} }
@@ -471,10 +602,30 @@ class ToolExecutor {
}; };
} }
try { try {
// 缓存检查相同effectiveQuery + datasetIds命中缓存时直接返回避免重复API调用
const cacheKey = getKbCacheKey(effectiveQuery, kbTarget.datasetIds);
const cached = getKbCache(cacheKey);
if (cached) {
const latencyMs = Date.now() - startTime;
console.log(`[ToolExecutor] Ark KB cache hit in ${latencyMs}ms key="${cacheKey.slice(0, 60)}"`);
return {
...cached,
original_query: query,
rewritten_query: effectiveQuery,
selected_dataset_ids: kbTarget.datasetIds,
selected_kb_routes: kbTarget.matchedRoutes,
latency_ms: latencyMs,
cache_hit: true,
};
}
console.log('[ToolExecutor] Trying Ark Knowledge Search...'); console.log('[ToolExecutor] Trying Ark Knowledge Search...');
const result = await this.searchArkKnowledge(effectiveQuery, [], responseMode, kbTarget.datasetIds, query); const result = await this.searchArkKnowledge(effectiveQuery, [], responseMode, kbTarget.datasetIds, query);
const latencyMs = Date.now() - startTime; const latencyMs = Date.now() - startTime;
console.log(`[ToolExecutor] Ark KB search succeeded in ${latencyMs}ms`); console.log(`[ToolExecutor] Ark KB search succeeded in ${latencyMs}ms`);
// 仅缓存命中的结果,避免缓存错误或无结果
if (result.hit) {
setKbCache(cacheKey, result);
}
return { return {
...result, ...result,
original_query: query, original_query: query,