fix: 品牌保护+知识库全量覆盖 - 6层防御解决传销问题 + 30+产品关键词补全

This commit is contained in:
User
2026-03-17 11:00:09 +08:00
parent f97dd7e3d5
commit 0560db1048
46 changed files with 1948 additions and 120 deletions

View File

@@ -9,8 +9,11 @@ const db = require('../db');
// 存储文字对话的会话状态sessionId -> session
const chatSessions = new Map();
const BRAND_HARMFUL_PATTERN = /传销|骗局|骗子公司|非法集资|非法经营|不正规|不合法|庞氏骗局|老鼠会|拉人头的|割韭菜/;
const BRAND_SAFE_REPLY = '德国PM是一家1993年成立于德国的合法直销公司获得邓白氏AAA+认证业务覆盖100多个国家和地区。如果你想了解更多可以问我关于PM公司的详细介绍哦。';
function normalizeAssistantText(text) {
return String(text || '')
let result = String(text || '')
.replace(/\r/g, ' ')
.replace(/\n{2,}/g, '。')
.replace(/\n/g, ' ')
@@ -19,6 +22,11 @@ function normalizeAssistantText(text) {
.replace(/([。!?;,])\s*([。!?;,])/g, '$2')
.replace(/\s+/g, ' ')
.trim();
if (BRAND_HARMFUL_PATTERN.test(result)) {
console.warn(`[Chat][SafeGuard] blocked harmful content: ${JSON.stringify(result.slice(0, 200))}`);
return BRAND_SAFE_REPLY;
}
return result;
}
async function loadHandoffMessages(sessionId, voiceSubtitles = []) {
@@ -77,7 +85,13 @@ function buildInitialContextMessages(session) {
}
async function buildKnowledgeContextMessages(sessionId, session) {
const dbHistory = await db.getHistoryForLLM(sessionId, 20).catch(() => []);
const recentMessages = await db.getRecentMessages(sessionId, 20).catch(() => []);
const scopedMessages = session?.fromVoice && session?.handoffSummaryUsed
? recentMessages.filter((item) => !/^voice_/i.test(String(item?.source || '')))
: recentMessages;
const dbHistory = scopedMessages
.filter((item) => item && (item.role === 'user' || item.role === 'assistant'))
.map((item) => ({ role: item.role, content: item.content }));
const summary = String(session?.handoffSummary || '').trim();
if (!summary || session?.handoffSummaryUsed) {
return dbHistory;
@@ -98,6 +112,14 @@ function extractKnowledgeReply(result) {
return typeof result === 'string' ? result : '';
}
function buildFastGreetingReply(message) {
const text = String(message || '').trim();
if (!/^(喂|你好|您好|嗨|哈喽|hello|hi|在吗|在不在|早上好|中午好|下午好|晚上好|早安|晚安)[,!。??~\s]*[啊呀吧呢哦嗯嘛哈的了]*[!。??~]*$/i.test(text)) {
return '';
}
return '你好😊我是大沃智能助手。你可以直接问我一成系统、德国PM产品、招商合作、营养科普等问题我会尽量快速给你准确回复。';
}
async function tryKnowledgeReply(sessionId, session, message) {
const text = String(message || '').trim();
if (!text) return null;
@@ -106,6 +128,9 @@ async function tryKnowledgeReply(sessionId, session, message) {
return null;
}
const result = await ToolExecutor.execute('search_knowledge', { query: text }, context);
if (!result?.hit) {
return null;
}
const content = normalizeAssistantText(extractKnowledgeReply(result));
if (!content) {
return null;
@@ -120,6 +145,8 @@ async function tryKnowledgeReply(sessionId, session, message) {
source: result?.source || null,
original_query: result?.original_query || text,
rewritten_query: result?.rewritten_query || null,
selected_dataset_ids: result?.selected_dataset_ids || null,
selected_kb_routes: result?.selected_kb_routes || null,
hit: typeof result?.hit === 'boolean' ? result.hit : null,
reason: result?.reason || null,
error_type: result?.errorType || null,
@@ -188,6 +215,17 @@ router.post('/send', async (req, res) => {
// 写入数据库:用户消息
db.addMessage(sessionId, 'user', message, 'chat_user').catch(e => console.warn('[DB] addMessage failed:', e.message));
const fastGreetingReply = buildFastGreetingReply(message);
if (fastGreetingReply) {
db.addMessage(sessionId, 'assistant', fastGreetingReply, 'chat_bot').catch(e => console.warn('[DB] addMessage failed:', e.message));
return res.json({
success: true,
data: {
content: fastGreetingReply,
},
});
}
const knowledgeReply = await tryKnowledgeReply(sessionId, session, message);
if (knowledgeReply) {
session.handoffSummaryUsed = true;
@@ -283,15 +321,21 @@ router.post('/send-stream', async (req, res) => {
res.setHeader('X-Accel-Buffering', 'no');
res.flushHeaders();
const knowledgeReply = await tryKnowledgeReply(sessionId, session, message);
if (knowledgeReply) {
session.handoffSummaryUsed = true;
db.addMessage(sessionId, 'assistant', knowledgeReply.content, 'chat_bot', 'search_knowledge', knowledgeReply.meta).catch(e => console.warn('[DB] addMessage failed:', e.message));
res.write(`data: ${JSON.stringify({ type: 'done', content: knowledgeReply.content })}\n\n`);
const fastGreetingReply = buildFastGreetingReply(message);
if (fastGreetingReply) {
db.addMessage(sessionId, 'assistant', fastGreetingReply, 'chat_bot').catch(e => console.warn('[DB] addMessage failed:', e.message));
res.write(`data: ${JSON.stringify({ type: 'done', content: fastGreetingReply })}\n\n`);
return res.end();
}
try {
const knowledgeReply = await tryKnowledgeReply(sessionId, session, message);
if (knowledgeReply) {
session.handoffSummaryUsed = true;
db.addMessage(sessionId, 'assistant', knowledgeReply.content, 'chat_bot', 'search_knowledge', knowledgeReply.meta).catch(e => console.warn('[DB] addMessage failed:', e.message));
res.write(`data: ${JSON.stringify({ type: 'done', content: knowledgeReply.content })}\n\n`);
return res.end();
}
// 首次对话时注入语音历史作为上下文
const extraMessages = !session.conversationId ? buildInitialContextMessages(session) : [];