UNPKG

agentis

Version:

A TypeScript framework for building sophisticated multi-agent systems

261 lines (260 loc) 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnhancedToolOrchestrator = exports.ExecutionMode = void 0; const Logger_1 = require("../logs/Logger"); var ExecutionMode; (function (ExecutionMode) { ExecutionMode["SEQUENTIAL"] = "sequential"; ExecutionMode["PARALLEL"] = "parallel"; ExecutionMode["CONDITIONAL"] = "conditional"; })(ExecutionMode || (exports.ExecutionMode = ExecutionMode = {})); class EnhancedToolOrchestrator { constructor(options = {}) { this.tools = new Map(); this.executionHistory = new Map(); this.executionCache = new Map(); this.cacheExpiryMs = 60000; // 1 minute default if (options.defaultTools) { options.defaultTools.forEach(tool => { this.registerTool(tool); }); } if (options.cacheExpiryMs) { this.cacheExpiryMs = options.cacheExpiryMs; } } registerTool(tool) { this.tools.set(tool.name, tool); } getTool(name) { return this.tools.get(name); } getTools() { return Array.from(this.tools.values()); } getCacheKey(toolName, input) { return `${toolName}:${input}`; } async executeSingleNode(node, context) { const tool = this.tools.get(node.toolName); if (!tool) { throw new Error(`Tool ${node.toolName} not found`); } // Determine input - either static string or function that generates input const inputValue = typeof node.input === 'function' ? node.input(context) : node.input; // Check cache first const cacheKey = this.getCacheKey(node.toolName, inputValue); if (this.executionCache.has(cacheKey)) { await Logger_1.Logger.log(context.agentId, Logger_1.LogType.TOOL_CALL, { tool: node.toolName, input: inputValue, result: "CACHED", nodeId: node.id }); return this.executionCache.get(cacheKey); } // Execute with retry logic if configured let retries = 0; const maxRetries = node.retryConfig?.maxRetries || 0; while (true) { try { const result = await tool.execute(inputValue); // Store in history if (!this.executionHistory.has(node.id)) { this.executionHistory.set(node.id, []); } this.executionHistory.get(node.id).push(result); // Store in cache this.executionCache.set(cacheKey, result); setTimeout(() => { this.executionCache.delete(cacheKey); }, this.cacheExpiryMs); // Transform output if needed const finalResult = node.transformOutput ? { ...result, result: node.transformOutput(result, context) } : result; // Log the execution await Logger_1.Logger.log(context.agentId, Logger_1.LogType.TOOL_CALL, { tool: node.toolName, input: inputValue, result: finalResult, nodeId: node.id }); return finalResult; } catch (error) { const isRetryable = node.retryConfig?.shouldRetry ? node.retryConfig.shouldRetry(error, retries) : retries < maxRetries; if (retries < maxRetries && isRetryable) { retries++; await new Promise(resolve => setTimeout(resolve, node.retryConfig?.delayMs || 1000)); await Logger_1.Logger.log(context.agentId, Logger_1.LogType.TOOL_CALL, { tool: node.toolName, input: inputValue, status: 'RETRY', attempt: retries, error: error instanceof Error ? error.message : String(error) }); } else { await Logger_1.Logger.log(context.agentId, Logger_1.LogType.ERROR, { tool: node.toolName, nodeId: node.id, error: error instanceof Error ? error.message : String(error) }); throw error; } } } } async executeGraph(graph, agentId) { const results = new Map(); const context = { results, history: this.executionHistory, agentId, getPreviousResult: (nodeId) => results.get(nodeId), getAllResults: () => new Map(results) }; if (graph.mode === ExecutionMode.SEQUENTIAL) { // Sort by dependencies and priority const sorted = this.topologicalSort(graph.nodes); for (const node of sorted) { // Skip if condition fails if (node.mode === ExecutionMode.CONDITIONAL && node.condition && !node.condition(results)) { continue; } results.set(node.id, await this.executeSingleNode(node, context)); } } else if (graph.mode === ExecutionMode.PARALLEL) { // Group by dependency level const levels = this.groupByDependencyLevel(graph.nodes); for (const level of levels) { const concurrentLimit = graph.maxConcurrency || Infinity; // Execute nodes in this level in parallel, respecting concurrency limits for (let i = 0; i < level.length; i += concurrentLimit) { const batch = level.slice(i, i + concurrentLimit); const executableNodes = batch.filter(node => node.mode !== ExecutionMode.CONDITIONAL || !node.condition || node.condition(results)); const nodePromises = executableNodes.map(async (node) => { try { const result = await this.executeSingleNode(node, context); results.set(node.id, result); } catch (error) { // Log error but don't fail the entire execution await Logger_1.Logger.log(agentId, Logger_1.LogType.ERROR, { message: `Node ${node.id} execution failed`, error }); } }); await Promise.all(nodePromises); } } } return results; } // Sort nodes in topological order (dependencies first) topologicalSort(nodes) { const nodeMap = new Map(); nodes.forEach(node => nodeMap.set(node.id, node)); const visited = new Set(); const temp = new Set(); const order = []; function visit(nodeId) { if (temp.has(nodeId)) { throw new Error(`Circular dependency detected involving node ${nodeId}`); } if (visited.has(nodeId)) return; temp.add(nodeId); const node = nodeMap.get(nodeId); if (!node) return; const deps = node.dependsOn || []; for (const dep of deps) { if (nodeMap.has(dep)) { visit(dep); } } temp.delete(nodeId); visited.add(nodeId); order.push(node); } for (const node of nodes) { if (!visited.has(node.id)) { visit(node.id); } } // Sort by priority within same dependency level return order.sort((a, b) => { const aDeps = new Set(a.dependsOn || []); const bDeps = new Set(b.dependsOn || []); if (aDeps.has(b.id)) return 1; if (bDeps.has(a.id)) return -1; return a.priority - b.priority; }); } // Group nodes by dependency level for parallel execution groupByDependencyLevel(nodes) { const nodeMap = new Map(); nodes.forEach(node => nodeMap.set(node.id, node)); const levels = new Map(); // Calculate the level for each node function calculateLevel(nodeId) { if (levels.has(nodeId)) return levels.get(nodeId); const node = nodeMap.get(nodeId); if (!node) return 0; if (!node.dependsOn || node.dependsOn.length === 0) { levels.set(nodeId, 0); return 0; } let maxDependencyLevel = -1; for (const depId of node.dependsOn) { const depLevel = calculateLevel(depId); maxDependencyLevel = Math.max(maxDependencyLevel, depLevel); } const level = maxDependencyLevel + 1; levels.set(nodeId, level); return level; } // Calculate level for all nodes for (const node of nodes) { calculateLevel(node.id); } // Group by level const levelGroups = new Map(); for (const node of nodes) { const level = levels.get(node.id) || 0; if (!levelGroups.has(level)) { levelGroups.set(level, []); } levelGroups.get(level).push(node); } // Sort and return as array of arrays const result = []; const sortedLevels = Array.from(levelGroups.keys()).sort((a, b) => a - b); for (const level of sortedLevels) { const nodesInLevel = levelGroups.get(level); // Within each level, sort by priority nodesInLevel.sort((a, b) => a.priority - b.priority); result.push(nodesInLevel); } return result; } } exports.EnhancedToolOrchestrator = EnhancedToolOrchestrator;