@agentforge/patterns 0.7.0 → 0.8.1
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 +144 -305
- package/dist/index.d.cts +117 -26
- package/dist/index.d.ts +117 -26
- package/dist/index.js +144 -305
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -180,8 +180,8 @@ function generateToolCallCacheKey(toolName, args) {
|
|
|
180
180
|
return `${toolName}:${sortedArgs}`;
|
|
181
181
|
}
|
|
182
182
|
function createPatternLogger(name, defaultLevel = "info") {
|
|
183
|
-
const
|
|
184
|
-
return createLogger(name, { level:
|
|
183
|
+
const logLevel4 = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
|
|
184
|
+
return createLogger(name, { level: logLevel4 });
|
|
185
185
|
}
|
|
186
186
|
function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
|
|
187
187
|
if (duplicatesSkipped === 0) {
|
|
@@ -501,7 +501,8 @@ function createReActAgent(config, options) {
|
|
|
501
501
|
return ACTION_NODE;
|
|
502
502
|
};
|
|
503
503
|
const workflow = new StateGraph(ReActState).addNode(REASONING_NODE, reasoningNode).addNode(ACTION_NODE, actionNode).addNode(OBSERVATION_NODE, observationNode).addEdge("__start__", REASONING_NODE).addConditionalEdges(REASONING_NODE, shouldContinue).addEdge(ACTION_NODE, OBSERVATION_NODE).addEdge(OBSERVATION_NODE, REASONING_NODE);
|
|
504
|
-
|
|
504
|
+
const checkpointerConfig = checkpointer === true ? { checkpointer: true } : checkpointer ? { checkpointer } : void 0;
|
|
505
|
+
return workflow.compile(checkpointerConfig);
|
|
505
506
|
}
|
|
506
507
|
|
|
507
508
|
// src/react/builder.ts
|
|
@@ -586,6 +587,43 @@ var ReActAgentBuilder = class {
|
|
|
586
587
|
this.options.nodeNames = nodeNames;
|
|
587
588
|
return this;
|
|
588
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Set the checkpointer for state persistence (optional)
|
|
592
|
+
*
|
|
593
|
+
* Can be:
|
|
594
|
+
* - A BaseCheckpointSaver instance (e.g., MemorySaver) for standalone agents
|
|
595
|
+
* - `true` to use the parent graph's checkpointer with a separate namespace (for nested graphs)
|
|
596
|
+
*
|
|
597
|
+
* Required for human-in-the-loop workflows (askHuman tool) and conversation continuity.
|
|
598
|
+
*
|
|
599
|
+
* @param checkpointer - Checkpointer instance or `true` for nested graphs
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* Standalone agent with its own checkpointer:
|
|
603
|
+
* ```typescript
|
|
604
|
+
* import { MemorySaver } from '@langchain/langgraph';
|
|
605
|
+
*
|
|
606
|
+
* const agent = new ReActAgentBuilder()
|
|
607
|
+
* .withModel(model)
|
|
608
|
+
* .withTools(tools)
|
|
609
|
+
* .withCheckpointer(new MemorySaver())
|
|
610
|
+
* .build();
|
|
611
|
+
* ```
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* Nested agent using parent's checkpointer (for multi-agent systems):
|
|
615
|
+
* ```typescript
|
|
616
|
+
* const agent = new ReActAgentBuilder()
|
|
617
|
+
* .withModel(model)
|
|
618
|
+
* .withTools(tools)
|
|
619
|
+
* .withCheckpointer(true) // Use parent's checkpointer with separate namespace
|
|
620
|
+
* .build();
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
withCheckpointer(checkpointer) {
|
|
624
|
+
this.config.checkpointer = checkpointer;
|
|
625
|
+
return this;
|
|
626
|
+
}
|
|
589
627
|
/**
|
|
590
628
|
* Build the ReAct agent
|
|
591
629
|
*
|
|
@@ -605,7 +643,8 @@ var ReActAgentBuilder = class {
|
|
|
605
643
|
systemPrompt: this.config.systemPrompt || DEFAULT_REACT_SYSTEM_PROMPT,
|
|
606
644
|
maxIterations: this.config.maxIterations ?? 10,
|
|
607
645
|
returnIntermediateSteps: this.config.returnIntermediateSteps ?? false,
|
|
608
|
-
stopCondition: this.config.stopCondition
|
|
646
|
+
stopCondition: this.config.stopCondition,
|
|
647
|
+
checkpointer: this.config.checkpointer
|
|
609
648
|
};
|
|
610
649
|
return createReActAgent(finalConfig, this.options);
|
|
611
650
|
}
|
|
@@ -2060,67 +2099,7 @@ var MultiAgentStateConfig = {
|
|
|
2060
2099
|
var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
|
|
2061
2100
|
|
|
2062
2101
|
// src/multi-agent/routing.ts
|
|
2063
|
-
import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4
|
|
2064
|
-
import { createLogger as createLogger2, LogLevel } from "@agentforge/core";
|
|
2065
|
-
var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
|
|
2066
|
-
var logger = createLogger2("multi-agent:routing", { level: logLevel });
|
|
2067
|
-
async function executeTools(toolCalls, tools) {
|
|
2068
|
-
const results = [];
|
|
2069
|
-
logger.debug("Executing tools", {
|
|
2070
|
-
toolCallCount: toolCalls.length,
|
|
2071
|
-
toolNames: toolCalls.map((tc) => tc.name)
|
|
2072
|
-
});
|
|
2073
|
-
for (const toolCall of toolCalls) {
|
|
2074
|
-
const tool = tools.find((t) => t.metadata.name === toolCall.name);
|
|
2075
|
-
if (!tool) {
|
|
2076
|
-
logger.warn("Tool not found", {
|
|
2077
|
-
toolName: toolCall.name,
|
|
2078
|
-
availableTools: tools.map((t) => t.metadata.name)
|
|
2079
|
-
});
|
|
2080
|
-
results.push(new ToolMessage2({
|
|
2081
|
-
content: `Error: Tool '${toolCall.name}' not found`,
|
|
2082
|
-
tool_call_id: toolCall.id
|
|
2083
|
-
}));
|
|
2084
|
-
continue;
|
|
2085
|
-
}
|
|
2086
|
-
try {
|
|
2087
|
-
logger.debug("Executing tool", {
|
|
2088
|
-
toolName: toolCall.name,
|
|
2089
|
-
args: toolCall.args
|
|
2090
|
-
});
|
|
2091
|
-
const result = await tool.execute(toolCall.args);
|
|
2092
|
-
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
2093
|
-
logger.debug("Tool execution successful", {
|
|
2094
|
-
toolName: toolCall.name,
|
|
2095
|
-
resultLength: content.length
|
|
2096
|
-
});
|
|
2097
|
-
results.push(new ToolMessage2({
|
|
2098
|
-
content,
|
|
2099
|
-
tool_call_id: toolCall.id
|
|
2100
|
-
}));
|
|
2101
|
-
} catch (error) {
|
|
2102
|
-
logger.error("Tool execution failed", {
|
|
2103
|
-
toolName: toolCall.name,
|
|
2104
|
-
error: error.message
|
|
2105
|
-
});
|
|
2106
|
-
results.push(new ToolMessage2({
|
|
2107
|
-
content: `Error executing tool: ${error.message}`,
|
|
2108
|
-
tool_call_id: toolCall.id
|
|
2109
|
-
}));
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
logger.debug("Tool execution complete", {
|
|
2113
|
-
successCount: results.filter((r) => {
|
|
2114
|
-
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2115
|
-
return !content.startsWith("Error");
|
|
2116
|
-
}).length,
|
|
2117
|
-
errorCount: results.filter((r) => {
|
|
2118
|
-
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2119
|
-
return content.startsWith("Error");
|
|
2120
|
-
}).length
|
|
2121
|
-
});
|
|
2122
|
-
return results;
|
|
2123
|
-
}
|
|
2102
|
+
import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4 } from "@langchain/core/messages";
|
|
2124
2103
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2125
2104
|
|
|
2126
2105
|
Your job is to:
|
|
@@ -2156,151 +2135,48 @@ Choose parallel routing when the task benefits from multiple perspectives or dat
|
|
|
2156
2135
|
var llmBasedRouting = {
|
|
2157
2136
|
name: "llm-based",
|
|
2158
2137
|
async route(state, config) {
|
|
2159
|
-
logger.info("Starting LLM-based routing", {
|
|
2160
|
-
iteration: state.iteration,
|
|
2161
|
-
availableWorkers: Object.keys(state.workers).length
|
|
2162
|
-
});
|
|
2163
2138
|
if (!config.model) {
|
|
2164
2139
|
throw new Error("LLM-based routing requires a model to be configured");
|
|
2165
2140
|
}
|
|
2166
2141
|
const systemPrompt = config.systemPrompt || DEFAULT_SUPERVISOR_SYSTEM_PROMPT;
|
|
2167
|
-
const maxRetries = config.maxToolRetries || 3;
|
|
2168
|
-
const tools = config.tools || [];
|
|
2169
2142
|
const workerInfo = Object.entries(state.workers).map(([id, caps]) => {
|
|
2170
2143
|
const skills = caps.skills.join(", ");
|
|
2171
|
-
const
|
|
2144
|
+
const tools = caps.tools.join(", ");
|
|
2172
2145
|
const available = caps.available ? "available" : "busy";
|
|
2173
|
-
return `- ${id}: Skills: [${skills}], Tools: [${
|
|
2146
|
+
return `- ${id}: Skills: [${skills}], Tools: [${tools}], Status: ${available}, Workload: ${caps.currentWorkload}`;
|
|
2174
2147
|
}).join("\n");
|
|
2175
|
-
logger.debug("Worker capabilities", {
|
|
2176
|
-
workers: Object.entries(state.workers).map(([id, caps]) => ({
|
|
2177
|
-
id,
|
|
2178
|
-
skills: caps.skills,
|
|
2179
|
-
available: caps.available,
|
|
2180
|
-
workload: caps.currentWorkload
|
|
2181
|
-
}))
|
|
2182
|
-
});
|
|
2183
2148
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2184
2149
|
const taskContext = lastMessage?.content || state.input;
|
|
2185
|
-
logger.debug("Task context", {
|
|
2186
|
-
taskLength: taskContext.length,
|
|
2187
|
-
taskPreview: taskContext.substring(0, 100)
|
|
2188
|
-
});
|
|
2189
2150
|
const userPrompt = `Current task: ${taskContext}
|
|
2190
2151
|
|
|
2191
2152
|
Available workers:
|
|
2192
2153
|
${workerInfo}
|
|
2193
2154
|
|
|
2194
2155
|
Select the best worker(s) for this task and explain your reasoning.`;
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
const response = await config.model.invoke(messages);
|
|
2209
|
-
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
2210
|
-
logger.info("LLM requested tool calls", {
|
|
2211
|
-
toolCount: response.tool_calls.length,
|
|
2212
|
-
toolNames: response.tool_calls.map((tc) => tc.name)
|
|
2213
|
-
});
|
|
2214
|
-
if (tools.length === 0) {
|
|
2215
|
-
throw new Error("LLM requested tool calls but no tools are configured");
|
|
2216
|
-
}
|
|
2217
|
-
const toolResults = await executeTools(response.tool_calls, tools);
|
|
2218
|
-
conversationHistory.push(
|
|
2219
|
-
new AIMessage2({ content: response.content || "", tool_calls: response.tool_calls }),
|
|
2220
|
-
...toolResults
|
|
2221
|
-
);
|
|
2222
|
-
attempt++;
|
|
2223
|
-
logger.debug("Retrying routing with tool results", { attempt });
|
|
2224
|
-
continue;
|
|
2225
|
-
}
|
|
2226
|
-
logger.debug("Parsing routing decision from LLM response");
|
|
2227
|
-
let decision;
|
|
2228
|
-
if (response && typeof response === "object" && ("targetAgent" in response || "targetAgents" in response)) {
|
|
2229
|
-
logger.debug("Response is structured output", {
|
|
2230
|
-
hasTargetAgent: "targetAgent" in response,
|
|
2231
|
-
hasTargetAgents: "targetAgents" in response
|
|
2232
|
-
});
|
|
2233
|
-
decision = response;
|
|
2234
|
-
} else if (response.content) {
|
|
2235
|
-
if (typeof response.content === "string") {
|
|
2236
|
-
try {
|
|
2237
|
-
decision = JSON.parse(response.content);
|
|
2238
|
-
logger.debug("Parsed JSON from string response");
|
|
2239
|
-
} catch (error) {
|
|
2240
|
-
logger.error("Failed to parse routing decision", {
|
|
2241
|
-
content: response.content,
|
|
2242
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2243
|
-
});
|
|
2244
|
-
throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
|
|
2245
|
-
}
|
|
2246
|
-
} else if (typeof response.content === "object") {
|
|
2247
|
-
logger.debug("Response content is already an object");
|
|
2248
|
-
decision = response.content;
|
|
2249
|
-
} else {
|
|
2250
|
-
logger.error("Unexpected response content type", {
|
|
2251
|
-
type: typeof response.content
|
|
2252
|
-
});
|
|
2253
|
-
throw new Error(`Unexpected response content type: ${typeof response.content}`);
|
|
2254
|
-
}
|
|
2255
|
-
} else {
|
|
2256
|
-
logger.error("Unexpected response format", {
|
|
2257
|
-
response: JSON.stringify(response)
|
|
2258
|
-
});
|
|
2259
|
-
throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
|
|
2260
|
-
}
|
|
2261
|
-
const result = {
|
|
2262
|
-
targetAgent: decision.targetAgent,
|
|
2263
|
-
targetAgents: decision.targetAgents,
|
|
2264
|
-
reasoning: decision.reasoning,
|
|
2265
|
-
confidence: decision.confidence,
|
|
2266
|
-
strategy: "llm-based",
|
|
2267
|
-
timestamp: Date.now()
|
|
2268
|
-
};
|
|
2269
|
-
logger.info("LLM routing decision made", {
|
|
2270
|
-
targetAgent: result.targetAgent,
|
|
2271
|
-
targetAgents: result.targetAgents,
|
|
2272
|
-
isParallel: result.targetAgents && result.targetAgents.length > 1,
|
|
2273
|
-
confidence: result.confidence,
|
|
2274
|
-
reasoning: result.reasoning
|
|
2275
|
-
});
|
|
2276
|
-
return result;
|
|
2277
|
-
}
|
|
2278
|
-
logger.error("Max tool retries exceeded", { maxRetries });
|
|
2279
|
-
throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
|
|
2156
|
+
const messages = [
|
|
2157
|
+
new SystemMessage4(systemPrompt),
|
|
2158
|
+
new HumanMessage4(userPrompt)
|
|
2159
|
+
];
|
|
2160
|
+
const decision = await config.model.invoke(messages);
|
|
2161
|
+
return {
|
|
2162
|
+
targetAgent: decision.targetAgent,
|
|
2163
|
+
targetAgents: decision.targetAgents,
|
|
2164
|
+
reasoning: decision.reasoning,
|
|
2165
|
+
confidence: decision.confidence,
|
|
2166
|
+
strategy: "llm-based",
|
|
2167
|
+
timestamp: Date.now()
|
|
2168
|
+
};
|
|
2280
2169
|
}
|
|
2281
2170
|
};
|
|
2282
2171
|
var roundRobinRouting = {
|
|
2283
2172
|
name: "round-robin",
|
|
2284
2173
|
async route(state, config) {
|
|
2285
|
-
logger.info("Starting round-robin routing", {
|
|
2286
|
-
iteration: state.iteration
|
|
2287
|
-
});
|
|
2288
2174
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
|
|
2289
|
-
logger.debug("Available workers for round-robin", {
|
|
2290
|
-
count: availableWorkers.length,
|
|
2291
|
-
workers: availableWorkers
|
|
2292
|
-
});
|
|
2293
2175
|
if (availableWorkers.length === 0) {
|
|
2294
|
-
logger.error("No available workers for round-robin routing");
|
|
2295
2176
|
throw new Error("No available workers for round-robin routing");
|
|
2296
2177
|
}
|
|
2297
2178
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
2298
2179
|
const targetAgent = availableWorkers[lastRoutingIndex];
|
|
2299
|
-
logger.info("Round-robin routing decision", {
|
|
2300
|
-
targetAgent,
|
|
2301
|
-
index: lastRoutingIndex + 1,
|
|
2302
|
-
totalWorkers: availableWorkers.length
|
|
2303
|
-
});
|
|
2304
2180
|
return {
|
|
2305
2181
|
targetAgent,
|
|
2306
2182
|
targetAgents: null,
|
|
@@ -2314,15 +2190,8 @@ var roundRobinRouting = {
|
|
|
2314
2190
|
var skillBasedRouting = {
|
|
2315
2191
|
name: "skill-based",
|
|
2316
2192
|
async route(state, config) {
|
|
2317
|
-
logger.info("Starting skill-based routing", {
|
|
2318
|
-
iteration: state.iteration
|
|
2319
|
-
});
|
|
2320
2193
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2321
2194
|
const taskContent = (lastMessage?.content || state.input).toLowerCase();
|
|
2322
|
-
logger.debug("Task content for skill matching", {
|
|
2323
|
-
taskLength: taskContent.length,
|
|
2324
|
-
taskPreview: taskContent.substring(0, 100)
|
|
2325
|
-
});
|
|
2326
2195
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2327
2196
|
const skillMatches = caps.skills.filter(
|
|
2328
2197
|
(skill) => taskContent.includes(skill.toLowerCase())
|
|
@@ -2333,20 +2202,11 @@ var skillBasedRouting = {
|
|
|
2333
2202
|
const score = skillMatches * 2 + toolMatches;
|
|
2334
2203
|
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
2335
2204
|
}).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
|
|
2336
|
-
logger.debug("Worker skill scores", {
|
|
2337
|
-
scoredWorkers: workerScores.map((w) => ({ id: w.id, score: w.score }))
|
|
2338
|
-
});
|
|
2339
2205
|
if (workerScores.length === 0) {
|
|
2340
|
-
logger.warn("No skill matches found, using fallback");
|
|
2341
2206
|
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
2342
2207
|
if (!firstAvailable) {
|
|
2343
|
-
logger.error("No available workers for skill-based routing");
|
|
2344
2208
|
throw new Error("No available workers for skill-based routing");
|
|
2345
2209
|
}
|
|
2346
|
-
logger.info("Skill-based routing fallback decision", {
|
|
2347
|
-
targetAgent: firstAvailable[0],
|
|
2348
|
-
confidence: 0.5
|
|
2349
|
-
});
|
|
2350
2210
|
return {
|
|
2351
2211
|
targetAgent: firstAvailable[0],
|
|
2352
2212
|
targetAgents: null,
|
|
@@ -2358,12 +2218,6 @@ var skillBasedRouting = {
|
|
|
2358
2218
|
}
|
|
2359
2219
|
const best = workerScores[0];
|
|
2360
2220
|
const confidence = Math.min(best.score / 5, 1);
|
|
2361
|
-
logger.info("Skill-based routing decision", {
|
|
2362
|
-
targetAgent: best.id,
|
|
2363
|
-
score: best.score,
|
|
2364
|
-
confidence,
|
|
2365
|
-
matchedSkills: best.skills
|
|
2366
|
-
});
|
|
2367
2221
|
return {
|
|
2368
2222
|
targetAgent: best.id,
|
|
2369
2223
|
targetAgents: null,
|
|
@@ -2377,26 +2231,13 @@ var skillBasedRouting = {
|
|
|
2377
2231
|
var loadBalancedRouting = {
|
|
2378
2232
|
name: "load-balanced",
|
|
2379
2233
|
async route(state, config) {
|
|
2380
|
-
logger.info("Starting load-balanced routing", {
|
|
2381
|
-
iteration: state.iteration
|
|
2382
|
-
});
|
|
2383
2234
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
|
|
2384
|
-
logger.debug("Worker workloads", {
|
|
2385
|
-
workers: availableWorkers.map((w) => ({ id: w.id, workload: w.workload }))
|
|
2386
|
-
});
|
|
2387
2235
|
if (availableWorkers.length === 0) {
|
|
2388
|
-
logger.error("No available workers for load-balanced routing");
|
|
2389
2236
|
throw new Error("No available workers for load-balanced routing");
|
|
2390
2237
|
}
|
|
2391
2238
|
const targetWorker = availableWorkers[0];
|
|
2392
2239
|
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2393
2240
|
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2394
|
-
logger.info("Load-balanced routing decision", {
|
|
2395
|
-
targetAgent: targetWorker.id,
|
|
2396
|
-
workload: targetWorker.workload,
|
|
2397
|
-
avgWorkload: avgWorkload.toFixed(1),
|
|
2398
|
-
confidence
|
|
2399
|
-
});
|
|
2400
2241
|
return {
|
|
2401
2242
|
targetAgent: targetWorker.id,
|
|
2402
2243
|
targetAgents: null,
|
|
@@ -2410,24 +2251,13 @@ var loadBalancedRouting = {
|
|
|
2410
2251
|
var ruleBasedRouting = {
|
|
2411
2252
|
name: "rule-based",
|
|
2412
2253
|
async route(state, config) {
|
|
2413
|
-
logger.info("Starting rule-based routing", {
|
|
2414
|
-
iteration: state.iteration
|
|
2415
|
-
});
|
|
2416
2254
|
if (!config.routingFn) {
|
|
2417
|
-
logger.error("Rule-based routing requires a custom routing function");
|
|
2418
2255
|
throw new Error("Rule-based routing requires a custom routing function");
|
|
2419
2256
|
}
|
|
2420
|
-
|
|
2421
|
-
logger.info("Rule-based routing decision", {
|
|
2422
|
-
targetAgent: decision.targetAgent,
|
|
2423
|
-
targetAgents: decision.targetAgents,
|
|
2424
|
-
confidence: decision.confidence
|
|
2425
|
-
});
|
|
2426
|
-
return decision;
|
|
2257
|
+
return await config.routingFn(state);
|
|
2427
2258
|
}
|
|
2428
2259
|
};
|
|
2429
2260
|
function getRoutingStrategy(name) {
|
|
2430
|
-
logger.debug("Getting routing strategy", { name });
|
|
2431
2261
|
switch (name) {
|
|
2432
2262
|
case "llm-based":
|
|
2433
2263
|
return llmBasedRouting;
|
|
@@ -2440,15 +2270,14 @@ function getRoutingStrategy(name) {
|
|
|
2440
2270
|
case "rule-based":
|
|
2441
2271
|
return ruleBasedRouting;
|
|
2442
2272
|
default:
|
|
2443
|
-
logger.error("Unknown routing strategy", { name });
|
|
2444
2273
|
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2445
2274
|
}
|
|
2446
2275
|
}
|
|
2447
2276
|
|
|
2448
2277
|
// src/multi-agent/utils.ts
|
|
2449
|
-
import { createLogger as
|
|
2450
|
-
var
|
|
2451
|
-
var
|
|
2278
|
+
import { createLogger as createLogger2, LogLevel } from "@agentforge/core";
|
|
2279
|
+
var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
|
|
2280
|
+
var logger = createLogger2("multi-agent", { level: logLevel });
|
|
2452
2281
|
function isReActAgent(obj) {
|
|
2453
2282
|
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
|
|
2454
2283
|
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
|
|
@@ -2456,9 +2285,9 @@ function isReActAgent(obj) {
|
|
|
2456
2285
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2457
2286
|
return async (state, config) => {
|
|
2458
2287
|
try {
|
|
2459
|
-
|
|
2288
|
+
logger.debug("Wrapping ReAct agent execution", { workerId });
|
|
2460
2289
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2461
|
-
|
|
2290
|
+
logger.debug("Extracted task", {
|
|
2462
2291
|
workerId,
|
|
2463
2292
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2464
2293
|
});
|
|
@@ -2466,25 +2295,39 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2466
2295
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2467
2296
|
);
|
|
2468
2297
|
if (!currentAssignment) {
|
|
2469
|
-
|
|
2298
|
+
logger.debug("No active assignment found", { workerId });
|
|
2470
2299
|
return {};
|
|
2471
2300
|
}
|
|
2301
|
+
const workerThreadId = config?.configurable?.thread_id ? `${config.configurable.thread_id}:worker:${workerId}` : void 0;
|
|
2302
|
+
const workerConfig = workerThreadId ? {
|
|
2303
|
+
...config,
|
|
2304
|
+
configurable: {
|
|
2305
|
+
...config.configurable,
|
|
2306
|
+
thread_id: workerThreadId
|
|
2307
|
+
}
|
|
2308
|
+
} : config;
|
|
2309
|
+
logger.debug("Invoking ReAct agent with worker-specific config", {
|
|
2310
|
+
workerId,
|
|
2311
|
+
parentThreadId: config?.configurable?.thread_id,
|
|
2312
|
+
workerThreadId,
|
|
2313
|
+
hasConfig: !!workerConfig
|
|
2314
|
+
});
|
|
2472
2315
|
const result = await agent.invoke(
|
|
2473
2316
|
{
|
|
2474
2317
|
messages: [{ role: "user", content: task }]
|
|
2475
2318
|
},
|
|
2476
|
-
|
|
2477
|
-
//
|
|
2319
|
+
workerConfig
|
|
2320
|
+
// Worker-specific config with unique thread_id
|
|
2478
2321
|
);
|
|
2479
2322
|
const response = result.messages?.[result.messages.length - 1]?.content || "No response";
|
|
2480
|
-
|
|
2323
|
+
logger.debug("Received response from ReAct agent", {
|
|
2481
2324
|
workerId,
|
|
2482
2325
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2483
2326
|
});
|
|
2484
2327
|
const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
|
|
2485
2328
|
const uniqueTools = [...new Set(toolsUsed)];
|
|
2486
2329
|
if (uniqueTools.length > 0) {
|
|
2487
|
-
|
|
2330
|
+
logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2488
2331
|
}
|
|
2489
2332
|
const taskResult = {
|
|
2490
2333
|
assignmentId: currentAssignment.id,
|
|
@@ -2503,7 +2346,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2503
2346
|
};
|
|
2504
2347
|
} catch (error) {
|
|
2505
2348
|
const errorMessage = handleNodeError(error, `react-agent:${workerId}`, false);
|
|
2506
|
-
|
|
2349
|
+
logger.error("Error in ReAct agent execution", {
|
|
2507
2350
|
workerId,
|
|
2508
2351
|
error: errorMessage
|
|
2509
2352
|
});
|
|
@@ -2535,9 +2378,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2535
2378
|
|
|
2536
2379
|
// src/multi-agent/nodes.ts
|
|
2537
2380
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2538
|
-
import { toLangChainTools as toLangChainTools2, createLogger as
|
|
2539
|
-
var
|
|
2540
|
-
var
|
|
2381
|
+
import { toLangChainTools as toLangChainTools2, createLogger as createLogger3, LogLevel as LogLevel2 } from "@agentforge/core";
|
|
2382
|
+
var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel2.INFO;
|
|
2383
|
+
var logger2 = createLogger3("multi-agent:nodes", { level: logLevel2 });
|
|
2541
2384
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2542
2385
|
|
|
2543
2386
|
Your job is to:
|
|
@@ -2555,19 +2398,19 @@ function createSupervisorNode(config) {
|
|
|
2555
2398
|
} = config;
|
|
2556
2399
|
return async (state) => {
|
|
2557
2400
|
try {
|
|
2558
|
-
|
|
2401
|
+
logger2.info("Supervisor node executing", {
|
|
2559
2402
|
iteration: state.iteration,
|
|
2560
2403
|
maxIterations,
|
|
2561
2404
|
activeAssignments: state.activeAssignments.length,
|
|
2562
2405
|
completedTasks: state.completedTasks.length
|
|
2563
2406
|
});
|
|
2564
|
-
|
|
2407
|
+
logger2.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
|
|
2565
2408
|
if (state.iteration >= maxIterations) {
|
|
2566
|
-
|
|
2409
|
+
logger2.warn("Max iterations reached", {
|
|
2567
2410
|
iteration: state.iteration,
|
|
2568
2411
|
maxIterations
|
|
2569
2412
|
});
|
|
2570
|
-
|
|
2413
|
+
logger2.debug("Max iterations reached, moving to aggregation");
|
|
2571
2414
|
return {
|
|
2572
2415
|
status: "aggregating",
|
|
2573
2416
|
currentAgent: "aggregator"
|
|
@@ -2576,26 +2419,26 @@ function createSupervisorNode(config) {
|
|
|
2576
2419
|
const allCompleted = state.activeAssignments.every(
|
|
2577
2420
|
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2578
2421
|
);
|
|
2579
|
-
|
|
2422
|
+
logger2.debug("Checking task completion", {
|
|
2580
2423
|
activeAssignments: state.activeAssignments.length,
|
|
2581
2424
|
completedTasks: state.completedTasks.length,
|
|
2582
2425
|
allCompleted
|
|
2583
2426
|
});
|
|
2584
2427
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2585
|
-
|
|
2428
|
+
logger2.info("All tasks completed, moving to aggregation", {
|
|
2586
2429
|
completedCount: state.completedTasks.length
|
|
2587
2430
|
});
|
|
2588
|
-
|
|
2431
|
+
logger2.debug("All tasks completed, moving to aggregation");
|
|
2589
2432
|
return {
|
|
2590
2433
|
status: "aggregating",
|
|
2591
2434
|
currentAgent: "aggregator"
|
|
2592
2435
|
};
|
|
2593
2436
|
}
|
|
2594
|
-
|
|
2437
|
+
logger2.debug("Getting routing strategy", { strategy });
|
|
2595
2438
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2596
2439
|
const decision = await routingImpl.route(state, config);
|
|
2597
2440
|
const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
|
|
2598
|
-
|
|
2441
|
+
logger2.debug("Target agents determined", {
|
|
2599
2442
|
targetAgents,
|
|
2600
2443
|
isParallel: targetAgents.length > 1,
|
|
2601
2444
|
decision: {
|
|
@@ -2604,17 +2447,17 @@ function createSupervisorNode(config) {
|
|
|
2604
2447
|
}
|
|
2605
2448
|
});
|
|
2606
2449
|
if (targetAgents.length === 0) {
|
|
2607
|
-
|
|
2450
|
+
logger2.error("No target agents specified in routing decision");
|
|
2608
2451
|
throw new Error("Routing decision must specify at least one target agent");
|
|
2609
2452
|
}
|
|
2610
2453
|
if (targetAgents.length === 1) {
|
|
2611
|
-
|
|
2454
|
+
logger2.info("Routing to single agent", {
|
|
2612
2455
|
targetAgent: targetAgents[0],
|
|
2613
2456
|
reasoning: decision.reasoning,
|
|
2614
2457
|
confidence: decision.confidence
|
|
2615
2458
|
});
|
|
2616
2459
|
} else {
|
|
2617
|
-
|
|
2460
|
+
logger2.info("Routing to multiple agents in parallel", {
|
|
2618
2461
|
targetAgents,
|
|
2619
2462
|
count: targetAgents.length,
|
|
2620
2463
|
reasoning: decision.reasoning,
|
|
@@ -2622,9 +2465,9 @@ function createSupervisorNode(config) {
|
|
|
2622
2465
|
});
|
|
2623
2466
|
}
|
|
2624
2467
|
if (targetAgents.length === 1) {
|
|
2625
|
-
|
|
2468
|
+
logger2.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2626
2469
|
} else {
|
|
2627
|
-
|
|
2470
|
+
logger2.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
|
|
2628
2471
|
}
|
|
2629
2472
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2630
2473
|
const assignments = targetAgents.map((workerId) => ({
|
|
@@ -2634,7 +2477,7 @@ function createSupervisorNode(config) {
|
|
|
2634
2477
|
priority: 5,
|
|
2635
2478
|
assignedAt: Date.now()
|
|
2636
2479
|
}));
|
|
2637
|
-
|
|
2480
|
+
logger2.debug("Created task assignments", {
|
|
2638
2481
|
assignmentCount: assignments.length,
|
|
2639
2482
|
assignments: assignments.map((a) => ({
|
|
2640
2483
|
id: a.id,
|
|
@@ -2654,7 +2497,7 @@ function createSupervisorNode(config) {
|
|
|
2654
2497
|
priority: assignment.priority
|
|
2655
2498
|
}
|
|
2656
2499
|
}));
|
|
2657
|
-
|
|
2500
|
+
logger2.info("Supervisor routing complete", {
|
|
2658
2501
|
currentAgent: targetAgents.join(","),
|
|
2659
2502
|
status: "executing",
|
|
2660
2503
|
assignmentCount: assignments.length,
|
|
@@ -2671,7 +2514,7 @@ function createSupervisorNode(config) {
|
|
|
2671
2514
|
iteration: state.iteration + 1
|
|
2672
2515
|
};
|
|
2673
2516
|
} catch (error) {
|
|
2674
|
-
|
|
2517
|
+
logger2.error("Supervisor node error", {
|
|
2675
2518
|
error: error instanceof Error ? error.message : String(error),
|
|
2676
2519
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2677
2520
|
iteration: state.iteration
|
|
@@ -2696,7 +2539,7 @@ function createWorkerNode(config) {
|
|
|
2696
2539
|
} = config;
|
|
2697
2540
|
return async (state, runConfig) => {
|
|
2698
2541
|
try {
|
|
2699
|
-
|
|
2542
|
+
logger2.info("Worker node executing", {
|
|
2700
2543
|
workerId: id,
|
|
2701
2544
|
iteration: state.iteration,
|
|
2702
2545
|
activeAssignments: state.activeAssignments.length
|
|
@@ -2705,39 +2548,39 @@ function createWorkerNode(config) {
|
|
|
2705
2548
|
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2706
2549
|
);
|
|
2707
2550
|
if (!currentAssignment) {
|
|
2708
|
-
|
|
2551
|
+
logger2.debug("No active assignment found for worker", {
|
|
2709
2552
|
workerId: id,
|
|
2710
2553
|
totalActiveAssignments: state.activeAssignments.length,
|
|
2711
2554
|
completedTasks: state.completedTasks.length
|
|
2712
2555
|
});
|
|
2713
2556
|
return {};
|
|
2714
2557
|
}
|
|
2715
|
-
|
|
2558
|
+
logger2.info("Worker processing assignment", {
|
|
2716
2559
|
workerId: id,
|
|
2717
2560
|
assignmentId: currentAssignment.id,
|
|
2718
2561
|
taskLength: currentAssignment.task.length,
|
|
2719
2562
|
taskPreview: currentAssignment.task.substring(0, 100)
|
|
2720
2563
|
});
|
|
2721
2564
|
if (executeFn) {
|
|
2722
|
-
|
|
2565
|
+
logger2.debug("Using custom execution function", { workerId: id });
|
|
2723
2566
|
return await executeFn(state, runConfig);
|
|
2724
2567
|
}
|
|
2725
2568
|
if (agent) {
|
|
2726
2569
|
if (isReActAgent(agent)) {
|
|
2727
|
-
|
|
2570
|
+
logger2.debug("Using ReAct agent", { workerId: id });
|
|
2728
2571
|
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
2729
2572
|
return await wrappedFn(state, runConfig);
|
|
2730
2573
|
} else {
|
|
2731
|
-
|
|
2574
|
+
logger2.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
|
|
2732
2575
|
}
|
|
2733
2576
|
}
|
|
2734
2577
|
if (!model) {
|
|
2735
|
-
|
|
2578
|
+
logger2.error("Worker missing required configuration", { workerId: id });
|
|
2736
2579
|
throw new Error(
|
|
2737
2580
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2738
2581
|
);
|
|
2739
2582
|
}
|
|
2740
|
-
|
|
2583
|
+
logger2.debug("Using default LLM execution", {
|
|
2741
2584
|
workerId: id,
|
|
2742
2585
|
hasTools: tools.length > 0,
|
|
2743
2586
|
toolCount: tools.length
|
|
@@ -2753,7 +2596,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2753
2596
|
];
|
|
2754
2597
|
let modelToUse = model;
|
|
2755
2598
|
if (tools.length > 0 && model.bindTools) {
|
|
2756
|
-
|
|
2599
|
+
logger2.debug("Binding tools to model", {
|
|
2757
2600
|
workerId: id,
|
|
2758
2601
|
toolCount: tools.length,
|
|
2759
2602
|
toolNames: tools.map((t) => t.metadata.name)
|
|
@@ -2761,10 +2604,10 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2761
2604
|
const langchainTools = toLangChainTools2(tools);
|
|
2762
2605
|
modelToUse = model.bindTools(langchainTools);
|
|
2763
2606
|
}
|
|
2764
|
-
|
|
2607
|
+
logger2.debug("Invoking LLM", { workerId: id });
|
|
2765
2608
|
const response = await modelToUse.invoke(messages);
|
|
2766
2609
|
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2767
|
-
|
|
2610
|
+
logger2.info("Worker task completed", {
|
|
2768
2611
|
workerId: id,
|
|
2769
2612
|
assignmentId: currentAssignment.id,
|
|
2770
2613
|
resultLength: result.length,
|
|
@@ -2799,7 +2642,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2799
2642
|
currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
|
|
2800
2643
|
}
|
|
2801
2644
|
};
|
|
2802
|
-
|
|
2645
|
+
logger2.debug("Worker state update", {
|
|
2803
2646
|
workerId: id,
|
|
2804
2647
|
newWorkload: updatedWorkers[id].currentWorkload
|
|
2805
2648
|
});
|
|
@@ -2810,7 +2653,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2810
2653
|
};
|
|
2811
2654
|
} catch (error) {
|
|
2812
2655
|
const errorMessage = handleNodeError(error, `worker:${id}`, false);
|
|
2813
|
-
|
|
2656
|
+
logger2.error("Worker node error", {
|
|
2814
2657
|
workerId: id,
|
|
2815
2658
|
error: errorMessage
|
|
2816
2659
|
});
|
|
@@ -2818,7 +2661,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2818
2661
|
(assignment) => assignment.workerId === id
|
|
2819
2662
|
);
|
|
2820
2663
|
if (currentAssignment) {
|
|
2821
|
-
|
|
2664
|
+
logger2.warn("Creating error result for assignment", {
|
|
2822
2665
|
workerId: id,
|
|
2823
2666
|
assignmentId: currentAssignment.id
|
|
2824
2667
|
});
|
|
@@ -2836,7 +2679,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2836
2679
|
status: "routing"
|
|
2837
2680
|
};
|
|
2838
2681
|
}
|
|
2839
|
-
|
|
2682
|
+
logger2.error("No assignment found for error handling", { workerId: id });
|
|
2840
2683
|
return {
|
|
2841
2684
|
status: "failed",
|
|
2842
2685
|
error: errorMessage
|
|
@@ -2853,16 +2696,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2853
2696
|
} = config;
|
|
2854
2697
|
return async (state) => {
|
|
2855
2698
|
try {
|
|
2856
|
-
|
|
2699
|
+
logger2.info("Aggregator node executing", {
|
|
2857
2700
|
completedTasks: state.completedTasks.length,
|
|
2858
2701
|
successfulTasks: state.completedTasks.filter((t) => t.success).length,
|
|
2859
2702
|
failedTasks: state.completedTasks.filter((t) => !t.success).length
|
|
2860
2703
|
});
|
|
2861
|
-
|
|
2704
|
+
logger2.debug("Combining results from workers");
|
|
2862
2705
|
if (aggregateFn) {
|
|
2863
|
-
|
|
2706
|
+
logger2.debug("Using custom aggregation function");
|
|
2864
2707
|
const response2 = await aggregateFn(state);
|
|
2865
|
-
|
|
2708
|
+
logger2.info("Custom aggregation complete", {
|
|
2866
2709
|
responseLength: response2.length
|
|
2867
2710
|
});
|
|
2868
2711
|
return {
|
|
@@ -2871,16 +2714,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2871
2714
|
};
|
|
2872
2715
|
}
|
|
2873
2716
|
if (state.completedTasks.length === 0) {
|
|
2874
|
-
|
|
2717
|
+
logger2.warn("No completed tasks to aggregate");
|
|
2875
2718
|
return {
|
|
2876
2719
|
response: "No tasks were completed.",
|
|
2877
2720
|
status: "completed"
|
|
2878
2721
|
};
|
|
2879
2722
|
}
|
|
2880
2723
|
if (!model) {
|
|
2881
|
-
|
|
2724
|
+
logger2.debug("No model provided, concatenating results");
|
|
2882
2725
|
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2883
|
-
|
|
2726
|
+
logger2.info("Simple concatenation complete", {
|
|
2884
2727
|
resultLength: combinedResults.length
|
|
2885
2728
|
});
|
|
2886
2729
|
return {
|
|
@@ -2888,7 +2731,7 @@ function createAggregatorNode(config = {}) {
|
|
|
2888
2731
|
status: "completed"
|
|
2889
2732
|
};
|
|
2890
2733
|
}
|
|
2891
|
-
|
|
2734
|
+
logger2.debug("Using LLM for intelligent aggregation", {
|
|
2892
2735
|
taskCount: state.completedTasks.length
|
|
2893
2736
|
});
|
|
2894
2737
|
const taskResults = state.completedTasks.map((task, idx) => {
|
|
@@ -2907,20 +2750,20 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2907
2750
|
new SystemMessage5(systemPrompt),
|
|
2908
2751
|
new HumanMessage5(userPrompt)
|
|
2909
2752
|
];
|
|
2910
|
-
|
|
2753
|
+
logger2.debug("Invoking aggregation LLM");
|
|
2911
2754
|
const response = await model.invoke(messages);
|
|
2912
2755
|
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2913
|
-
|
|
2756
|
+
logger2.info("Aggregation complete", {
|
|
2914
2757
|
responseLength: aggregatedResponse.length,
|
|
2915
2758
|
responsePreview: aggregatedResponse.substring(0, 100)
|
|
2916
2759
|
});
|
|
2917
|
-
|
|
2760
|
+
logger2.debug("Aggregation complete");
|
|
2918
2761
|
return {
|
|
2919
2762
|
response: aggregatedResponse,
|
|
2920
2763
|
status: "completed"
|
|
2921
2764
|
};
|
|
2922
2765
|
} catch (error) {
|
|
2923
|
-
|
|
2766
|
+
logger2.error("Aggregator node error", {
|
|
2924
2767
|
error: error instanceof Error ? error.message : String(error),
|
|
2925
2768
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2926
2769
|
completedTasks: state.completedTasks.length
|
|
@@ -2935,9 +2778,9 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2935
2778
|
|
|
2936
2779
|
// src/multi-agent/agent.ts
|
|
2937
2780
|
import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
|
|
2938
|
-
import {
|
|
2939
|
-
var
|
|
2940
|
-
var
|
|
2781
|
+
import { createLogger as createLogger4, LogLevel as LogLevel3 } from "@agentforge/core";
|
|
2782
|
+
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel3.INFO;
|
|
2783
|
+
var logger3 = createLogger4("multi-agent:system", { level: logLevel3 });
|
|
2941
2784
|
function createMultiAgentSystem(config) {
|
|
2942
2785
|
const {
|
|
2943
2786
|
supervisor,
|
|
@@ -2954,10 +2797,6 @@ function createMultiAgentSystem(config) {
|
|
|
2954
2797
|
if (supervisor.strategy === "llm-based") {
|
|
2955
2798
|
configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
|
|
2956
2799
|
}
|
|
2957
|
-
if (supervisor.tools && supervisor.tools.length > 0) {
|
|
2958
|
-
const langchainTools = toLangChainTools3(supervisor.tools);
|
|
2959
|
-
configuredModel = configuredModel.bindTools(langchainTools);
|
|
2960
|
-
}
|
|
2961
2800
|
supervisorConfig.model = configuredModel;
|
|
2962
2801
|
}
|
|
2963
2802
|
const supervisorNode = createSupervisorNode(supervisorConfig);
|
|
@@ -2979,46 +2818,46 @@ function createMultiAgentSystem(config) {
|
|
|
2979
2818
|
});
|
|
2980
2819
|
workflow.addNode("aggregator", aggregatorNode);
|
|
2981
2820
|
const supervisorRouter = (state) => {
|
|
2982
|
-
|
|
2821
|
+
logger3.debug("Supervisor router executing", {
|
|
2983
2822
|
status: state.status,
|
|
2984
2823
|
currentAgent: state.currentAgent,
|
|
2985
2824
|
iteration: state.iteration
|
|
2986
2825
|
});
|
|
2987
2826
|
if (state.status === "completed" || state.status === "failed") {
|
|
2988
|
-
|
|
2827
|
+
logger3.info("Supervisor router: ending workflow", { status: state.status });
|
|
2989
2828
|
return END4;
|
|
2990
2829
|
}
|
|
2991
2830
|
if (state.status === "aggregating") {
|
|
2992
|
-
|
|
2831
|
+
logger3.info("Supervisor router: routing to aggregator");
|
|
2993
2832
|
return "aggregator";
|
|
2994
2833
|
}
|
|
2995
2834
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
2996
2835
|
if (state.currentAgent.includes(",")) {
|
|
2997
2836
|
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
2998
|
-
|
|
2837
|
+
logger3.info("Supervisor router: parallel routing", {
|
|
2999
2838
|
agents,
|
|
3000
2839
|
count: agents.length
|
|
3001
2840
|
});
|
|
3002
2841
|
return agents;
|
|
3003
2842
|
}
|
|
3004
|
-
|
|
2843
|
+
logger3.info("Supervisor router: single agent routing", {
|
|
3005
2844
|
targetAgent: state.currentAgent
|
|
3006
2845
|
});
|
|
3007
2846
|
return state.currentAgent;
|
|
3008
2847
|
}
|
|
3009
|
-
|
|
2848
|
+
logger3.debug("Supervisor router: staying at supervisor");
|
|
3010
2849
|
return "supervisor";
|
|
3011
2850
|
};
|
|
3012
2851
|
const workerRouter = (state) => {
|
|
3013
|
-
|
|
2852
|
+
logger3.debug("Worker router executing", {
|
|
3014
2853
|
iteration: state.iteration,
|
|
3015
2854
|
completedTasks: state.completedTasks.length
|
|
3016
2855
|
});
|
|
3017
|
-
|
|
2856
|
+
logger3.debug("Worker router: returning to supervisor");
|
|
3018
2857
|
return "supervisor";
|
|
3019
2858
|
};
|
|
3020
2859
|
const aggregatorRouter = (state) => {
|
|
3021
|
-
|
|
2860
|
+
logger3.info("Aggregator router: ending workflow", {
|
|
3022
2861
|
completedTasks: state.completedTasks.length,
|
|
3023
2862
|
status: state.status
|
|
3024
2863
|
});
|