UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

293 lines (292 loc) 11.4 kB
/** * Strategies — Agent 执行策略 * * 核心思想: "如何组织工作" 与 "能做什么" 正交。 * * 四种策略: * 1. SingleStrategy — 单次 ReAct 循环 (最简单,用于对话) * 2. PipelineStrategy — 顺序多阶段 + 质量门控 (分析→提交) * 3. FanOutStrategy — 并行执行 + 合并 (多维度冷启动) * 4. AdaptiveStrategy — 运行时自动选择策略 (智能模式) * * 这就是为什么 "冷启动" 和 "扫描" 产出一致: * - 冷启动 = FanOut(items=dimensions, itemStrategy=Pipeline(analyze→gate→produce)) * - 扫描 = Pipeline(analyze→gate→produce) * - 唯一区别: 作用域 (全项目 vs 单目录) 和并行度 * * 借鉴: * - Anthropic: Prompt Chaining, Parallelization, Orchestrator-Worker * - LangGraph: StateGraph, parallel branches * - AutoGen: Sequential/Parallel teams * * @module strategies */ import { createLimit } from '#shared/concurrency.js'; import { AgentEventBus, AgentEvents } from './AgentEventBus.js'; import { AgentMessage } from './AgentMessage.js'; // ─── Base Strategy ───────────────────────────── /** Strategy 基类 — 定义 Agent 如何组织工作 */ export class Strategy { get name() { throw new Error('Subclass must implement name'); } /** * 执行策略 * * @param _runtime AgentRuntime 实例 * @param _message 输入消息 * @param [_opts] 策略特定选项 */ async execute(_runtime, _message, _opts) { throw new Error('Subclass must implement execute()'); } } // PipelineStrategy 已提取到独立模块: ./PipelineStrategy.js // 注意: 不在此处 re-export,因为 PipelineStrategy 需要 import Strategy 形成循环依赖 // ─── SingleStrategy — 直接 ReAct ───────────── /** * 最简单的策略: 直接运行 ReAct 循环。 * * 适合: 用户对话、简单分析、任何单步骤任务。 * * 等价于 Anthropic 的 "Augmented LLM" 模式。 */ export class SingleStrategy extends Strategy { get name() { return 'single'; } async execute(runtime, message, opts = {}) { return runtime.reactLoop(message.content, { history: message.history, context: message.metadata.context || {}, ...opts, }); } } // ─── FanOutStrategy — 并行执行 ────────────── /** * 并行执行多个子任务,每个子任务使用 itemStrategy (通常是 Pipeline)。 * 支持分层并发控制 (Tier)。 * * 适合: 冷启动多维度、批量分析 * * 等价于 Anthropic 的 "Parallelization" + "Orchestrator-Worker" 组合。 * * @example * new FanOutStrategy({ * itemStrategy: new PipelineStrategy({ * stages: [ * { name: 'analyze', capabilities: ['code_analysis'], budget: { maxIterations: 24 } }, * { name: 'gate', gate: { minEvidenceLength: 500, minFileRefs: 3 } }, * { name: 'produce', capabilities: ['knowledge_production'], budget: { maxIterations: 24 }, * promptTransform: (_, prev) => `将以下分析转为知识候选:\n${prev.analyze.reply}` }, * ], * }), * tiers: { 1: { concurrency: 3 }, 2: { concurrency: 2 }, 3: { concurrency: 1 } }, * }) */ export class FanOutStrategy extends Strategy { /** 每个子任务的执行策略 */ #itemStrategy; /** >} 分层并发配置 */ #tiers; /** 结果合并函数 */ #merge; /** * @param opts.itemStrategy 每个子任务使用的策略 * @param [opts.tiers] { 1: { concurrency: 3 }, 2: { concurrency: 2 }, ... } * @param [opts.merge] 自定义合并函数 (results[]) => finalResult */ constructor({ itemStrategy, tiers, merge } = {}) { super(); this.#itemStrategy = itemStrategy || new SingleStrategy(); this.#tiers = tiers || { 1: { concurrency: 3 } }; this.#merge = merge || FanOutStrategy.#defaultMerge; } get name() { return 'fan_out'; } /** * @param opts.items 子任务列表 */ async execute(runtime, message, opts = {}) { const { items = [] } = opts; const bus = AgentEventBus.getInstance(); if (items.length === 0) { return { reply: 'No items to process', toolCalls: [], tokenUsage: { input: 0, output: 0 }, iterations: 0, }; } // 按 tier 分组 const tierGroups = this.#groupByTier(items); const allResults = []; for (const [tier, tierItems] of Object.entries(tierGroups).sort(([a], [b]) => Number(a) - Number(b))) { const tierConfig = this.#tiers[tier] || this.#tiers[1] || { concurrency: 2 }; bus.publish(AgentEvents.PROGRESS, { type: 'fan_out_tier_start', tier: Number(tier), count: tierItems.length, concurrency: tierConfig.concurrency, }); // p-limit 并发控制(替代手动 chunk 分批) const limit = createLimit(tierConfig.concurrency); const tierResults = await Promise.all(tierItems.map((item) => limit(async () => { const itemMessage = AgentMessage.internal(item.prompt || `${message.content}\n\n## 当前维度: ${item.label}\n${item.guide || ''}`, { sessionId: message.session.id, dimension: item.id, parentAgentId: runtime.id, history: message.history, metadata: { ...message.metadata, dimension: item }, }); bus.publish(AgentEvents.PROGRESS, { type: 'fan_out_item_start', itemId: item.id, label: item.label, }); try { const result = await this.#itemStrategy.execute(runtime, itemMessage, { dimension: item, }); return { id: item.id, label: item.label, status: 'completed', ...result }; } catch (err) { return { id: item.id, label: item.label, status: 'failed', error: err instanceof Error ? err.message : String(err), reply: '', toolCalls: [], tokenUsage: { input: 0, output: 0 }, }; } }))); allResults.push(...tierResults); bus.publish(AgentEvents.PROGRESS, { type: 'fan_out_tier_done', tier: Number(tier), completed: allResults.filter((r) => r.status === 'completed').length, failed: allResults.filter((r) => r.status === 'failed').length, }); } return this.#merge(allResults); } #groupByTier(items) { const groups = {}; for (const item of items) { const tier = item.tier || 1; if (!groups[tier]) { groups[tier] = []; } groups[tier].push(item); } return groups; } static #defaultMerge(results) { const successful = results.filter((r) => r.status === 'completed'); const failed = results.filter((r) => r.status === 'failed'); return { reply: [ `## 执行总结\n完成: ${successful.length}, 失败: ${failed.length}\n`, ...successful.map((r) => `### ${r.label}\n${r.reply || '(无输出)'}`), ...failed.map((r) => `### ${r.label} ❌\n${r.error}`), ].join('\n\n'), toolCalls: results.flatMap((r) => r.toolCalls || []), tokenUsage: { input: results.reduce((sum, r) => sum + (r.tokenUsage?.input || 0), 0), output: results.reduce((sum, r) => sum + (r.tokenUsage?.output || 0), 0), }, iterations: results.reduce((sum, r) => sum + (r.iterations || 0), 0), itemResults: results, }; } } // ─── AdaptiveStrategy — 智能自适应 ────────── /** * 根据输入复杂度自动选择合适的策略。 * * 判断逻辑: * - 简单问答 → SingleStrategy * - 单模块深度分析 → PipelineStrategy * - 多维度/全项目 → FanOutStrategy * * 等价于 LangGraph 的 Router 节点 + 条件边。 * * @example * new AdaptiveStrategy({ * single: new SingleStrategy(), * pipeline: new PipelineStrategy({ stages: [...] }), * fanOut: new FanOutStrategy({ itemStrategy: ..., tiers: ... }), * }) */ export class AdaptiveStrategy extends Strategy { #strategies; constructor(strategies = {}) { super(); this.#strategies = { single: strategies.single || new SingleStrategy(), pipeline: strategies.pipeline || null, fanOut: strategies.fanOut || null, }; } get name() { return 'adaptive'; } async execute(runtime, message, opts = {}) { const complexity = this.#assessComplexity(message, opts); const bus = AgentEventBus.getInstance(); bus.publish(AgentEvents.PROGRESS, { type: 'adaptive_classification', complexity, selectedStrategy: complexity, }); // fan_out → pipeline → single 降级链 if (complexity === 'fan_out' && this.#strategies.fanOut) { return this.#strategies.fanOut.execute(runtime, message, opts); } if ((complexity === 'fan_out' || complexity === 'pipeline') && this.#strategies.pipeline) { return this.#strategies.pipeline.execute(runtime, message, opts); } return this.#strategies.single.execute(runtime, message, opts); } /** 复杂度评估 */ #assessComplexity(message, opts) { const text = message.content.toLowerCase(); // 有显式 items → fan_out if ((opts.items?.length ?? 0) > 1) { return 'fan_out'; } // 关键词启发 if (/冷启动|cold[\s-]?start|bootstrap|全项目|所有.*维度|all.*dimensions/i.test(text)) { return 'fan_out'; } if (/深度.*分析|扫描|审计|scan|deep.*analy|audit|知识提取|extract/i.test(text)) { return 'pipeline'; } return 'single'; } } // ─── Strategy 注册表 ───────────────────────── export const StrategyRegistry = { _registry: new Map([ ['single', SingleStrategy], // 'pipeline' 由 PipelineStrategy.js 自注册 (避免循环依赖) ['fan_out', FanOutStrategy], ['adaptive', AdaptiveStrategy], ]), create(name, opts = {}) { const Cls = this._registry.get(name); if (!Cls) { throw new Error(`Unknown strategy: ${name}`); } return Reflect.construct(Cls, [opts]); }, register(name, cls) { this._registry.set(name, cls); }, }; export default { Strategy, SingleStrategy, FanOutStrategy, AdaptiveStrategy, StrategyRegistry };