@agentforge/patterns 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +87 -301
- package/dist/index.d.cts +0 -25
- package/dist/index.d.ts +0 -25
- package/dist/index.js +87 -301
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -283,8 +283,8 @@ function generateToolCallCacheKey(toolName, args) {
|
|
|
283
283
|
return `${toolName}:${sortedArgs}`;
|
|
284
284
|
}
|
|
285
285
|
function createPatternLogger(name, defaultLevel = "info") {
|
|
286
|
-
const
|
|
287
|
-
return (0, import_core2.createLogger)(name, { level:
|
|
286
|
+
const logLevel4 = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
|
|
287
|
+
return (0, import_core2.createLogger)(name, { level: logLevel4 });
|
|
288
288
|
}
|
|
289
289
|
function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
|
|
290
290
|
if (duplicatesSkipped === 0) {
|
|
@@ -2164,66 +2164,6 @@ var MultiAgentState = (0, import_core7.createStateAnnotation)(MultiAgentStateCon
|
|
|
2164
2164
|
|
|
2165
2165
|
// src/multi-agent/routing.ts
|
|
2166
2166
|
var import_messages4 = require("@langchain/core/messages");
|
|
2167
|
-
var import_core8 = require("@agentforge/core");
|
|
2168
|
-
var logLevel = process.env.LOG_LEVEL?.toLowerCase() || import_core8.LogLevel.INFO;
|
|
2169
|
-
var logger = (0, import_core8.createLogger)("multi-agent:routing", { level: logLevel });
|
|
2170
|
-
async function executeTools(toolCalls, tools) {
|
|
2171
|
-
const results = [];
|
|
2172
|
-
logger.debug("Executing tools", {
|
|
2173
|
-
toolCallCount: toolCalls.length,
|
|
2174
|
-
toolNames: toolCalls.map((tc) => tc.name)
|
|
2175
|
-
});
|
|
2176
|
-
for (const toolCall of toolCalls) {
|
|
2177
|
-
const tool = tools.find((t) => t.metadata.name === toolCall.name);
|
|
2178
|
-
if (!tool) {
|
|
2179
|
-
logger.warn("Tool not found", {
|
|
2180
|
-
toolName: toolCall.name,
|
|
2181
|
-
availableTools: tools.map((t) => t.metadata.name)
|
|
2182
|
-
});
|
|
2183
|
-
results.push(new import_messages4.ToolMessage({
|
|
2184
|
-
content: `Error: Tool '${toolCall.name}' not found`,
|
|
2185
|
-
tool_call_id: toolCall.id
|
|
2186
|
-
}));
|
|
2187
|
-
continue;
|
|
2188
|
-
}
|
|
2189
|
-
try {
|
|
2190
|
-
logger.debug("Executing tool", {
|
|
2191
|
-
toolName: toolCall.name,
|
|
2192
|
-
args: toolCall.args
|
|
2193
|
-
});
|
|
2194
|
-
const result = await tool.execute(toolCall.args);
|
|
2195
|
-
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
2196
|
-
logger.debug("Tool execution successful", {
|
|
2197
|
-
toolName: toolCall.name,
|
|
2198
|
-
resultLength: content.length
|
|
2199
|
-
});
|
|
2200
|
-
results.push(new import_messages4.ToolMessage({
|
|
2201
|
-
content,
|
|
2202
|
-
tool_call_id: toolCall.id
|
|
2203
|
-
}));
|
|
2204
|
-
} catch (error) {
|
|
2205
|
-
logger.error("Tool execution failed", {
|
|
2206
|
-
toolName: toolCall.name,
|
|
2207
|
-
error: error.message
|
|
2208
|
-
});
|
|
2209
|
-
results.push(new import_messages4.ToolMessage({
|
|
2210
|
-
content: `Error executing tool: ${error.message}`,
|
|
2211
|
-
tool_call_id: toolCall.id
|
|
2212
|
-
}));
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
logger.debug("Tool execution complete", {
|
|
2216
|
-
successCount: results.filter((r) => {
|
|
2217
|
-
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2218
|
-
return !content.startsWith("Error");
|
|
2219
|
-
}).length,
|
|
2220
|
-
errorCount: results.filter((r) => {
|
|
2221
|
-
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2222
|
-
return content.startsWith("Error");
|
|
2223
|
-
}).length
|
|
2224
|
-
});
|
|
2225
|
-
return results;
|
|
2226
|
-
}
|
|
2227
2167
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2228
2168
|
|
|
2229
2169
|
Your job is to:
|
|
@@ -2259,151 +2199,48 @@ Choose parallel routing when the task benefits from multiple perspectives or dat
|
|
|
2259
2199
|
var llmBasedRouting = {
|
|
2260
2200
|
name: "llm-based",
|
|
2261
2201
|
async route(state, config) {
|
|
2262
|
-
logger.info("Starting LLM-based routing", {
|
|
2263
|
-
iteration: state.iteration,
|
|
2264
|
-
availableWorkers: Object.keys(state.workers).length
|
|
2265
|
-
});
|
|
2266
2202
|
if (!config.model) {
|
|
2267
2203
|
throw new Error("LLM-based routing requires a model to be configured");
|
|
2268
2204
|
}
|
|
2269
2205
|
const systemPrompt = config.systemPrompt || DEFAULT_SUPERVISOR_SYSTEM_PROMPT;
|
|
2270
|
-
const maxRetries = config.maxToolRetries || 3;
|
|
2271
|
-
const tools = config.tools || [];
|
|
2272
2206
|
const workerInfo = Object.entries(state.workers).map(([id, caps]) => {
|
|
2273
2207
|
const skills = caps.skills.join(", ");
|
|
2274
|
-
const
|
|
2208
|
+
const tools = caps.tools.join(", ");
|
|
2275
2209
|
const available = caps.available ? "available" : "busy";
|
|
2276
|
-
return `- ${id}: Skills: [${skills}], Tools: [${
|
|
2210
|
+
return `- ${id}: Skills: [${skills}], Tools: [${tools}], Status: ${available}, Workload: ${caps.currentWorkload}`;
|
|
2277
2211
|
}).join("\n");
|
|
2278
|
-
logger.debug("Worker capabilities", {
|
|
2279
|
-
workers: Object.entries(state.workers).map(([id, caps]) => ({
|
|
2280
|
-
id,
|
|
2281
|
-
skills: caps.skills,
|
|
2282
|
-
available: caps.available,
|
|
2283
|
-
workload: caps.currentWorkload
|
|
2284
|
-
}))
|
|
2285
|
-
});
|
|
2286
2212
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2287
2213
|
const taskContext = lastMessage?.content || state.input;
|
|
2288
|
-
logger.debug("Task context", {
|
|
2289
|
-
taskLength: taskContext.length,
|
|
2290
|
-
taskPreview: taskContext.substring(0, 100)
|
|
2291
|
-
});
|
|
2292
2214
|
const userPrompt = `Current task: ${taskContext}
|
|
2293
2215
|
|
|
2294
2216
|
Available workers:
|
|
2295
2217
|
${workerInfo}
|
|
2296
2218
|
|
|
2297
2219
|
Select the best worker(s) for this task and explain your reasoning.`;
|
|
2298
|
-
const
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
const response = await config.model.invoke(messages);
|
|
2312
|
-
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
2313
|
-
logger.info("LLM requested tool calls", {
|
|
2314
|
-
toolCount: response.tool_calls.length,
|
|
2315
|
-
toolNames: response.tool_calls.map((tc) => tc.name)
|
|
2316
|
-
});
|
|
2317
|
-
if (tools.length === 0) {
|
|
2318
|
-
throw new Error("LLM requested tool calls but no tools are configured");
|
|
2319
|
-
}
|
|
2320
|
-
const toolResults = await executeTools(response.tool_calls, tools);
|
|
2321
|
-
conversationHistory.push(
|
|
2322
|
-
new import_messages4.AIMessage({ content: response.content || "", tool_calls: response.tool_calls }),
|
|
2323
|
-
...toolResults
|
|
2324
|
-
);
|
|
2325
|
-
attempt++;
|
|
2326
|
-
logger.debug("Retrying routing with tool results", { attempt });
|
|
2327
|
-
continue;
|
|
2328
|
-
}
|
|
2329
|
-
logger.debug("Parsing routing decision from LLM response");
|
|
2330
|
-
let decision;
|
|
2331
|
-
if (response && typeof response === "object" && ("targetAgent" in response || "targetAgents" in response)) {
|
|
2332
|
-
logger.debug("Response is structured output", {
|
|
2333
|
-
hasTargetAgent: "targetAgent" in response,
|
|
2334
|
-
hasTargetAgents: "targetAgents" in response
|
|
2335
|
-
});
|
|
2336
|
-
decision = response;
|
|
2337
|
-
} else if (response.content) {
|
|
2338
|
-
if (typeof response.content === "string") {
|
|
2339
|
-
try {
|
|
2340
|
-
decision = JSON.parse(response.content);
|
|
2341
|
-
logger.debug("Parsed JSON from string response");
|
|
2342
|
-
} catch (error) {
|
|
2343
|
-
logger.error("Failed to parse routing decision", {
|
|
2344
|
-
content: response.content,
|
|
2345
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2346
|
-
});
|
|
2347
|
-
throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
|
|
2348
|
-
}
|
|
2349
|
-
} else if (typeof response.content === "object") {
|
|
2350
|
-
logger.debug("Response content is already an object");
|
|
2351
|
-
decision = response.content;
|
|
2352
|
-
} else {
|
|
2353
|
-
logger.error("Unexpected response content type", {
|
|
2354
|
-
type: typeof response.content
|
|
2355
|
-
});
|
|
2356
|
-
throw new Error(`Unexpected response content type: ${typeof response.content}`);
|
|
2357
|
-
}
|
|
2358
|
-
} else {
|
|
2359
|
-
logger.error("Unexpected response format", {
|
|
2360
|
-
response: JSON.stringify(response)
|
|
2361
|
-
});
|
|
2362
|
-
throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
|
|
2363
|
-
}
|
|
2364
|
-
const result = {
|
|
2365
|
-
targetAgent: decision.targetAgent,
|
|
2366
|
-
targetAgents: decision.targetAgents,
|
|
2367
|
-
reasoning: decision.reasoning,
|
|
2368
|
-
confidence: decision.confidence,
|
|
2369
|
-
strategy: "llm-based",
|
|
2370
|
-
timestamp: Date.now()
|
|
2371
|
-
};
|
|
2372
|
-
logger.info("LLM routing decision made", {
|
|
2373
|
-
targetAgent: result.targetAgent,
|
|
2374
|
-
targetAgents: result.targetAgents,
|
|
2375
|
-
isParallel: result.targetAgents && result.targetAgents.length > 1,
|
|
2376
|
-
confidence: result.confidence,
|
|
2377
|
-
reasoning: result.reasoning
|
|
2378
|
-
});
|
|
2379
|
-
return result;
|
|
2380
|
-
}
|
|
2381
|
-
logger.error("Max tool retries exceeded", { maxRetries });
|
|
2382
|
-
throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
|
|
2220
|
+
const messages = [
|
|
2221
|
+
new import_messages4.SystemMessage(systemPrompt),
|
|
2222
|
+
new import_messages4.HumanMessage(userPrompt)
|
|
2223
|
+
];
|
|
2224
|
+
const decision = await config.model.invoke(messages);
|
|
2225
|
+
return {
|
|
2226
|
+
targetAgent: decision.targetAgent,
|
|
2227
|
+
targetAgents: decision.targetAgents,
|
|
2228
|
+
reasoning: decision.reasoning,
|
|
2229
|
+
confidence: decision.confidence,
|
|
2230
|
+
strategy: "llm-based",
|
|
2231
|
+
timestamp: Date.now()
|
|
2232
|
+
};
|
|
2383
2233
|
}
|
|
2384
2234
|
};
|
|
2385
2235
|
var roundRobinRouting = {
|
|
2386
2236
|
name: "round-robin",
|
|
2387
2237
|
async route(state, config) {
|
|
2388
|
-
logger.info("Starting round-robin routing", {
|
|
2389
|
-
iteration: state.iteration
|
|
2390
|
-
});
|
|
2391
2238
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
|
|
2392
|
-
logger.debug("Available workers for round-robin", {
|
|
2393
|
-
count: availableWorkers.length,
|
|
2394
|
-
workers: availableWorkers
|
|
2395
|
-
});
|
|
2396
2239
|
if (availableWorkers.length === 0) {
|
|
2397
|
-
logger.error("No available workers for round-robin routing");
|
|
2398
2240
|
throw new Error("No available workers for round-robin routing");
|
|
2399
2241
|
}
|
|
2400
2242
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
2401
2243
|
const targetAgent = availableWorkers[lastRoutingIndex];
|
|
2402
|
-
logger.info("Round-robin routing decision", {
|
|
2403
|
-
targetAgent,
|
|
2404
|
-
index: lastRoutingIndex + 1,
|
|
2405
|
-
totalWorkers: availableWorkers.length
|
|
2406
|
-
});
|
|
2407
2244
|
return {
|
|
2408
2245
|
targetAgent,
|
|
2409
2246
|
targetAgents: null,
|
|
@@ -2417,15 +2254,8 @@ var roundRobinRouting = {
|
|
|
2417
2254
|
var skillBasedRouting = {
|
|
2418
2255
|
name: "skill-based",
|
|
2419
2256
|
async route(state, config) {
|
|
2420
|
-
logger.info("Starting skill-based routing", {
|
|
2421
|
-
iteration: state.iteration
|
|
2422
|
-
});
|
|
2423
2257
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2424
2258
|
const taskContent = (lastMessage?.content || state.input).toLowerCase();
|
|
2425
|
-
logger.debug("Task content for skill matching", {
|
|
2426
|
-
taskLength: taskContent.length,
|
|
2427
|
-
taskPreview: taskContent.substring(0, 100)
|
|
2428
|
-
});
|
|
2429
2259
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2430
2260
|
const skillMatches = caps.skills.filter(
|
|
2431
2261
|
(skill) => taskContent.includes(skill.toLowerCase())
|
|
@@ -2436,20 +2266,11 @@ var skillBasedRouting = {
|
|
|
2436
2266
|
const score = skillMatches * 2 + toolMatches;
|
|
2437
2267
|
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
2438
2268
|
}).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
|
|
2439
|
-
logger.debug("Worker skill scores", {
|
|
2440
|
-
scoredWorkers: workerScores.map((w) => ({ id: w.id, score: w.score }))
|
|
2441
|
-
});
|
|
2442
2269
|
if (workerScores.length === 0) {
|
|
2443
|
-
logger.warn("No skill matches found, using fallback");
|
|
2444
2270
|
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
2445
2271
|
if (!firstAvailable) {
|
|
2446
|
-
logger.error("No available workers for skill-based routing");
|
|
2447
2272
|
throw new Error("No available workers for skill-based routing");
|
|
2448
2273
|
}
|
|
2449
|
-
logger.info("Skill-based routing fallback decision", {
|
|
2450
|
-
targetAgent: firstAvailable[0],
|
|
2451
|
-
confidence: 0.5
|
|
2452
|
-
});
|
|
2453
2274
|
return {
|
|
2454
2275
|
targetAgent: firstAvailable[0],
|
|
2455
2276
|
targetAgents: null,
|
|
@@ -2461,12 +2282,6 @@ var skillBasedRouting = {
|
|
|
2461
2282
|
}
|
|
2462
2283
|
const best = workerScores[0];
|
|
2463
2284
|
const confidence = Math.min(best.score / 5, 1);
|
|
2464
|
-
logger.info("Skill-based routing decision", {
|
|
2465
|
-
targetAgent: best.id,
|
|
2466
|
-
score: best.score,
|
|
2467
|
-
confidence,
|
|
2468
|
-
matchedSkills: best.skills
|
|
2469
|
-
});
|
|
2470
2285
|
return {
|
|
2471
2286
|
targetAgent: best.id,
|
|
2472
2287
|
targetAgents: null,
|
|
@@ -2480,26 +2295,13 @@ var skillBasedRouting = {
|
|
|
2480
2295
|
var loadBalancedRouting = {
|
|
2481
2296
|
name: "load-balanced",
|
|
2482
2297
|
async route(state, config) {
|
|
2483
|
-
logger.info("Starting load-balanced routing", {
|
|
2484
|
-
iteration: state.iteration
|
|
2485
|
-
});
|
|
2486
2298
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
|
|
2487
|
-
logger.debug("Worker workloads", {
|
|
2488
|
-
workers: availableWorkers.map((w) => ({ id: w.id, workload: w.workload }))
|
|
2489
|
-
});
|
|
2490
2299
|
if (availableWorkers.length === 0) {
|
|
2491
|
-
logger.error("No available workers for load-balanced routing");
|
|
2492
2300
|
throw new Error("No available workers for load-balanced routing");
|
|
2493
2301
|
}
|
|
2494
2302
|
const targetWorker = availableWorkers[0];
|
|
2495
2303
|
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2496
2304
|
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2497
|
-
logger.info("Load-balanced routing decision", {
|
|
2498
|
-
targetAgent: targetWorker.id,
|
|
2499
|
-
workload: targetWorker.workload,
|
|
2500
|
-
avgWorkload: avgWorkload.toFixed(1),
|
|
2501
|
-
confidence
|
|
2502
|
-
});
|
|
2503
2305
|
return {
|
|
2504
2306
|
targetAgent: targetWorker.id,
|
|
2505
2307
|
targetAgents: null,
|
|
@@ -2513,24 +2315,13 @@ var loadBalancedRouting = {
|
|
|
2513
2315
|
var ruleBasedRouting = {
|
|
2514
2316
|
name: "rule-based",
|
|
2515
2317
|
async route(state, config) {
|
|
2516
|
-
logger.info("Starting rule-based routing", {
|
|
2517
|
-
iteration: state.iteration
|
|
2518
|
-
});
|
|
2519
2318
|
if (!config.routingFn) {
|
|
2520
|
-
logger.error("Rule-based routing requires a custom routing function");
|
|
2521
2319
|
throw new Error("Rule-based routing requires a custom routing function");
|
|
2522
2320
|
}
|
|
2523
|
-
|
|
2524
|
-
logger.info("Rule-based routing decision", {
|
|
2525
|
-
targetAgent: decision.targetAgent,
|
|
2526
|
-
targetAgents: decision.targetAgents,
|
|
2527
|
-
confidence: decision.confidence
|
|
2528
|
-
});
|
|
2529
|
-
return decision;
|
|
2321
|
+
return await config.routingFn(state);
|
|
2530
2322
|
}
|
|
2531
2323
|
};
|
|
2532
2324
|
function getRoutingStrategy(name) {
|
|
2533
|
-
logger.debug("Getting routing strategy", { name });
|
|
2534
2325
|
switch (name) {
|
|
2535
2326
|
case "llm-based":
|
|
2536
2327
|
return llmBasedRouting;
|
|
@@ -2543,15 +2334,14 @@ function getRoutingStrategy(name) {
|
|
|
2543
2334
|
case "rule-based":
|
|
2544
2335
|
return ruleBasedRouting;
|
|
2545
2336
|
default:
|
|
2546
|
-
logger.error("Unknown routing strategy", { name });
|
|
2547
2337
|
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2548
2338
|
}
|
|
2549
2339
|
}
|
|
2550
2340
|
|
|
2551
2341
|
// src/multi-agent/utils.ts
|
|
2552
|
-
var
|
|
2553
|
-
var
|
|
2554
|
-
var
|
|
2342
|
+
var import_core8 = require("@agentforge/core");
|
|
2343
|
+
var logLevel = process.env.LOG_LEVEL?.toLowerCase() || import_core8.LogLevel.INFO;
|
|
2344
|
+
var logger = (0, import_core8.createLogger)("multi-agent", { level: logLevel });
|
|
2555
2345
|
function isReActAgent(obj) {
|
|
2556
2346
|
return obj && typeof obj === "object" && typeof obj.invoke === "function" && typeof obj.stream === "function" && // Additional check to ensure it's not just any object with invoke/stream
|
|
2557
2347
|
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
|
|
@@ -2559,9 +2349,9 @@ function isReActAgent(obj) {
|
|
|
2559
2349
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2560
2350
|
return async (state, config) => {
|
|
2561
2351
|
try {
|
|
2562
|
-
|
|
2352
|
+
logger.debug("Wrapping ReAct agent execution", { workerId });
|
|
2563
2353
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2564
|
-
|
|
2354
|
+
logger.debug("Extracted task", {
|
|
2565
2355
|
workerId,
|
|
2566
2356
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2567
2357
|
});
|
|
@@ -2569,7 +2359,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2569
2359
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2570
2360
|
);
|
|
2571
2361
|
if (!currentAssignment) {
|
|
2572
|
-
|
|
2362
|
+
logger.debug("No active assignment found", { workerId });
|
|
2573
2363
|
return {};
|
|
2574
2364
|
}
|
|
2575
2365
|
const result = await agent.invoke(
|
|
@@ -2580,14 +2370,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2580
2370
|
// Pass through the config for checkpointing and interrupt support
|
|
2581
2371
|
);
|
|
2582
2372
|
const response = result.messages?.[result.messages.length - 1]?.content || "No response";
|
|
2583
|
-
|
|
2373
|
+
logger.debug("Received response from ReAct agent", {
|
|
2584
2374
|
workerId,
|
|
2585
2375
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2586
2376
|
});
|
|
2587
2377
|
const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
|
|
2588
2378
|
const uniqueTools = [...new Set(toolsUsed)];
|
|
2589
2379
|
if (uniqueTools.length > 0) {
|
|
2590
|
-
|
|
2380
|
+
logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2591
2381
|
}
|
|
2592
2382
|
const taskResult = {
|
|
2593
2383
|
assignmentId: currentAssignment.id,
|
|
@@ -2606,7 +2396,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2606
2396
|
};
|
|
2607
2397
|
} catch (error) {
|
|
2608
2398
|
const errorMessage = handleNodeError(error, `react-agent:${workerId}`, false);
|
|
2609
|
-
|
|
2399
|
+
logger.error("Error in ReAct agent execution", {
|
|
2610
2400
|
workerId,
|
|
2611
2401
|
error: errorMessage
|
|
2612
2402
|
});
|
|
@@ -2638,9 +2428,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2638
2428
|
|
|
2639
2429
|
// src/multi-agent/nodes.ts
|
|
2640
2430
|
var import_messages5 = require("@langchain/core/messages");
|
|
2641
|
-
var
|
|
2642
|
-
var
|
|
2643
|
-
var
|
|
2431
|
+
var import_core9 = require("@agentforge/core");
|
|
2432
|
+
var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || import_core9.LogLevel.INFO;
|
|
2433
|
+
var logger2 = (0, import_core9.createLogger)("multi-agent:nodes", { level: logLevel2 });
|
|
2644
2434
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2645
2435
|
|
|
2646
2436
|
Your job is to:
|
|
@@ -2658,19 +2448,19 @@ function createSupervisorNode(config) {
|
|
|
2658
2448
|
} = config;
|
|
2659
2449
|
return async (state) => {
|
|
2660
2450
|
try {
|
|
2661
|
-
|
|
2451
|
+
logger2.info("Supervisor node executing", {
|
|
2662
2452
|
iteration: state.iteration,
|
|
2663
2453
|
maxIterations,
|
|
2664
2454
|
activeAssignments: state.activeAssignments.length,
|
|
2665
2455
|
completedTasks: state.completedTasks.length
|
|
2666
2456
|
});
|
|
2667
|
-
|
|
2457
|
+
logger2.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
|
|
2668
2458
|
if (state.iteration >= maxIterations) {
|
|
2669
|
-
|
|
2459
|
+
logger2.warn("Max iterations reached", {
|
|
2670
2460
|
iteration: state.iteration,
|
|
2671
2461
|
maxIterations
|
|
2672
2462
|
});
|
|
2673
|
-
|
|
2463
|
+
logger2.debug("Max iterations reached, moving to aggregation");
|
|
2674
2464
|
return {
|
|
2675
2465
|
status: "aggregating",
|
|
2676
2466
|
currentAgent: "aggregator"
|
|
@@ -2679,26 +2469,26 @@ function createSupervisorNode(config) {
|
|
|
2679
2469
|
const allCompleted = state.activeAssignments.every(
|
|
2680
2470
|
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2681
2471
|
);
|
|
2682
|
-
|
|
2472
|
+
logger2.debug("Checking task completion", {
|
|
2683
2473
|
activeAssignments: state.activeAssignments.length,
|
|
2684
2474
|
completedTasks: state.completedTasks.length,
|
|
2685
2475
|
allCompleted
|
|
2686
2476
|
});
|
|
2687
2477
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2688
|
-
|
|
2478
|
+
logger2.info("All tasks completed, moving to aggregation", {
|
|
2689
2479
|
completedCount: state.completedTasks.length
|
|
2690
2480
|
});
|
|
2691
|
-
|
|
2481
|
+
logger2.debug("All tasks completed, moving to aggregation");
|
|
2692
2482
|
return {
|
|
2693
2483
|
status: "aggregating",
|
|
2694
2484
|
currentAgent: "aggregator"
|
|
2695
2485
|
};
|
|
2696
2486
|
}
|
|
2697
|
-
|
|
2487
|
+
logger2.debug("Getting routing strategy", { strategy });
|
|
2698
2488
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2699
2489
|
const decision = await routingImpl.route(state, config);
|
|
2700
2490
|
const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
|
|
2701
|
-
|
|
2491
|
+
logger2.debug("Target agents determined", {
|
|
2702
2492
|
targetAgents,
|
|
2703
2493
|
isParallel: targetAgents.length > 1,
|
|
2704
2494
|
decision: {
|
|
@@ -2707,17 +2497,17 @@ function createSupervisorNode(config) {
|
|
|
2707
2497
|
}
|
|
2708
2498
|
});
|
|
2709
2499
|
if (targetAgents.length === 0) {
|
|
2710
|
-
|
|
2500
|
+
logger2.error("No target agents specified in routing decision");
|
|
2711
2501
|
throw new Error("Routing decision must specify at least one target agent");
|
|
2712
2502
|
}
|
|
2713
2503
|
if (targetAgents.length === 1) {
|
|
2714
|
-
|
|
2504
|
+
logger2.info("Routing to single agent", {
|
|
2715
2505
|
targetAgent: targetAgents[0],
|
|
2716
2506
|
reasoning: decision.reasoning,
|
|
2717
2507
|
confidence: decision.confidence
|
|
2718
2508
|
});
|
|
2719
2509
|
} else {
|
|
2720
|
-
|
|
2510
|
+
logger2.info("Routing to multiple agents in parallel", {
|
|
2721
2511
|
targetAgents,
|
|
2722
2512
|
count: targetAgents.length,
|
|
2723
2513
|
reasoning: decision.reasoning,
|
|
@@ -2725,9 +2515,9 @@ function createSupervisorNode(config) {
|
|
|
2725
2515
|
});
|
|
2726
2516
|
}
|
|
2727
2517
|
if (targetAgents.length === 1) {
|
|
2728
|
-
|
|
2518
|
+
logger2.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2729
2519
|
} else {
|
|
2730
|
-
|
|
2520
|
+
logger2.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
|
|
2731
2521
|
}
|
|
2732
2522
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2733
2523
|
const assignments = targetAgents.map((workerId) => ({
|
|
@@ -2737,7 +2527,7 @@ function createSupervisorNode(config) {
|
|
|
2737
2527
|
priority: 5,
|
|
2738
2528
|
assignedAt: Date.now()
|
|
2739
2529
|
}));
|
|
2740
|
-
|
|
2530
|
+
logger2.debug("Created task assignments", {
|
|
2741
2531
|
assignmentCount: assignments.length,
|
|
2742
2532
|
assignments: assignments.map((a) => ({
|
|
2743
2533
|
id: a.id,
|
|
@@ -2757,7 +2547,7 @@ function createSupervisorNode(config) {
|
|
|
2757
2547
|
priority: assignment.priority
|
|
2758
2548
|
}
|
|
2759
2549
|
}));
|
|
2760
|
-
|
|
2550
|
+
logger2.info("Supervisor routing complete", {
|
|
2761
2551
|
currentAgent: targetAgents.join(","),
|
|
2762
2552
|
status: "executing",
|
|
2763
2553
|
assignmentCount: assignments.length,
|
|
@@ -2774,7 +2564,7 @@ function createSupervisorNode(config) {
|
|
|
2774
2564
|
iteration: state.iteration + 1
|
|
2775
2565
|
};
|
|
2776
2566
|
} catch (error) {
|
|
2777
|
-
|
|
2567
|
+
logger2.error("Supervisor node error", {
|
|
2778
2568
|
error: error instanceof Error ? error.message : String(error),
|
|
2779
2569
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2780
2570
|
iteration: state.iteration
|
|
@@ -2799,7 +2589,7 @@ function createWorkerNode(config) {
|
|
|
2799
2589
|
} = config;
|
|
2800
2590
|
return async (state, runConfig) => {
|
|
2801
2591
|
try {
|
|
2802
|
-
|
|
2592
|
+
logger2.info("Worker node executing", {
|
|
2803
2593
|
workerId: id,
|
|
2804
2594
|
iteration: state.iteration,
|
|
2805
2595
|
activeAssignments: state.activeAssignments.length
|
|
@@ -2808,39 +2598,39 @@ function createWorkerNode(config) {
|
|
|
2808
2598
|
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2809
2599
|
);
|
|
2810
2600
|
if (!currentAssignment) {
|
|
2811
|
-
|
|
2601
|
+
logger2.debug("No active assignment found for worker", {
|
|
2812
2602
|
workerId: id,
|
|
2813
2603
|
totalActiveAssignments: state.activeAssignments.length,
|
|
2814
2604
|
completedTasks: state.completedTasks.length
|
|
2815
2605
|
});
|
|
2816
2606
|
return {};
|
|
2817
2607
|
}
|
|
2818
|
-
|
|
2608
|
+
logger2.info("Worker processing assignment", {
|
|
2819
2609
|
workerId: id,
|
|
2820
2610
|
assignmentId: currentAssignment.id,
|
|
2821
2611
|
taskLength: currentAssignment.task.length,
|
|
2822
2612
|
taskPreview: currentAssignment.task.substring(0, 100)
|
|
2823
2613
|
});
|
|
2824
2614
|
if (executeFn) {
|
|
2825
|
-
|
|
2615
|
+
logger2.debug("Using custom execution function", { workerId: id });
|
|
2826
2616
|
return await executeFn(state, runConfig);
|
|
2827
2617
|
}
|
|
2828
2618
|
if (agent) {
|
|
2829
2619
|
if (isReActAgent(agent)) {
|
|
2830
|
-
|
|
2620
|
+
logger2.debug("Using ReAct agent", { workerId: id });
|
|
2831
2621
|
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
2832
2622
|
return await wrappedFn(state, runConfig);
|
|
2833
2623
|
} else {
|
|
2834
|
-
|
|
2624
|
+
logger2.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
|
|
2835
2625
|
}
|
|
2836
2626
|
}
|
|
2837
2627
|
if (!model) {
|
|
2838
|
-
|
|
2628
|
+
logger2.error("Worker missing required configuration", { workerId: id });
|
|
2839
2629
|
throw new Error(
|
|
2840
2630
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2841
2631
|
);
|
|
2842
2632
|
}
|
|
2843
|
-
|
|
2633
|
+
logger2.debug("Using default LLM execution", {
|
|
2844
2634
|
workerId: id,
|
|
2845
2635
|
hasTools: tools.length > 0,
|
|
2846
2636
|
toolCount: tools.length
|
|
@@ -2856,18 +2646,18 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2856
2646
|
];
|
|
2857
2647
|
let modelToUse = model;
|
|
2858
2648
|
if (tools.length > 0 && model.bindTools) {
|
|
2859
|
-
|
|
2649
|
+
logger2.debug("Binding tools to model", {
|
|
2860
2650
|
workerId: id,
|
|
2861
2651
|
toolCount: tools.length,
|
|
2862
2652
|
toolNames: tools.map((t) => t.metadata.name)
|
|
2863
2653
|
});
|
|
2864
|
-
const langchainTools = (0,
|
|
2654
|
+
const langchainTools = (0, import_core9.toLangChainTools)(tools);
|
|
2865
2655
|
modelToUse = model.bindTools(langchainTools);
|
|
2866
2656
|
}
|
|
2867
|
-
|
|
2657
|
+
logger2.debug("Invoking LLM", { workerId: id });
|
|
2868
2658
|
const response = await modelToUse.invoke(messages);
|
|
2869
2659
|
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2870
|
-
|
|
2660
|
+
logger2.info("Worker task completed", {
|
|
2871
2661
|
workerId: id,
|
|
2872
2662
|
assignmentId: currentAssignment.id,
|
|
2873
2663
|
resultLength: result.length,
|
|
@@ -2902,7 +2692,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2902
2692
|
currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
|
|
2903
2693
|
}
|
|
2904
2694
|
};
|
|
2905
|
-
|
|
2695
|
+
logger2.debug("Worker state update", {
|
|
2906
2696
|
workerId: id,
|
|
2907
2697
|
newWorkload: updatedWorkers[id].currentWorkload
|
|
2908
2698
|
});
|
|
@@ -2913,7 +2703,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2913
2703
|
};
|
|
2914
2704
|
} catch (error) {
|
|
2915
2705
|
const errorMessage = handleNodeError(error, `worker:${id}`, false);
|
|
2916
|
-
|
|
2706
|
+
logger2.error("Worker node error", {
|
|
2917
2707
|
workerId: id,
|
|
2918
2708
|
error: errorMessage
|
|
2919
2709
|
});
|
|
@@ -2921,7 +2711,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2921
2711
|
(assignment) => assignment.workerId === id
|
|
2922
2712
|
);
|
|
2923
2713
|
if (currentAssignment) {
|
|
2924
|
-
|
|
2714
|
+
logger2.warn("Creating error result for assignment", {
|
|
2925
2715
|
workerId: id,
|
|
2926
2716
|
assignmentId: currentAssignment.id
|
|
2927
2717
|
});
|
|
@@ -2939,7 +2729,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2939
2729
|
status: "routing"
|
|
2940
2730
|
};
|
|
2941
2731
|
}
|
|
2942
|
-
|
|
2732
|
+
logger2.error("No assignment found for error handling", { workerId: id });
|
|
2943
2733
|
return {
|
|
2944
2734
|
status: "failed",
|
|
2945
2735
|
error: errorMessage
|
|
@@ -2956,16 +2746,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2956
2746
|
} = config;
|
|
2957
2747
|
return async (state) => {
|
|
2958
2748
|
try {
|
|
2959
|
-
|
|
2749
|
+
logger2.info("Aggregator node executing", {
|
|
2960
2750
|
completedTasks: state.completedTasks.length,
|
|
2961
2751
|
successfulTasks: state.completedTasks.filter((t) => t.success).length,
|
|
2962
2752
|
failedTasks: state.completedTasks.filter((t) => !t.success).length
|
|
2963
2753
|
});
|
|
2964
|
-
|
|
2754
|
+
logger2.debug("Combining results from workers");
|
|
2965
2755
|
if (aggregateFn) {
|
|
2966
|
-
|
|
2756
|
+
logger2.debug("Using custom aggregation function");
|
|
2967
2757
|
const response2 = await aggregateFn(state);
|
|
2968
|
-
|
|
2758
|
+
logger2.info("Custom aggregation complete", {
|
|
2969
2759
|
responseLength: response2.length
|
|
2970
2760
|
});
|
|
2971
2761
|
return {
|
|
@@ -2974,16 +2764,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2974
2764
|
};
|
|
2975
2765
|
}
|
|
2976
2766
|
if (state.completedTasks.length === 0) {
|
|
2977
|
-
|
|
2767
|
+
logger2.warn("No completed tasks to aggregate");
|
|
2978
2768
|
return {
|
|
2979
2769
|
response: "No tasks were completed.",
|
|
2980
2770
|
status: "completed"
|
|
2981
2771
|
};
|
|
2982
2772
|
}
|
|
2983
2773
|
if (!model) {
|
|
2984
|
-
|
|
2774
|
+
logger2.debug("No model provided, concatenating results");
|
|
2985
2775
|
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2986
|
-
|
|
2776
|
+
logger2.info("Simple concatenation complete", {
|
|
2987
2777
|
resultLength: combinedResults.length
|
|
2988
2778
|
});
|
|
2989
2779
|
return {
|
|
@@ -2991,7 +2781,7 @@ function createAggregatorNode(config = {}) {
|
|
|
2991
2781
|
status: "completed"
|
|
2992
2782
|
};
|
|
2993
2783
|
}
|
|
2994
|
-
|
|
2784
|
+
logger2.debug("Using LLM for intelligent aggregation", {
|
|
2995
2785
|
taskCount: state.completedTasks.length
|
|
2996
2786
|
});
|
|
2997
2787
|
const taskResults = state.completedTasks.map((task, idx) => {
|
|
@@ -3010,20 +2800,20 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
3010
2800
|
new import_messages5.SystemMessage(systemPrompt),
|
|
3011
2801
|
new import_messages5.HumanMessage(userPrompt)
|
|
3012
2802
|
];
|
|
3013
|
-
|
|
2803
|
+
logger2.debug("Invoking aggregation LLM");
|
|
3014
2804
|
const response = await model.invoke(messages);
|
|
3015
2805
|
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
3016
|
-
|
|
2806
|
+
logger2.info("Aggregation complete", {
|
|
3017
2807
|
responseLength: aggregatedResponse.length,
|
|
3018
2808
|
responsePreview: aggregatedResponse.substring(0, 100)
|
|
3019
2809
|
});
|
|
3020
|
-
|
|
2810
|
+
logger2.debug("Aggregation complete");
|
|
3021
2811
|
return {
|
|
3022
2812
|
response: aggregatedResponse,
|
|
3023
2813
|
status: "completed"
|
|
3024
2814
|
};
|
|
3025
2815
|
} catch (error) {
|
|
3026
|
-
|
|
2816
|
+
logger2.error("Aggregator node error", {
|
|
3027
2817
|
error: error instanceof Error ? error.message : String(error),
|
|
3028
2818
|
stack: error instanceof Error ? error.stack : void 0,
|
|
3029
2819
|
completedTasks: state.completedTasks.length
|
|
@@ -3038,9 +2828,9 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
3038
2828
|
|
|
3039
2829
|
// src/multi-agent/agent.ts
|
|
3040
2830
|
var import_langgraph4 = require("@langchain/langgraph");
|
|
3041
|
-
var
|
|
3042
|
-
var
|
|
3043
|
-
var
|
|
2831
|
+
var import_core10 = require("@agentforge/core");
|
|
2832
|
+
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || import_core10.LogLevel.INFO;
|
|
2833
|
+
var logger3 = (0, import_core10.createLogger)("multi-agent:system", { level: logLevel3 });
|
|
3044
2834
|
function createMultiAgentSystem(config) {
|
|
3045
2835
|
const {
|
|
3046
2836
|
supervisor,
|
|
@@ -3057,10 +2847,6 @@ function createMultiAgentSystem(config) {
|
|
|
3057
2847
|
if (supervisor.strategy === "llm-based") {
|
|
3058
2848
|
configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
|
|
3059
2849
|
}
|
|
3060
|
-
if (supervisor.tools && supervisor.tools.length > 0) {
|
|
3061
|
-
const langchainTools = (0, import_core11.toLangChainTools)(supervisor.tools);
|
|
3062
|
-
configuredModel = configuredModel.bindTools(langchainTools);
|
|
3063
|
-
}
|
|
3064
2850
|
supervisorConfig.model = configuredModel;
|
|
3065
2851
|
}
|
|
3066
2852
|
const supervisorNode = createSupervisorNode(supervisorConfig);
|
|
@@ -3082,46 +2868,46 @@ function createMultiAgentSystem(config) {
|
|
|
3082
2868
|
});
|
|
3083
2869
|
workflow.addNode("aggregator", aggregatorNode);
|
|
3084
2870
|
const supervisorRouter = (state) => {
|
|
3085
|
-
|
|
2871
|
+
logger3.debug("Supervisor router executing", {
|
|
3086
2872
|
status: state.status,
|
|
3087
2873
|
currentAgent: state.currentAgent,
|
|
3088
2874
|
iteration: state.iteration
|
|
3089
2875
|
});
|
|
3090
2876
|
if (state.status === "completed" || state.status === "failed") {
|
|
3091
|
-
|
|
2877
|
+
logger3.info("Supervisor router: ending workflow", { status: state.status });
|
|
3092
2878
|
return import_langgraph4.END;
|
|
3093
2879
|
}
|
|
3094
2880
|
if (state.status === "aggregating") {
|
|
3095
|
-
|
|
2881
|
+
logger3.info("Supervisor router: routing to aggregator");
|
|
3096
2882
|
return "aggregator";
|
|
3097
2883
|
}
|
|
3098
2884
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
3099
2885
|
if (state.currentAgent.includes(",")) {
|
|
3100
2886
|
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
3101
|
-
|
|
2887
|
+
logger3.info("Supervisor router: parallel routing", {
|
|
3102
2888
|
agents,
|
|
3103
2889
|
count: agents.length
|
|
3104
2890
|
});
|
|
3105
2891
|
return agents;
|
|
3106
2892
|
}
|
|
3107
|
-
|
|
2893
|
+
logger3.info("Supervisor router: single agent routing", {
|
|
3108
2894
|
targetAgent: state.currentAgent
|
|
3109
2895
|
});
|
|
3110
2896
|
return state.currentAgent;
|
|
3111
2897
|
}
|
|
3112
|
-
|
|
2898
|
+
logger3.debug("Supervisor router: staying at supervisor");
|
|
3113
2899
|
return "supervisor";
|
|
3114
2900
|
};
|
|
3115
2901
|
const workerRouter = (state) => {
|
|
3116
|
-
|
|
2902
|
+
logger3.debug("Worker router executing", {
|
|
3117
2903
|
iteration: state.iteration,
|
|
3118
2904
|
completedTasks: state.completedTasks.length
|
|
3119
2905
|
});
|
|
3120
|
-
|
|
2906
|
+
logger3.debug("Worker router: returning to supervisor");
|
|
3121
2907
|
return "supervisor";
|
|
3122
2908
|
};
|
|
3123
2909
|
const aggregatorRouter = (state) => {
|
|
3124
|
-
|
|
2910
|
+
logger3.info("Aggregator router: ending workflow", {
|
|
3125
2911
|
completedTasks: state.completedTasks.length,
|
|
3126
2912
|
status: state.status
|
|
3127
2913
|
});
|