const axios = require('axios'); const https = require('https'); const arkChatService = require('./arkChatService'); const { buildKnowledgeAnswerPrompt } = require('./assistantProfileConfig'); // HTTP keep-alive agent:复用TCP连接,避免每次请求重新握手 const kbHttpAgent = new https.Agent({ keepAlive: true, keepAliveMsecs: 30000, maxSockets: 6, timeout: 15000, }); // 连接预热:服务启动后自动建立到方舟API的TLS连接,避免首次查询的握手延迟 setTimeout(() => { const warmupKey = process.env.VOLC_ARK_API_KEY || process.env.VOLC_ACCESS_KEY_ID; if (warmupKey) { axios.post('https://ark.cn-beijing.volces.com/api/v3/chat/completions', { model: process.env.VOLC_ARK_KNOWLEDGE_ENDPOINT_ID || process.env.VOLC_ARK_ENDPOINT_ID || 'warmup', messages: [{ role: 'user', content: 'ping' }], max_tokens: 1, stream: false, }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${warmupKey}` }, timeout: 8000, httpsAgent: kbHttpAgent, }).then(() => { console.log('[ToolExecutor] KB connection pool warmed up'); }).catch(() => { console.log('[ToolExecutor] KB connection warmup sent (pool established)'); }); } }, 2000); 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分钟 (hit结果) const KB_CACHE_NOHIT_TTL_MS = 2 * 60 * 1000; // 2分钟 (no-hit结果,较短TTL) const KB_CACHE_MAX_SIZE = 200; 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; const ttl = entry.hit ? KB_CACHE_TTL_MS : KB_CACHE_NOHIT_TTL_MS; if (Date.now() - entry.timestamp > ttl) { 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(), hit: !!result.hit }); } // 高频问题本地预缓存:匹配到的问题直接返回预计算答案,0ms延迟 const HOT_ANSWERS = [ { patterns: [/基础三合一.*怎么吃/, /三合一.*怎么吃/, /基础套装.*怎么吃/, /大白小红小白.*怎么吃/, /怎么吃.*基础三合一/, /怎么吃.*三合一/], answer: '基础三合一这样吃:1.大白Basics:早上空腹,1平勺兑200-300ml温水,快速搅拌后马上喝。2.小红Activize:大白喝完15-30分钟后,兑100-150ml温水,小口慢饮。3.小白Restorate:睡前空腹,1平勺兑200ml温水搅拌饮用。注意:刚开始可以半量适应3-5天,水温不超过40度。', }, { patterns: [/PM.*传销/, /传销.*PM/, /PM.*正规/, /PM.*合法/, /PM.*骗局/, /FitLine.*传销/, /是不是传销/, /直销还是传销/], answer: '德国PM绝对不是传销,它是正规合法的直销企业。1.成立于1993年,有30多年历史。2.获得国际邓白氏AAA+最高信用认证,评分99分。3.业务覆盖全球100多个国家和地区。4.旗下FitLine品牌是德国运动员信赖的营养品牌。所以它是一家老牌正规企业。', }, { patterns: [/NTC.*核心优势/, /NTC.*原理/, /NTC.*厉害/, /核心优势.*NTC/, /核心竞争力.*NTC/, /营养保送系统/], answer: 'NTC营养保送系统是PM产品的核心技术优势。它能确保营养素在体内精准保送到细胞层面,而不是在消化过程中被破坏或流失。这就是PM产品和普通保健品的最大区别——不仅仅是补充营养,更重要的是保证营养被细胞真正吸收利用。', }, { patterns: [/多久见效/, /多久有效/, /多长时间见效/, /几天见效/, /什么时候见效/], answer: 'PM产品见效时间因人而异。一般来说:1.小红Activize提升能量,很多人当天就能感受到精力提升。2.整体改善通常需要1-3个月持续使用。3.具体效果取决于个人体质、生活习惯和使用方法。建议全套搭配使用,按推荐方法坚持服用,效果会更明显。', }, { patterns: [/为什么.*全套/, /为什么.*搭配/, /为什么.*三合一/, /为何.*全套/, /产品需要全套/], answer: '全套搭配使用是因为NTC营养保送系统的协同作用:1.大白Basics提供基础营养素打底。2.小红Activize激活细胞能量代谢。3.小白Restorate补充矿物质,在睡眠中修复。三者配合形成完整的营养循环,单独使用效果会打折扣。就像火炉原理,需要同时具备柴火、引火物和氧气才能充分燃烧。', }, { patterns: [/好转反应/, /整应反应/, /排毒反应/, /副作用/, /不良反应/], answer: '这是正常的好转反应,也叫整应反应,说明身体在调整和修复。常见表现有:疲倦感、轻微头痛、皮肤变化、排便增多等。一般持续3-7天会逐渐消失。这不是副作用,而是身体排出毒素、细胞开始修复的表现。建议多喝水,继续正常服用,症状会慢慢好转。', }, { patterns: [/PM.*公司介绍/, /德国PM介绍/, /介绍.*德国PM/, /PM公司介绍/], answer: '德国PM-International是1993年创立的国际营养品直销企业。核心品牌FitLine专注细胞营养,拥有独家NTC营养保送系统技术。公司获邓白氏AAA+最高信用认证(99分),业务覆盖全球100多个国家。总部位于德国,在日本、美国、加拿大、香港等地设有分公司。', }, { patterns: [/小红.*功效/, /小红.*作用/, /Activize.*功效/, /Activize.*作用/, /小红产品/], answer: 'FitLine小红Activize Oxyplus的核心功效是提升细胞能量。它含有维生素B族、C和辅酶Q10等成分,通过NTC营养保送系统直达细胞。主要作用:1.提升精力和体能。2.促进细胞氧气利用。3.增强免疫力。很多人喝完当天就能感受到精力明显提升。', }, { patterns: [/大白.*功效/, /大白.*作用/, /Basics.*功效/, /大白产品/], answer: '德国PM大白Basics是基础营养素,为身体提供全面的维生素和矿物质基础。它的核心作用:1.补充每日所需的基础营养。2.为细胞提供全面的营养支持。3.搭配小红和小白形成完整的NTC营养循环。建议早上空腹服用,1平勺兑200-300ml温水。', }, { patterns: [/小白.*功效/, /小白.*作用/, /Restorate.*功效/, /小白产品/], answer: '德国PM小白Restorate的核心功效是夜间修复。它富含矿物质和微量元素,通过NTC营养保送系统在睡眠期间帮助身体修复。主要作用:1.补充矿物质和微量元素。2.促进夜间细胞修复。3.改善睡眠质量。建议睡前空腹服用,1平勺兑200ml温水。', }, { patterns: [/与.*保健品.*区别/, /和.*保健品.*区别/, /保健品.*区别/], answer: 'PM产品和普通保健品的最大区别在于NTC营养保送系统。普通保健品只是补充营养,但大部分在消化过程中被破坏或无法被细胞吸收。PM产品通过NTC技术,确保营养素精准保送到细胞层面,真正被身体吸收利用。这就好比,不仅要买好食材,更要保证营养送到嘴里并消化吸收。', }, { patterns: [/CC.*套装/, /CC.*胶囊/, /CC-?Cell/, /CC.*功效/, /CC.*作用/, /CC.*怎么用/], answer: 'CC套装包含CC-Cell胶囊和CC-Cell乳霜,是PM的细胞抗衰产品。CC胶囊含有葡萄籽提取物、白藜芦醇、辅酶Q10等抗氧化成分,从内部保护细胞。CC乳霜从外部滋养修护皮肤。建议内服外用搭配,早晚各1粒胶囊,乳霜早晚涂抹面部。', }, { patterns: [/Q10.*功效/, /Q10.*作用/, /辅酵素.*功效/, /辅酵素.*作用/, /Q10.*怎么用/, /Q10.*氧修护/], answer: 'Q10辅酵素氧修护是PM的抗氧化明星产品。辅酶Q10是人体细胞产生能量的关键物质,随年龄增长会减少。它的主要功效:1.抗氧化保护细胞。2.支持心脏健康。3.提升皮肤弹性。通过NTC技术直达细胞,吸收率远超普通Q10产品。', }, { patterns: [/IB5/, /口腔.*喷雾/, /免疫喷雾/, /口腔免疫/], answer: 'IB5口腔免疫喷雾是PM独特的免疫支持产品。它通过口腔黏膜快速吸收,含有益生菌和免疫活性成分。使用方法:每天2-3次,每次喷2-3下到口腔内。特别适合换季、出行或免疫力需要加强的时候使用。', }, { patterns: [/邓白氏.*认证/, /邓白氏.*评级/, /邓白氏.*是什么/, /邓白氏.*什么意思/, /AAA\+/, /99分/], answer: '邓白氏是全球最权威的商业信用评估机构,类似企业界的"信用评分"。PM公司获得邓白氏AAA+最高评级,评分99分(满分100),这意味着PM在财务健康、信用风险、经营稳定性方面都达到了最高标准。全球能拿到这个评级的企业非常少。', }, { patterns: [/一成系统.*是什么/, /一成系统.*介绍/, /一成系统.*怎么/, /一成.*赋能/, /一成.*平台/, /三大平台.*四大/], answer: '一成系统是德国PM事业发展的智能赋能工具,包含三大平台和四大AI生态。三大平台:数字化工作室、盛咖学愿培训平台、盟主社区互动平台。四大AI生态:AI众享智能推荐、AI智能生产力、智能客服、数据分析。帮助团队实现零成本高效率运行。', }, { patterns: [/火炉原理/, /暖炉原理/, /火炉.*是什么/, /火炉.*意思/], answer: '火炉原理是PM产品的核心理念比喻。就像生火需要三个条件:柴火(大白提供基础营养)、引火物(小红激活能量代谢)、氧气(小白补充矿物质促进修复)。三者缺一不可,只有同时具备才能让细胞这个"火炉"充分燃烧,发挥最大效果。这就是为什么建议全套搭配使用。', }, { patterns: [/D-?Drink/, /小绿.*排毒/, /排毒饮/, /14天排毒/, /排毒.*怎么用/], answer: 'D-Drink小绿是PM的14天排毒饮料。它含有草本植物精华,帮助身体温和排毒清理。使用方法:每天1包兑水饮用,连续14天为一个周期。建议每3-6个月做一次排毒周期。排毒期间多喝水,配合清淡饮食效果更好。', }, { patterns: [/如何加入/, /怎么加入/, /成为代理/, /怎么做PM/, /加入PM/, /如何代理/, /怎么代理/, /想做PM/], answer: '加入PM非常简单,直接联系推荐人注册即可。PM采用直销模式,注册后可以享受会员价购买产品自用,也可以发展团队赚取收入。一成系统会提供全面的培训和智能工具支持,帮助你快速上手。具体流程可以咨询你的推荐人。', }, { patterns: [/孕妇.*能吃/, /怀孕.*能吃/, /儿童.*能吃/, /小孩.*能吃/, /孕妇.*可以/, /小孩.*可以/, /老人.*能吃/, /适合.*人群/], answer: 'PM产品是营养补充品,大部分成人可以正常服用。但孕妇、哺乳期女性和儿童建议先咨询医生。PM有专门的儿童产品PowerCocktail Junior适合儿童使用。老年人可以正常服用基础三合一。如果正在服药或有特殊健康状况,建议先咨询医生。', }, { patterns: [/多少钱/, /价格/, /售价/, /怎么买/, /哪里买/, /在哪.*买/, /贵不贵/], answer: '产品价格因国家和地区有所不同,建议直接咨询你的PM推荐人获取最新价格。注册为会员后可以享受会员优惠价。PM产品通过直销渠道销售,官方不在电商平台直接销售,请通过正规渠道购买以确保正品。', }, ]; function matchHotAnswer(query) { const text = String(query || '').trim(); if (!text) return null; for (const item of HOT_ANSWERS) { for (const pattern of item.patterns) { if (pattern.test(text)) return item.answer; } } return null; } class ToolExecutor { static hasCanonicalKnowledgeTerm(query) { return hasCanonicalKnowledgeTermMatch(query); } static extractKnowledgeEntities(text) { return extractKnowledgeEntityMatches(text); } static classifyQuestionSlot(query) { const text = String(query || '').trim(); if (/(多少钱|价格|售价|费用|价钱)/.test(text)) return 'price'; if (/(成分|配方|原料|含什么|包含什么)/.test(text)) return 'ingredient'; if (/(规格|包装|剂型|形态|粉末|粉剂|粉状|胶囊|软胶囊|片剂|颗粒|喷雾|乳霜|乳液|凝胶|膏状|口服液|每盒|每袋|每瓶|每支|多少袋|多少粒|多少片|多少毫升|多大规格)/.test(text)) return 'specification'; if (/(怎么吃|怎么用|怎么服用|服用方法|用法|用量|一天几次|每天几次|每日几次|一天吃几次|每天吃几次|一天服用几次|每日服用几次)/.test(text)) return 'usage'; if (/(副作用|不良反应|好转反应|排毒反应|整应反应|皮肤发痒|皮肤微痒)/.test(text)) return 'side_effect'; if (/(多久见效|多久有效|多久能见效|多长时间见效|几天见效|什么时候见效)/.test(text)) return 'effect_time'; if (/(治病|治疗|能治|治愈|药品|药物|替代药|包治|治百病)/.test(text)) return 'medical_claim'; if (/(为什么.*(全套|搭配|三合一)|为什么要.*(全套|搭配|三合一)|为何.*(全套|搭配|三合一)|产品需要全套)/.test(text)) return 'bundle_reason'; if (/(如何发展PM事业|怎么发展PM事业|PM事业发展逻辑|商机|PM价值|为什么选择德国PM|为何选择德国PM|为什么选德国PM|为什么选PM|为何选PM|陌生客户|陌生人沟通|线上拓客|成交|拓客|邀约|自我介绍|故事分享|三大平台|四大Ai生态|AI智能生产力|AI生产力)/.test(text)) return 'business_growth'; if (/(功效|作用|有什么用|有什么好处|效果)/.test(text)) return 'benefit'; if (/(适合谁|适合什么人|哪些人|适用人群)/.test(text)) return 'audience'; if (/(正规吗|合法|是不是传销|传销|骗局|骗子|直销还是传销|合不合法|正不正规)/.test(text)) return 'legality'; if (/(地址|电话|联系方式|联系|总部|公司地址)/.test(text)) return 'address_contact'; if (/(区别|不同|哪个好|有什么区别|差别)/.test(text)) return 'difference'; return 'general'; } static buildQuestionSlotInstruction(query) { const slot = this.classifyQuestionSlot(query); const instructions = { price: '用户当前只关心价格或费用,请只回答价格、收费或是否未提及价格,不要扩展到产品总介绍。', ingredient: '用户当前只关心成分或配方,请只回答成分、原料或是否未提及成分,不要扩展到品牌背景。', specification: '用户当前只关心规格、包装、剂型、形态或每盒每袋等产品细节,请只回答这些明确规格信息;如果知识库没写,就直接说明未提及。', usage: '用户当前只关心用法、吃法、服用频次或剂量,请只回答这一点。', side_effect: '用户当前只关心副作用或好转反应,请只回答可能的不良反应、好转反应或注意事项。', effect_time: '用户当前只关心多久见效或效果周期,请只回答见效时间、周期或个体差异,不要扩展无关信息。', medical_claim: '用户当前只关心产品能不能治病、是不是药,请只回答是否属于药品、能否替代药物以及相关注意事项。', bundle_reason: '用户当前只关心为什么要全套、搭配或三合一,请只回答搭配原理、协同作用或NTC相关原因。', business_growth: '用户当前只关心PM事业发展、商机、PM价值、为何选择、线上拓客、陌生客户沟通、一成系统赋能、三大平台四大Ai生态或自我介绍,请只回答这类业务发展问题。凡涉及“一部手机做天下、0门槛启动、0成本高效率、足不出户梦想横扫全球、身未动梦已成、批发式晋级”等表述,必须明确这是“一成系统”的优势标签,是对德国PM事业的软件赋能,不要混同为德国PM公司或产品本身。', benefit: '用户当前只关心功效或作用,请只回答作用点,不要扩展到无关信息。', audience: '用户当前只关心适合人群,请只回答适用对象。', legality: '用户当前只关心正规性、合法性或是否传销,请只围绕合法合规问题直接回答。', address_contact: '用户当前只关心地址或联系方式,请只回答地址、电话、联系信息。', difference: '用户当前只关心区别或对比,请直接做差异对比,不要扩写成单个产品长介绍。', general: '请优先直接回答用户当前这一问,不要离题扩展。', }; return instructions[slot] || instructions.general; } static answerMatchesQuestionSlot(query, content) { const text = String(content || '').trim(); const lowerText = text.toLowerCase(); const slot = this.classifyQuestionSlot(query); const entities = this.extractKnowledgeEntities(query); // 中英文别名映射:改写后的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; } if (!mentionsEntity && slot !== 'legality' && slot !== 'address_contact') { return false; } const slotPatterns = { price: /(元|价格|售价|费用|人民币|¥|¥)/, ingredient: /(成分|配方|原料|含有|包含|营养素|葡萄籽|白藜芦醇|益生菌|胶原蛋白肽|辅酵素|Q10)/, specification: /(规格|包装|剂型|形态|粉末|粉剂|粉状|胶囊|软胶囊|片剂|颗粒|喷雾|乳霜|乳液|凝胶|膏状|口服液|每盒|每袋|每瓶|每支|袋装|盒装|瓶装|支装|多少袋|多少粒|多少片|多少毫升|克|g|ml)/, usage: /(服用|用法|用量|每日|每天|一次|次|饭前|饭后|早餐|晚餐|早晚|空腹|睡前)/, side_effect: /(副作用|不良反应|好转反应|排毒|整应|皮肤.*痒|排便|反应|注意事项|正常现象)/, effect_time: /(见效|有效|几天|几周|几个月|周期|坚持|因人而异|吸收利用)/, medical_claim: /(不是药|不能替代药|不能代替药物|不是用于治疗|不能治疗|保健食品|营养补充|就医|医生)/, bundle_reason: /(全套|搭配|协同|三合一|组合|NTC|吸收|运输|利用|代谢|原理)/, business_growth: /(一成系统|PM事业|商机|价值|选择|拓客|成交|邀约|陌生客户|沟通|三大平台|四大Ai生态|数字化工作室|Ai众享|盛咖学愿|故事|自我介绍|赋能|智能生产力|软件赋能|一部手机|0门槛|零门槛|0成本|零成本|身未动梦已成|批发式晋级)/, benefit: /(功效|作用|帮助|支持|改善|提升|有助于)/, audience: /(适合|适用|人群|适宜|可以)/, legality: /(合法|正规|直销|认证|邓白氏|不是传销)/, address_contact: /(地址|电话|联系方式|联系|总部|香港|德国|美国|加拿大)/, difference: /(区别|不同|相比|分别|一个|另一个|而|更适合)/, }; if (slotPatterns[slot]) { return slotPatterns[slot].test(text); } return text.length >= 10; } static getKnowledgeBaseRoutingRules() { const raw = process.env.VOLC_ARK_KNOWLEDGE_BASE_ROUTING || process.env.VOLC_ARK_KNOWLEDGE_BASE_MAP; if (!raw) { return []; } try { const parsed = JSON.parse(raw); const entries = Array.isArray(parsed) ? parsed : Object.entries(parsed).map(([name, config]) => ({ name, ...(config || {}) })); return entries .map((item) => ({ name: String(item.name || '').trim(), dataset_ids: Array.isArray(item.dataset_ids) ? item.dataset_ids.map((id) => String(id || '').trim()).filter(Boolean) : String(item.dataset_ids || item.datasetIds || '') .split(',') .map((id) => id.trim()) .filter(Boolean), keywords: Array.isArray(item.keywords) ? item.keywords.map((keyword) => String(keyword || '').trim()).filter(Boolean) : String(item.keywords || '') .split(',') .map((keyword) => keyword.trim()) .filter(Boolean), })) .filter((item) => item.name && item.dataset_ids.length > 0 && item.keywords.length > 0); } catch (error) { console.warn('[ToolExecutor] parse knowledge base routing failed:', error.message); return []; } } static selectKnowledgeBaseTargets(query, context = []) { const defaultDatasetIds = String(process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS || '') .split(',') .map((id) => id.trim()) .filter(Boolean); 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 = `${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 = []; if (hasSystemIntent) priorityRouteNames.push('system'); if (hasProductIntent) priorityRouteNames.push('product'); if (hasCompanyIntent) priorityRouteNames.push('company'); if (hasFaqIntent && !hasProductIntent) priorityRouteNames.push('faq'); if (hasScienceIntent && !hasProductIntent && !hasFaqIntent) priorityRouteNames.push('science'); if (priorityRouteNames.length > 0) { 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(priorityRouteNames)], }; } } // 通用env规则匹配回退 const matchedDatasetIds = []; const matchedRoutes = []; for (const rule of this.getKnowledgeBaseRoutingRules()) { if (rule.keywords.some((keyword) => haystack.includes(keyword.toLowerCase()))) { matchedRoutes.push(rule.name); matchedDatasetIds.push(...rule.dataset_ids); } } const datasetIds = [...new Set((matchedDatasetIds.length ? matchedDatasetIds : defaultDatasetIds).filter(Boolean))]; return { datasetIds, matchedRoutes: matchedRoutes.length ? [...new Set(matchedRoutes)] : (datasetIds.length ? ['default'] : []), }; } static buildDeterministicKnowledgeQuery(query, context = []) { 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 = `${text}\n${recentContextText}`; const questionDimension = text.match(/(功效|作用|成分|配方|原料|怎么吃|怎么用|怎么服用|服用方法|吃法|用法|用量|一天几次|每天几次|每日几次|副作用|好转反应|价格|多少钱|适合谁|适用人群|区别|不同|搭配|原理|规格|包装|剂型|形态|粉末|胶囊|片剂|颗粒|喷雾|乳霜|口服液)/); // 第一层:当前查询文本中有明确产品/系统/主题关键词 → 直接改写(不依赖上下文) if (/(基础三合一|三合一基础套|基础套装|大白小红小白)/i.test(text)) return '德国PM细胞营养素 基础套装 大白 小红 小白'; if (/(一成系统|Ai众享|数字化工作室|盛咖学愿|三大平台|四大Ai生态|四大生态|智能生产力)/i.test(text)) { if (/(核心竞争力|竞争力|核心优势|优势)/i.test(text)) return '一成系统 核心竞争力 三大平台 四大Ai生态 零成本高效率'; if (/(发展|怎么做|怎么用|如何用|如何做|关键点|关键|方法|步骤)/i.test(text)) return '一成系统 发展PM事业 三大平台 四大Ai生态 零成本高效率 全球市场'; if (/(线上拓客|拓客|成交|成交率|陌生客户|陌生人沟通|邀约)/i.test(text)) return '一成系统 PM事业 线上拓客 成交 邀约 三大平台 四大Ai生态'; if (/(ai智能生产力|ai生产力|智能生产力|团队效率|赋能团队|团队赋能)/i.test(text)) return '一成系统 AI智能生产力 赋能团队 三大平台 四大Ai生态'; if (/(一部手机|0门槛|零门槛|0成本|零成本|足不出户|梦想横扫全球|一部手机做天下)/i.test(text)) return '一成系统 软件赋能 0成本高效率 一部手机做天下 足不出户梦想横扫全球'; if (/(故事|自我介绍|分享)/i.test(text)) return '一成系统 PM事业 故事分享 自我介绍'; if (/(邀约|话术)/i.test(text)) return '一成系统 邀约话术'; if (/文化/i.test(text)) return '一成系统 文化解析'; if (/(赋能团队|团队发展|AI赋能|ai赋能)/i.test(text)) return '一成系统用AI赋能团队发展'; if (/(三大平台|四大生态|Ai生态)/i.test(text)) return '一成系统 三大平台 四大Ai生态'; return '一成系统 德国PM事业发展的强大赋能工具 三大平台 四大Ai生态'; } if (/(一部手机做天下|一部手机即可运营全球市场|0门槛启动|零门槛启动|0成本高效率|零成本高效率|足不出户梦想横扫全球|身未动,?梦已成|批发式晋级)/i.test(text)) { return '一成系统 软件赋能 德国PM事业 0成本高效率 一部手机做天下 身未动梦已成 批发式晋级'; } 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 儿童倍适'; } if (/(地址|电话|联系方式)/i.test(text)) return '德国PM 日本 美国 加拿大 香港 地址 电话'; if (/(实力|背景)/i.test(text)) return '德国PM 公司实力介绍 邓白氏 99分 AAA+'; return '德国PM 1993年 创立 100多个国家 FitLine 公司介绍'; } 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 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细胞营养素 小白'; if (/(NTC营养保送系统|Nutrient Transport Concept)/i.test(text)) return 'NTC营养保送系统'; if (/火炉原理/i.test(text)) return '火炉原理'; if (/(阿育吠陀|Ayurveda)/i.test(text)) return '阿育吠陀医学原理'; if (/(PM-FitLine|PM细胞营养素)/i.test(text)) return '德国PM细胞营养素 基础套装 大白 小红 小白'; 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 '培训新人起步三关'; if (/(精品会议|会议组织)/i.test(text)) return '培训打造精品会议具体如下'; if (/成长上总裁/i.test(text)) return '培训成长上总裁'; if (/(招商|代理|加盟|合作|事业机会|招商稿|代理政策)/i.test(text)) return '一成系统 PM事业 招商与代理 软件赋能 0成本高效率'; if (/(如何发展PM事业|怎么发展PM事业|PM事业发展逻辑|介绍PM事业|两分钟介绍PM事业|分享.*故事.*自我介绍|自我介绍|商机|PM价值)/i.test(text)) return '一成系统 PM事业 发展逻辑 商机 价值 软件赋能 三大平台 四大Ai生态 0成本高效率'; if (/(为什么选择德国PM|为何选择德国PM|为什么选德国PM|为什么选PM|为何选PM)/i.test(text)) return '一成系统 德国PM 选择理由 公司实力 产品优势 软件赋能 0成本高效率'; if (/(陌生客户|陌生人).*(沟通|开口|邀约|交流|切入).*(PM事业|德国PM|PM)/i.test(text)) return '一成系统 PM事业 陌生客户 沟通 邀约 话术 软件赋能'; if (/(线上拓客|线上成交|线上开发客户|线上获客|线上成交率)/i.test(text)) return '一成系统 PM事业 线上拓客 成交 获客'; if (/(团队.*AI智能生产力|AI智能生产力.*团队|团队.*AI生产力|AI生产力.*团队)/i.test(text)) return '一成系统 AI智能生产力 赋能团队'; if (/(三大平台|四大Ai生态|四大生态)/i.test(text)) return '一成系统 三大平台 四大Ai生态'; if (/(请分享.*故事.*自我介绍|故事.*自我介绍|个人故事.*自我介绍)/i.test(text)) return '一成系统 PM事业 故事分享 自我介绍 软件赋能'; if (/(一成AI|AI落地|ai落地|转观念|落地对比)/i.test(text)) return '2026一成Ai落地对比与转观念'; if (/(传销|骗局|骗子|正规吗|合法吗|正不正规|合不合法|是不是传销|直销还是传销|层级分销|非法集资|拉人头|下线|发展下线|报单|人头费)/i.test(text)) return '德国PM 1993年 创立 100多个国家 FitLine 公司介绍 邓白氏 99分 AAA+ 合法直销'; 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 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 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 (/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 ''; } static applyKnowledgeQueryAnchor(query) { let anchoredQuery = String(query || '').trim(); if (/一成系统/.test(anchoredQuery) && !/(德国PM|PM事业|赋能工具|Ai众享|数字化工作室|盛咖学愿)/i.test(anchoredQuery)) { anchoredQuery = anchoredQuery.replace(/一成系统/g, '一成系统 德国PM事业赋能工具'); } return anchoredQuery.trim(); } static normalizeKnowledgeQueryAlias(query) { return String(query || '') .replace(/^[啊哦嗯呢呀哎诶额,。!?、\s]+/g, '') .replace(/[啊哦嗯呢呀哎诶额,。!?、\s]+$/g, '') .replace(/^(你|你们|帮我|麻烦你|请你?|我想|我要|能不能|可以|可不可以|能否)[给帮]?(我)?(查一下|查查|查下|搜一下|搜搜|搜下|找一下|找找|找下|看一下|看看|看下|说一下|说说|说下|讲一下|讲讲|讲下|介绍一下|介绍下)?/g, '') .replace(/(的)?(相关|详细)?(内容|信息|资料|介绍|说明)[。??!]*$/g, '') .replace(/一成[,,、。!?\s]+系统/g, '一成系统') .replace(/X{2}系统/gi, '一成系统') .replace(/[\u4e00-\u9fff]{1,3}(?:成|城|程|诚|乘|声|生)[,,、\s]*系统/g, '一成系统') .replace(/(?:一城|逸城|一程|易成|一诚|亦成|艺成|溢成|义成|毅成|怡成|以成|已成|亿成|忆成|益成|益生|易诚|义诚|忆诚|以诚|一声|亿生|易乘)系统/g, '一成系统') .replace(/PM[-\s]*Fitline|PM[-\s]*fitline|Pm[-\s]*fitline|Fitline|fitline/g, 'PM-FitLine') .replace(/PM细胞营养|PM营养素|德国PM营养素/g, 'PM细胞营养素') .replace(/NTC科技/g, 'NTC营养保送系统') .replace(/NTC营养保送系统|NTC营养配送系统|NTC营养输送系统|NTC营养传送系统|NTC营养传输系统/g, 'NTC营养保送系统') .replace(/Nutrient Transport Concept/gi, 'NTC营养保送系统') .replace(/Activize Oxyplus|Activize/gi, 'Activize Oxyplus') .replace(/Restorate/gi, 'Restorate') .replace(/Basics/gi, 'Basics') .replace(/基础三合一|三合一基础套|大白小红小白|基础套装?/g, 'PM细胞营养素 基础套装') .replace(/儿童倍适|儿童产品/g, '儿童倍适') .replace(/小红产品/g, '小红产品 Activize Oxyplus') .replace(/大白产品/g, '大白产品 Basics') .replace(/小白产品/g, '小白产品 Restorate') .replace(/(?=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}"直接相关的信息,请换个更具体的问法再试。`, }; } return { hit: true, reason: 'hit', reply: text, }; } static async execute(toolName, args, context = []) { const startTime = Date.now(); console.log(`[ToolExecutor] Executing: ${toolName}`, args); const handlers = { query_weather: this.queryWeather, query_order: this.queryOrder, search_knowledge: this.searchKnowledge, get_current_time: this.getCurrentTime, calculate: this.calculate, }; const handler = handlers[toolName]; if (!handler) { console.warn(`[ToolExecutor] Unknown tool: ${toolName}`); return { error: `未知的工具: ${toolName}` }; } try { const result = await handler.call(this, args, context); const ms = Date.now() - startTime; console.log(`[ToolExecutor] ${toolName} completed in ${ms}ms:`, JSON.stringify(result).substring(0, 200)); return result; } catch (error) { console.error(`[ToolExecutor] ${toolName} error:`, error); return { error: `工具执行失败: ${error.message}` }; } } static async queryWeather({ city }) { const mockData = { '北京': { temp: '22°C', weather: '晴', humidity: '45%', wind: '北风3级', aqi: 65, tips: '空气质量良好,适合户外活动' }, '上海': { temp: '26°C', weather: '多云', humidity: '72%', wind: '东南风2级', aqi: 78, tips: '注意防晒' }, '广州': { temp: '30°C', weather: '阵雨', humidity: '85%', wind: '南风1级', aqi: 55, tips: '记得带伞' }, '深圳': { temp: '29°C', weather: '多云', humidity: '80%', wind: '东风2级', aqi: 60, tips: '较为闷热,注意防暑' }, '杭州': { temp: '24°C', weather: '晴', humidity: '55%', wind: '西北风2级', aqi: 50, tips: '天气宜人' }, '成都': { temp: '20°C', weather: '阴', humidity: '70%', wind: '微风', aqi: 85, tips: '天气阴沉,适合室内活动' }, '武汉': { temp: '25°C', weather: '晴', humidity: '60%', wind: '东风3级', aqi: 72, tips: '适合出行' }, '南京': { temp: '23°C', weather: '多云', humidity: '58%', wind: '东北风2级', aqi: 68, tips: '温度适宜' }, '西安': { temp: '18°C', weather: '晴', humidity: '35%', wind: '西北风3级', aqi: 90, tips: '天气干燥,注意补水' }, '重庆': { temp: '27°C', weather: '阴转多云', humidity: '75%', wind: '微风', aqi: 80, tips: '注意防潮' }, }; const data = mockData[city]; if (data) { return { city, date: new Date().toLocaleDateString('zh-CN'), ...data }; } // 对未知城市生成随机数据 const weathers = ['晴', '多云', '阴', '小雨', '大风']; return { city, date: new Date().toLocaleDateString('zh-CN'), temp: `${Math.floor(Math.random() * 20 + 10)}°C`, weather: weathers[Math.floor(Math.random() * weathers.length)], humidity: `${Math.floor(Math.random() * 50 + 30)}%`, wind: '微风', aqi: Math.floor(Math.random() * 100 + 30), tips: '数据仅供参考', }; } static async queryOrder({ order_id }) { const statuses = ['待支付', '已支付', '拣货中', '已发货', '运输中', '已签收']; const hash = order_id.split('').reduce((a, c) => a + c.charCodeAt(0), 0); const statusIdx = hash % statuses.length; return { order_id, status: statuses[statusIdx], estimated_delivery: '2026-03-01', tracking_number: 'SF' + order_id.replace(/\D/g, '').padEnd(10, '0').substring(0, 10), items: [ { name: '智能音箱 Pro', quantity: 1, price: '¥299' }, ], create_time: '2026-02-20 14:30:00', }; } static async searchKnowledge({ query, response_mode = 'answer', context = [], session_id = null, original_text = '', _session = null }) { const startTime = Date.now(); query = query || ''; const responseMode = response_mode === 'snippet' ? 'snippet' : 'answer'; const knowledgeEndpointId = process.env.VOLC_ARK_KNOWLEDGE_ENDPOINT_ID || process.env.VOLC_ARK_ENDPOINT_ID; console.log(`[ToolExecutor] searchKnowledge called with query="${query}"`); // 注意:answer 模式必须依据知识库回答,因此不再允许本地热答案直接绕过知识库。 // HOT_ANSWERS 保留作运营内容资产,但此处不直接返回给用户。 if (!knowledgeEndpointId) { console.warn('[ToolExecutor] searchKnowledge skipped: knowledge endpoint not configured'); return { query, original_query: query, rewritten_query: query, results: [{ title: '配置缺失', content: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。` }], total: 1, source: 'ark_knowledge', hit: false, reason: 'endpoint_not_configured', }; } const rewrittenQuery = this.rewriteKnowledgeQuery(query, context, session_id, _session); const kbTarget = this.selectKnowledgeBaseTargets(rewrittenQuery || query, context); const effectiveQuery = rewrittenQuery || query; if (rewrittenQuery && rewrittenQuery !== query) { console.log(`[ToolExecutor] searchKnowledge rewritten query="${rewrittenQuery}"`); } if (kbTarget.datasetIds.length > 0) { console.log(`[ToolExecutor] searchKnowledge selected dataset_ids=${kbTarget.datasetIds.join(',')} routes=${kbTarget.matchedRoutes.join(',')}`); } const kbIds = process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS; if (kbIds && kbIds !== 'your_knowledge_base_dataset_id') { if (!knowledgeEndpointId || knowledgeEndpointId === 'your_ark_endpoint_id') { const latencyMs = Date.now() - startTime; console.warn('[ToolExecutor] Ark KB search skipped: knowledge endpoint not configured (knowledge base IDs are set but endpoint is missing)'); return { query, original_query: query, rewritten_query: effectiveQuery, selected_dataset_ids: kbTarget.datasetIds, selected_kb_routes: kbTarget.matchedRoutes, latency_ms: latencyMs, errorType: 'endpoint_not_configured', error: '知识库已配置但知识库方舟端点未配置,请检查 VOLC_ARK_KNOWLEDGE_ENDPOINT_ID 或 VOLC_ARK_ENDPOINT_ID', source: 'ark_knowledge', hit: false, reason: 'endpoint_not_configured', }; } 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 arkResult = await this.searchArkKnowledge(effectiveQuery, context, responseMode, kbTarget.datasetIds, query, _session?.assistantProfile || null); const latencyMs = Date.now() - startTime; console.log(`[ToolExecutor] Ark KB search succeeded in ${latencyMs}ms`); // 缓存所有结果(hit用5分钟TTL,no-hit用2分钟TTL),避免重复API调用 setKbCache(cacheKey, arkResult); return { ...arkResult, original_query: query, rewritten_query: effectiveQuery, selected_dataset_ids: kbTarget.datasetIds, selected_kb_routes: kbTarget.matchedRoutes, latency_ms: latencyMs, }; } catch (error) { const latencyMs = Date.now() - startTime; console.warn('[ToolExecutor] Ark Knowledge Search failed:', error.message); return { query, original_query: query, rewritten_query: effectiveQuery, selected_dataset_ids: kbTarget.datasetIds, selected_kb_routes: kbTarget.matchedRoutes, latency_ms: latencyMs, errorType: error.code === 'ECONNABORTED' || /timeout/i.test(error.message) ? 'timeout' : 'request_failed', error: `知识库查询失败: ${error.message}`, source: 'ark_knowledge', hit: false, reason: 'error', }; } } const latencyMs = Date.now() - startTime; console.warn('[ToolExecutor] Ark knowledge base is not configured'); return { query, original_query: query, rewritten_query: effectiveQuery, selected_dataset_ids: kbTarget.datasetIds, selected_kb_routes: kbTarget.matchedRoutes, latency_ms: latencyMs, errorType: 'not_configured', error: '知识库未配置,请检查 VOLC_ARK_KNOWLEDGE_BASE_IDS', source: 'ark_knowledge', hit: false, reason: 'not_configured', }; } static rewriteKnowledgeQuery(query, context = [], sessionId = null, session = null) { const originalQuery = String(query || '').trim(); if (!originalQuery) { return ''; } // 先做别名归一化(ASR变体如"移程系统"→"一成系统"),再尝试确定性改写 const aliasNormalized = this.normalizeKnowledgeQueryAlias(originalQuery); const deterministicQuery = this.buildDeterministicKnowledgeQuery(aliasNormalized, context); if (deterministicQuery) { console.log(`[ToolExecutor] deterministic rewrite: "${originalQuery}" → "${deterministicQuery}"`); return deterministicQuery; } let normalizedQuery = this.applyKnowledgeQueryAnchor(aliasNormalized); if (sessionId) { normalizedQuery = contextKeywordTracker.enrichQueryWithContext(sessionId, normalizedQuery, session); } return this.sanitizeRewrittenQuery(normalizedQuery); } static sanitizeRewrittenQuery(query) { let cleaned = String(query || '').trim(); if (!cleaned) return cleaned; // 1. 清理口语填充词/语气词 cleaned = cleaned.replace(/[啊哦嗯呢呀哎诶额嘛吧啦哇噢]+/g, ' '); // 2. 清理连续标点 cleaned = cleaned.replace(/[,,。!?!?\s]{2,}/g, ' '); // 3. 去除重复的问句片段(如"怎么吃 怎么吃") cleaned = cleaned.replace(/(.{3,}?)[??!!。,,\s]+\1/g, '$1'); // 4. 按空格分词去重(保序) const parts = cleaned.split(/\s+/).filter(Boolean); const seen = new Set(); const deduped = parts.filter(p => { if (seen.has(p)) return false; seen.add(p); return true; }); cleaned = deduped.join(' ').trim(); // 5. 截断:最大80字符,避免过长query降低KB检索质量 if (cleaned.length > 80) { cleaned = cleaned.slice(0, 80).replace(/\s\S*$/, '').trim(); console.log(`[ToolExecutor] query truncated to 80 chars: "${cleaned}"`); } return cleaned; } /** * 通过方舟 Chat Completions API + knowledge_base metadata 进行知识检索 * 使用独立的 LLM 调用,专门用于知识库检索场景(如语音通话的工具回调) */ static async searchArkKnowledge(query, context = [], responseMode = 'answer', datasetIdsOverride = null, originalQuery = null, assistantProfile = null) { const endpointId = process.env.VOLC_ARK_KNOWLEDGE_ENDPOINT_ID || process.env.VOLC_ARK_ENDPOINT_ID; const kbModel = process.env.VOLC_ARK_KB_MODEL || endpointId; const authKey = process.env.VOLC_ARK_API_KEY || process.env.VOLC_ACCESS_KEY_ID; const kbIds = process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS; if (!endpointId || endpointId === 'your_ark_endpoint_id') { console.warn('[ToolExecutor] searchArkKnowledge skipped: knowledge endpoint not configured'); return { query, results: [{ title: '配置缺失', content: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。` }], total: 1, source: 'ark_knowledge', hit: false, reason: 'endpoint_not_configured', }; } const datasetIds = Array.isArray(datasetIdsOverride) && datasetIdsOverride.length > 0 ? datasetIdsOverride.map((id) => String(id || '').trim()).filter(Boolean) : kbIds.split(',').map(id => id.trim()).filter(Boolean); const topK = parseInt(process.env.VOLC_ARK_KNOWLEDGE_TOP_K) || 3; const threshold = parseFloat(process.env.VOLC_ARK_KNOWLEDGE_THRESHOLD) || 0.3; // 当 query 为空时(FC 流式 chunks 乱序无法解析),使用简短的默认查询 const effectiveQuery = (query && query.trim()) ? query : '请介绍你们的产品和服务'; if (!query || !query.trim()) { console.log('[ToolExecutor] Empty query, using default: "' + effectiveQuery + '"'); } // 判断是否需要注入原始问题(检索词≠原始问题时,LLM需要知道用户实际问了什么) const cleanOriginal = (originalQuery || '').trim(); const answerTargetQuery = cleanOriginal || effectiveQuery; const hasRewrite = cleanOriginal && cleanOriginal !== effectiveQuery; if (hasRewrite) { console.log(`[ToolExecutor] searchArkKnowledge injecting original question: "${cleanOriginal}" (search query: "${effectiveQuery}")`); } // 提取最近2轮对话作为上下文(最多4条user/assistant消息),减少token量加速生成 const recentContext = context .filter(m => m.role === 'user' || m.role === 'assistant') .slice(responseMode === 'snippet' ? -2 : -4); const baseSnippetPrompt = '知识库片段提取助手。提取2-4条与问题最相关的简洁事实片段。只输出中文事实,不寒暄,不写"根据知识库",不补充未出现的内容,无相关内容则说未找到。'; const baseAnswerPrompt = buildKnowledgeAnswerPrompt(assistantProfile); let systemContent = responseMode === 'snippet' ? baseSnippetPrompt : baseAnswerPrompt; if (responseMode === 'answer' && answerTargetQuery) { systemContent += `\n\n当前必须优先直接回答用户当前这一个问题:“${answerTargetQuery}”。如果用户只问一个维度,例如成分、价格、用法、适合谁、区别、正规性、地址或联系方式,就只回答这个维度,不要扩展成整段产品或公司介绍。`; systemContent += `\n\n${this.buildQuestionSlotInstruction(answerTargetQuery)}`; } if (hasRewrite) { systemContent += `\n\n重要:用户的实际问题是"${cleanOriginal}",请围绕这个问题回答,不要偏离用户的真实意图。下方的检索词仅用于匹配知识库文档,不代表用户的真正提问。`; } const messages = [ { role: 'system', content: systemContent, }, ...recentContext, { role: 'user', content: effectiveQuery, }, ]; if (recentContext.length > 0) { console.log(`[ToolExecutor] Ark KB search with ${recentContext.length} context messages`); } const body = { model: kbModel, messages, metadata: { knowledge_base: { dataset_ids: datasetIds, top_k: topK, threshold: threshold, }, }, stream: false, max_tokens: 80, thinking: { type: 'disabled' }, }; const response = await axios.post( 'https://ark.cn-beijing.volces.com/api/v3/chat/completions', body, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authKey}`, }, timeout: 15000, httpsAgent: kbHttpAgent, } ); const content = response.data?.choices?.[0]?.message?.content || '未找到相关信息'; const classifyQuery = [effectiveQuery, (originalQuery || '').trim()].filter(Boolean).join(' '); const classified = this.classifyKnowledgeAnswer(classifyQuery, content); return { query, results: [{ title: '方舟知识库检索结果', content: classified.reply, }], total: 1, source: 'ark_knowledge', hit: classified.hit, reason: classified.reason, }; } /** * 通过 Coze v3 Chat API 进行知识库检索 * 需要在 Coze 平台创建 Bot 并挂载知识库插件 */ static async searchCozeKnowledge(query) { const apiToken = process.env.COZE_API_TOKEN; const botId = process.env.COZE_BOT_ID; const baseUrl = 'https://api.coze.cn/v3'; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiToken}`, }; // 1. 创建对话 const chatRes = await axios.post(`${baseUrl}/chat`, { bot_id: botId, user_id: 'kb_search_user', additional_messages: [ { role: 'user', content: query, content_type: 'text', }, ], stream: true, auto_save_history: false, }, { headers, timeout: 30000 }); const chatData = chatRes.data?.data; if (!chatData?.id || !chatData?.conversation_id) { throw new Error('Coze chat creation failed: ' + JSON.stringify(chatRes.data)); } const chatId = chatData.id; const conversationId = chatData.conversation_id; // 2. 轮询等待完成(最多 30 秒) const maxAttempts = 15; for (let i = 0; i < maxAttempts; i++) { await new Promise(r => setTimeout(r, 2000)); const statusRes = await axios.get( `${baseUrl}/chat/retrieve?chat_id=${chatId}&conversation_id=${conversationId}`, { headers, timeout: 10000 } ); const status = statusRes.data?.data?.status; if (status === 'completed') break; if (status === 'failed' || status === 'requires_action') { throw new Error(`Coze chat ended with status: ${status}`); } } // 3. 获取消息列表 const msgRes = await axios.get( `${baseUrl}/chat/message/list?chat_id=${chatId}&conversation_id=${conversationId}`, { headers, timeout: 10000 } ); const messages = msgRes.data?.data || []; const answerMsg = messages.find(m => m.role === 'assistant' && m.type === 'answer'); const content = answerMsg?.content || '未找到相关信息'; return { query, results: [{ title: 'Coze 知识库检索结果', content: content, }], total: 1, source: 'coze', }; } static async searchLocalKnowledge(query) { const knowledgeBase = { '退货': { title: '退货政策', content: '自签收之日起7天内可无理由退货,15天内可换货。请保持商品及包装完好。退货运费由买家承担(质量问题除外)。', }, '退款': { title: '退款流程', content: '退货审核通过后,退款将在3-5个工作日内原路返回。如超过时间未到账,请联系客服。', }, '配送': { title: '配送说明', content: '默认顺丰快递,普通订单1-3天送达,偏远地区3-7天。满99元免运费。', }, '保修': { title: '保修政策', content: '电子产品保修期1年,自购买之日起计算。人为损坏不在保修范围内。', }, '会员': { title: '会员权益', content: '会员享受9折优惠、免运费、专属客服、生日礼券等权益。年费128元。', }, }; const results = []; const q = query || ''; for (const [key, value] of Object.entries(knowledgeBase)) { if (q.includes(key) || key.includes(q)) { results.push(value); } } if (results.length === 0) { results.push({ title: '搜索结果', content: `未找到与"${query}"直接相关的知识库文档。建议联系人工客服获取更详细的帮助。`, }); } return { query, results, total: results.length, source: 'local' }; } static async getCurrentTime() { const now = new Date(); return { datetime: now.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }), timestamp: now.getTime(), timezone: 'Asia/Shanghai', weekday: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][now.getDay()], }; } static async calculate({ expression }) { try { // 仅允许数字和基本运算符,防止注入 const sanitized = expression.replace(/[^0-9+\-*/().% ]/g, ''); if (!sanitized || sanitized !== expression.replace(/\s/g, '')) { return { error: '表达式包含不支持的字符', expression }; } const result = Function('"use strict"; return (' + sanitized + ')')(); return { expression, result: Number(result), formatted: String(result) }; } catch (e) { return { error: '计算失败: ' + e.message, expression }; } } } module.exports = ToolExecutor;