@agentforge/patterns 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +87 -301
- package/dist/index.d.cts +0 -25
- package/dist/index.d.ts +0 -25
- package/dist/index.js +87 -301
- package/package.json +1 -1
package/dist/index.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) {
|
|
@@ -2060,67 +2060,7 @@ var MultiAgentStateConfig = {
|
|
|
2060
2060
|
var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
|
|
2061
2061
|
|
|
2062
2062
|
// 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
|
-
}
|
|
2063
|
+
import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4 } from "@langchain/core/messages";
|
|
2124
2064
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
2125
2065
|
|
|
2126
2066
|
Your job is to:
|
|
@@ -2156,151 +2096,48 @@ Choose parallel routing when the task benefits from multiple perspectives or dat
|
|
|
2156
2096
|
var llmBasedRouting = {
|
|
2157
2097
|
name: "llm-based",
|
|
2158
2098
|
async route(state, config) {
|
|
2159
|
-
logger.info("Starting LLM-based routing", {
|
|
2160
|
-
iteration: state.iteration,
|
|
2161
|
-
availableWorkers: Object.keys(state.workers).length
|
|
2162
|
-
});
|
|
2163
2099
|
if (!config.model) {
|
|
2164
2100
|
throw new Error("LLM-based routing requires a model to be configured");
|
|
2165
2101
|
}
|
|
2166
2102
|
const systemPrompt = config.systemPrompt || DEFAULT_SUPERVISOR_SYSTEM_PROMPT;
|
|
2167
|
-
const maxRetries = config.maxToolRetries || 3;
|
|
2168
|
-
const tools = config.tools || [];
|
|
2169
2103
|
const workerInfo = Object.entries(state.workers).map(([id, caps]) => {
|
|
2170
2104
|
const skills = caps.skills.join(", ");
|
|
2171
|
-
const
|
|
2105
|
+
const tools = caps.tools.join(", ");
|
|
2172
2106
|
const available = caps.available ? "available" : "busy";
|
|
2173
|
-
return `- ${id}: Skills: [${skills}], Tools: [${
|
|
2107
|
+
return `- ${id}: Skills: [${skills}], Tools: [${tools}], Status: ${available}, Workload: ${caps.currentWorkload}`;
|
|
2174
2108
|
}).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
2109
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2184
2110
|
const taskContext = lastMessage?.content || state.input;
|
|
2185
|
-
logger.debug("Task context", {
|
|
2186
|
-
taskLength: taskContext.length,
|
|
2187
|
-
taskPreview: taskContext.substring(0, 100)
|
|
2188
|
-
});
|
|
2189
2111
|
const userPrompt = `Current task: ${taskContext}
|
|
2190
2112
|
|
|
2191
2113
|
Available workers:
|
|
2192
2114
|
${workerInfo}
|
|
2193
2115
|
|
|
2194
2116
|
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`);
|
|
2117
|
+
const messages = [
|
|
2118
|
+
new SystemMessage4(systemPrompt),
|
|
2119
|
+
new HumanMessage4(userPrompt)
|
|
2120
|
+
];
|
|
2121
|
+
const decision = await config.model.invoke(messages);
|
|
2122
|
+
return {
|
|
2123
|
+
targetAgent: decision.targetAgent,
|
|
2124
|
+
targetAgents: decision.targetAgents,
|
|
2125
|
+
reasoning: decision.reasoning,
|
|
2126
|
+
confidence: decision.confidence,
|
|
2127
|
+
strategy: "llm-based",
|
|
2128
|
+
timestamp: Date.now()
|
|
2129
|
+
};
|
|
2280
2130
|
}
|
|
2281
2131
|
};
|
|
2282
2132
|
var roundRobinRouting = {
|
|
2283
2133
|
name: "round-robin",
|
|
2284
2134
|
async route(state, config) {
|
|
2285
|
-
logger.info("Starting round-robin routing", {
|
|
2286
|
-
iteration: state.iteration
|
|
2287
|
-
});
|
|
2288
2135
|
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
2136
|
if (availableWorkers.length === 0) {
|
|
2294
|
-
logger.error("No available workers for round-robin routing");
|
|
2295
2137
|
throw new Error("No available workers for round-robin routing");
|
|
2296
2138
|
}
|
|
2297
2139
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
2298
2140
|
const targetAgent = availableWorkers[lastRoutingIndex];
|
|
2299
|
-
logger.info("Round-robin routing decision", {
|
|
2300
|
-
targetAgent,
|
|
2301
|
-
index: lastRoutingIndex + 1,
|
|
2302
|
-
totalWorkers: availableWorkers.length
|
|
2303
|
-
});
|
|
2304
2141
|
return {
|
|
2305
2142
|
targetAgent,
|
|
2306
2143
|
targetAgents: null,
|
|
@@ -2314,15 +2151,8 @@ var roundRobinRouting = {
|
|
|
2314
2151
|
var skillBasedRouting = {
|
|
2315
2152
|
name: "skill-based",
|
|
2316
2153
|
async route(state, config) {
|
|
2317
|
-
logger.info("Starting skill-based routing", {
|
|
2318
|
-
iteration: state.iteration
|
|
2319
|
-
});
|
|
2320
2154
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2321
2155
|
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
2156
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2327
2157
|
const skillMatches = caps.skills.filter(
|
|
2328
2158
|
(skill) => taskContent.includes(skill.toLowerCase())
|
|
@@ -2333,20 +2163,11 @@ var skillBasedRouting = {
|
|
|
2333
2163
|
const score = skillMatches * 2 + toolMatches;
|
|
2334
2164
|
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
2335
2165
|
}).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
2166
|
if (workerScores.length === 0) {
|
|
2340
|
-
logger.warn("No skill matches found, using fallback");
|
|
2341
2167
|
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
2342
2168
|
if (!firstAvailable) {
|
|
2343
|
-
logger.error("No available workers for skill-based routing");
|
|
2344
2169
|
throw new Error("No available workers for skill-based routing");
|
|
2345
2170
|
}
|
|
2346
|
-
logger.info("Skill-based routing fallback decision", {
|
|
2347
|
-
targetAgent: firstAvailable[0],
|
|
2348
|
-
confidence: 0.5
|
|
2349
|
-
});
|
|
2350
2171
|
return {
|
|
2351
2172
|
targetAgent: firstAvailable[0],
|
|
2352
2173
|
targetAgents: null,
|
|
@@ -2358,12 +2179,6 @@ var skillBasedRouting = {
|
|
|
2358
2179
|
}
|
|
2359
2180
|
const best = workerScores[0];
|
|
2360
2181
|
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
2182
|
return {
|
|
2368
2183
|
targetAgent: best.id,
|
|
2369
2184
|
targetAgents: null,
|
|
@@ -2377,26 +2192,13 @@ var skillBasedRouting = {
|
|
|
2377
2192
|
var loadBalancedRouting = {
|
|
2378
2193
|
name: "load-balanced",
|
|
2379
2194
|
async route(state, config) {
|
|
2380
|
-
logger.info("Starting load-balanced routing", {
|
|
2381
|
-
iteration: state.iteration
|
|
2382
|
-
});
|
|
2383
2195
|
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
2196
|
if (availableWorkers.length === 0) {
|
|
2388
|
-
logger.error("No available workers for load-balanced routing");
|
|
2389
2197
|
throw new Error("No available workers for load-balanced routing");
|
|
2390
2198
|
}
|
|
2391
2199
|
const targetWorker = availableWorkers[0];
|
|
2392
2200
|
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2393
2201
|
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
2202
|
return {
|
|
2401
2203
|
targetAgent: targetWorker.id,
|
|
2402
2204
|
targetAgents: null,
|
|
@@ -2410,24 +2212,13 @@ var loadBalancedRouting = {
|
|
|
2410
2212
|
var ruleBasedRouting = {
|
|
2411
2213
|
name: "rule-based",
|
|
2412
2214
|
async route(state, config) {
|
|
2413
|
-
logger.info("Starting rule-based routing", {
|
|
2414
|
-
iteration: state.iteration
|
|
2415
|
-
});
|
|
2416
2215
|
if (!config.routingFn) {
|
|
2417
|
-
logger.error("Rule-based routing requires a custom routing function");
|
|
2418
2216
|
throw new Error("Rule-based routing requires a custom routing function");
|
|
2419
2217
|
}
|
|
2420
|
-
|
|
2421
|
-
logger.info("Rule-based routing decision", {
|
|
2422
|
-
targetAgent: decision.targetAgent,
|
|
2423
|
-
targetAgents: decision.targetAgents,
|
|
2424
|
-
confidence: decision.confidence
|
|
2425
|
-
});
|
|
2426
|
-
return decision;
|
|
2218
|
+
return await config.routingFn(state);
|
|
2427
2219
|
}
|
|
2428
2220
|
};
|
|
2429
2221
|
function getRoutingStrategy(name) {
|
|
2430
|
-
logger.debug("Getting routing strategy", { name });
|
|
2431
2222
|
switch (name) {
|
|
2432
2223
|
case "llm-based":
|
|
2433
2224
|
return llmBasedRouting;
|
|
@@ -2440,15 +2231,14 @@ function getRoutingStrategy(name) {
|
|
|
2440
2231
|
case "rule-based":
|
|
2441
2232
|
return ruleBasedRouting;
|
|
2442
2233
|
default:
|
|
2443
|
-
logger.error("Unknown routing strategy", { name });
|
|
2444
2234
|
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2445
2235
|
}
|
|
2446
2236
|
}
|
|
2447
2237
|
|
|
2448
2238
|
// src/multi-agent/utils.ts
|
|
2449
|
-
import { createLogger as
|
|
2450
|
-
var
|
|
2451
|
-
var
|
|
2239
|
+
import { createLogger as createLogger2, LogLevel } from "@agentforge/core";
|
|
2240
|
+
var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
|
|
2241
|
+
var logger = createLogger2("multi-agent", { level: logLevel });
|
|
2452
2242
|
function isReActAgent(obj) {
|
|
2453
2243
|
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
2244
|
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
|
|
@@ -2456,9 +2246,9 @@ function isReActAgent(obj) {
|
|
|
2456
2246
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2457
2247
|
return async (state, config) => {
|
|
2458
2248
|
try {
|
|
2459
|
-
|
|
2249
|
+
logger.debug("Wrapping ReAct agent execution", { workerId });
|
|
2460
2250
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2461
|
-
|
|
2251
|
+
logger.debug("Extracted task", {
|
|
2462
2252
|
workerId,
|
|
2463
2253
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2464
2254
|
});
|
|
@@ -2466,7 +2256,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2466
2256
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2467
2257
|
);
|
|
2468
2258
|
if (!currentAssignment) {
|
|
2469
|
-
|
|
2259
|
+
logger.debug("No active assignment found", { workerId });
|
|
2470
2260
|
return {};
|
|
2471
2261
|
}
|
|
2472
2262
|
const result = await agent.invoke(
|
|
@@ -2477,14 +2267,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2477
2267
|
// Pass through the config for checkpointing and interrupt support
|
|
2478
2268
|
);
|
|
2479
2269
|
const response = result.messages?.[result.messages.length - 1]?.content || "No response";
|
|
2480
|
-
|
|
2270
|
+
logger.debug("Received response from ReAct agent", {
|
|
2481
2271
|
workerId,
|
|
2482
2272
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2483
2273
|
});
|
|
2484
2274
|
const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
|
|
2485
2275
|
const uniqueTools = [...new Set(toolsUsed)];
|
|
2486
2276
|
if (uniqueTools.length > 0) {
|
|
2487
|
-
|
|
2277
|
+
logger.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2488
2278
|
}
|
|
2489
2279
|
const taskResult = {
|
|
2490
2280
|
assignmentId: currentAssignment.id,
|
|
@@ -2503,7 +2293,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2503
2293
|
};
|
|
2504
2294
|
} catch (error) {
|
|
2505
2295
|
const errorMessage = handleNodeError(error, `react-agent:${workerId}`, false);
|
|
2506
|
-
|
|
2296
|
+
logger.error("Error in ReAct agent execution", {
|
|
2507
2297
|
workerId,
|
|
2508
2298
|
error: errorMessage
|
|
2509
2299
|
});
|
|
@@ -2535,9 +2325,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2535
2325
|
|
|
2536
2326
|
// src/multi-agent/nodes.ts
|
|
2537
2327
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2538
|
-
import { toLangChainTools as toLangChainTools2, createLogger as
|
|
2539
|
-
var
|
|
2540
|
-
var
|
|
2328
|
+
import { toLangChainTools as toLangChainTools2, createLogger as createLogger3, LogLevel as LogLevel2 } from "@agentforge/core";
|
|
2329
|
+
var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel2.INFO;
|
|
2330
|
+
var logger2 = createLogger3("multi-agent:nodes", { level: logLevel2 });
|
|
2541
2331
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2542
2332
|
|
|
2543
2333
|
Your job is to:
|
|
@@ -2555,19 +2345,19 @@ function createSupervisorNode(config) {
|
|
|
2555
2345
|
} = config;
|
|
2556
2346
|
return async (state) => {
|
|
2557
2347
|
try {
|
|
2558
|
-
|
|
2348
|
+
logger2.info("Supervisor node executing", {
|
|
2559
2349
|
iteration: state.iteration,
|
|
2560
2350
|
maxIterations,
|
|
2561
2351
|
activeAssignments: state.activeAssignments.length,
|
|
2562
2352
|
completedTasks: state.completedTasks.length
|
|
2563
2353
|
});
|
|
2564
|
-
|
|
2354
|
+
logger2.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
|
|
2565
2355
|
if (state.iteration >= maxIterations) {
|
|
2566
|
-
|
|
2356
|
+
logger2.warn("Max iterations reached", {
|
|
2567
2357
|
iteration: state.iteration,
|
|
2568
2358
|
maxIterations
|
|
2569
2359
|
});
|
|
2570
|
-
|
|
2360
|
+
logger2.debug("Max iterations reached, moving to aggregation");
|
|
2571
2361
|
return {
|
|
2572
2362
|
status: "aggregating",
|
|
2573
2363
|
currentAgent: "aggregator"
|
|
@@ -2576,26 +2366,26 @@ function createSupervisorNode(config) {
|
|
|
2576
2366
|
const allCompleted = state.activeAssignments.every(
|
|
2577
2367
|
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2578
2368
|
);
|
|
2579
|
-
|
|
2369
|
+
logger2.debug("Checking task completion", {
|
|
2580
2370
|
activeAssignments: state.activeAssignments.length,
|
|
2581
2371
|
completedTasks: state.completedTasks.length,
|
|
2582
2372
|
allCompleted
|
|
2583
2373
|
});
|
|
2584
2374
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2585
|
-
|
|
2375
|
+
logger2.info("All tasks completed, moving to aggregation", {
|
|
2586
2376
|
completedCount: state.completedTasks.length
|
|
2587
2377
|
});
|
|
2588
|
-
|
|
2378
|
+
logger2.debug("All tasks completed, moving to aggregation");
|
|
2589
2379
|
return {
|
|
2590
2380
|
status: "aggregating",
|
|
2591
2381
|
currentAgent: "aggregator"
|
|
2592
2382
|
};
|
|
2593
2383
|
}
|
|
2594
|
-
|
|
2384
|
+
logger2.debug("Getting routing strategy", { strategy });
|
|
2595
2385
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2596
2386
|
const decision = await routingImpl.route(state, config);
|
|
2597
2387
|
const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
|
|
2598
|
-
|
|
2388
|
+
logger2.debug("Target agents determined", {
|
|
2599
2389
|
targetAgents,
|
|
2600
2390
|
isParallel: targetAgents.length > 1,
|
|
2601
2391
|
decision: {
|
|
@@ -2604,17 +2394,17 @@ function createSupervisorNode(config) {
|
|
|
2604
2394
|
}
|
|
2605
2395
|
});
|
|
2606
2396
|
if (targetAgents.length === 0) {
|
|
2607
|
-
|
|
2397
|
+
logger2.error("No target agents specified in routing decision");
|
|
2608
2398
|
throw new Error("Routing decision must specify at least one target agent");
|
|
2609
2399
|
}
|
|
2610
2400
|
if (targetAgents.length === 1) {
|
|
2611
|
-
|
|
2401
|
+
logger2.info("Routing to single agent", {
|
|
2612
2402
|
targetAgent: targetAgents[0],
|
|
2613
2403
|
reasoning: decision.reasoning,
|
|
2614
2404
|
confidence: decision.confidence
|
|
2615
2405
|
});
|
|
2616
2406
|
} else {
|
|
2617
|
-
|
|
2407
|
+
logger2.info("Routing to multiple agents in parallel", {
|
|
2618
2408
|
targetAgents,
|
|
2619
2409
|
count: targetAgents.length,
|
|
2620
2410
|
reasoning: decision.reasoning,
|
|
@@ -2622,9 +2412,9 @@ function createSupervisorNode(config) {
|
|
|
2622
2412
|
});
|
|
2623
2413
|
}
|
|
2624
2414
|
if (targetAgents.length === 1) {
|
|
2625
|
-
|
|
2415
|
+
logger2.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2626
2416
|
} else {
|
|
2627
|
-
|
|
2417
|
+
logger2.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
|
|
2628
2418
|
}
|
|
2629
2419
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2630
2420
|
const assignments = targetAgents.map((workerId) => ({
|
|
@@ -2634,7 +2424,7 @@ function createSupervisorNode(config) {
|
|
|
2634
2424
|
priority: 5,
|
|
2635
2425
|
assignedAt: Date.now()
|
|
2636
2426
|
}));
|
|
2637
|
-
|
|
2427
|
+
logger2.debug("Created task assignments", {
|
|
2638
2428
|
assignmentCount: assignments.length,
|
|
2639
2429
|
assignments: assignments.map((a) => ({
|
|
2640
2430
|
id: a.id,
|
|
@@ -2654,7 +2444,7 @@ function createSupervisorNode(config) {
|
|
|
2654
2444
|
priority: assignment.priority
|
|
2655
2445
|
}
|
|
2656
2446
|
}));
|
|
2657
|
-
|
|
2447
|
+
logger2.info("Supervisor routing complete", {
|
|
2658
2448
|
currentAgent: targetAgents.join(","),
|
|
2659
2449
|
status: "executing",
|
|
2660
2450
|
assignmentCount: assignments.length,
|
|
@@ -2671,7 +2461,7 @@ function createSupervisorNode(config) {
|
|
|
2671
2461
|
iteration: state.iteration + 1
|
|
2672
2462
|
};
|
|
2673
2463
|
} catch (error) {
|
|
2674
|
-
|
|
2464
|
+
logger2.error("Supervisor node error", {
|
|
2675
2465
|
error: error instanceof Error ? error.message : String(error),
|
|
2676
2466
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2677
2467
|
iteration: state.iteration
|
|
@@ -2696,7 +2486,7 @@ function createWorkerNode(config) {
|
|
|
2696
2486
|
} = config;
|
|
2697
2487
|
return async (state, runConfig) => {
|
|
2698
2488
|
try {
|
|
2699
|
-
|
|
2489
|
+
logger2.info("Worker node executing", {
|
|
2700
2490
|
workerId: id,
|
|
2701
2491
|
iteration: state.iteration,
|
|
2702
2492
|
activeAssignments: state.activeAssignments.length
|
|
@@ -2705,39 +2495,39 @@ function createWorkerNode(config) {
|
|
|
2705
2495
|
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2706
2496
|
);
|
|
2707
2497
|
if (!currentAssignment) {
|
|
2708
|
-
|
|
2498
|
+
logger2.debug("No active assignment found for worker", {
|
|
2709
2499
|
workerId: id,
|
|
2710
2500
|
totalActiveAssignments: state.activeAssignments.length,
|
|
2711
2501
|
completedTasks: state.completedTasks.length
|
|
2712
2502
|
});
|
|
2713
2503
|
return {};
|
|
2714
2504
|
}
|
|
2715
|
-
|
|
2505
|
+
logger2.info("Worker processing assignment", {
|
|
2716
2506
|
workerId: id,
|
|
2717
2507
|
assignmentId: currentAssignment.id,
|
|
2718
2508
|
taskLength: currentAssignment.task.length,
|
|
2719
2509
|
taskPreview: currentAssignment.task.substring(0, 100)
|
|
2720
2510
|
});
|
|
2721
2511
|
if (executeFn) {
|
|
2722
|
-
|
|
2512
|
+
logger2.debug("Using custom execution function", { workerId: id });
|
|
2723
2513
|
return await executeFn(state, runConfig);
|
|
2724
2514
|
}
|
|
2725
2515
|
if (agent) {
|
|
2726
2516
|
if (isReActAgent(agent)) {
|
|
2727
|
-
|
|
2517
|
+
logger2.debug("Using ReAct agent", { workerId: id });
|
|
2728
2518
|
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
2729
2519
|
return await wrappedFn(state, runConfig);
|
|
2730
2520
|
} else {
|
|
2731
|
-
|
|
2521
|
+
logger2.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
|
|
2732
2522
|
}
|
|
2733
2523
|
}
|
|
2734
2524
|
if (!model) {
|
|
2735
|
-
|
|
2525
|
+
logger2.error("Worker missing required configuration", { workerId: id });
|
|
2736
2526
|
throw new Error(
|
|
2737
2527
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2738
2528
|
);
|
|
2739
2529
|
}
|
|
2740
|
-
|
|
2530
|
+
logger2.debug("Using default LLM execution", {
|
|
2741
2531
|
workerId: id,
|
|
2742
2532
|
hasTools: tools.length > 0,
|
|
2743
2533
|
toolCount: tools.length
|
|
@@ -2753,7 +2543,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2753
2543
|
];
|
|
2754
2544
|
let modelToUse = model;
|
|
2755
2545
|
if (tools.length > 0 && model.bindTools) {
|
|
2756
|
-
|
|
2546
|
+
logger2.debug("Binding tools to model", {
|
|
2757
2547
|
workerId: id,
|
|
2758
2548
|
toolCount: tools.length,
|
|
2759
2549
|
toolNames: tools.map((t) => t.metadata.name)
|
|
@@ -2761,10 +2551,10 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2761
2551
|
const langchainTools = toLangChainTools2(tools);
|
|
2762
2552
|
modelToUse = model.bindTools(langchainTools);
|
|
2763
2553
|
}
|
|
2764
|
-
|
|
2554
|
+
logger2.debug("Invoking LLM", { workerId: id });
|
|
2765
2555
|
const response = await modelToUse.invoke(messages);
|
|
2766
2556
|
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2767
|
-
|
|
2557
|
+
logger2.info("Worker task completed", {
|
|
2768
2558
|
workerId: id,
|
|
2769
2559
|
assignmentId: currentAssignment.id,
|
|
2770
2560
|
resultLength: result.length,
|
|
@@ -2799,7 +2589,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2799
2589
|
currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
|
|
2800
2590
|
}
|
|
2801
2591
|
};
|
|
2802
|
-
|
|
2592
|
+
logger2.debug("Worker state update", {
|
|
2803
2593
|
workerId: id,
|
|
2804
2594
|
newWorkload: updatedWorkers[id].currentWorkload
|
|
2805
2595
|
});
|
|
@@ -2810,7 +2600,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2810
2600
|
};
|
|
2811
2601
|
} catch (error) {
|
|
2812
2602
|
const errorMessage = handleNodeError(error, `worker:${id}`, false);
|
|
2813
|
-
|
|
2603
|
+
logger2.error("Worker node error", {
|
|
2814
2604
|
workerId: id,
|
|
2815
2605
|
error: errorMessage
|
|
2816
2606
|
});
|
|
@@ -2818,7 +2608,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2818
2608
|
(assignment) => assignment.workerId === id
|
|
2819
2609
|
);
|
|
2820
2610
|
if (currentAssignment) {
|
|
2821
|
-
|
|
2611
|
+
logger2.warn("Creating error result for assignment", {
|
|
2822
2612
|
workerId: id,
|
|
2823
2613
|
assignmentId: currentAssignment.id
|
|
2824
2614
|
});
|
|
@@ -2836,7 +2626,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2836
2626
|
status: "routing"
|
|
2837
2627
|
};
|
|
2838
2628
|
}
|
|
2839
|
-
|
|
2629
|
+
logger2.error("No assignment found for error handling", { workerId: id });
|
|
2840
2630
|
return {
|
|
2841
2631
|
status: "failed",
|
|
2842
2632
|
error: errorMessage
|
|
@@ -2853,16 +2643,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2853
2643
|
} = config;
|
|
2854
2644
|
return async (state) => {
|
|
2855
2645
|
try {
|
|
2856
|
-
|
|
2646
|
+
logger2.info("Aggregator node executing", {
|
|
2857
2647
|
completedTasks: state.completedTasks.length,
|
|
2858
2648
|
successfulTasks: state.completedTasks.filter((t) => t.success).length,
|
|
2859
2649
|
failedTasks: state.completedTasks.filter((t) => !t.success).length
|
|
2860
2650
|
});
|
|
2861
|
-
|
|
2651
|
+
logger2.debug("Combining results from workers");
|
|
2862
2652
|
if (aggregateFn) {
|
|
2863
|
-
|
|
2653
|
+
logger2.debug("Using custom aggregation function");
|
|
2864
2654
|
const response2 = await aggregateFn(state);
|
|
2865
|
-
|
|
2655
|
+
logger2.info("Custom aggregation complete", {
|
|
2866
2656
|
responseLength: response2.length
|
|
2867
2657
|
});
|
|
2868
2658
|
return {
|
|
@@ -2871,16 +2661,16 @@ function createAggregatorNode(config = {}) {
|
|
|
2871
2661
|
};
|
|
2872
2662
|
}
|
|
2873
2663
|
if (state.completedTasks.length === 0) {
|
|
2874
|
-
|
|
2664
|
+
logger2.warn("No completed tasks to aggregate");
|
|
2875
2665
|
return {
|
|
2876
2666
|
response: "No tasks were completed.",
|
|
2877
2667
|
status: "completed"
|
|
2878
2668
|
};
|
|
2879
2669
|
}
|
|
2880
2670
|
if (!model) {
|
|
2881
|
-
|
|
2671
|
+
logger2.debug("No model provided, concatenating results");
|
|
2882
2672
|
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2883
|
-
|
|
2673
|
+
logger2.info("Simple concatenation complete", {
|
|
2884
2674
|
resultLength: combinedResults.length
|
|
2885
2675
|
});
|
|
2886
2676
|
return {
|
|
@@ -2888,7 +2678,7 @@ function createAggregatorNode(config = {}) {
|
|
|
2888
2678
|
status: "completed"
|
|
2889
2679
|
};
|
|
2890
2680
|
}
|
|
2891
|
-
|
|
2681
|
+
logger2.debug("Using LLM for intelligent aggregation", {
|
|
2892
2682
|
taskCount: state.completedTasks.length
|
|
2893
2683
|
});
|
|
2894
2684
|
const taskResults = state.completedTasks.map((task, idx) => {
|
|
@@ -2907,20 +2697,20 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2907
2697
|
new SystemMessage5(systemPrompt),
|
|
2908
2698
|
new HumanMessage5(userPrompt)
|
|
2909
2699
|
];
|
|
2910
|
-
|
|
2700
|
+
logger2.debug("Invoking aggregation LLM");
|
|
2911
2701
|
const response = await model.invoke(messages);
|
|
2912
2702
|
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2913
|
-
|
|
2703
|
+
logger2.info("Aggregation complete", {
|
|
2914
2704
|
responseLength: aggregatedResponse.length,
|
|
2915
2705
|
responsePreview: aggregatedResponse.substring(0, 100)
|
|
2916
2706
|
});
|
|
2917
|
-
|
|
2707
|
+
logger2.debug("Aggregation complete");
|
|
2918
2708
|
return {
|
|
2919
2709
|
response: aggregatedResponse,
|
|
2920
2710
|
status: "completed"
|
|
2921
2711
|
};
|
|
2922
2712
|
} catch (error) {
|
|
2923
|
-
|
|
2713
|
+
logger2.error("Aggregator node error", {
|
|
2924
2714
|
error: error instanceof Error ? error.message : String(error),
|
|
2925
2715
|
stack: error instanceof Error ? error.stack : void 0,
|
|
2926
2716
|
completedTasks: state.completedTasks.length
|
|
@@ -2935,9 +2725,9 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2935
2725
|
|
|
2936
2726
|
// src/multi-agent/agent.ts
|
|
2937
2727
|
import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
|
|
2938
|
-
import {
|
|
2939
|
-
var
|
|
2940
|
-
var
|
|
2728
|
+
import { createLogger as createLogger4, LogLevel as LogLevel3 } from "@agentforge/core";
|
|
2729
|
+
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel3.INFO;
|
|
2730
|
+
var logger3 = createLogger4("multi-agent:system", { level: logLevel3 });
|
|
2941
2731
|
function createMultiAgentSystem(config) {
|
|
2942
2732
|
const {
|
|
2943
2733
|
supervisor,
|
|
@@ -2954,10 +2744,6 @@ function createMultiAgentSystem(config) {
|
|
|
2954
2744
|
if (supervisor.strategy === "llm-based") {
|
|
2955
2745
|
configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
|
|
2956
2746
|
}
|
|
2957
|
-
if (supervisor.tools && supervisor.tools.length > 0) {
|
|
2958
|
-
const langchainTools = toLangChainTools3(supervisor.tools);
|
|
2959
|
-
configuredModel = configuredModel.bindTools(langchainTools);
|
|
2960
|
-
}
|
|
2961
2747
|
supervisorConfig.model = configuredModel;
|
|
2962
2748
|
}
|
|
2963
2749
|
const supervisorNode = createSupervisorNode(supervisorConfig);
|
|
@@ -2979,46 +2765,46 @@ function createMultiAgentSystem(config) {
|
|
|
2979
2765
|
});
|
|
2980
2766
|
workflow.addNode("aggregator", aggregatorNode);
|
|
2981
2767
|
const supervisorRouter = (state) => {
|
|
2982
|
-
|
|
2768
|
+
logger3.debug("Supervisor router executing", {
|
|
2983
2769
|
status: state.status,
|
|
2984
2770
|
currentAgent: state.currentAgent,
|
|
2985
2771
|
iteration: state.iteration
|
|
2986
2772
|
});
|
|
2987
2773
|
if (state.status === "completed" || state.status === "failed") {
|
|
2988
|
-
|
|
2774
|
+
logger3.info("Supervisor router: ending workflow", { status: state.status });
|
|
2989
2775
|
return END4;
|
|
2990
2776
|
}
|
|
2991
2777
|
if (state.status === "aggregating") {
|
|
2992
|
-
|
|
2778
|
+
logger3.info("Supervisor router: routing to aggregator");
|
|
2993
2779
|
return "aggregator";
|
|
2994
2780
|
}
|
|
2995
2781
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
2996
2782
|
if (state.currentAgent.includes(",")) {
|
|
2997
2783
|
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
2998
|
-
|
|
2784
|
+
logger3.info("Supervisor router: parallel routing", {
|
|
2999
2785
|
agents,
|
|
3000
2786
|
count: agents.length
|
|
3001
2787
|
});
|
|
3002
2788
|
return agents;
|
|
3003
2789
|
}
|
|
3004
|
-
|
|
2790
|
+
logger3.info("Supervisor router: single agent routing", {
|
|
3005
2791
|
targetAgent: state.currentAgent
|
|
3006
2792
|
});
|
|
3007
2793
|
return state.currentAgent;
|
|
3008
2794
|
}
|
|
3009
|
-
|
|
2795
|
+
logger3.debug("Supervisor router: staying at supervisor");
|
|
3010
2796
|
return "supervisor";
|
|
3011
2797
|
};
|
|
3012
2798
|
const workerRouter = (state) => {
|
|
3013
|
-
|
|
2799
|
+
logger3.debug("Worker router executing", {
|
|
3014
2800
|
iteration: state.iteration,
|
|
3015
2801
|
completedTasks: state.completedTasks.length
|
|
3016
2802
|
});
|
|
3017
|
-
|
|
2803
|
+
logger3.debug("Worker router: returning to supervisor");
|
|
3018
2804
|
return "supervisor";
|
|
3019
2805
|
};
|
|
3020
2806
|
const aggregatorRouter = (state) => {
|
|
3021
|
-
|
|
2807
|
+
logger3.info("Aggregator router: ending workflow", {
|
|
3022
2808
|
completedTasks: state.completedTasks.length,
|
|
3023
2809
|
status: state.status
|
|
3024
2810
|
});
|