feat(kb): VikingDB纯检索+重排+Redis上下文+全库搜索+别名扩展+KB保护窗口+RAG语气引导
- 新增 kbRetriever.js: VikingDB search_knowledge 纯检索替代 Ark chat/completions, doubao-seed-rerank 重排, RAG payload 语气引导缓解音色差异 - 新增 redisClient.js: Redis 连接管理 + 5轮对话历史 + KB缓存双写 - toolExecutor.js: 产品别名扩展25条, 全库检索topK=25, 检索阈值0.01, 精简 buildDeterministicKnowledgeQuery - nativeVoiceGateway.js: isPureChitchat扩展, KB保护窗口60s, prequery参数调优 - realtimeDialogRouting.js: resolveReply感知KB保护窗口, fast-path适配raw模式 - app.js: 健康检查新增 redis/reranker/kbRetrievalMode - 新增测试: alias A/B测试, KB retriever测试, Redis客户端测试, raw模式集成测试
This commit is contained in:
@@ -3,6 +3,8 @@ const https = require('https');
|
||||
const arkChatService = require('./arkChatService');
|
||||
const { buildKnowledgeAnswerPrompt, resolveAssistantProfile } = require('./assistantProfileConfig');
|
||||
const { getAssistantProfile } = require('./assistantProfileService');
|
||||
const kbRetriever = require('./kbRetriever');
|
||||
const redisClient = require('./redisClient');
|
||||
|
||||
// HTTP keep-alive agent:复用TCP连接,避免每次请求重新握手
|
||||
const kbHttpAgent = new https.Agent({
|
||||
@@ -51,13 +53,15 @@ const KB_CACHE_MAX_SIZE = 200;
|
||||
const kbQueryCache = new Map();
|
||||
|
||||
function getKbCacheKey(query, datasetIds, profileScope = 'global') {
|
||||
return `${String(profileScope || 'global').trim() || 'global'}|${(query || '').trim()}|${(datasetIds || []).sort().join(',')}`;
|
||||
const mode = process.env.VOLC_ARK_KB_RETRIEVAL_MODE || 'answer';
|
||||
return `vdb2|${mode}|${String(profileScope || 'global').trim() || 'global'}|${(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;
|
||||
// hit: 5min TTL; no-hit: 10s 短 TTL(仅防同一轮次重复查 VikingDB)
|
||||
const ttl = entry.hit ? KB_CACHE_TTL_MS : 10000;
|
||||
if (Date.now() - entry.timestamp > ttl) {
|
||||
kbQueryCache.delete(key);
|
||||
return null;
|
||||
@@ -70,6 +74,7 @@ function setKbCache(key, result) {
|
||||
const oldest = kbQueryCache.keys().next().value;
|
||||
kbQueryCache.delete(oldest);
|
||||
}
|
||||
// hit: 正常缓存; no-hit: 内存 10s 去重(防止同一轮次重复查 VikingDB,不写 Redis)
|
||||
kbQueryCache.set(key, { result, timestamp: Date.now(), hit: !!result.hit });
|
||||
}
|
||||
|
||||
@@ -354,10 +359,20 @@ class ToolExecutor {
|
||||
// 确定路由:多意图可并行,只排除真正冲突的组合
|
||||
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 (hasProductIntent) {
|
||||
priorityRouteNames.push('product');
|
||||
// 产品问题同时搜FAQ和科普,获取更全面的回答(好转反应、科普误区等补充信息)
|
||||
if (!hasFaqIntent) priorityRouteNames.push('faq');
|
||||
if (!hasScienceIntent) priorityRouteNames.push('science');
|
||||
}
|
||||
if (hasCompanyIntent) {
|
||||
priorityRouteNames.push('company');
|
||||
// 公司问题同时搜产品和系统培训,test collection 内容有限
|
||||
if (!hasProductIntent) priorityRouteNames.push('product');
|
||||
if (!hasSystemIntent) priorityRouteNames.push('system');
|
||||
}
|
||||
if (hasFaqIntent) priorityRouteNames.push('faq');
|
||||
if (hasScienceIntent) priorityRouteNames.push('science');
|
||||
|
||||
if (priorityRouteNames.length > 0) {
|
||||
const routingRules = this.getKnowledgeBaseRoutingRules();
|
||||
@@ -392,16 +407,14 @@ class ToolExecutor {
|
||||
|
||||
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细胞营养素 基础套装 大白 小红 小白';
|
||||
// ====================================================================
|
||||
// 精简版:只保留 VikingDB 语义检索已知会失败的场景
|
||||
// 产品/公司/认证等查询全部交给 VikingDB + reranker 处理原始语义
|
||||
// 追问/代词由 enrichQueryWithContext + KB保护窗口 处理
|
||||
// ====================================================================
|
||||
|
||||
// === 一成系统子话题分流(内部术语,向量检索难区分子话题) ===
|
||||
if (/(一成系统|Ai众享|数字化工作室|盛咖学愿|三大平台|四大Ai生态|四大生态|智能生产力)/i.test(text)) {
|
||||
if (/(核心竞争力|竞争力|核心优势|优势)/i.test(text)) return '一成系统 核心竞争力 三大平台 四大Ai生态 零成本高效率';
|
||||
if (/(发展|怎么做|怎么用|如何用|如何做|关键点|关键|方法|步骤)/i.test(text)) return '一成系统 发展PM事业 三大平台 四大Ai生态 零成本高效率 全球市场';
|
||||
@@ -421,115 +434,24 @@ class ToolExecutor {
|
||||
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 (/(招商|代理|加盟|事业机会|招商稿|代理政策)/i.test(text)) return '一成系统 PM事业 招商与代理 软件赋能 0成本高效率';
|
||||
if (/(如何发展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 '';
|
||||
// === 敏感话题兜底(必须精确控制回复内容) ===
|
||||
if (/(传销|骗局|骗子|正规吗|合法吗|正不正规|合不合法|是不是传销|直销还是传销|层级分销|非法集资|拉人头|下线|发展下线|报单|人头费)/i.test(text)) return '德国PM 1993年 创立 100多个国家 FitLine 公司介绍 邓白氏 99分 AAA+ 合法直销';
|
||||
|
||||
// === 别名纠正(向量检索不认的别名) ===
|
||||
if (/暖炉原理/i.test(text)) return '火炉原理';
|
||||
|
||||
// 所有其它查询(产品/公司/认证/培训等):不做确定性改写
|
||||
// 依赖 normalizeKnowledgeQueryAlias(别名归一化)+ enrichQueryWithContext(上下文补充)+ VikingDB + reranker
|
||||
return null;
|
||||
}
|
||||
|
||||
static applyKnowledgeQueryAnchor(query) {
|
||||
@@ -560,15 +482,39 @@ class ToolExecutor {
|
||||
.replace(/Basics/gi, 'Basics')
|
||||
.replace(/基础三合一|三合一基础套|大白小红小白|基础套装?/g, 'PM细胞营养素 基础套装')
|
||||
.replace(/儿童倍适|儿童产品/g, '儿童倍适')
|
||||
.replace(/小红精华液/g, 'Activize Serum 小红精华液')
|
||||
.replace(/小红产品/g, '小红产品 Activize Oxyplus')
|
||||
.replace(/大白产品/g, '大白产品 Basics')
|
||||
.replace(/小白产品/g, '小白产品 Restorate')
|
||||
.replace(/(?<!小红产品\s*)(?<!大白产品\s*)(?<!小白产品\s*)小红/g, '小红产品 Activize Oxyplus')
|
||||
.replace(/(?<!小红产品\s*)(?<!大白产品\s*)(?<!小白产品\s*)小红(?!精华)/g, '小红产品 Activize Oxyplus')
|
||||
.replace(/(?<!小红产品\s*)(?<!大白产品\s*)(?<!小白产品\s*)大白/g, '大白产品 Basics')
|
||||
.replace(/(?<!小红产品\s*)(?<!大白产品\s*)(?<!小白产品\s*)(?<!儿童)小白/g, '小白产品 Restorate')
|
||||
.replace(/维适多/g, '小白产品 Restorate')
|
||||
.replace(/火炉原理/g, '火炉原理')
|
||||
.replace(/阿育吠陀|Ayurveda/gi, '阿育吠陀')
|
||||
// === 产品俗名/简称 → 标准名+英文名(增强向量检索命中率)===
|
||||
.replace(/小绿/g, 'D-Drink 小绿 排毒饮')
|
||||
.replace(/(?<!小绿 )排毒饮/g, 'D-Drink 排毒饮')
|
||||
.replace(/(?<!草本护理)牙膏/g, '草本护理牙膏 Med Dental+')
|
||||
.replace(/(?:口腔免疫喷雾|口腔喷雾|免疫喷雾)/g, 'IB5 口腔免疫喷雾')
|
||||
.replace(/(?<!免疫)喷雾/g, 'IB5 口腔免疫喷雾')
|
||||
.replace(/(?<!Herbal Tea )草本茶/g, 'Herbal Tea 草本茶')
|
||||
.replace(/发宝|发健/g, 'Med Hair+ 发宝')
|
||||
.replace(/(?:男士乳霜|男士护肤|男士面霜)/g, 'Men Face 男士护肤乳霜')
|
||||
.replace(/纤萃/g, 'TopShape 纤萃')
|
||||
.replace(/运动饮料/g, 'Fitness-Drink 运动饮料')
|
||||
.replace(/(?<!Generation 50\+? )乐活/g, 'Generation 50+ 乐活')
|
||||
.replace(/(?<!Zellschutz )细胞抗氧素/g, 'Zellschutz 细胞抗氧素')
|
||||
.replace(/CC套装|CC胶囊|CC乳霜/g, 'CC-Cell')
|
||||
.replace(/(?<!Q10 )辅酵素/g, 'Q10 辅酵素')
|
||||
.replace(/氧修护/g, 'Q10 氧修护')
|
||||
.replace(/小黑/g, 'MEN+ 倍力健 小黑')
|
||||
.replace(/(?<!MEN\+? )倍力健/g, 'MEN+ 倍力健')
|
||||
.replace(/(?<!ProShape Amino )氨基酸/g, 'ProShape Amino 氨基酸')
|
||||
.replace(/BCAA/gi, 'ProShape Amino BCAA')
|
||||
.replace(/(?<!胶原蛋白)胶原蛋白(?!肽)/g, '胶原蛋白肽')
|
||||
.replace(/乳酪煲|乳酪饮品|乳酪/g, '乳酪煲 乳酪饮品')
|
||||
.replace(/(?<!关节套装 )关节舒缓/g, '关节套装 关节舒缓')
|
||||
.trim();
|
||||
}
|
||||
|
||||
@@ -694,7 +640,7 @@ class ToolExecutor {
|
||||
};
|
||||
}
|
||||
|
||||
static async searchKnowledge({ query, response_mode = 'answer', context = [], session_id = null, original_text = '', _session = null }) {
|
||||
static async searchKnowledge({ query, response_mode = 'answer', context = [], session_id = null, original_text = '', _session = null, skipCache = false }) {
|
||||
const startTime = Date.now();
|
||||
query = query || '';
|
||||
const responseMode = response_mode === 'snippet' ? 'snippet' : 'answer';
|
||||
@@ -729,14 +675,15 @@ class ToolExecutor {
|
||||
}
|
||||
|
||||
const rewrittenQuery = this.rewriteKnowledgeQuery(query, context, session_id, _session);
|
||||
const kbTarget = this.selectKnowledgeBaseTargets(rewrittenQuery || query, context);
|
||||
// 全库检索:始终搜索所有 collection,由 VikingDB + reranker 判断相关性
|
||||
const allDatasetIds = String(process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS || '')
|
||||
.split(',').map(id => id.trim()).filter(Boolean);
|
||||
const kbTarget = { datasetIds: allDatasetIds, matchedRoutes: ['all'] };
|
||||
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(',')}`);
|
||||
}
|
||||
console.log(`[ToolExecutor] searchKnowledge full-scan all ${allDatasetIds.length} collections`);
|
||||
|
||||
const kbIds = process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS;
|
||||
if (kbIds && kbIds !== 'your_knowledge_base_dataset_id') {
|
||||
@@ -758,12 +705,13 @@ class ToolExecutor {
|
||||
};
|
||||
}
|
||||
try {
|
||||
// 缓存检查:相同effectiveQuery + datasetIds命中缓存时直接返回,避免重复API调用
|
||||
// 缓存检查:优先 Redis,降级内存 Map(skipCache 时跳过)
|
||||
const cacheKey = getKbCacheKey(effectiveQuery, kbTarget.datasetIds, profileScope);
|
||||
const cached = getKbCache(cacheKey);
|
||||
const redisCached = skipCache ? null : await redisClient.getKbCache(cacheKey).catch(() => null);
|
||||
const cached = skipCache ? null : (redisCached || getKbCache(cacheKey));
|
||||
if (cached) {
|
||||
const latencyMs = Date.now() - startTime;
|
||||
console.log(`[ToolExecutor] Ark KB cache hit in ${latencyMs}ms key="${cacheKey.slice(0, 60)}"`);
|
||||
console.log(`[ToolExecutor] Ark KB cache hit in ${latencyMs}ms key="${cacheKey.slice(0, 60)}" source=${redisCached ? 'redis' : 'memory'}`);
|
||||
return {
|
||||
...cached,
|
||||
original_query: query,
|
||||
@@ -774,12 +722,45 @@ class ToolExecutor {
|
||||
cache_hit: true,
|
||||
};
|
||||
}
|
||||
console.log('[ToolExecutor] Trying Ark Knowledge Search...');
|
||||
const arkResult = await this.searchArkKnowledge(effectiveQuery, context, responseMode, kbTarget.datasetIds, query, assistantProfile);
|
||||
|
||||
// 根据检索模式选择链路
|
||||
const retrievalMode = process.env.VOLC_ARK_KB_RETRIEVAL_MODE || 'answer';
|
||||
let arkResult;
|
||||
|
||||
if (retrievalMode === 'raw') {
|
||||
// ★ 新链路:纯检索 + 重排,不经 LLM 加工
|
||||
console.log('[ToolExecutor] Using RAW retrieval mode (kbRetriever)');
|
||||
const rawResult = await kbRetriever.searchAndRerank(effectiveQuery, {
|
||||
datasetIds: kbTarget.datasetIds,
|
||||
sessionId: session_id,
|
||||
session: _session,
|
||||
originalQuery: query,
|
||||
});
|
||||
// 转换为与旧格式兼容的结构
|
||||
arkResult = {
|
||||
query: rawResult.query,
|
||||
results: rawResult.ragPayload.length > 0
|
||||
? rawResult.ragPayload.map(item => ({ title: item.title, content: item.content }))
|
||||
: [{ title: '未找到', content: `知识库中暂未找到与"${query}"直接相关的信息,请换个更具体的问法再试。` }],
|
||||
total: rawResult.ragPayload.length,
|
||||
source: 'ark_knowledge',
|
||||
hit: rawResult.hit,
|
||||
reason: rawResult.reason,
|
||||
retrieval_mode: 'raw',
|
||||
top_score: rawResult.topScore,
|
||||
chunks_count: rawResult.rerankedChunks?.length || 0,
|
||||
};
|
||||
} else {
|
||||
// 旧链路:LLM 加工模式
|
||||
console.log('[ToolExecutor] Using ANSWER retrieval mode (searchArkKnowledge)');
|
||||
arkResult = await this.searchArkKnowledge(effectiveQuery, context, responseMode, kbTarget.datasetIds, query, assistantProfile);
|
||||
}
|
||||
|
||||
const latencyMs = Date.now() - startTime;
|
||||
console.log(`[ToolExecutor] Ark KB search succeeded in ${latencyMs}ms`);
|
||||
// 缓存所有结果(hit用5分钟TTL,no-hit用2分钟TTL),避免重复API调用
|
||||
console.log(`[ToolExecutor] Ark KB search succeeded in ${latencyMs}ms mode=${retrievalMode}`);
|
||||
// 缓存到 Redis + 内存双写
|
||||
setKbCache(cacheKey, arkResult);
|
||||
redisClient.setKbCache(cacheKey, arkResult).catch(() => {});
|
||||
return {
|
||||
...arkResult,
|
||||
original_query: query,
|
||||
|
||||
Reference in New Issue
Block a user