UNPKG

crewai-ts

Version:

TypeScript port of crewAI for agent-based workflows

577 lines (576 loc) 21.1 kB
import { EventEmitter } from 'events'; import { FlowExecutionTracker } from './FlowExecutionTracker.js'; export class MultiFlowOrchestrator extends EventEmitter { #orchestrators = new Map(); #tracker; #options; #pendingFlows = new Map(); #completedFlows = new Map(); #failedFlows = new Map(); #currentExecution = { startTime: 0, endTime: undefined, status: 'running' }; #criticalPath = { flows: [], time: 0 }; #runningFlows = new Set(); #flowExecutionTimes = new Map(); #timeSeriesData = []; #errors = new Map(); #executionStartTime = 0; #executionEndTime; #checkpointTimer; #memoryUsage = { initial: 0, peak: 0, current: 0 }; #performanceMetrics = { totalExecutionTime: 0, averageFlowTime: 0, maxFlowTime: 0, minFlowTime: Infinity, flowCount: 0, completedFlows: 0, failedFlows: 0 }; #visualizationPath; #visualizationOptions; constructor(options = {}) { super(); this.#orchestrators = new Map(); this.#tracker = new FlowExecutionTracker(); this.#options = { optimizeFor: options.optimizeFor ?? 'balanced', enableMemoryIntegration: options.enableMemoryIntegration ?? false, memoryConnector: options.memoryConnector ?? new (class { async saveState(flowId, state) { // implement save state logic } async loadState(flowId) { // implement load state logic return null; } async deleteState(flowId) { // implement delete state logic return true; } async saveResult(flowId, methodName, result) { // implement save result logic } async loadResult(flowId, methodName) { // implement load result logic return null; } async deleteResult(flowId) { // implement delete result logic return true; } async saveMetrics(flowId, metrics) { // implement save metrics logic } async loadMetrics(flowId) { // implement load metrics logic return null; } async deleteMetrics(flowId) { // implement delete metrics logic return true; } async saveFlowState() { // implement save flow state logic } async loadFlowState() { // implement load flow state logic return null; } async saveConfig(flowId, config) { // implement save config logic } async loadConfig(flowId) { // implement load config logic return null; } async clearFlowData(flowId, options) { // implement clear flow data logic } async getDistinctFlowIds() { // implement get distinct flow ids logic return []; } async getFlowCount() { // implement get flow count logic return 0; } async getFlowIds() { // implement get flow ids logic return []; } async getFlowData(flowId) { // implement get flow data logic return null; } async getFlowStates() { // implement get flow states logic return {}; } async getFlowResults() { // implement get flow results logic return {}; } async getFlowMetrics() { // implement get flow metrics logic return {}; } async saveFlowData(flowId, data) { // implement save flow data logic } async loadFlowData(flowId, type) { // implement load flow data logic return null; } async getAllFlowDataByType(flowId, type) { // implement get all flow data by type logic return []; } })(), scheduler: { maxParallelFlows: options.scheduler?.maxParallelFlows ?? 1, priorityStrategy: options.scheduler?.priorityStrategy ?? 'fifo', backoffStrategy: options.scheduler?.backoffStrategy ?? 'exponential' }, visualization: { enabled: options.visualization?.enabled ?? false, format: options.visualization?.format ?? 'png', outputDirectory: options.visualization?.outputDirectory ?? '.', includeDependencies: options.visualization?.includeDependencies ?? true, includeMetrics: options.visualization?.includeMetrics ?? true } }; this.#visualizationOptions = this.#options.visualization; this.#executionStartTime = Date.now(); this.#memoryUsage.initial = process.memoryUsage().heapUsed; } getOrchestrator() { return Array.from(this.#orchestrators.values())[0]; } getFlowOrchestrator(flowId) { for (const orchestrator of this.#orchestrators.values()) { if (orchestrator.getFlow(flowId)) { return orchestrator; } } return undefined; } registerFlow(definition) { const orchestrator = this.getOrchestrator(); if (!orchestrator) { throw new Error('No orchestrator available'); } const flowId = orchestrator.registerFlow(definition); const flow = orchestrator.getFlow(flowId); if (flow) { this.#pendingFlows.set(flowId, flow); } return flowId; } getFlow(flowId) { return this.#pendingFlows.get(flowId) || this.#completedFlows.get(flowId) || this.#failedFlows.get(flowId); } removeFlow(flowId) { const orchestrator = this.getFlowOrchestrator(flowId); if (orchestrator) { orchestrator.removeFlow(flowId); this.#pendingFlows.delete(flowId); this.#completedFlows.delete(flowId); this.#failedFlows.delete(flowId); this.#runningFlows.delete(flowId); this.#flowExecutionTimes.delete(flowId); this.#errors.delete(flowId); } } reset() { this.#pendingFlows.clear(); this.#completedFlows.clear(); this.#failedFlows.clear(); this.#runningFlows.clear(); this.#flowExecutionTimes.clear(); this.#errors.clear(); this.#executionStartTime = 0; this.#executionEndTime = undefined; this.#checkpointTimer = undefined; this.#performanceMetrics = { totalExecutionTime: 0, averageFlowTime: 0, maxFlowTime: 0, minFlowTime: Infinity, flowCount: 0, completedFlows: 0, failedFlows: 0 }; for (const orchestrator of this.#orchestrators.values()) { orchestrator.reset(); } } execute(options) { return Promise.all(Array.from(this.#orchestrators.values()).map(orchestrator => orchestrator.execute(options))).then(results => results[0]); } startFlow(flowId, options) { const orchestrator = this.getFlowOrchestrator(flowId); if (!orchestrator) { throw new Error(`Flow ${flowId} not found`); } return orchestrator.startFlow(flowId, options); } waitForAnyFlowToComplete() { return Promise.race(Array.from(this.#orchestrators.values()).map(orchestrator => orchestrator.waitForAnyFlowToComplete())); } saveExecutionCheckpoint() { return Promise.all(Array.from(this.#orchestrators.values()).map(orchestrator => orchestrator.saveExecutionCheckpoint())).then(() => { }); } loadExecutionCheckpoint() { return Promise.all(Array.from(this.#orchestrators.values()).map(orchestrator => orchestrator.loadExecutionCheckpoint())).then(() => { }); } getMetrics() { const metrics = { totalExecutionTime: 0, flowCount: 0, completedFlows: 0, failedFlows: 0, pendingFlows: 0, runningFlows: 0, averageFlowExecutionTime: 0, medianFlowExecutionTime: 0, maxFlowExecutionTime: 0, minFlowExecutionTime: Infinity, p95FlowExecutionTime: 0, flowExecutionTimes: new Map(), statusCounts: { pending: 0, running: 0, completed: 0, failed: 0 }, bottlenecks: [], timeSeriesData: [], errors: new Map() }; for (const orchestrator of this.#orchestrators.values()) { const orchestratorMetrics = orchestrator.getMetrics(); if (!orchestratorMetrics) continue; metrics.totalExecutionTime += orchestratorMetrics.totalExecutionTime || 0; metrics.flowCount += orchestratorMetrics.flowCount || 0; metrics.completedFlows += orchestratorMetrics.completedFlows || 0; metrics.failedFlows += orchestratorMetrics.failedFlows || 0; metrics.pendingFlows += orchestratorMetrics.pendingFlows || 0; metrics.runningFlows += orchestratorMetrics.runningFlows || 0; if (orchestratorMetrics.flowExecutionTimes) { for (const [flowId, time] of orchestratorMetrics.flowExecutionTimes.entries()) { metrics.flowExecutionTimes.set(flowId, time); } } if (orchestratorMetrics.errors) { for (const [flowId, error] of orchestratorMetrics.errors.entries()) { metrics.errors.set(flowId, error); } } metrics.timeSeriesData.push(...(orchestratorMetrics.timeSeriesData || [])); // Update status counts metrics.statusCounts.pending += orchestratorMetrics.statusCounts?.pending || 0; metrics.statusCounts.running += orchestratorMetrics.statusCounts?.running || 0; metrics.statusCounts.completed += orchestratorMetrics.statusCounts?.completed || 0; metrics.statusCounts.failed += orchestratorMetrics.statusCounts?.failed || 0; } // Calculate derived metrics const times = Array.from(metrics.flowExecutionTimes.values()); if (times.length > 0) { metrics.averageFlowExecutionTime = times.reduce((a, b) => (a || 0) + (b || 0), 0) / times.length; const sortedTimes = [...times].sort((a, b) => (a || 0) - (b || 0)); metrics.medianFlowExecutionTime = sortedTimes[Math.floor(sortedTimes.length / 2)] || 0; metrics.p95FlowExecutionTime = sortedTimes[Math.floor(sortedTimes.length * 0.95)] || 0; metrics.maxFlowExecutionTime = Math.max(...times) || 0; metrics.minFlowExecutionTime = Math.min(...times) || Infinity; } return metrics; } getMetricsForFlow(flowId) { const orchestrator = this.getFlowOrchestrator(flowId); const flowMetrics = orchestrator?.getMetricsForFlow(flowId); if (!flowMetrics) { throw new Error(`Metrics not available for flow ${flowId}`); } return flowMetrics; } getFlowDependencyOrder() { return Array.from(this.#pendingFlows.keys()) .sort((a, b) => { const aMetrics = this.#pendingFlows.get(a)?.state.metrics; const bMetrics = this.#pendingFlows.get(b)?.state.metrics; if (!aMetrics?.dependencies?.size) return -1; if (!bMetrics?.dependencies?.size) return 1; return aMetrics.dependencies.size - bMetrics.dependencies.size; }); } getFlowDependencyMatrix() { const matrix = {}; for (const [flowId, flow] of this.#pendingFlows) { matrix[flowId] = {}; if (flow.state.metrics?.dependencies) { for (const dep of flow.state.metrics.dependencies) { matrix[flowId][dep] = true; } } } return matrix; } getFlowDependencyTree() { const tree = {}; for (const [flowId, flow] of this.#pendingFlows) { tree[flowId] = Array.from(flow.state.metrics?.dependencies || new Set()); } return tree; } getFlowDependencyList() { return Array.from(this.#pendingFlows.entries()).map(([id, flow]) => ({ id, dependencies: Array.from(flow.state.metrics?.dependencies || new Set()) })); } getFlowDependencyDepth() { const depths = {}; const visited = new Set(); const calculateDepth = (flowId, currentDepth = 0) => { if (visited.has(flowId)) return depths[flowId] || currentDepth; visited.add(flowId); const flow = this.#pendingFlows.get(flowId); if (!flow?.state.metrics?.dependencies) { depths[flowId] = currentDepth; return currentDepth; } if (flow.state.metrics.dependencies.size === 0) { depths[flowId] = currentDepth; return currentDepth; } const maxDependencyDepth = Math.max(...Array.from(flow.state.metrics.dependencies).map(dep => calculateDepth(dep, currentDepth + 1))); depths[flowId] = maxDependencyDepth; return maxDependencyDepth; }; for (const flowId of this.#pendingFlows.keys()) { calculateDepth(flowId, 0); } return depths; } getFlowDependencyWidth() { const widths = {}; for (const [flowId, flow] of this.#pendingFlows) { widths[flowId] = flow.state.metrics?.dependencies?.size || 0; } return widths; } getFlowDependencyFanIn() { const fanIn = {}; for (const [flowId, flow] of this.#pendingFlows) { fanIn[flowId] = flow.state.metrics?.dependencies?.size || 0; } return fanIn; } getFlowDependencyFanOut() { const fanOut = {}; for (const flowId of this.#pendingFlows.keys()) { const flow = this.#pendingFlows.get(flowId); if (!flow?.state.metrics?.dependencies) continue; fanOut[flowId] = this.getDependentFlows(flowId).length; } return fanOut; } getFlowDependencyCycles() { const cycles = []; const visited = new Set(); const visiting = new Set(); const findCycles = (flowId, path = []) => { if (visited.has(flowId)) return; if (visiting.has(flowId)) { const cycle = path.slice(path.indexOf(flowId)); cycle.push(flowId); cycles.push(cycle); return; } visiting.add(flowId); path.push(flowId); const flow = this.#pendingFlows.get(flowId); if (!flow?.state.metrics?.dependencies) return; for (const dep of flow.state.metrics.dependencies) { findCycles(dep, [...path]); } visiting.delete(flowId); visited.add(flowId); }; for (const flowId of this.#pendingFlows.keys()) { findCycles(flowId, []); } return cycles; } getFlowDependencyCriticalPath() { const criticalPath = []; let maxTime = 0; for (const flowId of this.getFlowDependencyOrder()) { const flow = this.getFlow(flowId); if (!flow?.state.metrics?.endTime) continue; const flowTime = flow.state.metrics.endTime - flow.state.metrics.startTime; if (flowTime > maxTime) { maxTime = flowTime; criticalPath.length = 0; criticalPath.push(flowId); } else if (flowTime === maxTime) { criticalPath.push(flowId); } } return criticalPath; } getFlowDependencyCriticalPathFlows() { return this.getFlowDependencyCriticalPath(); } getDependentFlows(flowId) { const dependents = new Set(); for (const [id, flow] of this.#pendingFlows) { if (flow.state.metrics?.dependencies?.has(flowId)) { dependents.add(id); } } return Array.from(dependents); } getFlowGraph() { const nodes = Array.from(this.pendingFlows.keys()).map(id => ({ id })); const edges = []; for (const [flowId, flow] of this.pendingFlows) { if (flow.state.metrics?.dependencies) { for (const dep of flow.state.metrics.dependencies) { edges.push({ from: dep, to: flowId }); } } } return { nodes, edges }; } getFlowDependencies(flowId) { const flow = this.getFlow(flowId); return flow?.state.metrics?.dependencies ? Array.from(flow.state.metrics.dependencies) : []; } get nodes() { const nodes = []; for (const orchestrator of this.#orchestrators.values()) { nodes.push(...orchestrator.nodes); } return nodes; } get edges() { const edges = []; for (const orchestrator of this.#orchestrators.values()) { edges.push(...orchestrator.edges); } return edges; } get options() { return this.#options; } get pendingFlows() { return this.#pendingFlows; } get completedFlows() { return this.#completedFlows; } get failedFlows() { return this.#failedFlows; } get currentExecution() { return this.#currentExecution; } get criticalPath() { return this.#criticalPath; } get runningFlows() { return this.#runningFlows; } get flowExecutionTimes() { return this.#flowExecutionTimes; } get timeSeriesData() { return this.#timeSeriesData; } get errors() { return this.#errors; } get executionStartTime() { return this.#executionStartTime; } get executionEndTime() { return this.#executionEndTime; } get checkpointTimer() { return this.#checkpointTimer; } get memoryUsage() { return this.#memoryUsage; } get performanceMetrics() { return this.#performanceMetrics; } get visualizationPath() { return this.#visualizationPath; } get visualizationOptions() { return this.#visualizationOptions; } getOptimizedOptions() { const options = { optimizeFor: this.#options.optimizeFor, enableMemoryIntegration: this.#options.enableMemoryIntegration, scheduler: { maxParallelFlows: this.#options.scheduler.maxParallelFlows, priorityStrategy: this.#options.scheduler.priorityStrategy, backoffStrategy: this.#options.scheduler.backoffStrategy }, visualization: this.#options.visualization }; return options; } getFlowMetrics(flowId) { const flow = this.getFlow(flowId); if (!flow?.state?.metrics) return undefined; return flow.state.metrics; } getFlowExecutionTime(flowId) { const metrics = this.getFlowMetrics(flowId); if (!metrics?.startTime) return 0; const endTime = metrics.endTime ?? Date.now(); return endTime - metrics.startTime; } getFlowResourceUsage(flowId) { const metrics = this.getFlowMetrics(flowId); if (!metrics?.nodeExecutions) return undefined; return metrics.nodeExecutions; } getFlowStatus(flowId) { const flow = this.getFlow(flowId); return flow?.state?.status ?? 'pending'; } getFlowState(flowId) { const flow = this.getFlow(flowId); if (!flow?.state) return undefined; return flow.state; } }