From 93b8135d5131fb8bc9824d816182e3cb42b77c9f Mon Sep 17 00:00:00 2001 From: User Date: Fri, 20 Mar 2026 10:56:29 +0800 Subject: [PATCH] feat(kb-routing): expand 5-way keyword routing coverage --- .../server/services/contextKeywordTracker.js | 10 +- test2/server/services/knowledgeKeywords.js | 948 ++++++++++++++++++ .../server/services/realtimeDialogRouting.js | 145 +-- test2/server/services/toolExecutor.js | 263 +++-- 4 files changed, 1195 insertions(+), 171 deletions(-) create mode 100644 test2/server/services/knowledgeKeywords.js diff --git a/test2/server/services/contextKeywordTracker.js b/test2/server/services/contextKeywordTracker.js index 4bbcf71..d203c03 100644 --- a/test2/server/services/contextKeywordTracker.js +++ b/test2/server/services/contextKeywordTracker.js @@ -3,18 +3,14 @@ * 记忆最近的产品/主题关键词,用于追问理解 */ +const { TRACKER_KEYWORD_GROUPS, buildKeywordRegex } = require('./knowledgeKeywords'); + class ContextKeywordTracker { constructor() { this.sessionKeywords = new Map(); this.TTL = 30 * 60 * 1000; this.MAX_KEYWORDS = 8; - this.keywordPatterns = [ - /(一成系统|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.keywordPatterns = TRACKER_KEYWORD_GROUPS.map((group) => buildKeywordRegex(group, 'gi')); this.cleanupTimer = setInterval(() => this.cleanup(), this.TTL); if (typeof this.cleanupTimer.unref === 'function') { this.cleanupTimer.unref(); diff --git a/test2/server/services/knowledgeKeywords.js b/test2/server/services/knowledgeKeywords.js new file mode 100644 index 0000000..031af2f --- /dev/null +++ b/test2/server/services/knowledgeKeywords.js @@ -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, +}; diff --git a/test2/server/services/realtimeDialogRouting.js b/test2/server/services/realtimeDialogRouting.js index fbd0fb3..38a34dd 100644 --- a/test2/server/services/realtimeDialogRouting.js +++ b/test2/server/services/realtimeDialogRouting.js @@ -1,7 +1,6 @@ const ToolExecutor = require('./toolExecutor'); -const arkChatService = require('./arkChatService'); const db = require('../db'); -const contextKeywordTracker = require('./contextKeywordTracker'); +const { hasKnowledgeRouteKeyword } = require('./knowledgeKeywords'); function normalizeTextForSpeech(text) { return (text || '') @@ -63,89 +62,11 @@ function estimateSpeechDurationMs(text) { 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_knowledge,args 中必须包含 query。', - '如果 route=query_weather,args 中必须包含 city。', - '如果 route=query_order,args 中必须包含 order_id。', - '如果 route=calculate,args 中必须包含 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) { return String(text || '') .replace(/X{2}系统/gi, '一成系统') - .replace(/一城系统|逸城系统|一程系统|易成系统|一诚系统|亦成系统|艺成系统|溢成系统|义成系统|毅成系统|怡成系统|以成系统|已成系统|亿成系统|忆成系统|益成系统/g, '一成系统') - .replace(/(?= 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) { const text = (userText || '').trim(); if (!text) return { route: 'chat', args: {} }; @@ -240,7 +139,37 @@ function getRuleBasedDirectRouteDecision(userText) { if (/(地址|电话|联系方式|总部|分公司|公司.*实力|公司.*背景|PM公司|德国PM)/.test(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 } }; } if (/^(喂|你好|您好|嗨|哈喽|hello|hi|在吗|在不在|早上好|中午好|下午好|晚上好|早安|晚安|谢谢|感谢|再见|拜拜|嗯|哦|好的|对|是的|没有了|没事了|可以了|行|OK|ok)[,,!。??~~\s]*[啊呀吧呢哦嗯嘛哈的了]*[!。??~~]*$/.test(text)) { diff --git a/test2/server/services/toolExecutor.js b/test2/server/services/toolExecutor.js index fddb556..61c4ff6 100644 --- a/test2/server/services/toolExecutor.js +++ b/test2/server/services/toolExecutor.js @@ -1,25 +1,51 @@ const axios = require('axios'); const arkChatService = require('./arkChatService'); 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 { static hasCanonicalKnowledgeTerm(query) { - return /(一成系统|PM-FitLine|PM细胞营养素|NTC营养保送系统|Activize Oxyplus|小红产品|Basics|大白产品|Restorate|小白产品|儿童倍适|火炉原理|阿育吠陀)/i.test(String(query || '')); + return hasCanonicalKnowledgeTermMatch(query); } 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) || []; - 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; + return extractKnowledgeEntityMatches(text); } static classifyQuestionSlot(query) { @@ -66,7 +92,44 @@ class ToolExecutor { const lowerText = text.toLowerCase(); const slot = this.classifyQuestionSlot(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') { return false; @@ -137,46 +200,63 @@ class ToolExecutor { .split(',') .map((id) => id.trim()) .filter(Boolean); - const rules = this.getKnowledgeBaseRoutingRules(); - if (!rules.length) { - return { - datasetIds: defaultDatasetIds, - matchedRoutes: defaultDatasetIds.length ? ['default'] : [], - }; - } - + const text = String(query || '').trim(); const recentContextText = (Array.isArray(context) ? context : []) .slice(-6) .map((item) => String(item?.content || '').trim()) .filter(Boolean) .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 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) { priorityRouteNames.push('system'); } if (hasCompanyIntent && !hasSystemIntent && !hasProductIntent) { 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) { - 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))]; if (priorityDatasetIds.length > 0) { + console.log(`[ToolExecutor] KB 5-way route: intents=[${priorityRouteNames.join(',')}] datasets=[${priorityDatasetIds.join(',')}]`); return { datasetIds: priorityDatasetIds, - matchedRoutes: [...new Set(priorityRules.map((rule) => rule.name))], + matchedRoutes: [...new Set(priorityRouteNames)], }; } } + // 通用env规则匹配回退 const matchedDatasetIds = []; const matchedRoutes = []; - for (const rule of rules) { + for (const rule of this.getKnowledgeBaseRoutingRules()) { if (rule.keywords.some((keyword) => haystack.includes(keyword.toLowerCase()))) { matchedRoutes.push(rule.name); matchedDatasetIds.push(...rule.dataset_ids); @@ -214,6 +294,16 @@ class ToolExecutor { if (/(三大平台|四大生态|Ai生态)/i.test(text)) return '一成系统 三大平台 四大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 (/(产品|细胞营养素|基础套装|基础三合一|小红|大白|小白|activize|basics|restorate|fitline|儿童倍适)/i.test(text)) { 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 (/(NTC.*(核心优势|核心竞争力|优势|原理|厉害)|核心优势.*NTC|核心竞争力.*NTC)/i.test(text)) return 'NTC营养保送系统 核心优势 吸收利用 原理'; 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 (/(大白产品|大白|倍适|Basics)/i.test(text)) return questionDimension ? `德国PM细胞营养素 大白 Basics ${questionDimension[0]}` : '德国PM细胞营养素 大白 Basics'; 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产品 不是药 不能替代药物 保健食品 营养补充'; 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 '培训新人起步三关'; if (/(精品会议|会议组织)/i.test(text)) return '培训打造精品会议具体如下'; @@ -254,35 +344,66 @@ class ToolExecutor { if (/(好转反应|整应反应|排毒反应|副作用|不良反应|皮肤发痒)/i.test(text)) return 'PM产品整应反应好转反应解析'; if (/(促销活动|促销|优惠|打折|活动分数|5\+1)/i.test(text)) return '促销活动 5+1活动分数'; if (/暖炉原理/i.test(text)) return '火炉原理'; - if (/(CC套装|CC胶囊)/i.test(text)) return 'CC套装 CC胶囊'; - if (/(IB5|口腔免疫喷雾)/i.test(text)) return 'IB5口腔免疫喷雾'; - if (/(Q10|辅酵素|氧修护)/i.test(text)) return 'Q10辅酵素氧修护'; - if (/Women\+/i.test(text)) return 'Women+'; - if (/乐活奶昔|乐活/i.test(text)) return '乐活奶昔'; - if (/(乳清蛋白|蛋白粉)/i.test(text)) return '乳清蛋白粉'; - if (/(乳酪煲|乳酪饮品|乳酪)/i.test(text)) return '乳酪煲 乳酪饮品'; - if (/(基础二合一|二合一)/i.test(text)) return '基础二合一'; - if (/倍力健/i.test(text)) return '倍力健'; - if (/(关节套装|关节舒缓)/i.test(text)) return '关节套装 关节舒缓膏'; - if (/(男士乳霜|男士护肤)/i.test(text)) return '全效男士乳霜'; - if (/(去角质|面膜)/i.test(text)) return '去角质面膜'; - if (/发宝/i.test(text)) return '发宝'; - if (/叶黄素/i.test(text)) return '叶黄素'; - if (/(奶昔)/i.test(text)) return '奶昔'; - if (/(健康饮品)/i.test(text)) return '健康饮品'; + if (/(CC套装|CC胶囊)/i.test(text)) return questionDimension ? `CC套装 CC胶囊 ${questionDimension[0]}` : 'CC套装 CC胶囊'; + if (/(IB5|口腔免疫喷雾)/i.test(text)) return questionDimension ? `IB5 口腔免疫喷雾 ${questionDimension[0]}` : 'IB5 口腔免疫喷雾'; + if (/(Q10|辅酵素|氧修护)/i.test(text)) return questionDimension ? `Q10 辅酵素 氧修护 ${questionDimension[0]}` : 'Q10 辅酵素 氧修护'; + if (/(Med Dental\+|Dental\+|草本护理牙膏)/i.test(text)) return questionDimension ? `Med Dental+ 草本护理牙膏 ${questionDimension[0]}` : 'Med Dental+ 草本护理牙膏'; + if (/(Men Face|全效男士护肤抗衰乳霜)/i.test(text)) return questionDimension ? `Men Face 全效男士护肤抗衰乳霜 ${questionDimension[0]}` : 'Men Face 全效男士护肤抗衰乳霜'; + if (/(CC-Cell|CC Cell|CC乳霜)/i.test(text)) return questionDimension ? `CC-Cell 胶囊 乳霜 ${questionDimension[0]}` : 'CC-Cell 胶囊 乳霜'; + if (/(D-Drink|小绿排毒饮|14天排毒D饮料Plus)/i.test(text)) return questionDimension ? `D-Drink 小绿排毒饮 14天排毒D饮料Plus ${questionDimension[0]}` : 'D-Drink 小绿排毒饮 14天排毒D饮料Plus'; + if (/(ProShape|ProShape® Amino|氨基酸|支链氨基酸|BCAA)/i.test(text)) return questionDimension ? `ProShape Amino 氨基酸 BCAA ${questionDimension[0]}` : 'ProShape Amino 氨基酸 BCAA'; + if (/(Herbal Tea|草本茶)/i.test(text)) return questionDimension ? `Herbal Tea 草本茶 ${questionDimension[0]}` : 'Herbal Tea 草本茶'; + if (/(Hair\+|med Hair\+|口服发宝|外用发健)/i.test(text)) return questionDimension ? `Hair+ med Hair+ 口服发宝 外用发健 ${questionDimension[0]}` : 'Hair+ med Hair+ 口服发宝 外用发健'; + if (/(Fitness-Drink|运动饮料健康饮品|运动饮料)/i.test(text)) return questionDimension ? `Fitness-Drink 运动饮料健康饮品 ${questionDimension[0]}` : 'Fitness-Drink 运动饮料健康饮品'; + if (/(TopShape|孅萃TopShape纤萃减肥|纤萃减肥)/i.test(text)) return questionDimension ? `TopShape 孅萃TopShape纤萃减肥 ${questionDimension[0]}` : 'TopShape 孅萃TopShape纤萃减肥'; + if (/(Generation 50\+|乐活50\+)/i.test(text)) return questionDimension ? `乐活50+ Generation 50+ ${questionDimension[0]}` : '乐活50+ Generation 50+'; + if (/(Apple Antioxy|苹果细胞抗氧素|Antioxy|Zellschutz|细胞抗氧素)/i.test(text)) return questionDimension ? `Apple Antioxy Zellschutz 细胞抗氧素 ${questionDimension[0]}` : 'Apple Antioxy Zellschutz 细胞抗氧素'; + if (/Women\+/i.test(text)) return questionDimension ? `Women+ ${questionDimension[0]}` : 'Women+'; + 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); 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 (/(小红产品|小红|Activize)/i.test(recentContextText)) return 'Fitline小红产品提升能量原理'; - if (/(大白产品|大白|Basics)/i.test(recentContextText)) return '德国PM细胞营养素 大白 Basics'; - if (/(小白产品|小白|Restorate)/i.test(recentContextText)) return '德国PM细胞营养素 小白'; - if (/儿童倍适/i.test(recentContextText)) return '儿童倍适'; + if (/DSN/i.test(recentContextText)) return '德国PM DSN 全球100强 欧洲第1'; + if (/(ELAB|科隆名单|Halal|GMP)/i.test(recentContextText)) return '德国PM ELAB 科隆名单 Halal GMP 安全认证'; + if (/(邓白氏|AAA\+)/i.test(recentContextText)) return '德国PM 邓白氏 AAA+ 99分'; + 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 (/(阿育吠陀|Ayurveda)/i.test(recentContextText)) return '阿育吠陀医学原理'; 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 ''; } @@ -329,24 +450,34 @@ class ToolExecutor { return { hit: false, reason: 'empty', - reply: `知识库中暂未找到与“${query}”直接相关的信息,请换个更具体的问法再试。`, + reply: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。`, }; } - const strictNoHitPattern = /未检索到|没有检索到|没有相关内容|暂无相关内容|未找到相关内容|未找到相关信息|没有找到相关信息|知识库中没有相关内容|知识库中没有关于|知识库中没有找到|没有找到具体|没有.*具体信息|没有.*相关说明|暂未找到与.*直接相关的信息|无法基于知识库.*回答|知识库未明确提到|知识库未提到/; + const strictNoHitPattern = /未检索到|没有检索到|没有相关内容|暂无相关内容|未找到相关内容|未找到相关信息|没有找到相关信息|知识库中没有相关内容|知识库中没有关于|知识库中没有找到|没有找到具体|没有.*具体信息|没有.*相关说明|暂未找到与.*直接相关的信息|无法基于知识库.*回答|知识库未明确提到|知识库未提到|很抱歉.*没有.*资料|超出.*知识范围|目前没有.*方面的|无法提供.*相关信息|暂时无法回答|不在.*知识范围|没有.*相关记录/; if (strictNoHitPattern.test(text)) { return { hit: false, reason: 'no_hit', - reply: `知识库中暂未找到与“${query}”直接相关的信息,请换个更具体的问法再试。`, + reply: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。`, }; } 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 { hit: false, reason: 'slot_mismatch', - reply: `知识库中暂未找到与“${query}”直接相关的信息,请换个更具体的问法再试。`, + reply: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。`, }; } @@ -471,10 +602,30 @@ class ToolExecutor { }; } 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...'); const result = await this.searchArkKnowledge(effectiveQuery, [], responseMode, kbTarget.datasetIds, query); const latencyMs = Date.now() - startTime; console.log(`[ToolExecutor] Ark KB search succeeded in ${latencyMs}ms`); + // 仅缓存命中的结果,避免缓存错误或无结果 + if (result.hit) { + setKbCache(cacheKey, result); + } return { ...result, original_query: query,