Files
crm-ai/bridge/src/escalation.js
T

66 lines
3.1 KiB
JavaScript

'use strict';
// 三级升级策略(旧 bridge escalation/manager 的 LibreDesk 版):
// Level 0 正常;Level 1 追加“是否转人工”;Level 2 私聊备注 + 分配人工。
function createEscalation({ client, handoffUserId = 1 }) {
function shouldEscalate(state, intent, kbConfidence, consecutiveFails, kbHitCount = 0) {
const processed = Number(state && state.messagesProcessed) || 0;
if (consecutiveFails >= 3) return { level: 2, reason: 'consecutive_fails' };
if (processed > 8 && !(state && state.opportunityId)) return { level: 2, reason: 'long_no_lead' };
if (intent === 'policy_faq' && kbConfidence === 'low' && kbHitCount === 0) return { level: 1, reason: 'low_kb_confidence' };
if (kbConfidence === 'none' && !['product_search', 'sales_lead', 'order_tracking', 'inventory_check'].includes(intent)) return { level: 1, reason: 'no_kb_match' };
if (processed > 5 && consecutiveFails >= 2) return { level: 1, reason: 'repeated_low_confidence' };
return { level: 0, reason: null };
}
function appendEscalationOption(reply, query) {
const text = String(reply || '').replace(/\s+$/, '');
const hints = {
zh: '您想转人工客服吗?',
de: 'Möchten Sie mit einem Mitarbeiter sprechen?',
fr: 'Souhaitez-vous parler à un conseiller ?',
it: 'Vuole parlare con un operatore?',
nl: 'Wilt u met een medewerker spreken?',
};
let lang = null;
if (/[\u4e00-\u9fff]/.test(query)) lang = 'zh';
else if (/[äöüß]/i.test(query)) lang = 'de';
else if (/[éèêëàâùûîïôç]/i.test(query)) lang = 'fr';
else if (/\b(hoe|voor|wilt|bent|geen|verzending|levering|bestelling)\b/i.test(query)) lang = 'nl';
const option = lang ? `\n\n${hints[lang]} / Would you like to speak to a human agent?` : '\n\nWould you like to speak to a human agent?';
return text + option;
}
async function escalateToHuman({ convUuid, trigger, history }) {
const lines = [`Escalation trigger: ${trigger || 'unknown'}`];
if (history && history.length) {
lines.push('Recent context:');
for (const m of history.slice(-4)) {
const role = m.role === 'user' ? 'Customer' : 'AI';
lines.push(` [${role}] ${String(m.content || '').slice(0, 150)}`);
}
}
try {
await client.sendPrivateNote(convUuid, lines.join('\n'));
await client.assignUser(convUuid, handoffUserId);
return true;
} catch (err) {
console.error(JSON.stringify({ ts: new Date().toISOString(), warn: `escalate failed: ${err.message}` }));
return false;
}
}
function applyEmotionStrategy(emotion) {
switch (emotion) {
case 'angry': return { skipAI: false, escalate: true, maxToolTurns: 2, tone: 'empathetic' };
case 'frustrated': return { skipAI: false, escalate: false, maxToolTurns: 3, tone: 'patient' };
case 'urgent': return { skipAI: false, escalate: false, maxToolTurns: 3, tone: 'direct' };
default: return { skipAI: false, escalate: false, maxToolTurns: 5, tone: 'normal' };
}
}
return { shouldEscalate, appendEscalationOption, escalateToHuman, applyEmotionStrategy };
}
module.exports = { createEscalation };