crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
577 lines (576 loc) • 21.1 kB
JavaScript
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;
}
}