Files
memind/intent-classifier.mjs
T
john 9dfafb99ca
Memind CI / Test, build, and release guards (push) Successful in 3m43s
fix(schedule): harden reminder utterance routing for WeChat ITL
Expand ASR normalization and colloquial reminder parsing so simple timed reminders route to L1 confirm cards instead of the agent, and disable schedule-guard false failures when ITL is enabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 18:02:19 +08:00

103 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { detectQueryGuard } from './intent-query-guard.mjs';
import {
parseScheduleIntent,
shouldUseScheduleAssistant,
tryResolveTimedReminderIntent,
} from './schedule-intent.mjs';
import {
isScheduledTaskIntent,
parseScheduledTaskIntent,
shouldUseScheduledTaskAutomation,
} from './scheduled-task-intent.mjs';
function detectAmbiguity(text) {
const compact = String(text ?? '').replace(/\s+/g, '');
const notifyCue =
/(?:提醒我|设置提醒|设个?提醒|闹钟|闹铃|叫我|到点提醒|提醒一下)/u.test(compact)
|| /(?:设置|设|加|添加|创建|安排|定)(?:一个|个|一下)?(?:每天|每日|天天)?(?:.{0,40})?提醒/u.test(compact);
const actCue = /(?:生成|制作|创建|做|执行|推送|发送|整理).{0,12}(?:页面|日报|报告|摘要)/u.test(compact);
if (notifyCue && actCue) return true;
if (notifyCue && /(?:自动|生成)/u.test(compact) && actCue) return true;
return false;
}
export function classifyUserIntent(text, { now = Date.now(), timezone = 'Asia/Shanghai' } = {}) {
if (detectQueryGuard(text)) {
return { layer: 'L0', kind: 'query', action: 'answer_only' };
}
const schedTask = parseScheduledTaskIntent(text, { now, timezone });
if (isScheduledTaskIntent(schedTask)) {
if (schedTask.action === 'create_scheduled_task') {
return {
layer: 'L2',
kind: 'scheduled_task',
action: schedTask.action,
detail: schedTask,
clarify: schedTask.needsClarification ?? [],
};
}
return { layer: 'L2', kind: 'manage', action: schedTask.action, detail: schedTask };
}
const sched = parseScheduleIntent(text, { timezone, now });
if (sched.action === 'create_timed_reminder') {
return {
layer: 'L1',
kind: 'timed_reminder',
action: sched.action,
detail: sched,
clarify: sched.needsClarification ?? [],
};
}
if (['create_todo', 'create_daily_todo_digest', 'create_balance_alert'].includes(sched.action)) {
return {
layer: 'L1',
kind: sched.action,
action: sched.action,
detail: sched,
clarify: sched.needsClarification ?? [],
};
}
if (sched.action === 'query_schedule') {
return { layer: 'L0', kind: 'query', action: sched.action, detail: sched };
}
if (detectAmbiguity(text)) {
return { layer: 'ambiguous', kind: 'clarify', action: 'clarify_notify_vs_act' };
}
if (shouldUseScheduledTaskAutomation(text)) {
return { layer: 'L2', kind: 'scheduled_task', action: 'agent_automation', detail: schedTask };
}
const timedFallback = tryResolveTimedReminderIntent(text, { now, timezone });
if (timedFallback?.action === 'create_timed_reminder') {
return {
layer: 'L1',
kind: 'timed_reminder',
action: timedFallback.action,
detail: timedFallback,
clarify: timedFallback.needsClarification ?? [],
};
}
if (shouldUseScheduleAssistant(text)) {
const multiTime = (String(text).match(/[0-9零一二两三四五六七八九十]{1,3}(?:点|:|)/gu) ?? []).length > 1;
return { layer: multiTime ? 'L3' : 'L1', kind: 'agent_schedule', action: 'agent_schedule' };
}
return { layer: null, kind: 'none', action: 'none' };
}
export function inferActionLevel(classification, text = '') {
const { layer, kind, detail } = classification;
if (layer === 'L0' || layer === null) return 0;
if (layer === 'ambiguous') return 2;
if (/(?:客户|报价|群发|发邮件)/u.test(text)) return 3;
if (layer === 'L2') return 2;
if (kind === 'create_balance_alert') return 2;
if (detail?.recurrence === 'daily' || /(?:每天|每日)/u.test(text)) return 2;
return 1;
}