UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

338 lines (333 loc) 15.4 kB
/** * IntentClassifier — 自然语言意图分类器 * * 核心问题: 用户通过飞书发了一句自然语言,应该交给谁处理? * * ┌──────────────────────────────────────────────────────────┐ * │ "帮我搜索一下项目里关于用户认证的知识" │ * │ → bot_agent (知识管理任务,服务端 AgentRuntime 处理) │ * │ │ * │ "把 src/auth.ts 里的 JWT 验证改成 OAuth2" │ * │ → ide_agent (编程任务,转发到 VSCode Copilot) │ * │ │ * │ "现在服务状态怎么样" │ * │ → system (系统状态查询,本地直接处理) │ * └──────────────────────────────────────────────────────────┘ * * 三层分类策略 (延迟递增): * 1. 规则匹配 — 零延迟关键词/模式 (~0ms) * 2. 嵌入相似度 — 轻量向量匹配 (~50ms) [可选] * 3. LLM 分类 — 精确但需 AI 调用 (~500ms) * * 设计原则: * - 宁可多走 LLM 也不要误分类 — 用户体验优先 * - bot_agent 是默认,除非明确检测到编程意图 * - 系统查询是硬编码模式,不走 AI * * @module IntentClassifier */ import Logger from '#infra/logging/Logger.js'; /** 意图类型 */ export const Intent = Object.freeze({ /** 知识管理任务 — 搜索/创建/分析知识,由服务端 AgentRuntime 处理 */ BOT_AGENT: 'bot_agent', /** IDE 编程任务 — 代码编写/修改/调试/重构,转发到 VSCode Copilot */ IDE_AGENT: 'ide_agent', /** 系统操作 — 状态查询/截图/连接管理,本地直接处理 */ SYSTEM: 'system', }); /** 分类结果 */ // ─── 规则匹配表 ───────────────────────────────── // ── 自然语言 meta 包装模式 ── // 用户说"在编辑器内输入X"、"让 Copilot 帮我X",实际指令是 X const META_WRAPPER_PATTERNS = [ /^(?:在|去|到)\s*(?:编辑器|IDE|VSCode|VS Code|Copilot|代码编辑器)\s*(?:内|里|中|上)?\s*(?:输入|写|执行|处理|帮我)?\s*/i, /^(?:让|请|麻烦)\s*(?:编辑器|IDE|VSCode|VS Code|Copilot|Agent)\s*(?:帮我|帮忙|来)?\s*/i, /^(?:请|麻烦)?\s*(?:在|去|到)\s*(?:IDE|编辑器|VSCode)\s*(?:里|中|内)?\s*(?:帮我)?\s*/i, /^(?:帮我|请)?\s*(?:在|用)\s*(?:编辑器|IDE|Copilot)\s*(?:里|中)?\s*/i, ]; /** * 系统操作规则 — 硬编码匹配,优先级最高 * 这些过去是 /command,现在用自然语言检测 */ const SYSTEM_RULES = [ { pattern: /状态|status|连接.*状态|诊断|服务.*状态|链路/i, action: 'status' }, { pattern: /截图|screenshot|screen|截屏|画面/i, action: 'screen' }, { pattern: /帮助|help|怎么用|使用说明/i, action: 'help' }, { pattern: /队列|queue|排队|待执行/i, action: 'queue' }, { pattern: /取消|cancel|撤销|不要了|别执行了/i, action: 'cancel' }, { pattern: /清[理空]|clear|clean.*历史/i, action: 'clear' }, { pattern: /^(ping|pong|测试连[通接])/i, action: 'ping' }, ]; /** * IDE 编程意图 — 强信号关键词 * 如果匹配到这些,大概率是编程任务 */ const IDE_STRONG_SIGNALS = [ // 直接编程动作 /修改|修复|改一下|改成|fix|refactor|重构|优化.*代码/i, /写一个|实现|implement|创建.*文件|新建.*组件|添加.*功能/i, /删除.*代码|移除|remove.*from|把.*去掉/i, /调试|debug|排查.*bug|解决.*报错|修.*error/i, // 文件/代码引用 /\.(?:ts|js|tsx|jsx|py|go|rs|java|swift|vue|css|html|json)\b/i, /src\/|lib\/|components\/|pages\/|app\//i, // 技术术语 + 动作 /(?:函数|方法|接口|类|组件|模块|hook).{0,10}(?:改|加|删|写|重构|优化)/i, /(?:改|加|删|写|重构|优化).{0,10}(?:函数|方法|接口|类|组件|模块|hook)/i, // 终端/构建命令 /运行|执行|run.*command|exec|npm|yarn|pnpm|cargo|go run|python/i, /编译|build|compile|部署|deploy/i, // Git 操作 /commit|push|pull|merge|branch|rebase|cherry.?pick|stash|git\s+(log|status|diff|show|add|reset|checkout|switch|tag)/i, // 代码审查 /review.*代码|看看.*写得|检查.*实现/i, ]; /** Bot Agent 意图 — 知识管理相关关键词 */ const BOT_STRONG_SIGNALS = [ // 知识库操作 /知识库|knowledge.*base|搜索知识|查[找询].*知识/i, /recipe|候选|candidate|snippet/i, /冷启动|bootstrap|初始化.*项目/i, // 分析/理解(非编程修改) /解释|explain|帮我理解|什么意思|是什么/i, /分析.*架构|项目.*结构|代码.*组织/i, /总结|summarize|概括|提取.*要点/i, // 翻译 /翻[译成]|translate/i, // 知识管理对话 /聊聊|讨论|你觉得|建议|推荐/i, /guard|规则|约束|violation|违规/i, ]; // ─── LLM 分类 Schema ──────────────────────────── const CLASSIFY_SCHEMA = { name: 'classify_lark_intent', description: 'Classify a Lark message and extract the core command to forward', parameters: { type: 'object', properties: { intent: { type: 'string', enum: ['bot_agent', 'ide_agent'], description: 'bot_agent: knowledge search/management/analysis/conversation tasks handled on the server. ide_agent: code writing/editing/debugging/refactoring/terminal tasks forwarded to VSCode Copilot.', }, confidence: { type: 'number', description: 'Confidence 0-1', }, reasoning: { type: 'string', description: 'Brief reasoning in Chinese', }, extractedCommand: { type: 'string', description: 'The core task/command extracted from the user message, removing meta-instructions like "在编辑器内输入", "让 Copilot 帮我", "请在 IDE 里" etc. If the message is already a direct command, return it as-is. For bot_agent, this is the core question/request.', }, }, required: ['intent', 'confidence', 'extractedCommand'], }, }; const CLASSIFY_SYSTEM_PROMPT = `你是一个意图分类器。用户通过飞书发送消息,你需要判断这条消息应该交给哪个 Agent 处理: **bot_agent** — 知识管理 Bot (服务端处理): - 搜索/查询/浏览项目知识库 - 创建/编辑/管理知识条目 (Recipe/Candidate) - 项目架构分析、代码解释、翻译、总结 - 一般性对话、建议、推荐 - 知识库冷启动、Guard 规则管理 - 不需要直接修改源代码文件 **ide_agent** — IDE 编程 Agent (VSCode Copilot 处理): - 创建/修改/删除源代码文件 - 调试、修复 bug、重构代码 - 运行终端命令 (npm/yarn/cargo/git 等) - 代码审查、PR 相关操作 - 任何需要直接操作项目文件的任务 关键判断原则: 1. 如果任务涉及"修改源代码"→ ide_agent 2. 如果任务涉及"理解/搜索/管理知识"→ bot_agent 3. 模糊时倾向 bot_agent (成本更低,用户可重新触发) **extractedCommand 提取规则:** 用户消息可能包含 meta 指令包装,你需要提取出核心任务: - "在编辑器内输入新增按钮" → extractedCommand: "新增按钮" - "让 Copilot 帮我重构 auth 模块" → extractedCommand: "重构 auth 模块" - "请在 IDE 里把登录页改成暗色主题" → extractedCommand: "把登录页改成暗色主题" - "帮我搜索一下认证相关知识" → extractedCommand: "搜索认证相关知识" - "修复 src/app.ts 的类型错误" → extractedCommand: "修复 src/app.ts 的类型错误" (已是直接指令,原样返回) 去掉"在编辑器/IDE/Copilot 里"、"帮我输入"、"请执行"等 meta 包装,保留核心意图。`; // ─── IntentClassifier 实现 ────────────────────── export class IntentClassifier { #aiProvider; #logger; /** * 从用户消息中提取核心指令,去除 meta 包装 * * "在编辑器内输入新增按钮" → "新增按钮" * "让 Copilot 帮我重构 auth" → "重构 auth" * "修复 bug" → "修复 bug" (无包装,原样返回) */ static extractCommand(text) { let result = text; for (const pattern of META_WRAPPER_PATTERNS) { const match = result.match(pattern); if (match && match[0].length < result.length) { result = result.slice(match[0].length).trim(); break; // 只匹配第一个 meta 包装 } } return result || text; // 防止提取为空 } constructor({ aiProvider = null } = {}) { this.#aiProvider = aiProvider; this.#logger = Logger.getInstance(); } /** * 分类用户消息意图 * * @param text 用户原始消息文本 * @param [context] 额外上下文 (如对话历史、最近操作) */ async classify(text, context = {}) { if (!text?.trim()) { return { intent: Intent.BOT_AGENT, confidence: 1, reasoning: '空消息', method: 'rule' }; } const trimmed = text.trim(); // ── Layer 0: 提取 meta 包装中的核心指令 ── // "在编辑器内输入新增按钮" → extracted="新增按钮", 原始保留用于后续分类 const extracted = IntentClassifier.extractCommand(trimmed); // ── Layer 1: 系统操作 (硬编码,零延迟) ── // 对原始文本和提取后文本都检查 const sysMatch = this.#matchSystem(trimmed) || (extracted !== trimmed ? this.#matchSystem(extracted) : null); if (sysMatch) { return sysMatch; } // ── Layer 2: 强信号关键词匹配 ── // 如果有 meta 包装("在编辑器里输入X"),直接判定为 ide_agent if (extracted !== trimmed) { return { intent: Intent.IDE_AGENT, confidence: 0.95, reasoning: `Meta 包装检测: 原始="${trimmed.slice(0, 40)}" → 核心="${extracted.slice(0, 40)}"`, method: 'rule', extractedCommand: extracted, }; } const ruleMatch = this.#matchRules(trimmed); if (ruleMatch && ruleMatch.confidence >= 0.8) { this.#logger.info(`[IntentClassifier] Rule match: ${ruleMatch.intent} (${ruleMatch.confidence})`); return ruleMatch; } // ── Layer 3: LLM 分类 (精确,需 AI) ── if (this.#aiProvider && this.#aiProvider.name !== 'mock') { const llmResult = await this.#classifyWithLLM(trimmed, context); if (llmResult) { this.#logger.info(`[IntentClassifier] LLM: ${llmResult.intent} (${llmResult.confidence}) — ${llmResult.reasoning}`); return llmResult; } } // ── Fallback: 使用规则结果或默认 bot_agent ── if (ruleMatch) { return ruleMatch; } return { intent: Intent.BOT_AGENT, confidence: 0.5, reasoning: '无法确定意图,默认使用 Bot Agent', method: 'fallback', }; } // ─── 私有方法 ──────────────────────────────── /** 系统操作匹配 (优先级最高) */ #matchSystem(text) { for (const rule of SYSTEM_RULES) { if (rule.pattern.test(text)) { return { intent: Intent.SYSTEM, confidence: 1, reasoning: `系统操作: ${rule.action}`, method: 'rule', action: rule.action, }; } } return null; } /** 强信号关键词匹配 */ #matchRules(text) { let ideScore = 0; let botScore = 0; const ideMatches = []; const botMatches = []; for (const re of IDE_STRONG_SIGNALS) { if (re.test(text)) { ideScore++; ideMatches.push(re.source.slice(0, 30)); } } for (const re of BOT_STRONG_SIGNALS) { if (re.test(text)) { botScore++; botMatches.push(re.source.slice(0, 30)); } } // 没有任何匹配 → null (交给 LLM) if (ideScore === 0 && botScore === 0) { return null; } // 明确的分差 if (ideScore > botScore && ideScore >= 2) { return { intent: Intent.IDE_AGENT, confidence: Math.min(0.6 + ideScore * 0.1, 0.95), reasoning: `IDE 信号: ${ideMatches.join(', ')}`, method: 'rule', }; } if (botScore > ideScore && botScore >= 2) { return { intent: Intent.BOT_AGENT, confidence: Math.min(0.6 + botScore * 0.1, 0.95), reasoning: `Bot 信号: ${botMatches.join(', ')}`, method: 'rule', }; } // 分差不明确 → 返回低置信度结果 (让 LLM 决定) const winner = ideScore > botScore ? Intent.IDE_AGENT : Intent.BOT_AGENT; return { intent: winner, confidence: 0.5 + Math.abs(ideScore - botScore) * 0.1, reasoning: `弱信号: IDE=${ideScore} Bot=${botScore}`, method: 'rule', }; } /** LLM 分类 */ async #classifyWithLLM(text, context = {}) { try { const recentHistory = context.recentHistory; const userMsg = recentHistory ? `最近对话:\n${recentHistory}\n\n当前消息: "${text}"` : `用户消息: "${text}"`; const result = await this.#aiProvider.chatWithTools(userMsg, { messages: [], toolSchemas: [CLASSIFY_SCHEMA], toolChoice: 'required', systemPrompt: CLASSIFY_SYSTEM_PROMPT, temperature: 0, maxTokens: 200, }); const call = result.functionCalls?.[0]?.args; if (call?.intent) { return { intent: call.intent, confidence: call.confidence ?? 0.8, reasoning: call.reasoning || 'LLM 分类', method: 'llm', extractedCommand: call.extractedCommand || undefined, }; } } catch (err) { this.#logger.warn(`[IntentClassifier] LLM error: ${err instanceof Error ? err.message : String(err)}`); } return null; } } export default IntentClassifier;