agentis
Version:
A TypeScript framework for building sophisticated multi-agent systems
261 lines (260 loc) • 10.4 kB
JavaScript
;
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;