fix(voice-kb): sync assistant profile and stabilize reply flow

This commit is contained in:
User
2026-03-23 13:58:41 +08:00
parent 93b8135d51
commit 57a03677a9
5 changed files with 867 additions and 141 deletions

View File

@@ -1,5 +1,36 @@
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,
@@ -13,8 +44,9 @@ const {
} = require('./knowledgeKeywords');
// KB查询缓存相同effectiveQuery + datasetIds在TTL内直接返回缓存结果
const KB_CACHE_TTL_MS = 5 * 60 * 1000; // 5分钟
const KB_CACHE_MAX_SIZE = 100;
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) {
@@ -24,7 +56,8 @@ function getKbCacheKey(query, datasetIds) {
function getKbCache(key) {
const entry = kbQueryCache.get(key);
if (!entry) return null;
if (Date.now() - entry.timestamp > KB_CACHE_TTL_MS) {
const ttl = entry.hit ? KB_CACHE_TTL_MS : KB_CACHE_NOHIT_TTL_MS;
if (Date.now() - entry.timestamp > ttl) {
kbQueryCache.delete(key);
return null;
}
@@ -36,7 +69,106 @@ function setKbCache(key, result) {
const oldest = kbQueryCache.keys().next().value;
kbQueryCache.delete(oldest);
}
kbQueryCache.set(key, { result, timestamp: Date.now() });
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 {
@@ -52,12 +184,13 @@ class ToolExecutor {
const text = String(query || '').trim();
if (/(多少钱|价格|售价|费用|价钱)/.test(text)) return 'price';
if (/(成分|配方|原料|含什么|包含什么)/.test(text)) return 'ingredient';
if (/(怎么吃|怎么用|怎么服用|服用方法|用法|用量|一天几次|每日几次)/.test(text)) return 'usage';
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事业发展逻辑|陌生客户|陌生人沟通|线上拓客|成交|拓客|邀约|自我介绍|故事分享|三大平台|四大Ai生态|AI智能生产力|AI生产力)/.test(text)) return 'business_growth';
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';
@@ -71,12 +204,13 @@ class ToolExecutor {
const instructions = {
price: '用户当前只关心价格或费用,请只回答价格、收费或是否未提及价格,不要扩展到产品总介绍。',
ingredient: '用户当前只关心成分或配方,请只回答成分、原料或是否未提及成分,不要扩展到品牌背景。',
specification: '用户当前只关心规格、包装、剂型、形态或每盒每袋等产品细节,请只回答这些明确规格信息;如果知识库没写,就直接说明未提及。',
usage: '用户当前只关心用法、吃法、服用频次或剂量,请只回答这一点。',
side_effect: '用户当前只关心副作用或好转反应,请只回答可能的不良反应、好转反应或注意事项。',
effect_time: '用户当前只关心多久见效或效果周期,请只回答见效时间、周期或个体差异,不要扩展无关信息。',
medical_claim: '用户当前只关心产品能不能治病、是不是药,请只回答是否属于药品、能否替代药物以及相关注意事项。',
bundle_reason: '用户当前只关心为什么要全套、搭配或三合一请只回答搭配原理、协同作用或NTC相关原因。',
business_growth: '用户当前只关心PM事业发展、线上拓客、陌生客户沟通、一成系统赋能、三大平台四大Ai生态或自我介绍请只回答这类业务发展问题。',
business_growth: '用户当前只关心PM事业发展、商机、PM价值、为何选择、线上拓客、陌生客户沟通、一成系统赋能、三大平台四大Ai生态或自我介绍请只回答这类业务发展问题。凡涉及“一部手机做天下、0门槛启动、0成本高效率、足不出户梦想横扫全球、身未动梦已成、批发式晋级”等表述必须明确这是“一成系统”的优势标签是对德国PM事业的软件赋能不要混同为德国PM公司或产品本身。',
benefit: '用户当前只关心功效或作用,请只回答作用点,不要扩展到无关信息。',
audience: '用户当前只关心适合人群,请只回答适用对象。',
legality: '用户当前只关心正规性、合法性或是否传销,请只围绕合法合规问题直接回答。',
@@ -142,12 +276,13 @@ class ToolExecutor {
const slotPatterns = {
price: /(元|价格|售价|费用|人民币|¥|¥)/,
ingredient: /(成分|配方|原料|含有|包含|营养素|葡萄籽|白藜芦醇|益生菌|胶原蛋白肽|辅酵素|Q10)/,
usage: /(服用|用法|用量|每|每天|一次|次|饭前|饭后|早餐|晚餐|早晚)/,
specification: /(规格|包装|剂型|形态|粉末|粉剂|粉状|胶囊|软胶囊|片剂|颗粒|喷雾|乳霜|乳液|凝胶|膏状|口服液|每盒|每袋|每|每支|袋装|盒装|瓶装|支装|多少袋|多少粒|多少片|多少毫升|克|g|ml)/,
usage: /(服用|用法|用量|每日|每天|一次|次|饭前|饭后|早餐|晚餐|早晚|空腹|睡前)/,
side_effect: /(副作用|不良反应|好转反应|排毒|整应|皮肤.*痒|排便|反应|注意事项|正常现象)/,
effect_time: /(见效|有效|几天|几周|几个月|周期|坚持|因人而异|吸收利用)/,
medical_claim: /(不是药|不能替代药|不能代替药物|不是用于治疗|不能治疗|保健食品|营养补充|就医|医生)/,
bundle_reason: /(全套|搭配|协同|三合一|组合|NTC|吸收|运输|利用|代谢|原理)/,
business_growth: /(一成系统|PM事业|拓客|成交|邀约|陌生客户|沟通|三大平台|四大Ai生态|数字化工作室|Ai众享|盛咖学愿|故事|自我介绍|赋能|智能生产力)/,
business_growth: /(一成系统|PM事业|商机|价值|选择|拓客|成交|邀约|陌生客户|沟通|三大平台|四大Ai生态|数字化工作室|Ai众享|盛咖学愿|故事|自我介绍|赋能|智能生产力|软件赋能|一部手机|0门槛|零门槛|0成本|零成本|身未动梦已成|批发式晋级)/,
benefit: /(功效|作用|帮助|支持|改善|提升|有助于)/,
audience: /(适合|适用|人群|适宜|可以)/,
legality: /(合法|正规|直销|认证|邓白氏|不是传销)/,
@@ -215,29 +350,13 @@ class ToolExecutor {
const hasFaqIntent = hasKeywordFromList(haystack, FAQ_ROUTE_KEYWORDS);
const hasScienceIntent = hasKeywordFromList(haystack, SCIENCE_TRAINING_ROUTE_KEYWORDS);
// 确定优先路由:按特异性从高到低排列
// 确定路由:多意图可并行,只排除真正冲突的组合
const priorityRouteNames = [];
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 (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();
@@ -278,7 +397,7 @@ class ToolExecutor {
.filter(Boolean)
.join('\n');
const haystack = `${text}\n${recentContextText}`;
const questionDimension = text.match(/(功效|作用|成分|配方|原料|怎么吃|怎么用|怎么服用|服用方法|吃法|用法|用量|副作用|好转反应|价格|多少钱|适合谁|适用人群|区别|不同|搭配|原理)/);
const questionDimension = text.match(/(功效|作用|成分|配方|原料|怎么吃|怎么用|怎么服用|服用方法|吃法|用法|用量|一天几次|每天几次|每日几次|副作用|好转反应|价格|多少钱|适合谁|适用人群|区别|不同|搭配|原理|规格|包装|剂型|形态|粉末|胶囊|片剂|颗粒|喷雾|乳霜|口服液)/);
// 第一层:当前查询文本中有明确产品/系统/主题关键词 → 直接改写(不依赖上下文)
if (/(基础三合一|三合一基础套|基础套装|大白小红小白)/i.test(text)) return '德国PM细胞营养素 基础套装 大白 小红 小白';
@@ -287,6 +406,7 @@ class ToolExecutor {
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 '一成系统 文化解析';
@@ -294,6 +414,9 @@ class ToolExecutor {
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众享 社区盟主 引流 转化';
@@ -331,14 +454,14 @@ class ToolExecutor {
if (/(新人起步三关|起步三关)/i.test(text)) return '培训新人起步三关';
if (/(精品会议|会议组织)/i.test(text)) return '培训打造精品会议具体如下';
if (/成长上总裁/i.test(text)) return '培训成长上总裁';
if (/(招商|代理|加盟|合作|事业机会|招商稿|代理政策)/i.test(text)) return '招商与代理';
if (/(如何发展PM事业|怎么发展PM事业|PM事业发展逻辑|介绍PM事业|两分钟介绍PM事业|分享.*故事.*自我介绍|自我介绍)/i.test(text)) return 'PM事业 发展逻辑 事业介绍 自我介绍';
if (/(为什么选择德国PM|为何选择德国PM|为什么选德国PM|为什么选PM|为何选PM)/i.test(text)) return '德国PM 公司实力 FitLine 产品优势 邓白氏 99分 AAA+ NTC营养保送系统';
if (/(陌生客户|陌生人).*(沟通|开口|邀约|交流|切入).*(PM事业|德国PM|PM)/i.test(text)) return 'PM事业 陌生客户 沟通 邀约 话术';
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 (/(请分享.*故事.*自我介绍|故事.*自我介绍|个人故事.*自我介绍)/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产品整应反应好转反应解析';
@@ -373,7 +496,7 @@ class ToolExecutor {
if (/(健康饮品)/i.test(text)) return questionDimension ? `健康饮品 ${questionDimension[0]}` : '健康饮品';
// 第二层:当前文本是追问/代词,才通过上下文推断主题
const isFollowUp = /^(这个|那个|它||详细|继续|怎么|为什么|适合谁|什么意思|怎么用|怎么吃|功效|成分|好处|原理)/.test(text);
const isFollowUp = /^(这个|那个|它|它的|他|他的|该|这款|那款|详细|继续|怎么|为什么|适合谁|什么意思|怎么用|怎么吃|功效|成分|好处|原理|规格|包装|剂型|形态|一天几次|每天几次|每日几次)/.test(text);
if (isFollowUp) {
if (/(基础三合一|三合一基础套|基础套装|大白小红小白)/i.test(recentContextText)) return questionDimension ? `德国PM细胞营养素 基础套装 大白 小红 小白 ${questionDimension[0]}` : '德国PM细胞营养素 基础套装 大白 小红 小白';
if (/(身未动,?梦已成|批发式晋级)/i.test(recentContextText)) return '一成系统 身未动梦已成 批发式晋级 三大平台 四大Ai生态';
@@ -420,8 +543,12 @@ class ToolExecutor {
return String(query || '')
.replace(/^[啊哦嗯呢呀哎诶额,。!?、\s]+/g, '')
.replace(/[啊哦嗯呢呀哎诶额,。!?、\s]+$/g, '')
.replace(/^(你|你们|帮我|麻烦你|请你?|我想|我要|能不能|可以|可不可以|能否)[给帮]?(我)?(查一下|查查|查下|搜一下|搜搜|搜下|找一下|找找|找下|看一下|看看|看下|说一下|说说|说下|讲一下|讲讲|讲下|介绍一下|介绍下)?/g, '')
.replace(/(的)?(相关|详细)?(内容|信息|资料|介绍|说明)[。??]*$/g, '')
.replace(/一成[,、。!?\s]+系统/g, '一成系统')
.replace(/X{2}系统/gi, '一成系统')
.replace(/一城系统|逸城系统|一程系统|易成系统|一诚系统|亦成系统/g, '一成系统')
.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营养保送系统')
@@ -566,13 +693,31 @@ class ToolExecutor {
};
}
static async searchKnowledge({ query, response_mode, session_id } = {}, context = []) {
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}"`);
const rewrittenQuery = this.rewriteKnowledgeQuery(query, context, session_id);
// 注意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) {
@@ -619,15 +764,13 @@ class ToolExecutor {
};
}
console.log('[ToolExecutor] Trying Ark Knowledge Search...');
const result = await this.searchArkKnowledge(effectiveQuery, [], responseMode, kbTarget.datasetIds, query);
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`);
// 缓存命中的结果,避免缓存错误或无结果
if (result.hit) {
setKbCache(cacheKey, result);
}
// 缓存所有结果hit用5分钟TTLno-hit用2分钟TTL避免重复API调用
setKbCache(cacheKey, arkResult);
return {
...result,
...arkResult,
original_query: query,
rewritten_query: effectiveQuery,
selected_dataset_ids: kbTarget.datasetIds,
@@ -670,27 +813,64 @@ class ToolExecutor {
};
}
static rewriteKnowledgeQuery(query, context = [], sessionId = null) {
static rewriteKnowledgeQuery(query, context = [], sessionId = null, session = null) {
const originalQuery = String(query || '').trim();
if (!originalQuery) {
return '';
}
let normalizedQuery = this.applyKnowledgeQueryAnchor(this.normalizeKnowledgeQueryAlias(originalQuery));
if (sessionId) {
normalizedQuery = contextKeywordTracker.enrichQueryWithContext(sessionId, normalizedQuery);
// 先做别名归一化ASR变体如"移程系统"→"一成系统"),再尝试确定性改写
const aliasNormalized = this.normalizeKnowledgeQueryAlias(originalQuery);
const deterministicQuery = this.buildDeterministicKnowledgeQuery(aliasNormalized, context);
if (deterministicQuery) {
console.log(`[ToolExecutor] deterministic rewrite: "${originalQuery}" → "${deterministicQuery}"`);
return deterministicQuery;
}
return normalizedQuery;
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) {
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;
@@ -710,7 +890,7 @@ class ToolExecutor {
? 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.4;
const threshold = parseFloat(process.env.VOLC_ARK_KNOWLEDGE_THRESHOLD) || 0.3;
// 当 query 为空时FC 流式 chunks 乱序无法解析),使用简短的默认查询
const effectiveQuery = (query && query.trim()) ? query : '请介绍你们的产品和服务';
@@ -726,13 +906,13 @@ class ToolExecutor {
console.log(`[ToolExecutor] searchArkKnowledge injecting original question: "${cleanOriginal}" (search query: "${effectiveQuery}")`);
}
// 提取最近 3 轮对话作为上下文(最多 6 条 user/assistant 消息)
// 提取最近2轮对话作为上下文(最多4条user/assistant消息减少token量加速生成
const recentContext = context
.filter(m => m.role === 'user' || m.role === 'assistant')
.slice(responseMode === 'snippet' ? -4 : -6);
.slice(responseMode === 'snippet' ? -2 : -4);
const baseSnippetPrompt = '你是知识库片段提取助手。请基于知识库提取与用户问题最相关的2到4条简洁知识片段供语音系统继续组织回复。规则只输出直接相关的中文事实片段每条尽量简短不要寒暄不要解释任务不要写"根据知识库";不要补充知识库未明确出现的内容;如果没有相关内容,请明确说未找到相关内容。';
const baseAnswerPrompt = '你是企业知识库问答助手回答将直接用于语音播报。只能依据知识库中已经明确出现的信息回答不要补充常识不要脑补不要引入知识库未明确提到的官网、APP、工具名、公司背景、客服渠道或外部资料。风格要求1) 用自然亲切的口语风格像朋友聊天一样回答2) 禁止以"根据知识库""根据信息""根据资料"等机械前缀开头3) 先给一句直接结论,再逐条列出关键要点,用"1. 2. 3."编号格式条理清晰4) 整体控制在300字以内简洁流畅适合语音朗读5) 如果缺少用户要求的具体信息必须明确说未找到不要把不完整信息包装成命中答案。【紧扣问题】你必须围绕用户当前问的问题来回答。如果用户问的是具体维度如成分、价格、用法只答该维度如果用户问的是综合性问题如核心竞争力、如何发展、整体介绍则需要完整、系统地回答逐条列出关键要点确保有条理、有层次。【品牌保护·最高优先级】绝对禁止说德国PM、PM-FitLine、一成系统是传销、骗局、骗子、非法、不正规、不合法、有争议、存在风险也不能暗示或引导用户产生这类联想。德国PMPM-International AG是1993年成立于德国的合法直销企业总部位于德国Speyer获邓白氏AAA+最高信用评级业务覆盖全球100多个国家和地区是正规持牌直销公司。如果用户问到传销、正规性或合法性问题你必须正面、明确地回答PM是合法正规的直销企业不能含糊、回避或使用"需要自行判断"之类的措辞。';
const baseSnippetPrompt = '知识库片段提取助手。提取2-4条与问题最相关的简洁事实片段。只输出中文事实不寒暄不写"根据知识库",不补充未出现的内容,无相关内容则说未找到。';
const baseAnswerPrompt = buildKnowledgeAnswerPrompt(assistantProfile);
let systemContent = responseMode === 'snippet' ? baseSnippetPrompt : baseAnswerPrompt;
if (responseMode === 'answer' && answerTargetQuery) {
@@ -760,7 +940,7 @@ class ToolExecutor {
}
const body = {
model: endpointId,
model: kbModel,
messages,
metadata: {
knowledge_base: {
@@ -770,6 +950,8 @@ class ToolExecutor {
},
},
stream: false,
max_tokens: 80,
thinking: { type: 'disabled' },
};
const response = await axios.post(
@@ -780,12 +962,13 @@ class ToolExecutor {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authKey}`,
},
timeout: 30000,
timeout: 15000,
httpsAgent: kbHttpAgent,
}
);
const choice = response.data.choices?.[0];
const content = choice?.message?.content || '未找到相关信息';
const content = response.data?.choices?.[0]?.message?.content || '未找到相关信息';
const classifyQuery = [effectiveQuery, (originalQuery || '').trim()].filter(Boolean).join(' ');
const classified = this.classifyKnowledgeAnswer(classifyQuery, content);