@agentforge/patterns 0.16.31 → 0.16.32
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 +160 -99
- package/dist/index.d.cts +0 -9
- package/dist/index.d.ts +0 -9
- package/dist/index.js +160 -99
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -313,9 +313,9 @@ function stringifyActionArguments(arguments_) {
|
|
|
313
313
|
function getLatestThought(thoughts) {
|
|
314
314
|
return thoughts[thoughts.length - 1]?.content ?? "";
|
|
315
315
|
}
|
|
316
|
-
function debugIfVerbose(
|
|
316
|
+
function debugIfVerbose(logger5, verbose, message, data) {
|
|
317
317
|
if (verbose) {
|
|
318
|
-
|
|
318
|
+
logger5.debug(message, data);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
|
|
@@ -2295,6 +2295,82 @@ var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
|
|
|
2295
2295
|
|
|
2296
2296
|
// src/multi-agent/routing.ts
|
|
2297
2297
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2298
|
+
var logger = createPatternLogger("agentforge:patterns:multi-agent:routing");
|
|
2299
|
+
function hasStructuredOutput(model) {
|
|
2300
|
+
return typeof model.withStructuredOutput === "function";
|
|
2301
|
+
}
|
|
2302
|
+
function isRecord(value) {
|
|
2303
|
+
return typeof value === "object" && value !== null;
|
|
2304
|
+
}
|
|
2305
|
+
function isContentCarrier(value) {
|
|
2306
|
+
return isRecord(value) && "content" in value;
|
|
2307
|
+
}
|
|
2308
|
+
function serializeRoutingContent(content) {
|
|
2309
|
+
if (typeof content === "string") {
|
|
2310
|
+
return content;
|
|
2311
|
+
}
|
|
2312
|
+
if (Array.isArray(content)) {
|
|
2313
|
+
const textParts = content.flatMap((part) => {
|
|
2314
|
+
if (typeof part === "string") {
|
|
2315
|
+
return [part];
|
|
2316
|
+
}
|
|
2317
|
+
if (isRecord(part) && typeof part.text === "string") {
|
|
2318
|
+
return [part.text];
|
|
2319
|
+
}
|
|
2320
|
+
return [];
|
|
2321
|
+
});
|
|
2322
|
+
if (textParts.length > 0) {
|
|
2323
|
+
return textParts.join("\n");
|
|
2324
|
+
}
|
|
2325
|
+
return JSON.stringify(content);
|
|
2326
|
+
}
|
|
2327
|
+
return JSON.stringify(content);
|
|
2328
|
+
}
|
|
2329
|
+
function normalizeRoutingDecisionInput(decision) {
|
|
2330
|
+
if (isContentCarrier(decision)) {
|
|
2331
|
+
return JSON.parse(serializeRoutingContent(decision.content));
|
|
2332
|
+
}
|
|
2333
|
+
return decision;
|
|
2334
|
+
}
|
|
2335
|
+
function parseRoutingDecision(decision) {
|
|
2336
|
+
try {
|
|
2337
|
+
return RoutingDecisionSchema.parse(normalizeRoutingDecisionInput(decision));
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2340
|
+
throw new Error(`Invalid LLM routing decision: ${message}`);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
function finalizeLlmRoutingDecision(decision) {
|
|
2344
|
+
const parsed = parseRoutingDecision(decision);
|
|
2345
|
+
return {
|
|
2346
|
+
targetAgent: parsed.targetAgent,
|
|
2347
|
+
targetAgents: parsed.targetAgents,
|
|
2348
|
+
reasoning: parsed.reasoning,
|
|
2349
|
+
confidence: parsed.confidence,
|
|
2350
|
+
strategy: "llm-based",
|
|
2351
|
+
timestamp: Date.now()
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
async function invokeRoutingDecision(model, messages) {
|
|
2355
|
+
if (hasStructuredOutput(model)) {
|
|
2356
|
+
let structuredModel;
|
|
2357
|
+
try {
|
|
2358
|
+
structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
|
|
2359
|
+
} catch (error) {
|
|
2360
|
+
logger.warn("Structured output unavailable, using direct routing fallback", {
|
|
2361
|
+
strategy: "llm-based",
|
|
2362
|
+
fallback: "direct-model-invoke",
|
|
2363
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2364
|
+
});
|
|
2365
|
+
const decision3 = await model.invoke(messages);
|
|
2366
|
+
return finalizeLlmRoutingDecision(decision3);
|
|
2367
|
+
}
|
|
2368
|
+
const decision2 = await structuredModel.invoke(messages);
|
|
2369
|
+
return finalizeLlmRoutingDecision(decision2);
|
|
2370
|
+
}
|
|
2371
|
+
const decision = await model.invoke(messages);
|
|
2372
|
+
return finalizeLlmRoutingDecision(decision);
|
|
2373
|
+
}
|
|
2298
2374
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2299
2375
|
|
|
2300
2376
|
Your job is to:
|
|
@@ -2352,20 +2428,12 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
2352
2428
|
new SystemMessage5(systemPrompt),
|
|
2353
2429
|
new HumanMessage5(userPrompt)
|
|
2354
2430
|
];
|
|
2355
|
-
|
|
2356
|
-
return {
|
|
2357
|
-
targetAgent: decision.targetAgent,
|
|
2358
|
-
targetAgents: decision.targetAgents,
|
|
2359
|
-
reasoning: decision.reasoning,
|
|
2360
|
-
confidence: decision.confidence,
|
|
2361
|
-
strategy: "llm-based",
|
|
2362
|
-
timestamp: Date.now()
|
|
2363
|
-
};
|
|
2431
|
+
return await invokeRoutingDecision(config.model, messages);
|
|
2364
2432
|
}
|
|
2365
2433
|
};
|
|
2366
2434
|
var roundRobinRouting = {
|
|
2367
2435
|
name: "round-robin",
|
|
2368
|
-
async route(state,
|
|
2436
|
+
async route(state, _config) {
|
|
2369
2437
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
|
|
2370
2438
|
if (availableWorkers.length === 0) {
|
|
2371
2439
|
throw new Error("No available workers for round-robin routing");
|
|
@@ -2384,7 +2452,7 @@ var roundRobinRouting = {
|
|
|
2384
2452
|
};
|
|
2385
2453
|
var skillBasedRouting = {
|
|
2386
2454
|
name: "skill-based",
|
|
2387
|
-
async route(state,
|
|
2455
|
+
async route(state, _config) {
|
|
2388
2456
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2389
2457
|
const taskContent = (lastMessage?.content || state.input).toLowerCase();
|
|
2390
2458
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
@@ -2425,7 +2493,7 @@ var skillBasedRouting = {
|
|
|
2425
2493
|
};
|
|
2426
2494
|
var loadBalancedRouting = {
|
|
2427
2495
|
name: "load-balanced",
|
|
2428
|
-
async route(state,
|
|
2496
|
+
async route(state, _config) {
|
|
2429
2497
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
|
|
2430
2498
|
if (availableWorkers.length === 0) {
|
|
2431
2499
|
throw new Error("No available workers for load-balanced routing");
|
|
@@ -2470,22 +2538,22 @@ function getRoutingStrategy(name) {
|
|
|
2470
2538
|
}
|
|
2471
2539
|
|
|
2472
2540
|
// src/multi-agent/utils.ts
|
|
2473
|
-
var
|
|
2474
|
-
function
|
|
2541
|
+
var logger2 = createPatternLogger("agentforge:patterns:multi-agent:utils");
|
|
2542
|
+
function isRecord2(value) {
|
|
2475
2543
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2476
2544
|
}
|
|
2477
2545
|
function toRunnableConfig(config) {
|
|
2478
|
-
if (!
|
|
2546
|
+
if (!isRecord2(config)) {
|
|
2479
2547
|
return void 0;
|
|
2480
2548
|
}
|
|
2481
2549
|
return config;
|
|
2482
2550
|
}
|
|
2483
2551
|
function getReActResultShape(value) {
|
|
2484
|
-
if (!
|
|
2552
|
+
if (!isRecord2(value)) {
|
|
2485
2553
|
return {};
|
|
2486
2554
|
}
|
|
2487
|
-
const messages = Array.isArray(value.messages) ? value.messages.filter((message) =>
|
|
2488
|
-
const actions = Array.isArray(value.actions) ? value.actions.filter((action) =>
|
|
2555
|
+
const messages = Array.isArray(value.messages) ? value.messages.filter((message) => isRecord2(message)) : void 0;
|
|
2556
|
+
const actions = Array.isArray(value.actions) ? value.actions.filter((action) => isRecord2(action)) : void 0;
|
|
2489
2557
|
return {
|
|
2490
2558
|
messages,
|
|
2491
2559
|
actions,
|
|
@@ -2504,7 +2572,7 @@ function safeSerializeContent(content) {
|
|
|
2504
2572
|
if (typeof part === "string") {
|
|
2505
2573
|
return part;
|
|
2506
2574
|
}
|
|
2507
|
-
if (
|
|
2575
|
+
if (isRecord2(part) && typeof part.text === "string" && part.text.length > 0) {
|
|
2508
2576
|
return part.text;
|
|
2509
2577
|
}
|
|
2510
2578
|
try {
|
|
@@ -2549,17 +2617,17 @@ function isReActAgent(obj) {
|
|
|
2549
2617
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2550
2618
|
return async (state, config) => {
|
|
2551
2619
|
try {
|
|
2552
|
-
|
|
2620
|
+
logger2.debug("Wrapping ReAct agent execution", { workerId });
|
|
2553
2621
|
const runnableConfig = toRunnableConfig(config);
|
|
2554
2622
|
const currentAssignment = state.activeAssignments.find(
|
|
2555
2623
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2556
2624
|
);
|
|
2557
2625
|
if (!currentAssignment) {
|
|
2558
|
-
|
|
2626
|
+
logger2.debug("No active assignment found", { workerId });
|
|
2559
2627
|
return {};
|
|
2560
2628
|
}
|
|
2561
2629
|
const task = currentAssignment.task;
|
|
2562
|
-
|
|
2630
|
+
logger2.debug("Extracted task from assignment", {
|
|
2563
2631
|
workerId,
|
|
2564
2632
|
assignmentId: currentAssignment.id,
|
|
2565
2633
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
@@ -2572,7 +2640,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2572
2640
|
thread_id: workerThreadId
|
|
2573
2641
|
}
|
|
2574
2642
|
} : runnableConfig;
|
|
2575
|
-
|
|
2643
|
+
logger2.debug("Invoking ReAct agent with worker-specific config", {
|
|
2576
2644
|
workerId,
|
|
2577
2645
|
...runnableConfig?.configurable?.thread_id !== void 0 ? { parentThreadId: String(runnableConfig.configurable.thread_id) } : {},
|
|
2578
2646
|
...workerThreadId ? { workerThreadId } : {},
|
|
@@ -2587,13 +2655,13 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2587
2655
|
);
|
|
2588
2656
|
const resultShape = getReActResultShape(result);
|
|
2589
2657
|
const response = extractResponse(resultShape);
|
|
2590
|
-
|
|
2658
|
+
logger2.debug("Received response from ReAct agent", {
|
|
2591
2659
|
workerId,
|
|
2592
2660
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2593
2661
|
});
|
|
2594
2662
|
const uniqueTools = extractToolsUsed(resultShape);
|
|
2595
2663
|
if (uniqueTools.length > 0) {
|
|
2596
|
-
|
|
2664
|
+
logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2597
2665
|
}
|
|
2598
2666
|
const taskResult = {
|
|
2599
2667
|
assignmentId: currentAssignment.id,
|
|
@@ -2612,7 +2680,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2612
2680
|
};
|
|
2613
2681
|
} catch (error) {
|
|
2614
2682
|
const errorMessage = handleNodeError(error, `react-agent:${workerId}`, verbose);
|
|
2615
|
-
|
|
2683
|
+
logger2.error("Error in ReAct agent execution", {
|
|
2616
2684
|
workerId,
|
|
2617
2685
|
error: errorMessage
|
|
2618
2686
|
});
|
|
@@ -2645,7 +2713,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2645
2713
|
// src/multi-agent/nodes/shared.ts
|
|
2646
2714
|
import { HumanMessage as HumanMessage6, SystemMessage as SystemMessage6 } from "@langchain/core/messages";
|
|
2647
2715
|
import { toLangChainTools as toLangChainTools2 } from "@agentforge/core";
|
|
2648
|
-
var
|
|
2716
|
+
var logger3 = createPatternLogger("agentforge:patterns:multi-agent:nodes");
|
|
2649
2717
|
function createGeneratedId(prefix) {
|
|
2650
2718
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
2651
2719
|
}
|
|
@@ -2697,7 +2765,7 @@ function serializeModelContent(content) {
|
|
|
2697
2765
|
const error = new Error(
|
|
2698
2766
|
"Failed to serialize model content: JSON.stringify returned undefined"
|
|
2699
2767
|
);
|
|
2700
|
-
|
|
2768
|
+
logger3.error("Model content serialization failed", {
|
|
2701
2769
|
errorMessage: error.message,
|
|
2702
2770
|
contentType: content === null ? "null" : typeof content
|
|
2703
2771
|
});
|
|
@@ -2706,7 +2774,7 @@ function serializeModelContent(content) {
|
|
|
2706
2774
|
return serialized;
|
|
2707
2775
|
} catch (error) {
|
|
2708
2776
|
const normalizedError = error instanceof Error ? error : new Error("Unknown error during model content serialization");
|
|
2709
|
-
|
|
2777
|
+
logger3.error("Model content serialization threw an error", {
|
|
2710
2778
|
errorMessage: normalizedError.message,
|
|
2711
2779
|
contentType: content === null ? "null" : typeof content
|
|
2712
2780
|
});
|
|
@@ -2749,16 +2817,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2749
2817
|
} = config;
|
|
2750
2818
|
return async (state) => {
|
|
2751
2819
|
try {
|
|
2752
|
-
|
|
2820
|
+
logger3.info("Aggregator node executing", {
|
|
2753
2821
|
completedTasks: state.completedTasks.length,
|
|
2754
2822
|
successfulTasks: state.completedTasks.filter((task) => task.success).length,
|
|
2755
2823
|
failedTasks: state.completedTasks.filter((task) => !task.success).length
|
|
2756
2824
|
});
|
|
2757
|
-
|
|
2825
|
+
logger3.debug("Combining results from workers");
|
|
2758
2826
|
if (aggregateFn) {
|
|
2759
|
-
|
|
2827
|
+
logger3.debug("Using custom aggregation function");
|
|
2760
2828
|
const response2 = await aggregateFn(state);
|
|
2761
|
-
|
|
2829
|
+
logger3.info("Custom aggregation complete", {
|
|
2762
2830
|
responseLength: response2.length
|
|
2763
2831
|
});
|
|
2764
2832
|
return {
|
|
@@ -2767,16 +2835,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2767
2835
|
};
|
|
2768
2836
|
}
|
|
2769
2837
|
if (state.completedTasks.length === 0) {
|
|
2770
|
-
|
|
2838
|
+
logger3.warn("No completed tasks to aggregate");
|
|
2771
2839
|
return {
|
|
2772
2840
|
response: "No tasks were completed.",
|
|
2773
2841
|
status: "completed"
|
|
2774
2842
|
};
|
|
2775
2843
|
}
|
|
2776
2844
|
if (!model) {
|
|
2777
|
-
|
|
2845
|
+
logger3.debug("No model provided, concatenating results");
|
|
2778
2846
|
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2779
|
-
|
|
2847
|
+
logger3.info("Simple concatenation complete", {
|
|
2780
2848
|
resultLength: combinedResults.length
|
|
2781
2849
|
});
|
|
2782
2850
|
return {
|
|
@@ -2784,27 +2852,27 @@ function createAggregatorNode(config = {}) {
|
|
|
2784
2852
|
status: "completed"
|
|
2785
2853
|
};
|
|
2786
2854
|
}
|
|
2787
|
-
|
|
2855
|
+
logger3.debug("Using LLM for intelligent aggregation", {
|
|
2788
2856
|
taskCount: state.completedTasks.length
|
|
2789
2857
|
});
|
|
2790
2858
|
const messages = createPromptMessages(systemPrompt, createAggregationPrompt(state));
|
|
2791
|
-
|
|
2859
|
+
logger3.debug("Invoking aggregation LLM");
|
|
2792
2860
|
const response = await model.invoke(messages);
|
|
2793
2861
|
const aggregatedResponse = serializeModelContent(response.content);
|
|
2794
|
-
|
|
2862
|
+
logger3.info("Aggregation complete", {
|
|
2795
2863
|
responseLength: aggregatedResponse.length
|
|
2796
2864
|
});
|
|
2797
|
-
|
|
2865
|
+
logger3.debug("Aggregation response details", {
|
|
2798
2866
|
responseLength: aggregatedResponse.length
|
|
2799
2867
|
});
|
|
2800
|
-
|
|
2868
|
+
logger3.debug("Aggregation complete");
|
|
2801
2869
|
return {
|
|
2802
2870
|
response: aggregatedResponse,
|
|
2803
2871
|
status: "completed"
|
|
2804
2872
|
};
|
|
2805
2873
|
} catch (error) {
|
|
2806
2874
|
const errorMessage = handleNodeError(error, "aggregator", false);
|
|
2807
|
-
|
|
2875
|
+
logger3.error("Aggregator node error", {
|
|
2808
2876
|
error: errorMessage,
|
|
2809
2877
|
...error instanceof Error && error.stack ? { stack: error.stack } : {},
|
|
2810
2878
|
completedTasks: state.completedTasks.length
|
|
@@ -2828,7 +2896,7 @@ function incrementAssignedWorkerLoads(state, assignments) {
|
|
|
2828
2896
|
for (const assignment of assignments) {
|
|
2829
2897
|
const worker = updatedWorkers[assignment.workerId];
|
|
2830
2898
|
if (!worker) {
|
|
2831
|
-
|
|
2899
|
+
logger3.error("Worker not found in state", {
|
|
2832
2900
|
workerId: assignment.workerId,
|
|
2833
2901
|
availableWorkers: Object.keys(updatedWorkers)
|
|
2834
2902
|
});
|
|
@@ -2847,45 +2915,45 @@ function createSupervisorNode(config) {
|
|
|
2847
2915
|
const { strategy, maxIterations = 10 } = config;
|
|
2848
2916
|
return async (state) => {
|
|
2849
2917
|
try {
|
|
2850
|
-
|
|
2918
|
+
logger3.info("Supervisor node executing", {
|
|
2851
2919
|
iteration: state.iteration,
|
|
2852
2920
|
maxIterations,
|
|
2853
2921
|
activeAssignments: state.activeAssignments.length,
|
|
2854
2922
|
completedTasks: state.completedTasks.length
|
|
2855
2923
|
});
|
|
2856
|
-
|
|
2924
|
+
logger3.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
|
|
2857
2925
|
if (state.iteration >= maxIterations) {
|
|
2858
|
-
|
|
2926
|
+
logger3.warn("Max iterations reached", {
|
|
2859
2927
|
iteration: state.iteration,
|
|
2860
2928
|
maxIterations
|
|
2861
2929
|
});
|
|
2862
|
-
|
|
2930
|
+
logger3.debug("Max iterations reached, moving to aggregation");
|
|
2863
2931
|
return {
|
|
2864
2932
|
status: "aggregating",
|
|
2865
2933
|
currentAgent: "aggregator"
|
|
2866
2934
|
};
|
|
2867
2935
|
}
|
|
2868
2936
|
const allCompleted = allAssignmentsCompleted(state);
|
|
2869
|
-
|
|
2937
|
+
logger3.debug("Checking task completion", {
|
|
2870
2938
|
activeAssignments: state.activeAssignments.length,
|
|
2871
2939
|
completedTasks: state.completedTasks.length,
|
|
2872
2940
|
allCompleted
|
|
2873
2941
|
});
|
|
2874
2942
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2875
|
-
|
|
2943
|
+
logger3.info("All tasks completed, moving to aggregation", {
|
|
2876
2944
|
completedCount: state.completedTasks.length
|
|
2877
2945
|
});
|
|
2878
|
-
|
|
2946
|
+
logger3.debug("All tasks completed, moving to aggregation");
|
|
2879
2947
|
return {
|
|
2880
2948
|
status: "aggregating",
|
|
2881
2949
|
currentAgent: "aggregator"
|
|
2882
2950
|
};
|
|
2883
2951
|
}
|
|
2884
|
-
|
|
2952
|
+
logger3.debug("Getting routing strategy", { strategy });
|
|
2885
2953
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2886
2954
|
const decision = await routingImpl.route(state, config);
|
|
2887
2955
|
const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
|
|
2888
|
-
|
|
2956
|
+
logger3.debug("Target agents determined", {
|
|
2889
2957
|
targetAgents,
|
|
2890
2958
|
isParallel: targetAgents.length > 1,
|
|
2891
2959
|
decision: {
|
|
@@ -2894,30 +2962,30 @@ function createSupervisorNode(config) {
|
|
|
2894
2962
|
}
|
|
2895
2963
|
});
|
|
2896
2964
|
if (targetAgents.length === 0) {
|
|
2897
|
-
|
|
2965
|
+
logger3.error("No target agents specified in routing decision");
|
|
2898
2966
|
throw new Error("Routing decision must specify at least one target agent");
|
|
2899
2967
|
}
|
|
2900
2968
|
if (targetAgents.length === 1) {
|
|
2901
|
-
|
|
2969
|
+
logger3.info("Routing to single agent", {
|
|
2902
2970
|
targetAgent: targetAgents[0],
|
|
2903
2971
|
reasoning: decision.reasoning,
|
|
2904
2972
|
confidence: decision.confidence
|
|
2905
2973
|
});
|
|
2906
|
-
|
|
2974
|
+
logger3.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2907
2975
|
} else {
|
|
2908
|
-
|
|
2976
|
+
logger3.info("Routing to multiple agents in parallel", {
|
|
2909
2977
|
targetAgents,
|
|
2910
2978
|
count: targetAgents.length,
|
|
2911
2979
|
reasoning: decision.reasoning,
|
|
2912
2980
|
confidence: decision.confidence
|
|
2913
2981
|
});
|
|
2914
|
-
|
|
2982
|
+
logger3.debug(
|
|
2915
2983
|
`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`
|
|
2916
2984
|
);
|
|
2917
2985
|
}
|
|
2918
2986
|
const task = getLatestTaskContent(state);
|
|
2919
2987
|
const assignments = createTaskAssignments(targetAgents, task);
|
|
2920
|
-
|
|
2988
|
+
logger3.debug("Created task assignments", {
|
|
2921
2989
|
assignmentCount: assignments.length,
|
|
2922
2990
|
assignments: assignments.map((assignment) => ({
|
|
2923
2991
|
id: assignment.id,
|
|
@@ -2927,7 +2995,7 @@ function createSupervisorNode(config) {
|
|
|
2927
2995
|
});
|
|
2928
2996
|
const messages = createAssignmentMessages(assignments);
|
|
2929
2997
|
const updatedWorkers = incrementAssignedWorkerLoads(state, assignments);
|
|
2930
|
-
|
|
2998
|
+
logger3.info("Supervisor routing complete", {
|
|
2931
2999
|
currentAgent: targetAgents.join(","),
|
|
2932
3000
|
status: "executing",
|
|
2933
3001
|
assignmentCount: assignments.length,
|
|
@@ -2945,7 +3013,7 @@ function createSupervisorNode(config) {
|
|
|
2945
3013
|
};
|
|
2946
3014
|
} catch (error) {
|
|
2947
3015
|
const errorMessage = handleNodeError(error, "supervisor", false);
|
|
2948
|
-
|
|
3016
|
+
logger3.error("Supervisor node error", {
|
|
2949
3017
|
error: errorMessage,
|
|
2950
3018
|
...error instanceof Error && error.stack ? { stack: error.stack } : {},
|
|
2951
3019
|
iteration: state.iteration
|
|
@@ -2973,7 +3041,7 @@ async function invokeWorkerModel(model, config, assignment) {
|
|
|
2973
3041
|
);
|
|
2974
3042
|
let modelToUse = model;
|
|
2975
3043
|
if (config.tools && config.tools.length > 0 && model.bindTools) {
|
|
2976
|
-
|
|
3044
|
+
logger3.debug("Binding tools to model", {
|
|
2977
3045
|
workerId: config.id,
|
|
2978
3046
|
toolCount: config.tools.length,
|
|
2979
3047
|
toolNames: config.tools.map((tool) => tool.metadata.name)
|
|
@@ -2982,15 +3050,15 @@ async function invokeWorkerModel(model, config, assignment) {
|
|
|
2982
3050
|
convertWorkerToolsForLangChain(config.tools)
|
|
2983
3051
|
);
|
|
2984
3052
|
}
|
|
2985
|
-
|
|
3053
|
+
logger3.debug("Invoking LLM", { workerId: config.id });
|
|
2986
3054
|
const response = await modelToUse.invoke(messages);
|
|
2987
3055
|
const result = serializeModelContent(response.content);
|
|
2988
|
-
|
|
3056
|
+
logger3.info("Worker task completed", {
|
|
2989
3057
|
workerId: config.id,
|
|
2990
3058
|
assignmentId: assignment.id,
|
|
2991
3059
|
resultLength: result.length
|
|
2992
3060
|
});
|
|
2993
|
-
|
|
3061
|
+
logger3.debug("Worker result details", {
|
|
2994
3062
|
workerId: config.id,
|
|
2995
3063
|
assignmentId: assignment.id,
|
|
2996
3064
|
resultLength: result.length
|
|
@@ -3025,7 +3093,7 @@ async function invokeWorkerModel(model, config, assignment) {
|
|
|
3025
3093
|
function getStateWorkerOrThrow(state, workerId) {
|
|
3026
3094
|
const currentWorker = state.workers[workerId];
|
|
3027
3095
|
if (!currentWorker) {
|
|
3028
|
-
|
|
3096
|
+
logger3.error("Attempted to decrement workload for unknown worker", {
|
|
3029
3097
|
workerId,
|
|
3030
3098
|
availableWorkers: Object.keys(state.workers)
|
|
3031
3099
|
});
|
|
@@ -3041,7 +3109,7 @@ function resolvePreviousWorkload(workerId, currentWorker, workerFromExecution) {
|
|
|
3041
3109
|
if (typeof currentWorker.currentWorkload === "number" && Number.isFinite(currentWorker.currentWorkload)) {
|
|
3042
3110
|
return currentWorker.currentWorkload;
|
|
3043
3111
|
}
|
|
3044
|
-
|
|
3112
|
+
logger3.error("Worker workload is not a valid number; cannot decrement", {
|
|
3045
3113
|
workerId,
|
|
3046
3114
|
workloadFromState: currentWorker.currentWorkload,
|
|
3047
3115
|
...typeof workloadFromWorker === "number" ? { workloadFromWorker } : {}
|
|
@@ -3076,7 +3144,7 @@ function mergeWorkersWithDecrement(state, workerId, executionResult) {
|
|
|
3076
3144
|
...baseWorkers,
|
|
3077
3145
|
[workerId]: updatedWorker
|
|
3078
3146
|
};
|
|
3079
|
-
|
|
3147
|
+
logger3.debug("Worker workload decremented", {
|
|
3080
3148
|
workerId,
|
|
3081
3149
|
previousWorkload,
|
|
3082
3150
|
newWorkload: updatedWorkers[workerId].currentWorkload,
|
|
@@ -3094,7 +3162,7 @@ function decrementWorkerOnError(state, workerId) {
|
|
|
3094
3162
|
currentWorkload: Math.max(0, previousWorkload - 1)
|
|
3095
3163
|
}
|
|
3096
3164
|
};
|
|
3097
|
-
|
|
3165
|
+
logger3.debug("Worker workload decremented (error path)", {
|
|
3098
3166
|
workerId,
|
|
3099
3167
|
previousWorkload,
|
|
3100
3168
|
newWorkload: updatedWorkers[workerId].currentWorkload
|
|
@@ -3102,7 +3170,7 @@ function decrementWorkerOnError(state, workerId) {
|
|
|
3102
3170
|
return updatedWorkers;
|
|
3103
3171
|
}
|
|
3104
3172
|
function createErrorTaskResult(assignment, workerId, errorMessage) {
|
|
3105
|
-
|
|
3173
|
+
logger3.warn("Creating error result for assignment", {
|
|
3106
3174
|
workerId,
|
|
3107
3175
|
assignmentId: assignment.id
|
|
3108
3176
|
});
|
|
@@ -3119,47 +3187,47 @@ function createWorkerNode(config) {
|
|
|
3119
3187
|
const { id, model, executeFn, agent } = config;
|
|
3120
3188
|
return async (state, runConfig) => {
|
|
3121
3189
|
try {
|
|
3122
|
-
|
|
3190
|
+
logger3.info("Worker node executing", {
|
|
3123
3191
|
workerId: id,
|
|
3124
3192
|
iteration: state.iteration,
|
|
3125
3193
|
activeAssignments: state.activeAssignments.length
|
|
3126
3194
|
});
|
|
3127
3195
|
const currentAssignment = findCurrentAssignment(state, id);
|
|
3128
3196
|
if (!currentAssignment) {
|
|
3129
|
-
|
|
3197
|
+
logger3.debug("No active assignment found for worker", {
|
|
3130
3198
|
workerId: id,
|
|
3131
3199
|
totalActiveAssignments: state.activeAssignments.length,
|
|
3132
3200
|
completedTasks: state.completedTasks.length
|
|
3133
3201
|
});
|
|
3134
3202
|
return {};
|
|
3135
3203
|
}
|
|
3136
|
-
|
|
3204
|
+
logger3.info("Worker processing assignment", {
|
|
3137
3205
|
workerId: id,
|
|
3138
3206
|
assignmentId: currentAssignment.id,
|
|
3139
3207
|
taskLength: currentAssignment.task.length
|
|
3140
3208
|
});
|
|
3141
|
-
|
|
3209
|
+
logger3.debug("Worker assignment details", {
|
|
3142
3210
|
workerId: id,
|
|
3143
3211
|
assignmentId: currentAssignment.id,
|
|
3144
3212
|
taskLength: currentAssignment.task.length
|
|
3145
3213
|
});
|
|
3146
3214
|
let executionResult;
|
|
3147
3215
|
if (executeFn) {
|
|
3148
|
-
|
|
3216
|
+
logger3.debug("Using custom execution function", { workerId: id });
|
|
3149
3217
|
executionResult = await executeFn(state, runConfig);
|
|
3150
3218
|
} else if (agent && isReActAgent(agent)) {
|
|
3151
|
-
|
|
3219
|
+
logger3.debug("Using ReAct agent", { workerId: id });
|
|
3152
3220
|
const wrappedFn = wrapReActAgent(id, agent, config.verbose ?? false);
|
|
3153
3221
|
executionResult = await wrappedFn(state, runConfig);
|
|
3154
3222
|
} else if (model) {
|
|
3155
|
-
|
|
3223
|
+
logger3.debug("Using default LLM execution", {
|
|
3156
3224
|
workerId: id,
|
|
3157
3225
|
hasTools: (config.tools ?? []).length > 0,
|
|
3158
3226
|
toolCount: (config.tools ?? []).length
|
|
3159
3227
|
});
|
|
3160
3228
|
executionResult = await invokeWorkerModel(model, config, currentAssignment);
|
|
3161
3229
|
} else {
|
|
3162
|
-
|
|
3230
|
+
logger3.error("Worker missing required configuration", { workerId: id });
|
|
3163
3231
|
throw new Error(
|
|
3164
3232
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
3165
3233
|
);
|
|
@@ -3171,7 +3239,7 @@ function createWorkerNode(config) {
|
|
|
3171
3239
|
};
|
|
3172
3240
|
} catch (error) {
|
|
3173
3241
|
const errorMessage = handleNodeError(error, `worker:${id}`, false);
|
|
3174
|
-
|
|
3242
|
+
logger3.error("Worker node error", {
|
|
3175
3243
|
workerId: id,
|
|
3176
3244
|
error: errorMessage
|
|
3177
3245
|
});
|
|
@@ -3180,7 +3248,7 @@ function createWorkerNode(config) {
|
|
|
3180
3248
|
updatedWorkers = decrementWorkerOnError(state, id);
|
|
3181
3249
|
} catch (workloadError) {
|
|
3182
3250
|
const workloadErrorMessage = workloadError instanceof Error ? workloadError.message : String(workloadError);
|
|
3183
|
-
|
|
3251
|
+
logger3.error("Worker error handling failed", {
|
|
3184
3252
|
workerId: id,
|
|
3185
3253
|
error: workloadErrorMessage
|
|
3186
3254
|
});
|
|
@@ -3200,7 +3268,7 @@ function createWorkerNode(config) {
|
|
|
3200
3268
|
workers: updatedWorkers
|
|
3201
3269
|
};
|
|
3202
3270
|
}
|
|
3203
|
-
|
|
3271
|
+
logger3.error("No assignment found for error handling", { workerId: id });
|
|
3204
3272
|
return {
|
|
3205
3273
|
status: "failed",
|
|
3206
3274
|
error: errorMessage,
|
|
@@ -3212,7 +3280,7 @@ function createWorkerNode(config) {
|
|
|
3212
3280
|
|
|
3213
3281
|
// src/multi-agent/agent.ts
|
|
3214
3282
|
import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
|
|
3215
|
-
var
|
|
3283
|
+
var logger4 = createPatternLogger("agentforge:patterns:multi-agent:system");
|
|
3216
3284
|
function getToolName(tool) {
|
|
3217
3285
|
if (!tool || typeof tool !== "object") {
|
|
3218
3286
|
return "unknown";
|
|
@@ -3237,13 +3305,6 @@ function createMultiAgentSystem(config) {
|
|
|
3237
3305
|
} = config;
|
|
3238
3306
|
const workflow = new StateGraph4(MultiAgentState);
|
|
3239
3307
|
const supervisorConfig = { ...supervisor, maxIterations, verbose };
|
|
3240
|
-
if (supervisor.model) {
|
|
3241
|
-
let configuredModel = supervisor.model;
|
|
3242
|
-
if (supervisor.strategy === "llm-based") {
|
|
3243
|
-
configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
|
|
3244
|
-
}
|
|
3245
|
-
supervisorConfig.model = configuredModel;
|
|
3246
|
-
}
|
|
3247
3308
|
const supervisorNode = createSupervisorNode(supervisorConfig);
|
|
3248
3309
|
workflow.addNode("supervisor", supervisorNode);
|
|
3249
3310
|
const workerIds = [];
|
|
@@ -3263,46 +3324,46 @@ function createMultiAgentSystem(config) {
|
|
|
3263
3324
|
});
|
|
3264
3325
|
workflow.addNode("aggregator", aggregatorNode);
|
|
3265
3326
|
const supervisorRouter = (state) => {
|
|
3266
|
-
|
|
3327
|
+
logger4.debug("Supervisor router executing", {
|
|
3267
3328
|
status: state.status,
|
|
3268
3329
|
...state.currentAgent ? { currentAgent: state.currentAgent } : {},
|
|
3269
3330
|
iteration: state.iteration
|
|
3270
3331
|
});
|
|
3271
3332
|
if (state.status === "completed" || state.status === "failed") {
|
|
3272
|
-
|
|
3333
|
+
logger4.info("Supervisor router: ending workflow", { status: state.status });
|
|
3273
3334
|
return END4;
|
|
3274
3335
|
}
|
|
3275
3336
|
if (state.status === "aggregating") {
|
|
3276
|
-
|
|
3337
|
+
logger4.info("Supervisor router: routing to aggregator");
|
|
3277
3338
|
return "aggregator";
|
|
3278
3339
|
}
|
|
3279
3340
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
3280
3341
|
if (state.currentAgent.includes(",")) {
|
|
3281
3342
|
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
3282
|
-
|
|
3343
|
+
logger4.info("Supervisor router: parallel routing", {
|
|
3283
3344
|
agents,
|
|
3284
3345
|
count: agents.length
|
|
3285
3346
|
});
|
|
3286
3347
|
return agents;
|
|
3287
3348
|
}
|
|
3288
|
-
|
|
3349
|
+
logger4.info("Supervisor router: single agent routing", {
|
|
3289
3350
|
targetAgent: state.currentAgent
|
|
3290
3351
|
});
|
|
3291
3352
|
return state.currentAgent;
|
|
3292
3353
|
}
|
|
3293
|
-
|
|
3354
|
+
logger4.debug("Supervisor router: staying at supervisor");
|
|
3294
3355
|
return "supervisor";
|
|
3295
3356
|
};
|
|
3296
3357
|
const workerRouter = (state) => {
|
|
3297
|
-
|
|
3358
|
+
logger4.debug("Worker router executing", {
|
|
3298
3359
|
iteration: state.iteration,
|
|
3299
3360
|
completedTasks: state.completedTasks.length
|
|
3300
3361
|
});
|
|
3301
|
-
|
|
3362
|
+
logger4.debug("Worker router: returning to supervisor");
|
|
3302
3363
|
return "supervisor";
|
|
3303
3364
|
};
|
|
3304
3365
|
const aggregatorRouter = (state) => {
|
|
3305
|
-
|
|
3366
|
+
logger4.info("Aggregator router: ending workflow", {
|
|
3306
3367
|
completedTasks: state.completedTasks.length,
|
|
3307
3368
|
status: state.status
|
|
3308
3369
|
});
|