UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

441 lines (440 loc) 19.5 kB
/** * AgentFactory — 统一 Agent 创建工厂 * * 在新架构中,Factory 的职责是: * - 将 Preset 配置 + DI 依赖 → AgentRuntime 实例 * - 提供 Router (intent → preset → runtime) * - 提供快捷方法 (createChat, createInsight, ...) * * 关键变化 (vs 旧 AgentFactory): * - 不再创建独立 Agent 子类 * - 只创建 AgentRuntime,通过 Preset 配置差异化行为 * - 同一个工厂,同一种 Runtime,不同的配置 * * @module AgentFactory */ import Logger from '#infra/logging/Logger.js'; import { AgentMessage } from './AgentMessage.js'; import { AgentRouter, PresetName } from './AgentRouter.js'; import { AgentRuntime } from './AgentRuntime.js'; import { CapabilityRegistry } from './capabilities.js'; import { ContextWindow } from './context/ContextWindow.js'; import { ExplorationTracker } from './context/ExplorationTracker.js'; import { buildRelationsPipelineStages, buildScanPipelineStages, SCAN_TASK_CONFIGS, } from './domain/scan-prompts.js'; import { MemoryCoordinator } from './memory/MemoryCoordinator.js'; import { BudgetPolicy, PolicyEngine } from './policies.js'; import { getPreset } from './presets.js'; export class AgentFactory { #container; #toolRegistry; #aiProvider; #logger; #router = null; /** 共享的 Capability 实例缓存 (如 MemoryCoordinator) */ #sharedOpts; /** * @param opts.container ServiceContainer 实例 * @param [opts.memoryCoordinator] MemoryCoordinator 实例 (注入 Conversation) * @param [opts.projectBriefing] 项目概况文本 * @param [opts.projectRoot] 项目根目录 */ constructor({ container, toolRegistry, aiProvider, memoryCoordinator, projectBriefing, projectRoot, }) { this.#container = container; this.#toolRegistry = toolRegistry; this.#aiProvider = aiProvider; this.#logger = Logger.getInstance(); this.#sharedOpts = { memoryCoordinator: memoryCoordinator || null, projectBriefing: projectBriefing || null, projectRoot: projectRoot || process.cwd(), }; } // ─── Router ────────────────────────────────── /** 创建带路由器的自动调度系统 */ createRouter() { if (this.#router) { return this.#router; } const router = new AgentRouter(); router.setAiProvider(this.#aiProvider); router.setExecutor((presetName, message, opts) => { const runtime = this.createRuntime(presetName, opts); return runtime.execute(message, opts.strategyOpts || opts); }); this.#router = router; this.#logger.info('[AgentFactory] Router created'); return router; } // ─── 核心创建方法 ───────────────────────────── /** * 根据 Preset 名称创建 AgentRuntime * * 这是一切的根基 — 任何 "Agent 类型" 都通过这个方法创建。 * * @param presetName Preset 名称 (chat/insight/remote-exec) * @param [overrides] 覆盖 preset 配置 */ createRuntime(presetName, overrides = {}) { const preset = getPreset(presetName, overrides); // 实例化 Capabilities const capabilities = preset.capabilities.map((name) => { const opts = this.#getCapabilityOpts(name); return CapabilityRegistry.create(name, opts); }); // 实例化 Policies — 支持工厂函数延迟实例化 (Preset 中 policy 可为 instance 或 factory) const resolvedPolicies = (preset.policies || []).map((policyOrFactory) => typeof policyOrFactory === 'function' ? policyOrFactory(overrides) : policyOrFactory); const policyEngine = new PolicyEngine(resolvedPolicies); return new AgentRuntime({ presetName, aiProvider: this.#aiProvider, toolRegistry: this.#toolRegistry, container: this.#container, capabilities, strategy: preset.strategyInstance, policies: policyEngine, persona: preset.persona, memory: preset.memory, onProgress: overrides.onProgress || null, onToolCall: overrides.onToolCall || null, lang: overrides.lang || null, additionalTools: overrides.additionalTools || [], projectRoot: this.#sharedOpts.projectRoot, }); } // ─── 快捷方法 (语义化) ───────────────────── /** * 创建 ContextWindow (根据当前 AI Provider 自动解析 token 预算) * @param [opts] */ createContextWindow(opts = {}) { const modelName = this.#aiProvider?.model || ''; const tokenBudget = ContextWindow.resolveTokenBudget(modelName, opts); return new ContextWindow(tokenBudget); } /** * 构建系统级多轮执行上下文 — 统一基础设施 * * 抽取 bootstrap orchestrator 中创建 ExplorationTracker / ContextWindow / source 的 * 通用逻辑,供 scanKnowledge 等系统场景共用完整的多轮 Agent 框架。 * * 与 bootstrap orchestrator 保持一致的 MemoryCoordinator 管理模式: * - 创建轻量级 MemoryCoordinator (无 PersistentMemory/SessionStore) * - 通过 MC.createDimensionScope 创建并注册 ActiveContext * - trace 从 MC.getActiveContext 获取 (统一生命周期管理) * - memoryCoordinator 传入 strategyContext,供 reactLoop 每轮 buildDynamicMemoryPrompt * * 与 bootstrap orchestrator 对齐的关键字段: * - activeContext: 与 trace 同一实例 — insightGateEvaluator 通过此字段 * 决定走 buildAnalysisArtifact (完整: findings/evidenceMap/negativeSignals) * 还是 buildAnalysisReport (降级: 仅文本) * - outputType: 'candidate' — 设置 quality_gate 的评判标准 * - dimId: 维度 ID — buildAnalysisArtifact 的 dimensionId 参数 * * bootstrap orchestrator 不使用此方法(它还需要领域特定的 SessionStore / dimContext 等), * 但引擎层基础设施是一致的。 * * @param [opts.budget] 预算覆盖 (透传给 ExplorationTracker) * @param [opts.trackerStrategy='analyst'] tracker 策略名: 'analyst' | 'producer' | 'bootstrap' * @param [opts.label='default'] 作用域标签 (用于 scopeId 命名 + dimId) * @param [opts.lang] 项目语言 (透传给 sharedState._projectLanguage) * @returns } */ buildSystemContext({ budget, trackerStrategy = 'analyst', label = 'default', lang, } = {}) { // 创建轻量级 MemoryCoordinator (scan 场景无 PersistentMemory/SessionStore) const mc = new MemoryCoordinator({ mode: 'bootstrap' }); const scopeId = `scan:${label}`; mc.createDimensionScope(scopeId); const activeContext = mc.getActiveContext(scopeId); return { contextWindow: this.createContextWindow({ isSystem: true }), tracker: ExplorationTracker.resolve({ source: 'system', strategy: trackerStrategy }, budget || {}), // trace & activeContext 是同一个 ActiveContext 实例 // trace: AgentRuntime reactLoop 使用 (startRound/setThought/endRound) // activeContext: insightGateEvaluator 检查此字段决定 artifact 路径 trace: activeContext, activeContext, memoryCoordinator: mc, // outputType: bootstrap orchestrator 设为 'candidate'(insightGateEvaluator 的评判标准) outputType: 'candidate', // dimId: buildAnalysisArtifact 的 dimensionId 参数 dimId: label, sharedState: { submittedTitles: new Set(), submittedPatterns: new Set(), // G6: _projectLanguage — ToolExecutionPipeline 透传给工具 handler 上下文 _projectLanguage: lang || null, // G7: _dimensionScopeId — ToolExecutionPipeline 透传给工具 handler (note_finding scope) _dimensionScopeId: scopeId, }, source: 'system', scopeId, }; } /** * 获取 AI Provider 信息 (供 orchestrator 等外部使用) * @returns } */ getAiProviderInfo() { return { model: this.#aiProvider?.model || 'unknown', name: this.#aiProvider?.name || 'unknown', }; } /** 创建对话 Runtime (Dashboard / 飞书聊天) */ createChat(opts = {}) { return this.createRuntime(PresetName.CHAT, opts); } /** * 创建洞察 Runtime (深度代码分析 + 知识提取) * @param [opts.dimensions] 维度列表 (传给 FanOutStrategy 的 items) * @param [opts.projectInfo] 项目信息 */ createInsight(opts = {}) { return this.createRuntime(PresetName.INSIGHT, opts); } /** 创建飞书对话 Runtime (知识管理,服务端处理) */ createLark(opts = {}) { return this.createRuntime(PresetName.LARK, opts); } /** 创建远程执行 Runtime (飞书终端 / 远程操作) */ createRemoteExec(opts = {}) { return this.createRuntime(PresetName.REMOTE_EXEC, opts); } // ─── 领域语义方法 (意图驱动, Agent 直接完成 AI 推理) ───── /** * 统一知识扫描 — 走 insight 管线 (Analyze → QualityGate → Produce → RejectionGate) * * extract 和 summarize 共享工具驱动管线 (collect_scan_recipe), * 仅 Produce 阶段的 systemPrompt 和预算不同: * - extract: 多文件 target 扫描,24 iter analyze,24 iter produce * - summarize: 单文件/代码片段,12 iter analyze,12 iter produce * * 关系发现请使用单独的 discoverRelations() 方法。 * * @param opts.label 上下文标签(target 名 / 文件名) * @param opts.files 源文件 * @param [opts.task='extract'] 任务类型 * @param [opts.lang] 语言提示 * @param [opts.comprehensive] 深度扫描标志 * @returns task-specific JSON */ async scanKnowledge({ label, files, task = 'extract', lang, comprehensive, } = {}) { const taskConfig = SCAN_TASK_CONFIGS[task]; if (!taskConfig) { throw new Error(`Unknown scanKnowledge task: "${task}". Available: ${Object.keys(SCAN_TASK_CONFIGS).join(', ')}`); } const { producePrompt, fallback } = taskConfig; // extract 和 summarize 都使用 code_analysis 分析 + scan_production 工具驱动 const analyzeCaps = ['code_analysis']; const produceCaps = ['scan_production']; // ── 统一 4 阶段 Pipeline (与冷启动 orchestrator 对齐) ── // summarize (单文件) 使用较低预算 const analyzeMaxIter = task === 'summarize' ? 12 : 24; const stages = buildScanPipelineStages({ task, producePrompt, analyzeCaps, produceCaps, files, analyzeMaxIter, }); // ── 创建 Runtime — 使用 insight preset + 对齐 policies ── const runtime = this.createRuntime(PresetName.INSIGHT, { strategy: { type: 'pipeline', maxRetries: 1, stages }, capabilities: analyzeCaps, policies: [ new BudgetPolicy({ maxIterations: 30, // 24 stage budget + 6 tracker grace maxTokens: 8192, temperature: 0.3, timeoutMs: 3_600_000, }), ], memory: { enabled: false }, lang, }); if (files?.length) { runtime.setFileCache(files); } // ── 完整的系统级多轮基础设施 (含 MemoryCoordinator 管理 ActiveContext) ── const systemCtx = this.buildSystemContext({ budget: { maxIterations: analyzeMaxIter }, trackerStrategy: 'analyst', label: `${task}:${label}`, lang, }); // ── 执行 ── const message = AgentMessage.internal(`分析 "${label}" 的 ${files?.length || 0} 个源文件。${comprehensive ? '请进行深度分析。' : ''}`); const result = await runtime.execute(message, { strategyContext: systemCtx }); // ── 提取结果 — extract 和 summarize 统一从 toolCalls 提取 ── const allToolCalls = result.toolCalls || []; const recipes = allToolCalls .filter((tc) => (tc.tool || tc.name) === 'collect_scan_recipe') .map((tc) => { const res = tc.result; if (res && typeof res === 'object' && res.status === 'collected' && res.recipe) { return res.recipe; } return null; }) .filter((r) => Boolean(r)); if (recipes.length > 0) { // summarize 向后兼容: 扁平化首个 recipe 为 { title, summary, usageGuide, ... } if (task === 'summarize') { const first = recipes[0]; return { title: first.title || '', summary: first.description || first.summary || '', usageGuide: first.usageGuide || '', category: first.category || '', headers: first.headers || [], tags: first.tags || [], trigger: first.trigger || '', recipes, extracted: recipes.length, }; } return { targetName: label, extracted: recipes.length, recipes }; } // Fallback: 工具未被调用时,尝试从文本解析 const phases = result.phases; const produceReply = phases?.produce?.reply || result.reply; return this.#parseJsonResponse(produceReply, fallback(label)); } /** * 知识图谱关系发现 — 独立管线 (Explore → Synthesize) * * 与 scanKnowledge 不同,relations 不需要源文件输入, * 而是通过查询知识库 + 读取源码发现知识条目间的语义关系。 * * @param [opts.batchSize=20] 批次大小提示 * @returns >} */ async discoverRelations({ batchSize = 20 } = {}) { const stages = buildRelationsPipelineStages(); const runtime = this.createRuntime(PresetName.INSIGHT, { strategy: { type: 'pipeline', stages }, capabilities: ['knowledge_production', 'code_analysis'], policies: [ new BudgetPolicy({ maxIterations: 28, maxTokens: 8192, temperature: 0.3, timeoutMs: 420_000, }), ], memory: { enabled: false }, }); const message = AgentMessage.internal(`探索知识库中所有知识条目之间的语义关系。每批分析约 ${batchSize} 条知识。`); const result = await runtime.execute(message); const phases = result.phases; const synthesizeReply = phases?.synthesize?.reply || result.reply; return this.#parseJsonResponse(synthesizeReply, { analyzed: 0, relations: [] }); } /** * AI 翻译 — chat 模式,单轮生成 * * Agent(LLM) 直接翻译文本,无需工具。 * * @param summary 中文摘要 * @param [usageGuide] 中文使用指南 * @returns >} */ async translateToEnglish(summary, usageGuide) { if (!summary && !usageGuide) { return { summaryEn: '', usageGuideEn: '' }; } const runtime = this.createChat({ policies: [ new BudgetPolicy({ maxIterations: 1, maxTokens: 4096, temperature: 0.2, timeoutMs: 60_000, }), ], persona: { description: [ '你是技术文档翻译专家。将中文技术内容翻译为地道的英文。保持技术术语不变。', '', '## 输出格式(必须是纯 JSON,不包含任何其他文字)', '{ "summaryEn": "...", "usageGuideEn": "..." }', ].join('\n'), }, memory: { enabled: false }, }); const message = AgentMessage.internal(`翻译以下内容为英文,输出纯 JSON:\nsummary: ${summary || '(空)'}\nusageGuide: ${usageGuide || '(空)'}`); const result = await runtime.execute(message); return this.#parseJsonResponse(result.reply, { summaryEn: summary || '', usageGuideEn: usageGuide || '', }); } /** * 通用工具执行 — 直接调用工具 handler * * 纯数据工具直接执行,无需创建 Agent。 * AI 推理由各语义方法的 Agent 自主完成,此方法仅用于纯数据工具。 * * @param toolName 工具名称 * @param params 工具参数 * @returns 工具原始返回值 */ async invokeAgent(toolName, params) { return this.#toolRegistry.execute(toolName, params, this.#makeToolContext()); } // ─── 私有方法 ──────────────────────────────── /** * 解析 Agent 响应中的 JSON(支持 markdown 代码块包装) * @param text Agent 响应文本 * @param fallback 解析失败时的默认值 */ #parseJsonResponse(text, fallback) { if (!text) { return fallback; } try { // 尝试从 markdown 代码块中提取 JSON const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/); if (codeBlockMatch) { return JSON.parse(codeBlockMatch[1].trim()); } // 尝试直接提取 JSON 对象 const objMatch = text.match(/(\{[\s\S]*\})/); if (objMatch) { return JSON.parse(objMatch[1].trim()); } return JSON.parse(text.trim()); } catch { this.#logger.warn('[AgentFactory] Failed to parse JSON from Agent response'); return fallback; } } /** 构建工具 handler 执行所需的上下文对象 */ #makeToolContext() { return { aiProvider: this.#aiProvider, container: this.#container, logger: this.#logger, projectRoot: this.#sharedOpts.projectRoot, }; } /** 获取 Capability 实例化时需要的依赖注入参数 */ #getCapabilityOpts(capabilityName) { switch (capabilityName) { case 'conversation': return { memoryCoordinator: this.#sharedOpts.memoryCoordinator, projectBriefing: this.#sharedOpts.projectBriefing, }; case 'system_interaction': return { projectRoot: this.#sharedOpts.projectRoot, }; default: return {}; } } } export default AgentFactory;