@agentforge/patterns 0.16.32 → 0.16.33
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 +158 -122
- package/dist/index.d.cts +16 -24
- package/dist/index.d.ts +16 -24
- package/dist/index.js +158 -122
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -2391,9 +2391,41 @@ var MultiAgentStateConfig = {
|
|
|
2391
2391
|
};
|
|
2392
2392
|
var MultiAgentState = (0, import_core7.createStateAnnotation)(MultiAgentStateConfig);
|
|
2393
2393
|
|
|
2394
|
-
// src/multi-agent/routing.ts
|
|
2394
|
+
// src/multi-agent/routing-internal/llm-routing.ts
|
|
2395
2395
|
var import_messages5 = require("@langchain/core/messages");
|
|
2396
2396
|
var logger = createPatternLogger("agentforge:patterns:multi-agent:routing");
|
|
2397
|
+
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2398
|
+
|
|
2399
|
+
Your job is to:
|
|
2400
|
+
1. Analyze the current task and context
|
|
2401
|
+
2. Review available worker capabilities
|
|
2402
|
+
3. Select the most appropriate worker(s) for the task
|
|
2403
|
+
4. Provide clear reasoning for your decision
|
|
2404
|
+
|
|
2405
|
+
**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
|
|
2406
|
+
- The task requires information from multiple domains (e.g., code + documentation)
|
|
2407
|
+
- Multiple workers have complementary expertise
|
|
2408
|
+
- Parallel execution would provide a more comprehensive answer
|
|
2409
|
+
|
|
2410
|
+
**Response Format:**
|
|
2411
|
+
|
|
2412
|
+
For SINGLE worker routing:
|
|
2413
|
+
{
|
|
2414
|
+
"targetAgent": "worker_id",
|
|
2415
|
+
"reasoning": "explanation of why this worker is best suited",
|
|
2416
|
+
"confidence": 0.0-1.0,
|
|
2417
|
+
"strategy": "llm-based"
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
For PARALLEL multi-worker routing:
|
|
2421
|
+
{
|
|
2422
|
+
"targetAgents": ["worker_id_1", "worker_id_2", ...],
|
|
2423
|
+
"reasoning": "explanation of why these workers should work in parallel",
|
|
2424
|
+
"confidence": 0.0-1.0,
|
|
2425
|
+
"strategy": "llm-based"
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
|
|
2397
2429
|
function hasStructuredOutput(model) {
|
|
2398
2430
|
return typeof model.withStructuredOutput === "function";
|
|
2399
2431
|
}
|
|
@@ -2449,58 +2481,29 @@ function finalizeLlmRoutingDecision(decision) {
|
|
|
2449
2481
|
timestamp: Date.now()
|
|
2450
2482
|
};
|
|
2451
2483
|
}
|
|
2484
|
+
async function invokeStructuredRoutingDecision(model, messages) {
|
|
2485
|
+
let structuredModel;
|
|
2486
|
+
try {
|
|
2487
|
+
structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
logger.warn("Structured output unavailable, using direct routing fallback", {
|
|
2490
|
+
strategy: "llm-based",
|
|
2491
|
+
fallback: "direct-model-invoke",
|
|
2492
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2493
|
+
});
|
|
2494
|
+
const decision2 = await model.invoke(messages);
|
|
2495
|
+
return finalizeLlmRoutingDecision(decision2);
|
|
2496
|
+
}
|
|
2497
|
+
const decision = await structuredModel.invoke(messages);
|
|
2498
|
+
return finalizeLlmRoutingDecision(decision);
|
|
2499
|
+
}
|
|
2452
2500
|
async function invokeRoutingDecision(model, messages) {
|
|
2453
2501
|
if (hasStructuredOutput(model)) {
|
|
2454
|
-
|
|
2455
|
-
try {
|
|
2456
|
-
structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
|
|
2457
|
-
} catch (error) {
|
|
2458
|
-
logger.warn("Structured output unavailable, using direct routing fallback", {
|
|
2459
|
-
strategy: "llm-based",
|
|
2460
|
-
fallback: "direct-model-invoke",
|
|
2461
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2462
|
-
});
|
|
2463
|
-
const decision3 = await model.invoke(messages);
|
|
2464
|
-
return finalizeLlmRoutingDecision(decision3);
|
|
2465
|
-
}
|
|
2466
|
-
const decision2 = await structuredModel.invoke(messages);
|
|
2467
|
-
return finalizeLlmRoutingDecision(decision2);
|
|
2502
|
+
return invokeStructuredRoutingDecision(model, messages);
|
|
2468
2503
|
}
|
|
2469
2504
|
const decision = await model.invoke(messages);
|
|
2470
2505
|
return finalizeLlmRoutingDecision(decision);
|
|
2471
2506
|
}
|
|
2472
|
-
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2473
|
-
|
|
2474
|
-
Your job is to:
|
|
2475
|
-
1. Analyze the current task and context
|
|
2476
|
-
2. Review available worker capabilities
|
|
2477
|
-
3. Select the most appropriate worker(s) for the task
|
|
2478
|
-
4. Provide clear reasoning for your decision
|
|
2479
|
-
|
|
2480
|
-
**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
|
|
2481
|
-
- The task requires information from multiple domains (e.g., code + documentation)
|
|
2482
|
-
- Multiple workers have complementary expertise
|
|
2483
|
-
- Parallel execution would provide a more comprehensive answer
|
|
2484
|
-
|
|
2485
|
-
**Response Format:**
|
|
2486
|
-
|
|
2487
|
-
For SINGLE worker routing:
|
|
2488
|
-
{
|
|
2489
|
-
"targetAgent": "worker_id",
|
|
2490
|
-
"reasoning": "explanation of why this worker is best suited",
|
|
2491
|
-
"confidence": 0.0-1.0,
|
|
2492
|
-
"strategy": "llm-based"
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
For PARALLEL multi-worker routing:
|
|
2496
|
-
{
|
|
2497
|
-
"targetAgents": ["worker_id_1", "worker_id_2", ...],
|
|
2498
|
-
"reasoning": "explanation of why these workers should work in parallel",
|
|
2499
|
-
"confidence": 0.0-1.0,
|
|
2500
|
-
"strategy": "llm-based"
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
|
|
2504
2507
|
var llmBasedRouting = {
|
|
2505
2508
|
name: "llm-based",
|
|
2506
2509
|
async route(state, config) {
|
|
@@ -2526,56 +2529,119 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
2526
2529
|
new import_messages5.SystemMessage(systemPrompt),
|
|
2527
2530
|
new import_messages5.HumanMessage(userPrompt)
|
|
2528
2531
|
];
|
|
2529
|
-
return
|
|
2532
|
+
return invokeRoutingDecision(config.model, messages);
|
|
2530
2533
|
}
|
|
2531
2534
|
};
|
|
2535
|
+
|
|
2536
|
+
// src/multi-agent/routing-internal/load-balanced-routing.ts
|
|
2537
|
+
function getAvailableWorkerLoads(state) {
|
|
2538
|
+
return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
|
|
2539
|
+
}
|
|
2540
|
+
var loadBalancedRouting = {
|
|
2541
|
+
name: "load-balanced",
|
|
2542
|
+
async route(state, _config) {
|
|
2543
|
+
const availableWorkers = getAvailableWorkerLoads(state);
|
|
2544
|
+
if (availableWorkers.length === 0) {
|
|
2545
|
+
throw new Error("No available workers for load-balanced routing");
|
|
2546
|
+
}
|
|
2547
|
+
const targetWorker = availableWorkers[0];
|
|
2548
|
+
const avgWorkload = availableWorkers.reduce((sum, worker) => sum + worker.workload, 0) / availableWorkers.length;
|
|
2549
|
+
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2550
|
+
return {
|
|
2551
|
+
targetAgent: targetWorker.id,
|
|
2552
|
+
targetAgents: null,
|
|
2553
|
+
reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
|
|
2554
|
+
confidence,
|
|
2555
|
+
strategy: "load-balanced",
|
|
2556
|
+
timestamp: Date.now()
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
|
|
2561
|
+
// src/multi-agent/routing-internal/worker-selection.ts
|
|
2562
|
+
function getAvailableWorkerIds(state) {
|
|
2563
|
+
return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
|
|
2564
|
+
}
|
|
2565
|
+
function getTaskContent(state) {
|
|
2566
|
+
const lastMessage = state.messages[state.messages.length - 1];
|
|
2567
|
+
return String(lastMessage?.content || state.input);
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
// src/multi-agent/routing-internal/round-robin-routing.ts
|
|
2571
|
+
function createRoundRobinDecision(targetAgent, position, workerCount) {
|
|
2572
|
+
return {
|
|
2573
|
+
targetAgent,
|
|
2574
|
+
targetAgents: null,
|
|
2575
|
+
reasoning: `Round-robin selection: worker ${position} of ${workerCount}`,
|
|
2576
|
+
confidence: 1,
|
|
2577
|
+
strategy: "round-robin",
|
|
2578
|
+
timestamp: Date.now()
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2532
2581
|
var roundRobinRouting = {
|
|
2533
2582
|
name: "round-robin",
|
|
2534
2583
|
async route(state, _config) {
|
|
2535
|
-
const availableWorkers =
|
|
2584
|
+
const availableWorkers = getAvailableWorkerIds(state);
|
|
2536
2585
|
if (availableWorkers.length === 0) {
|
|
2537
2586
|
throw new Error("No available workers for round-robin routing");
|
|
2538
2587
|
}
|
|
2539
2588
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
2540
|
-
|
|
2589
|
+
return createRoundRobinDecision(
|
|
2590
|
+
availableWorkers[lastRoutingIndex],
|
|
2591
|
+
lastRoutingIndex + 1,
|
|
2592
|
+
availableWorkers.length
|
|
2593
|
+
);
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
|
|
2597
|
+
// src/multi-agent/routing-internal/rule-based-routing.ts
|
|
2598
|
+
var ruleBasedRouting = {
|
|
2599
|
+
name: "rule-based",
|
|
2600
|
+
async route(state, config) {
|
|
2601
|
+
if (!config.routingFn) {
|
|
2602
|
+
throw new Error("Rule-based routing requires a custom routing function");
|
|
2603
|
+
}
|
|
2604
|
+
return config.routingFn(state);
|
|
2605
|
+
}
|
|
2606
|
+
};
|
|
2607
|
+
|
|
2608
|
+
// src/multi-agent/routing-internal/skill-based-routing.ts
|
|
2609
|
+
function scoreWorkers(state, taskContent) {
|
|
2610
|
+
return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2611
|
+
const skillMatches = caps.skills.filter(
|
|
2612
|
+
(skill) => taskContent.includes(skill.toLowerCase())
|
|
2613
|
+
).length;
|
|
2614
|
+
const toolMatches = caps.tools.filter(
|
|
2615
|
+
(tool) => taskContent.includes(tool.toLowerCase())
|
|
2616
|
+
).length;
|
|
2541
2617
|
return {
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
confidence: 1,
|
|
2546
|
-
strategy: "round-robin",
|
|
2547
|
-
timestamp: Date.now()
|
|
2618
|
+
id,
|
|
2619
|
+
score: skillMatches * 2 + toolMatches,
|
|
2620
|
+
skills: caps.skills
|
|
2548
2621
|
};
|
|
2622
|
+
}).filter((worker) => worker.score > 0).sort((a, b) => b.score - a.score);
|
|
2623
|
+
}
|
|
2624
|
+
function createFallbackDecision(state) {
|
|
2625
|
+
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
2626
|
+
if (!firstAvailable) {
|
|
2627
|
+
throw new Error("No available workers for skill-based routing");
|
|
2549
2628
|
}
|
|
2550
|
-
|
|
2629
|
+
return {
|
|
2630
|
+
targetAgent: firstAvailable[0],
|
|
2631
|
+
targetAgents: null,
|
|
2632
|
+
reasoning: "No skill matches found, using first available worker",
|
|
2633
|
+
confidence: 0.5,
|
|
2634
|
+
strategy: "skill-based",
|
|
2635
|
+
timestamp: Date.now()
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2551
2638
|
var skillBasedRouting = {
|
|
2552
2639
|
name: "skill-based",
|
|
2553
2640
|
async route(state, _config) {
|
|
2554
|
-
const
|
|
2555
|
-
const
|
|
2556
|
-
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2557
|
-
const skillMatches = caps.skills.filter(
|
|
2558
|
-
(skill) => taskContent.includes(skill.toLowerCase())
|
|
2559
|
-
).length;
|
|
2560
|
-
const toolMatches = caps.tools.filter(
|
|
2561
|
-
(tool) => taskContent.includes(tool.toLowerCase())
|
|
2562
|
-
).length;
|
|
2563
|
-
const score = skillMatches * 2 + toolMatches;
|
|
2564
|
-
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
2565
|
-
}).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
|
|
2641
|
+
const taskContent = getTaskContent(state).toLowerCase();
|
|
2642
|
+
const workerScores = scoreWorkers(state, taskContent);
|
|
2566
2643
|
if (workerScores.length === 0) {
|
|
2567
|
-
|
|
2568
|
-
if (!firstAvailable) {
|
|
2569
|
-
throw new Error("No available workers for skill-based routing");
|
|
2570
|
-
}
|
|
2571
|
-
return {
|
|
2572
|
-
targetAgent: firstAvailable[0],
|
|
2573
|
-
targetAgents: null,
|
|
2574
|
-
reasoning: "No skill matches found, using first available worker",
|
|
2575
|
-
confidence: 0.5,
|
|
2576
|
-
strategy: "skill-based",
|
|
2577
|
-
timestamp: Date.now()
|
|
2578
|
-
};
|
|
2644
|
+
return createFallbackDecision(state);
|
|
2579
2645
|
}
|
|
2580
2646
|
const best = workerScores[0];
|
|
2581
2647
|
const confidence = Math.min(best.score / 5, 1);
|
|
@@ -2589,50 +2655,20 @@ var skillBasedRouting = {
|
|
|
2589
2655
|
};
|
|
2590
2656
|
}
|
|
2591
2657
|
};
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2601
|
-
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2602
|
-
return {
|
|
2603
|
-
targetAgent: targetWorker.id,
|
|
2604
|
-
targetAgents: null,
|
|
2605
|
-
reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
|
|
2606
|
-
confidence,
|
|
2607
|
-
strategy: "load-balanced",
|
|
2608
|
-
timestamp: Date.now()
|
|
2609
|
-
};
|
|
2610
|
-
}
|
|
2611
|
-
};
|
|
2612
|
-
var ruleBasedRouting = {
|
|
2613
|
-
name: "rule-based",
|
|
2614
|
-
async route(state, config) {
|
|
2615
|
-
if (!config.routingFn) {
|
|
2616
|
-
throw new Error("Rule-based routing requires a custom routing function");
|
|
2617
|
-
}
|
|
2618
|
-
return await config.routingFn(state);
|
|
2619
|
-
}
|
|
2658
|
+
|
|
2659
|
+
// src/multi-agent/routing.ts
|
|
2660
|
+
var routingStrategies = {
|
|
2661
|
+
"llm-based": llmBasedRouting,
|
|
2662
|
+
"round-robin": roundRobinRouting,
|
|
2663
|
+
"skill-based": skillBasedRouting,
|
|
2664
|
+
"load-balanced": loadBalancedRouting,
|
|
2665
|
+
"rule-based": ruleBasedRouting
|
|
2620
2666
|
};
|
|
2621
2667
|
function getRoutingStrategy(name) {
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
return llmBasedRouting;
|
|
2625
|
-
case "round-robin":
|
|
2626
|
-
return roundRobinRouting;
|
|
2627
|
-
case "skill-based":
|
|
2628
|
-
return skillBasedRouting;
|
|
2629
|
-
case "load-balanced":
|
|
2630
|
-
return loadBalancedRouting;
|
|
2631
|
-
case "rule-based":
|
|
2632
|
-
return ruleBasedRouting;
|
|
2633
|
-
default:
|
|
2634
|
-
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2668
|
+
if (!Object.hasOwn(routingStrategies, name)) {
|
|
2669
|
+
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2635
2670
|
}
|
|
2671
|
+
return routingStrategies[name];
|
|
2636
2672
|
}
|
|
2637
2673
|
|
|
2638
2674
|
// src/multi-agent/utils.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -3387,37 +3387,29 @@ interface RoutingStrategyImpl {
|
|
|
3387
3387
|
route: (state: MultiAgentStateType, config: SupervisorConfig) => Promise<RoutingDecision>;
|
|
3388
3388
|
}
|
|
3389
3389
|
|
|
3390
|
-
/**
|
|
3391
|
-
* Default system prompt for LLM-based routing
|
|
3392
|
-
*/
|
|
3393
3390
|
declare const DEFAULT_SUPERVISOR_SYSTEM_PROMPT = "You are a supervisor agent responsible for routing tasks to specialized worker agents.\n\nYour job is to:\n1. Analyze the current task and context\n2. Review available worker capabilities\n3. Select the most appropriate worker(s) for the task\n4. Provide clear reasoning for your decision\n\n**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**\n- The task requires information from multiple domains (e.g., code + documentation)\n- Multiple workers have complementary expertise\n- Parallel execution would provide a more comprehensive answer\n\n**Response Format:**\n\nFor SINGLE worker routing:\n{\n \"targetAgent\": \"worker_id\",\n \"reasoning\": \"explanation of why this worker is best suited\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nFor PARALLEL multi-worker routing:\n{\n \"targetAgents\": [\"worker_id_1\", \"worker_id_2\", ...],\n \"reasoning\": \"explanation of why these workers should work in parallel\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nChoose parallel routing when the task benefits from multiple perspectives or data sources.";
|
|
3394
|
-
/**
|
|
3395
|
-
* LLM-based routing strategy
|
|
3396
|
-
* Uses an LLM to intelligently route tasks based on worker capabilities
|
|
3397
|
-
*/
|
|
3398
3391
|
declare const llmBasedRouting: RoutingStrategyImpl;
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
*/
|
|
3392
|
+
|
|
3393
|
+
declare const loadBalancedRouting: RoutingStrategyImpl;
|
|
3394
|
+
|
|
3403
3395
|
declare const roundRobinRouting: RoutingStrategyImpl;
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
*/
|
|
3396
|
+
|
|
3397
|
+
declare const ruleBasedRouting: RoutingStrategyImpl;
|
|
3398
|
+
|
|
3408
3399
|
declare const skillBasedRouting: RoutingStrategyImpl;
|
|
3400
|
+
|
|
3409
3401
|
/**
|
|
3410
|
-
*
|
|
3411
|
-
*
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
*
|
|
3416
|
-
*
|
|
3402
|
+
* Routing Strategy Implementations for Multi-Agent Pattern
|
|
3403
|
+
*
|
|
3404
|
+
* This module is the thin public facade for multi-agent routing strategies.
|
|
3405
|
+
* Concrete strategy logic lives in focused internal modules so behavior can be
|
|
3406
|
+
* reviewed and extended without one oversized implementation file.
|
|
3407
|
+
*
|
|
3408
|
+
* @module patterns/multi-agent/routing
|
|
3417
3409
|
*/
|
|
3418
|
-
|
|
3410
|
+
|
|
3419
3411
|
/**
|
|
3420
|
-
* Get routing strategy implementation by name
|
|
3412
|
+
* Get routing strategy implementation by name.
|
|
3421
3413
|
*/
|
|
3422
3414
|
declare function getRoutingStrategy(name: string): RoutingStrategyImpl;
|
|
3423
3415
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3387,37 +3387,29 @@ interface RoutingStrategyImpl {
|
|
|
3387
3387
|
route: (state: MultiAgentStateType, config: SupervisorConfig) => Promise<RoutingDecision>;
|
|
3388
3388
|
}
|
|
3389
3389
|
|
|
3390
|
-
/**
|
|
3391
|
-
* Default system prompt for LLM-based routing
|
|
3392
|
-
*/
|
|
3393
3390
|
declare const DEFAULT_SUPERVISOR_SYSTEM_PROMPT = "You are a supervisor agent responsible for routing tasks to specialized worker agents.\n\nYour job is to:\n1. Analyze the current task and context\n2. Review available worker capabilities\n3. Select the most appropriate worker(s) for the task\n4. Provide clear reasoning for your decision\n\n**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**\n- The task requires information from multiple domains (e.g., code + documentation)\n- Multiple workers have complementary expertise\n- Parallel execution would provide a more comprehensive answer\n\n**Response Format:**\n\nFor SINGLE worker routing:\n{\n \"targetAgent\": \"worker_id\",\n \"reasoning\": \"explanation of why this worker is best suited\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nFor PARALLEL multi-worker routing:\n{\n \"targetAgents\": [\"worker_id_1\", \"worker_id_2\", ...],\n \"reasoning\": \"explanation of why these workers should work in parallel\",\n \"confidence\": 0.0-1.0,\n \"strategy\": \"llm-based\"\n}\n\nChoose parallel routing when the task benefits from multiple perspectives or data sources.";
|
|
3394
|
-
/**
|
|
3395
|
-
* LLM-based routing strategy
|
|
3396
|
-
* Uses an LLM to intelligently route tasks based on worker capabilities
|
|
3397
|
-
*/
|
|
3398
3391
|
declare const llmBasedRouting: RoutingStrategyImpl;
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
*/
|
|
3392
|
+
|
|
3393
|
+
declare const loadBalancedRouting: RoutingStrategyImpl;
|
|
3394
|
+
|
|
3403
3395
|
declare const roundRobinRouting: RoutingStrategyImpl;
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
*/
|
|
3396
|
+
|
|
3397
|
+
declare const ruleBasedRouting: RoutingStrategyImpl;
|
|
3398
|
+
|
|
3408
3399
|
declare const skillBasedRouting: RoutingStrategyImpl;
|
|
3400
|
+
|
|
3409
3401
|
/**
|
|
3410
|
-
*
|
|
3411
|
-
*
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
*
|
|
3416
|
-
*
|
|
3402
|
+
* Routing Strategy Implementations for Multi-Agent Pattern
|
|
3403
|
+
*
|
|
3404
|
+
* This module is the thin public facade for multi-agent routing strategies.
|
|
3405
|
+
* Concrete strategy logic lives in focused internal modules so behavior can be
|
|
3406
|
+
* reviewed and extended without one oversized implementation file.
|
|
3407
|
+
*
|
|
3408
|
+
* @module patterns/multi-agent/routing
|
|
3417
3409
|
*/
|
|
3418
|
-
|
|
3410
|
+
|
|
3419
3411
|
/**
|
|
3420
|
-
* Get routing strategy implementation by name
|
|
3412
|
+
* Get routing strategy implementation by name.
|
|
3421
3413
|
*/
|
|
3422
3414
|
declare function getRoutingStrategy(name: string): RoutingStrategyImpl;
|
|
3423
3415
|
|
package/dist/index.js
CHANGED
|
@@ -2293,9 +2293,41 @@ var MultiAgentStateConfig = {
|
|
|
2293
2293
|
};
|
|
2294
2294
|
var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
|
|
2295
2295
|
|
|
2296
|
-
// src/multi-agent/routing.ts
|
|
2296
|
+
// src/multi-agent/routing-internal/llm-routing.ts
|
|
2297
2297
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2298
2298
|
var logger = createPatternLogger("agentforge:patterns:multi-agent:routing");
|
|
2299
|
+
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2300
|
+
|
|
2301
|
+
Your job is to:
|
|
2302
|
+
1. Analyze the current task and context
|
|
2303
|
+
2. Review available worker capabilities
|
|
2304
|
+
3. Select the most appropriate worker(s) for the task
|
|
2305
|
+
4. Provide clear reasoning for your decision
|
|
2306
|
+
|
|
2307
|
+
**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
|
|
2308
|
+
- The task requires information from multiple domains (e.g., code + documentation)
|
|
2309
|
+
- Multiple workers have complementary expertise
|
|
2310
|
+
- Parallel execution would provide a more comprehensive answer
|
|
2311
|
+
|
|
2312
|
+
**Response Format:**
|
|
2313
|
+
|
|
2314
|
+
For SINGLE worker routing:
|
|
2315
|
+
{
|
|
2316
|
+
"targetAgent": "worker_id",
|
|
2317
|
+
"reasoning": "explanation of why this worker is best suited",
|
|
2318
|
+
"confidence": 0.0-1.0,
|
|
2319
|
+
"strategy": "llm-based"
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
For PARALLEL multi-worker routing:
|
|
2323
|
+
{
|
|
2324
|
+
"targetAgents": ["worker_id_1", "worker_id_2", ...],
|
|
2325
|
+
"reasoning": "explanation of why these workers should work in parallel",
|
|
2326
|
+
"confidence": 0.0-1.0,
|
|
2327
|
+
"strategy": "llm-based"
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
|
|
2299
2331
|
function hasStructuredOutput(model) {
|
|
2300
2332
|
return typeof model.withStructuredOutput === "function";
|
|
2301
2333
|
}
|
|
@@ -2351,58 +2383,29 @@ function finalizeLlmRoutingDecision(decision) {
|
|
|
2351
2383
|
timestamp: Date.now()
|
|
2352
2384
|
};
|
|
2353
2385
|
}
|
|
2386
|
+
async function invokeStructuredRoutingDecision(model, messages) {
|
|
2387
|
+
let structuredModel;
|
|
2388
|
+
try {
|
|
2389
|
+
structuredModel = model.withStructuredOutput(RoutingDecisionSchema);
|
|
2390
|
+
} catch (error) {
|
|
2391
|
+
logger.warn("Structured output unavailable, using direct routing fallback", {
|
|
2392
|
+
strategy: "llm-based",
|
|
2393
|
+
fallback: "direct-model-invoke",
|
|
2394
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2395
|
+
});
|
|
2396
|
+
const decision2 = await model.invoke(messages);
|
|
2397
|
+
return finalizeLlmRoutingDecision(decision2);
|
|
2398
|
+
}
|
|
2399
|
+
const decision = await structuredModel.invoke(messages);
|
|
2400
|
+
return finalizeLlmRoutingDecision(decision);
|
|
2401
|
+
}
|
|
2354
2402
|
async function invokeRoutingDecision(model, messages) {
|
|
2355
2403
|
if (hasStructuredOutput(model)) {
|
|
2356
|
-
|
|
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);
|
|
2404
|
+
return invokeStructuredRoutingDecision(model, messages);
|
|
2370
2405
|
}
|
|
2371
2406
|
const decision = await model.invoke(messages);
|
|
2372
2407
|
return finalizeLlmRoutingDecision(decision);
|
|
2373
2408
|
}
|
|
2374
|
-
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2375
|
-
|
|
2376
|
-
Your job is to:
|
|
2377
|
-
1. Analyze the current task and context
|
|
2378
|
-
2. Review available worker capabilities
|
|
2379
|
-
3. Select the most appropriate worker(s) for the task
|
|
2380
|
-
4. Provide clear reasoning for your decision
|
|
2381
|
-
|
|
2382
|
-
**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
|
|
2383
|
-
- The task requires information from multiple domains (e.g., code + documentation)
|
|
2384
|
-
- Multiple workers have complementary expertise
|
|
2385
|
-
- Parallel execution would provide a more comprehensive answer
|
|
2386
|
-
|
|
2387
|
-
**Response Format:**
|
|
2388
|
-
|
|
2389
|
-
For SINGLE worker routing:
|
|
2390
|
-
{
|
|
2391
|
-
"targetAgent": "worker_id",
|
|
2392
|
-
"reasoning": "explanation of why this worker is best suited",
|
|
2393
|
-
"confidence": 0.0-1.0,
|
|
2394
|
-
"strategy": "llm-based"
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
For PARALLEL multi-worker routing:
|
|
2398
|
-
{
|
|
2399
|
-
"targetAgents": ["worker_id_1", "worker_id_2", ...],
|
|
2400
|
-
"reasoning": "explanation of why these workers should work in parallel",
|
|
2401
|
-
"confidence": 0.0-1.0,
|
|
2402
|
-
"strategy": "llm-based"
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
|
|
2406
2409
|
var llmBasedRouting = {
|
|
2407
2410
|
name: "llm-based",
|
|
2408
2411
|
async route(state, config) {
|
|
@@ -2428,56 +2431,119 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
2428
2431
|
new SystemMessage5(systemPrompt),
|
|
2429
2432
|
new HumanMessage5(userPrompt)
|
|
2430
2433
|
];
|
|
2431
|
-
return
|
|
2434
|
+
return invokeRoutingDecision(config.model, messages);
|
|
2432
2435
|
}
|
|
2433
2436
|
};
|
|
2437
|
+
|
|
2438
|
+
// src/multi-agent/routing-internal/load-balanced-routing.ts
|
|
2439
|
+
function getAvailableWorkerLoads(state) {
|
|
2440
|
+
return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
|
|
2441
|
+
}
|
|
2442
|
+
var loadBalancedRouting = {
|
|
2443
|
+
name: "load-balanced",
|
|
2444
|
+
async route(state, _config) {
|
|
2445
|
+
const availableWorkers = getAvailableWorkerLoads(state);
|
|
2446
|
+
if (availableWorkers.length === 0) {
|
|
2447
|
+
throw new Error("No available workers for load-balanced routing");
|
|
2448
|
+
}
|
|
2449
|
+
const targetWorker = availableWorkers[0];
|
|
2450
|
+
const avgWorkload = availableWorkers.reduce((sum, worker) => sum + worker.workload, 0) / availableWorkers.length;
|
|
2451
|
+
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2452
|
+
return {
|
|
2453
|
+
targetAgent: targetWorker.id,
|
|
2454
|
+
targetAgents: null,
|
|
2455
|
+
reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
|
|
2456
|
+
confidence,
|
|
2457
|
+
strategy: "load-balanced",
|
|
2458
|
+
timestamp: Date.now()
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
|
|
2463
|
+
// src/multi-agent/routing-internal/worker-selection.ts
|
|
2464
|
+
function getAvailableWorkerIds(state) {
|
|
2465
|
+
return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
|
|
2466
|
+
}
|
|
2467
|
+
function getTaskContent(state) {
|
|
2468
|
+
const lastMessage = state.messages[state.messages.length - 1];
|
|
2469
|
+
return String(lastMessage?.content || state.input);
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/multi-agent/routing-internal/round-robin-routing.ts
|
|
2473
|
+
function createRoundRobinDecision(targetAgent, position, workerCount) {
|
|
2474
|
+
return {
|
|
2475
|
+
targetAgent,
|
|
2476
|
+
targetAgents: null,
|
|
2477
|
+
reasoning: `Round-robin selection: worker ${position} of ${workerCount}`,
|
|
2478
|
+
confidence: 1,
|
|
2479
|
+
strategy: "round-robin",
|
|
2480
|
+
timestamp: Date.now()
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2434
2483
|
var roundRobinRouting = {
|
|
2435
2484
|
name: "round-robin",
|
|
2436
2485
|
async route(state, _config) {
|
|
2437
|
-
const availableWorkers =
|
|
2486
|
+
const availableWorkers = getAvailableWorkerIds(state);
|
|
2438
2487
|
if (availableWorkers.length === 0) {
|
|
2439
2488
|
throw new Error("No available workers for round-robin routing");
|
|
2440
2489
|
}
|
|
2441
2490
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
2442
|
-
|
|
2491
|
+
return createRoundRobinDecision(
|
|
2492
|
+
availableWorkers[lastRoutingIndex],
|
|
2493
|
+
lastRoutingIndex + 1,
|
|
2494
|
+
availableWorkers.length
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
|
|
2499
|
+
// src/multi-agent/routing-internal/rule-based-routing.ts
|
|
2500
|
+
var ruleBasedRouting = {
|
|
2501
|
+
name: "rule-based",
|
|
2502
|
+
async route(state, config) {
|
|
2503
|
+
if (!config.routingFn) {
|
|
2504
|
+
throw new Error("Rule-based routing requires a custom routing function");
|
|
2505
|
+
}
|
|
2506
|
+
return config.routingFn(state);
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
// src/multi-agent/routing-internal/skill-based-routing.ts
|
|
2511
|
+
function scoreWorkers(state, taskContent) {
|
|
2512
|
+
return Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2513
|
+
const skillMatches = caps.skills.filter(
|
|
2514
|
+
(skill) => taskContent.includes(skill.toLowerCase())
|
|
2515
|
+
).length;
|
|
2516
|
+
const toolMatches = caps.tools.filter(
|
|
2517
|
+
(tool) => taskContent.includes(tool.toLowerCase())
|
|
2518
|
+
).length;
|
|
2443
2519
|
return {
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
confidence: 1,
|
|
2448
|
-
strategy: "round-robin",
|
|
2449
|
-
timestamp: Date.now()
|
|
2520
|
+
id,
|
|
2521
|
+
score: skillMatches * 2 + toolMatches,
|
|
2522
|
+
skills: caps.skills
|
|
2450
2523
|
};
|
|
2524
|
+
}).filter((worker) => worker.score > 0).sort((a, b) => b.score - a.score);
|
|
2525
|
+
}
|
|
2526
|
+
function createFallbackDecision(state) {
|
|
2527
|
+
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
2528
|
+
if (!firstAvailable) {
|
|
2529
|
+
throw new Error("No available workers for skill-based routing");
|
|
2451
2530
|
}
|
|
2452
|
-
|
|
2531
|
+
return {
|
|
2532
|
+
targetAgent: firstAvailable[0],
|
|
2533
|
+
targetAgents: null,
|
|
2534
|
+
reasoning: "No skill matches found, using first available worker",
|
|
2535
|
+
confidence: 0.5,
|
|
2536
|
+
strategy: "skill-based",
|
|
2537
|
+
timestamp: Date.now()
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2453
2540
|
var skillBasedRouting = {
|
|
2454
2541
|
name: "skill-based",
|
|
2455
2542
|
async route(state, _config) {
|
|
2456
|
-
const
|
|
2457
|
-
const
|
|
2458
|
-
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2459
|
-
const skillMatches = caps.skills.filter(
|
|
2460
|
-
(skill) => taskContent.includes(skill.toLowerCase())
|
|
2461
|
-
).length;
|
|
2462
|
-
const toolMatches = caps.tools.filter(
|
|
2463
|
-
(tool) => taskContent.includes(tool.toLowerCase())
|
|
2464
|
-
).length;
|
|
2465
|
-
const score = skillMatches * 2 + toolMatches;
|
|
2466
|
-
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
2467
|
-
}).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
|
|
2543
|
+
const taskContent = getTaskContent(state).toLowerCase();
|
|
2544
|
+
const workerScores = scoreWorkers(state, taskContent);
|
|
2468
2545
|
if (workerScores.length === 0) {
|
|
2469
|
-
|
|
2470
|
-
if (!firstAvailable) {
|
|
2471
|
-
throw new Error("No available workers for skill-based routing");
|
|
2472
|
-
}
|
|
2473
|
-
return {
|
|
2474
|
-
targetAgent: firstAvailable[0],
|
|
2475
|
-
targetAgents: null,
|
|
2476
|
-
reasoning: "No skill matches found, using first available worker",
|
|
2477
|
-
confidence: 0.5,
|
|
2478
|
-
strategy: "skill-based",
|
|
2479
|
-
timestamp: Date.now()
|
|
2480
|
-
};
|
|
2546
|
+
return createFallbackDecision(state);
|
|
2481
2547
|
}
|
|
2482
2548
|
const best = workerScores[0];
|
|
2483
2549
|
const confidence = Math.min(best.score / 5, 1);
|
|
@@ -2491,50 +2557,20 @@ var skillBasedRouting = {
|
|
|
2491
2557
|
};
|
|
2492
2558
|
}
|
|
2493
2559
|
};
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2503
|
-
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2504
|
-
return {
|
|
2505
|
-
targetAgent: targetWorker.id,
|
|
2506
|
-
targetAgents: null,
|
|
2507
|
-
reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
|
|
2508
|
-
confidence,
|
|
2509
|
-
strategy: "load-balanced",
|
|
2510
|
-
timestamp: Date.now()
|
|
2511
|
-
};
|
|
2512
|
-
}
|
|
2513
|
-
};
|
|
2514
|
-
var ruleBasedRouting = {
|
|
2515
|
-
name: "rule-based",
|
|
2516
|
-
async route(state, config) {
|
|
2517
|
-
if (!config.routingFn) {
|
|
2518
|
-
throw new Error("Rule-based routing requires a custom routing function");
|
|
2519
|
-
}
|
|
2520
|
-
return await config.routingFn(state);
|
|
2521
|
-
}
|
|
2560
|
+
|
|
2561
|
+
// src/multi-agent/routing.ts
|
|
2562
|
+
var routingStrategies = {
|
|
2563
|
+
"llm-based": llmBasedRouting,
|
|
2564
|
+
"round-robin": roundRobinRouting,
|
|
2565
|
+
"skill-based": skillBasedRouting,
|
|
2566
|
+
"load-balanced": loadBalancedRouting,
|
|
2567
|
+
"rule-based": ruleBasedRouting
|
|
2522
2568
|
};
|
|
2523
2569
|
function getRoutingStrategy(name) {
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
return llmBasedRouting;
|
|
2527
|
-
case "round-robin":
|
|
2528
|
-
return roundRobinRouting;
|
|
2529
|
-
case "skill-based":
|
|
2530
|
-
return skillBasedRouting;
|
|
2531
|
-
case "load-balanced":
|
|
2532
|
-
return loadBalancedRouting;
|
|
2533
|
-
case "rule-based":
|
|
2534
|
-
return ruleBasedRouting;
|
|
2535
|
-
default:
|
|
2536
|
-
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2570
|
+
if (!Object.hasOwn(routingStrategies, name)) {
|
|
2571
|
+
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2537
2572
|
}
|
|
2573
|
+
return routingStrategies[name];
|
|
2538
2574
|
}
|
|
2539
2575
|
|
|
2540
2576
|
// src/multi-agent/utils.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge/patterns",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.33",
|
|
4
4
|
"description": "Production-ready agent workflow patterns for TypeScript including ReAct and Planner-Executor, built on LangGraph.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"url": "https://github.com/TVScoundrel/agentforge/issues"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@agentforge/core": "0.16.
|
|
44
|
+
"@agentforge/core": "0.16.33",
|
|
45
45
|
"@langchain/core": "^1.1.17",
|
|
46
46
|
"@langchain/langgraph": "^1.1.2",
|
|
47
47
|
"zod": "^3.23.8"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@agentforge/testing": "0.16.
|
|
50
|
+
"@agentforge/testing": "0.16.33",
|
|
51
51
|
"@eslint/js": "^9.17.0",
|
|
52
52
|
"@types/node": "^22.10.2",
|
|
53
53
|
"eslint": "^9.17.0",
|