UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

208 lines (207 loc) 7.33 kB
/** * AgentRouter — Intent → Preset 解析器 * * 在统一架构下,Router 的职责简化为: * - 理解用户意图 (intent classification) * - 映射到正确的 Preset (配置组合) * - 创建 AgentRuntime 并执行 * * 路由策略 (优先级递减): * 1. 手动指定 (API `preset` 参数) * 2. 渠道特征 (如飞书终端命令以 > 开头 → remote-exec) * 3. 关键词匹配 (零延迟正则) * 4. LLM 意图分类 (精确但需 AI 调用) * 5. 默认 → chat * * 关键区别 (vs 旧 AgentRouter): * - 旧版路由到 AgentMode (chat/bootstrap/scan/lark_bridge) → 4 种 Agent 类型 * - 新版路由到 Preset (chat/insight/remote-exec) → 同一 Runtime 的不同配置 * - 不再存在 "LarkBridge" 模式 — 飞书和前端共享同一 Preset * * @module AgentRouter */ import Logger from '#infra/logging/Logger.js'; import { AgentEventBus } from './AgentEventBus.js'; import { Channel } from './AgentMessage.js'; /** Preset 名称枚举 */ export const PresetName = Object.freeze({ CHAT: 'chat', INSIGHT: 'insight', LARK: 'lark', REMOTE_EXEC: 'remote-exec', }); /** 关键词 → Preset 映射 (零延迟快速路径) */ const KEYWORD_ROUTES = [ { preset: PresetName.INSIGHT, keywords: [ /冷启动|cold[\s-]?start|初始化知识库|bootstrap/i, /重建知识库|rebuild.*knowledge/i, /全项目.*分析|analyze.*entire.*project/i, /扫描|scan|分析.*目录|分析.*文件夹|analyze.*folder/i, /检查.*target|审计.*模块|audit.*module/i, /深度分析.*路径|知识提取/i, ], }, { preset: PresetName.REMOTE_EXEC, keywords: [ /^[>$]\s*/, // 以 > 或 $ 开头 = 终端命令 /运行命令|执行命令|run.*command|exec.*command/i, ], }, ]; /** LLM 路由分类 Schema */ const ROUTE_CLASSIFICATION_SCHEMA = { name: 'classify_intent', description: 'Classify user intent to determine which agent preset to use', parameters: { type: 'object', properties: { preset: { type: 'string', enum: ['chat', 'insight', 'lark', 'remote-exec'], description: 'The preset to route to. chat=conversation, insight=code analysis and knowledge extraction, lark=Lark knowledge management, remote-exec=terminal commands', }, confidence: { type: 'number', description: 'Confidence score 0-1', }, reasoning: { type: 'string', description: 'Brief reasoning for the classification', }, }, required: ['preset', 'confidence'], }, }; export class AgentRouter { #logger; #bus; #aiProvider = null; #executor = null; constructor() { this.#logger = Logger.getInstance(); this.#bus = AgentEventBus.getInstance(); } /** 设置 AI Provider (用于 LLM 路由) */ setAiProvider(provider) { this.#aiProvider = provider; } /** 设置执行器 — Factory 提供的 (presetName, message, opts) => AgentResult */ setExecutor(executor) { this.#executor = executor; } /** * 路由并执行 * * @param message 统一消息 * @param [opts.preset] 手动指定 Preset (跳过路由) * @param [opts.strategyOpts] 策略特定选项 (如 FanOut 的 items) * @returns >} */ async route(message, opts = {}) { // 1. 手动指定 let preset = opts.preset || message.metadata?.mode; // 2. 渠道特征路由 if (!preset) { preset = this.#matchChannel(message); if (preset) { this.#logger.info(`[AgentRouter] Channel match → ${preset}`); } } // 3. 关键词匹配 if (!preset) { preset = this.#matchKeyword(message.content); if (preset) { this.#logger.info(`[AgentRouter] Keyword match → ${preset}`); } } // 4. LLM 分类 if (!preset && this.#aiProvider) { preset = await this.#classifyWithLLM(message.content); if (preset) { this.#logger.info(`[AgentRouter] LLM classification → ${preset}`); } } // 5. 默认 → chat if (!preset) { preset = PresetName.CHAT; } // 执行 if (!this.#executor) { throw new Error('[AgentRouter] No executor set. Call setExecutor() first.'); } this.#logger.info(`[AgentRouter] Dispatching → preset="${preset}" channel=${message.channel}`); const result = await this.#executor(preset, message, opts); return { preset, result }; } /** * 仅分类意图,不执行 * @returns preset name */ async classify(message) { let preset = message.metadata?.mode; if (!preset) { preset = this.#matchChannel(message); } if (!preset) { preset = this.#matchKeyword(message.content); } if (!preset && this.#aiProvider) { preset = await this.#classifyWithLLM(message.content); } return preset || PresetName.CHAT; } // ─── 私有方法 ──────────────────────────────── /** * 渠道特征路由 * 飞书消息默认 → lark preset (知识管理对话) * 飞书终端命令 (> 开头) → remote-exec */ #matchChannel(message) { if (message.channel === Channel.LARK) { const text = message.content.trim(); // 飞书消息以 > 或 $ 开头 → 终端命令 if (/^[>$]\s*/.test(text)) { return PresetName.REMOTE_EXEC; } // 默认走 lark preset (知识管理对话) return PresetName.LARK; } return null; } #matchKeyword(prompt) { for (const route of KEYWORD_ROUTES) { for (const re of route.keywords) { if (re.test(prompt)) { return route.preset; } } } return null; } async #classifyWithLLM(prompt) { try { const result = await this.#aiProvider.chatWithTools(`Classify this user message into the correct preset: "${prompt.slice(0, 300)}"`, { messages: [], toolSchemas: [ROUTE_CLASSIFICATION_SCHEMA], toolChoice: 'required', systemPrompt: 'You classify user intents for an AI coding assistant. Respond by calling the classify_intent function.', temperature: 0, maxTokens: 200, }); if (result.functionCalls?.[0]?.args?.preset) { const classified = result.functionCalls[0].args; if (classified.confidence > 0.6) { return classified.preset; } } } catch (err) { this.#logger.warn(`[AgentRouter] LLM classification failed: ${err.message}`); } return null; } } export default AgentRouter;