@agentforge/patterns 0.6.3 → 0.7.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 +867 -332
- package/dist/index.d.cts +96 -2
- package/dist/index.d.ts +96 -2
- package/dist/index.js +852 -321
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -22,7 +22,9 @@ var ToolResultSchema = z.object({
|
|
|
22
22
|
toolCallId: z.string(),
|
|
23
23
|
result: z.any(),
|
|
24
24
|
error: z.string().optional(),
|
|
25
|
-
timestamp: z.number().optional()
|
|
25
|
+
timestamp: z.number().optional(),
|
|
26
|
+
isDuplicate: z.boolean().optional()
|
|
27
|
+
// Flag indicating this was a duplicate tool call
|
|
26
28
|
});
|
|
27
29
|
var ScratchpadEntrySchema = z.object({
|
|
28
30
|
step: z.number(),
|
|
@@ -33,15 +35,46 @@ var ScratchpadEntrySchema = z.object({
|
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
// src/react/state.ts
|
|
36
|
-
import { z as
|
|
38
|
+
import { z as z3 } from "zod";
|
|
37
39
|
import { createStateAnnotation } from "@agentforge/core";
|
|
40
|
+
|
|
41
|
+
// src/shared/state-fields.ts
|
|
42
|
+
import { z as z2 } from "zod";
|
|
43
|
+
var iterationField = {
|
|
44
|
+
schema: z2.number().int().nonnegative(),
|
|
45
|
+
reducer: (left, right) => left + right,
|
|
46
|
+
default: () => 0,
|
|
47
|
+
description: "Current iteration number"
|
|
48
|
+
};
|
|
49
|
+
function maxIterationsField(defaultValue) {
|
|
50
|
+
return {
|
|
51
|
+
schema: z2.number().int().positive(),
|
|
52
|
+
default: () => defaultValue,
|
|
53
|
+
description: "Maximum number of iterations allowed"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
var errorField = {
|
|
57
|
+
schema: z2.string().optional(),
|
|
58
|
+
description: "Error message if execution failed"
|
|
59
|
+
};
|
|
60
|
+
var responseField = {
|
|
61
|
+
schema: z2.string().optional(),
|
|
62
|
+
description: "Final response after completion"
|
|
63
|
+
};
|
|
64
|
+
var inputField = {
|
|
65
|
+
schema: z2.string(),
|
|
66
|
+
default: () => "",
|
|
67
|
+
description: "Original user input or query"
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/react/state.ts
|
|
38
71
|
var ReActStateConfig = {
|
|
39
72
|
/**
|
|
40
73
|
* Conversation messages
|
|
41
74
|
* Accumulates all messages in the conversation
|
|
42
75
|
*/
|
|
43
76
|
messages: {
|
|
44
|
-
schema:
|
|
77
|
+
schema: z3.array(MessageSchema),
|
|
45
78
|
reducer: (left, right) => [...left, ...right],
|
|
46
79
|
default: () => [],
|
|
47
80
|
description: "Conversation message history"
|
|
@@ -51,7 +84,7 @@ var ReActStateConfig = {
|
|
|
51
84
|
* Accumulates all reasoning steps the agent takes
|
|
52
85
|
*/
|
|
53
86
|
thoughts: {
|
|
54
|
-
schema:
|
|
87
|
+
schema: z3.array(ThoughtSchema),
|
|
55
88
|
reducer: (left, right) => [...left, ...right],
|
|
56
89
|
default: () => [],
|
|
57
90
|
description: "Agent reasoning steps"
|
|
@@ -61,7 +94,7 @@ var ReActStateConfig = {
|
|
|
61
94
|
* Accumulates all tool calls made by the agent
|
|
62
95
|
*/
|
|
63
96
|
actions: {
|
|
64
|
-
schema:
|
|
97
|
+
schema: z3.array(ToolCallSchema),
|
|
65
98
|
reducer: (left, right) => [...left, ...right],
|
|
66
99
|
default: () => [],
|
|
67
100
|
description: "Tool calls made by the agent"
|
|
@@ -71,7 +104,7 @@ var ReActStateConfig = {
|
|
|
71
104
|
* Accumulates all observations from tool executions
|
|
72
105
|
*/
|
|
73
106
|
observations: {
|
|
74
|
-
schema:
|
|
107
|
+
schema: z3.array(ToolResultSchema),
|
|
75
108
|
reducer: (left, right) => [...left, ...right],
|
|
76
109
|
default: () => [],
|
|
77
110
|
description: "Results from tool executions"
|
|
@@ -81,7 +114,7 @@ var ReActStateConfig = {
|
|
|
81
114
|
* Accumulates step-by-step reasoning process
|
|
82
115
|
*/
|
|
83
116
|
scratchpad: {
|
|
84
|
-
schema:
|
|
117
|
+
schema: z3.array(ScratchpadEntrySchema),
|
|
85
118
|
reducer: (left, right) => [...left, ...right],
|
|
86
119
|
default: () => [],
|
|
87
120
|
description: "Intermediate reasoning scratchpad"
|
|
@@ -90,27 +123,19 @@ var ReActStateConfig = {
|
|
|
90
123
|
* Current iteration count
|
|
91
124
|
* Tracks how many thought-action-observation loops have been executed
|
|
92
125
|
*/
|
|
93
|
-
iteration:
|
|
94
|
-
schema: z2.number(),
|
|
95
|
-
reducer: (left, right) => left + right,
|
|
96
|
-
default: () => 0,
|
|
97
|
-
description: "Current iteration count"
|
|
98
|
-
},
|
|
126
|
+
iteration: iterationField,
|
|
99
127
|
/**
|
|
100
128
|
* Whether the agent should continue iterating
|
|
101
129
|
*/
|
|
102
130
|
shouldContinue: {
|
|
103
|
-
schema:
|
|
131
|
+
schema: z3.boolean().optional(),
|
|
104
132
|
default: () => true,
|
|
105
133
|
description: "Whether to continue the ReAct loop"
|
|
106
134
|
},
|
|
107
135
|
/**
|
|
108
136
|
* Final response (if any)
|
|
109
137
|
*/
|
|
110
|
-
response:
|
|
111
|
-
schema: z2.string().optional(),
|
|
112
|
-
description: "Final response from the agent"
|
|
113
|
-
}
|
|
138
|
+
response: responseField
|
|
114
139
|
};
|
|
115
140
|
var ReActState = createStateAnnotation(ReActStateConfig);
|
|
116
141
|
|
|
@@ -147,13 +172,67 @@ function formatScratchpad(scratchpad) {
|
|
|
147
172
|
// src/react/nodes.ts
|
|
148
173
|
import { HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages";
|
|
149
174
|
import { toLangChainTools } from "@agentforge/core";
|
|
175
|
+
|
|
176
|
+
// src/shared/deduplication.ts
|
|
177
|
+
import { createLogger } from "@agentforge/core";
|
|
178
|
+
function generateToolCallCacheKey(toolName, args) {
|
|
179
|
+
const sortedArgs = JSON.stringify(args, Object.keys(args || {}).sort());
|
|
180
|
+
return `${toolName}:${sortedArgs}`;
|
|
181
|
+
}
|
|
182
|
+
function createPatternLogger(name, defaultLevel = "info") {
|
|
183
|
+
const logLevel5 = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
|
|
184
|
+
return createLogger(name, { level: logLevel5 });
|
|
185
|
+
}
|
|
186
|
+
function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
|
|
187
|
+
if (duplicatesSkipped === 0) {
|
|
188
|
+
return "0%";
|
|
189
|
+
}
|
|
190
|
+
const total = toolsExecuted + duplicatesSkipped;
|
|
191
|
+
return `${Math.round(duplicatesSkipped / total * 100)}%`;
|
|
192
|
+
}
|
|
193
|
+
function buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, totalObservations) {
|
|
194
|
+
return {
|
|
195
|
+
toolsExecuted,
|
|
196
|
+
duplicatesSkipped,
|
|
197
|
+
totalObservations,
|
|
198
|
+
deduplicationSavings: calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/shared/error-handling.ts
|
|
203
|
+
function isGraphInterrupt(error) {
|
|
204
|
+
return error !== null && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt";
|
|
205
|
+
}
|
|
206
|
+
function handleNodeError(error, context, verbose = false) {
|
|
207
|
+
if (isGraphInterrupt(error)) {
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
211
|
+
if (verbose) {
|
|
212
|
+
console.error(`[${context}] Error:`, errorMessage);
|
|
213
|
+
if (error instanceof Error && error.stack) {
|
|
214
|
+
console.error(`[${context}] Stack:`, error.stack);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return errorMessage;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/react/nodes.ts
|
|
221
|
+
var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
|
|
222
|
+
var actionLogger = createPatternLogger("agentforge:patterns:react:action");
|
|
223
|
+
var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
|
|
150
224
|
function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
|
|
151
225
|
const langchainTools = toLangChainTools(tools);
|
|
152
226
|
const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
|
|
153
227
|
return async (state) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
228
|
+
const currentIteration = state.iteration || 0;
|
|
229
|
+
const startTime = Date.now();
|
|
230
|
+
reasoningLogger.debug("Reasoning iteration started", {
|
|
231
|
+
iteration: currentIteration + 1,
|
|
232
|
+
maxIterations,
|
|
233
|
+
observationCount: state.observations?.length || 0,
|
|
234
|
+
hasActions: !!state.actions?.length
|
|
235
|
+
});
|
|
157
236
|
const stateMessages = state.messages || [];
|
|
158
237
|
const messages = [
|
|
159
238
|
new SystemMessage(systemPrompt),
|
|
@@ -183,8 +262,15 @@ ${scratchpadText}`));
|
|
|
183
262
|
});
|
|
184
263
|
}
|
|
185
264
|
}
|
|
186
|
-
const currentIteration = state.iteration || 0;
|
|
187
265
|
const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
|
|
266
|
+
reasoningLogger.info("Reasoning complete", {
|
|
267
|
+
iteration: currentIteration + 1,
|
|
268
|
+
thoughtGenerated: !!thought,
|
|
269
|
+
actionCount: toolCalls.length,
|
|
270
|
+
shouldContinue,
|
|
271
|
+
isFinalResponse: toolCalls.length === 0,
|
|
272
|
+
duration: Date.now() - startTime
|
|
273
|
+
});
|
|
188
274
|
return {
|
|
189
275
|
messages: [{ role: "assistant", content: thought }],
|
|
190
276
|
thoughts: thought ? [{ content: thought, timestamp: Date.now() }] : [],
|
|
@@ -197,16 +283,71 @@ ${scratchpadText}`));
|
|
|
197
283
|
};
|
|
198
284
|
};
|
|
199
285
|
}
|
|
200
|
-
function createActionNode(tools, verbose = false) {
|
|
286
|
+
function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
201
287
|
const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
|
|
202
288
|
return async (state) => {
|
|
203
289
|
const actions = state.actions || [];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
290
|
+
const allObservations = state.observations || [];
|
|
291
|
+
const iteration = state.iteration || 0;
|
|
292
|
+
const startTime = Date.now();
|
|
293
|
+
actionLogger.debug("Action node started", {
|
|
294
|
+
actionCount: actions.length,
|
|
295
|
+
iteration,
|
|
296
|
+
cacheEnabled: enableDeduplication
|
|
297
|
+
});
|
|
207
298
|
const recentActions = actions.slice(-10);
|
|
208
299
|
const observations = [];
|
|
300
|
+
const executionCache = /* @__PURE__ */ new Map();
|
|
301
|
+
let cacheSize = 0;
|
|
302
|
+
if (enableDeduplication) {
|
|
303
|
+
for (const observation of allObservations) {
|
|
304
|
+
const correspondingAction = actions.find((a) => a.id === observation.toolCallId);
|
|
305
|
+
if (correspondingAction) {
|
|
306
|
+
const cacheKey = generateToolCallCacheKey(correspondingAction.name, correspondingAction.arguments);
|
|
307
|
+
executionCache.set(cacheKey, observation);
|
|
308
|
+
cacheSize++;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (cacheSize > 0) {
|
|
312
|
+
actionLogger.debug("Deduplication cache built", {
|
|
313
|
+
cacheSize,
|
|
314
|
+
totalObservations: allObservations.length
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
let duplicatesSkipped = 0;
|
|
319
|
+
let toolsExecuted = 0;
|
|
209
320
|
for (const action of recentActions) {
|
|
321
|
+
const existingObservation = allObservations.find((obs) => obs.toolCallId === action.id);
|
|
322
|
+
if (existingObservation) {
|
|
323
|
+
actionLogger.debug("Skipping already-processed action", {
|
|
324
|
+
toolName: action.name,
|
|
325
|
+
toolCallId: action.id,
|
|
326
|
+
iteration
|
|
327
|
+
});
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (enableDeduplication) {
|
|
331
|
+
const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
|
|
332
|
+
const cachedResult = executionCache.get(cacheKey);
|
|
333
|
+
if (cachedResult) {
|
|
334
|
+
duplicatesSkipped++;
|
|
335
|
+
actionLogger.info("Duplicate tool call prevented", {
|
|
336
|
+
toolName: action.name,
|
|
337
|
+
arguments: action.arguments,
|
|
338
|
+
iteration,
|
|
339
|
+
cacheHit: true
|
|
340
|
+
});
|
|
341
|
+
observations.push({
|
|
342
|
+
toolCallId: action.id,
|
|
343
|
+
result: cachedResult.result,
|
|
344
|
+
error: cachedResult.error,
|
|
345
|
+
timestamp: Date.now(),
|
|
346
|
+
isDuplicate: true
|
|
347
|
+
});
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
210
351
|
const tool = toolMap.get(action.name);
|
|
211
352
|
if (!tool) {
|
|
212
353
|
observations.push({
|
|
@@ -218,31 +359,48 @@ function createActionNode(tools, verbose = false) {
|
|
|
218
359
|
continue;
|
|
219
360
|
}
|
|
220
361
|
try {
|
|
362
|
+
const startTime2 = Date.now();
|
|
221
363
|
const result = await tool.execute(action.arguments);
|
|
222
|
-
|
|
364
|
+
const executionTime = Date.now() - startTime2;
|
|
365
|
+
toolsExecuted++;
|
|
366
|
+
actionLogger.debug("Tool executed successfully", {
|
|
367
|
+
toolName: action.name,
|
|
368
|
+
executionTime,
|
|
369
|
+
iteration
|
|
370
|
+
});
|
|
371
|
+
const observation = {
|
|
223
372
|
toolCallId: action.id,
|
|
224
373
|
result,
|
|
225
374
|
timestamp: Date.now()
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
375
|
+
};
|
|
376
|
+
observations.push(observation);
|
|
377
|
+
if (enableDeduplication) {
|
|
378
|
+
const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
|
|
379
|
+
executionCache.set(cacheKey, observation);
|
|
229
380
|
}
|
|
230
381
|
} catch (error) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
382
|
+
const errorMessage = handleNodeError(error, `action:${action.name}`, false);
|
|
383
|
+
actionLogger.error("Tool execution failed", {
|
|
384
|
+
toolName: action.name,
|
|
385
|
+
error: errorMessage,
|
|
386
|
+
iteration
|
|
387
|
+
});
|
|
235
388
|
observations.push({
|
|
236
389
|
toolCallId: action.id,
|
|
237
390
|
result: null,
|
|
238
391
|
error: errorMessage,
|
|
239
392
|
timestamp: Date.now()
|
|
240
393
|
});
|
|
241
|
-
if (verbose) {
|
|
242
|
-
console.error(`[action] Tool '${action.name}' failed:`, errorMessage);
|
|
243
|
-
}
|
|
244
394
|
}
|
|
245
395
|
}
|
|
396
|
+
if (duplicatesSkipped > 0 || toolsExecuted > 0) {
|
|
397
|
+
const metrics = buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, observations.length);
|
|
398
|
+
actionLogger.info("Action node complete", {
|
|
399
|
+
iteration,
|
|
400
|
+
...metrics,
|
|
401
|
+
duration: Date.now() - startTime
|
|
402
|
+
});
|
|
403
|
+
}
|
|
246
404
|
return {
|
|
247
405
|
observations
|
|
248
406
|
};
|
|
@@ -253,9 +411,11 @@ function createObservationNode(verbose = false) {
|
|
|
253
411
|
const observations = state.observations || [];
|
|
254
412
|
const thoughts = state.thoughts || [];
|
|
255
413
|
const actions = state.actions || [];
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
414
|
+
const iteration = state.iteration || 0;
|
|
415
|
+
observationLogger.debug("Processing observations", {
|
|
416
|
+
observationCount: observations.length,
|
|
417
|
+
iteration
|
|
418
|
+
});
|
|
259
419
|
const recentObservations = observations.slice(-10);
|
|
260
420
|
const currentStep = state.iteration;
|
|
261
421
|
const latestThought = thoughts[thoughts.length - 1]?.content || "";
|
|
@@ -281,6 +441,11 @@ function createObservationNode(verbose = false) {
|
|
|
281
441
|
name: latestActions.find((a) => a.id === obs.toolCallId)?.name
|
|
282
442
|
};
|
|
283
443
|
});
|
|
444
|
+
observationLogger.debug("Observation node complete", {
|
|
445
|
+
iteration,
|
|
446
|
+
scratchpadUpdated: true,
|
|
447
|
+
messageCount: observationMessages.length
|
|
448
|
+
});
|
|
284
449
|
return {
|
|
285
450
|
scratchpad: [scratchpadEntry],
|
|
286
451
|
messages: observationMessages
|
|
@@ -299,7 +464,9 @@ function createReActAgent(config, options) {
|
|
|
299
464
|
maxIterations = 10,
|
|
300
465
|
returnIntermediateSteps = false,
|
|
301
466
|
stopCondition,
|
|
302
|
-
checkpointer
|
|
467
|
+
checkpointer,
|
|
468
|
+
enableDeduplication = true
|
|
469
|
+
// Enable by default
|
|
303
470
|
} = config;
|
|
304
471
|
const {
|
|
305
472
|
verbose = false,
|
|
@@ -316,7 +483,7 @@ function createReActAgent(config, options) {
|
|
|
316
483
|
maxIterations,
|
|
317
484
|
verbose
|
|
318
485
|
);
|
|
319
|
-
const actionNode = createActionNode(toolArray, verbose);
|
|
486
|
+
const actionNode = createActionNode(toolArray, verbose, enableDeduplication);
|
|
320
487
|
const observationNode = createObservationNode(verbose);
|
|
321
488
|
const shouldContinue = (state) => {
|
|
322
489
|
if (stopCondition && stopCondition(state)) {
|
|
@@ -451,34 +618,34 @@ function createReActAgentBuilder() {
|
|
|
451
618
|
import { StateGraph as StateGraph2, END as END2 } from "@langchain/langgraph";
|
|
452
619
|
|
|
453
620
|
// src/plan-execute/state.ts
|
|
454
|
-
import { z as
|
|
621
|
+
import { z as z5 } from "zod";
|
|
455
622
|
import { createStateAnnotation as createStateAnnotation2 } from "@agentforge/core";
|
|
456
623
|
|
|
457
624
|
// src/plan-execute/schemas.ts
|
|
458
|
-
import { z as
|
|
459
|
-
var PlanStepSchema =
|
|
625
|
+
import { z as z4 } from "zod";
|
|
626
|
+
var PlanStepSchema = z4.object({
|
|
460
627
|
/**
|
|
461
628
|
* Unique identifier for the step
|
|
462
629
|
*/
|
|
463
|
-
id:
|
|
630
|
+
id: z4.string().describe("Unique identifier for the step"),
|
|
464
631
|
/**
|
|
465
632
|
* Description of what this step should accomplish
|
|
466
633
|
*/
|
|
467
|
-
description:
|
|
634
|
+
description: z4.string().describe("Description of what this step should accomplish"),
|
|
468
635
|
/**
|
|
469
636
|
* Optional dependencies on other steps (by ID)
|
|
470
637
|
*/
|
|
471
|
-
dependencies:
|
|
638
|
+
dependencies: z4.array(z4.string()).optional().describe("IDs of steps that must complete before this one"),
|
|
472
639
|
/**
|
|
473
640
|
* Optional tool to use for this step
|
|
474
641
|
*/
|
|
475
|
-
tool:
|
|
642
|
+
tool: z4.string().optional().describe("Name of the tool to use for this step"),
|
|
476
643
|
/**
|
|
477
644
|
* Optional arguments for the tool
|
|
478
645
|
*/
|
|
479
|
-
args:
|
|
646
|
+
args: z4.record(z4.any()).optional().describe("Arguments to pass to the tool")
|
|
480
647
|
});
|
|
481
|
-
var CompletedStepSchema =
|
|
648
|
+
var CompletedStepSchema = z4.object({
|
|
482
649
|
/**
|
|
483
650
|
* The step that was executed
|
|
484
651
|
*/
|
|
@@ -486,53 +653,53 @@ var CompletedStepSchema = z3.object({
|
|
|
486
653
|
/**
|
|
487
654
|
* The result of executing the step
|
|
488
655
|
*/
|
|
489
|
-
result:
|
|
656
|
+
result: z4.any().describe("The result of executing the step"),
|
|
490
657
|
/**
|
|
491
658
|
* Whether the step succeeded
|
|
492
659
|
*/
|
|
493
|
-
success:
|
|
660
|
+
success: z4.boolean().describe("Whether the step succeeded"),
|
|
494
661
|
/**
|
|
495
662
|
* Optional error message if the step failed
|
|
496
663
|
*/
|
|
497
|
-
error:
|
|
664
|
+
error: z4.string().optional().describe("Error message if the step failed"),
|
|
498
665
|
/**
|
|
499
666
|
* Timestamp when the step was completed
|
|
500
667
|
*/
|
|
501
|
-
timestamp:
|
|
668
|
+
timestamp: z4.string().datetime().describe("ISO timestamp when the step was completed")
|
|
502
669
|
});
|
|
503
|
-
var PlanSchema =
|
|
670
|
+
var PlanSchema = z4.object({
|
|
504
671
|
/**
|
|
505
672
|
* List of steps in the plan
|
|
506
673
|
*/
|
|
507
|
-
steps:
|
|
674
|
+
steps: z4.array(PlanStepSchema).describe("List of steps in the plan"),
|
|
508
675
|
/**
|
|
509
676
|
* Overall goal of the plan
|
|
510
677
|
*/
|
|
511
|
-
goal:
|
|
678
|
+
goal: z4.string().describe("Overall goal of the plan"),
|
|
512
679
|
/**
|
|
513
680
|
* Timestamp when the plan was created
|
|
514
681
|
*/
|
|
515
|
-
createdAt:
|
|
682
|
+
createdAt: z4.string().datetime().describe("ISO timestamp when the plan was created"),
|
|
516
683
|
/**
|
|
517
684
|
* Optional confidence score (0-1)
|
|
518
685
|
*/
|
|
519
|
-
confidence:
|
|
686
|
+
confidence: z4.number().min(0).max(1).optional().describe("Confidence score for the plan (0-1)")
|
|
520
687
|
});
|
|
521
|
-
var ReplanDecisionSchema =
|
|
688
|
+
var ReplanDecisionSchema = z4.object({
|
|
522
689
|
/**
|
|
523
690
|
* Whether to replan
|
|
524
691
|
*/
|
|
525
|
-
shouldReplan:
|
|
692
|
+
shouldReplan: z4.boolean().describe("Whether to replan based on current results"),
|
|
526
693
|
/**
|
|
527
694
|
* Reason for the decision
|
|
528
695
|
*/
|
|
529
|
-
reason:
|
|
696
|
+
reason: z4.string().describe("Reason for the replan decision"),
|
|
530
697
|
/**
|
|
531
698
|
* Optional new goal if replanning
|
|
532
699
|
*/
|
|
533
|
-
newGoal:
|
|
700
|
+
newGoal: z4.string().optional().describe("Updated goal if replanning")
|
|
534
701
|
});
|
|
535
|
-
var ExecutionStatusSchema =
|
|
702
|
+
var ExecutionStatusSchema = z4.enum([
|
|
536
703
|
"planning",
|
|
537
704
|
"executing",
|
|
538
705
|
"replanning",
|
|
@@ -545,11 +712,7 @@ var PlanExecuteStateConfig = {
|
|
|
545
712
|
/**
|
|
546
713
|
* Original user input/query
|
|
547
714
|
*/
|
|
548
|
-
input:
|
|
549
|
-
schema: z4.string(),
|
|
550
|
-
default: () => "",
|
|
551
|
-
description: "Original user input or query"
|
|
552
|
-
},
|
|
715
|
+
input: inputField,
|
|
553
716
|
/**
|
|
554
717
|
* The current plan
|
|
555
718
|
*/
|
|
@@ -562,7 +725,7 @@ var PlanExecuteStateConfig = {
|
|
|
562
725
|
* Accumulates all completed steps
|
|
563
726
|
*/
|
|
564
727
|
pastSteps: {
|
|
565
|
-
schema:
|
|
728
|
+
schema: z5.array(CompletedStepSchema),
|
|
566
729
|
reducer: (left, right) => [...left, ...right],
|
|
567
730
|
default: () => [],
|
|
568
731
|
description: "Completed steps with their results"
|
|
@@ -571,7 +734,7 @@ var PlanExecuteStateConfig = {
|
|
|
571
734
|
* Index of the current step being executed
|
|
572
735
|
*/
|
|
573
736
|
currentStepIndex: {
|
|
574
|
-
schema:
|
|
737
|
+
schema: z5.number().int().nonnegative().optional(),
|
|
575
738
|
description: "Index of the current step being executed"
|
|
576
739
|
},
|
|
577
740
|
/**
|
|
@@ -585,34 +748,19 @@ var PlanExecuteStateConfig = {
|
|
|
585
748
|
/**
|
|
586
749
|
* Final response
|
|
587
750
|
*/
|
|
588
|
-
response:
|
|
589
|
-
schema: z4.string().optional(),
|
|
590
|
-
description: "Final response after plan execution"
|
|
591
|
-
},
|
|
751
|
+
response: responseField,
|
|
592
752
|
/**
|
|
593
753
|
* Error message if execution failed
|
|
594
754
|
*/
|
|
595
|
-
error:
|
|
596
|
-
schema: z4.string().optional(),
|
|
597
|
-
description: "Error message if execution failed"
|
|
598
|
-
},
|
|
755
|
+
error: errorField,
|
|
599
756
|
/**
|
|
600
757
|
* Iteration counter for replanning
|
|
601
758
|
*/
|
|
602
|
-
iteration:
|
|
603
|
-
schema: z4.number().int().nonnegative(),
|
|
604
|
-
reducer: (left, right) => left + right,
|
|
605
|
-
default: () => 0,
|
|
606
|
-
description: "Number of planning iterations"
|
|
607
|
-
},
|
|
759
|
+
iteration: iterationField,
|
|
608
760
|
/**
|
|
609
761
|
* Maximum iterations allowed
|
|
610
762
|
*/
|
|
611
|
-
maxIterations:
|
|
612
|
-
schema: z4.number().int().positive(),
|
|
613
|
-
default: () => 5,
|
|
614
|
-
description: "Maximum number of planning iterations allowed"
|
|
615
|
-
}
|
|
763
|
+
maxIterations: maxIterationsField(5)
|
|
616
764
|
};
|
|
617
765
|
var PlanExecuteState = createStateAnnotation2(PlanExecuteStateConfig);
|
|
618
766
|
|
|
@@ -679,6 +827,9 @@ var REMAINING_STEP_TEMPLATE = `Step {stepNumber}: {description}
|
|
|
679
827
|
{dependencies}`;
|
|
680
828
|
|
|
681
829
|
// src/plan-execute/nodes.ts
|
|
830
|
+
var plannerLogger = createPatternLogger("agentforge:patterns:plan-execute:planner");
|
|
831
|
+
var executorLogger = createPatternLogger("agentforge:patterns:plan-execute:executor");
|
|
832
|
+
var replannerLogger = createPatternLogger("agentforge:patterns:plan-execute:replanner");
|
|
682
833
|
function createPlannerNode(config) {
|
|
683
834
|
const {
|
|
684
835
|
model,
|
|
@@ -687,7 +838,13 @@ function createPlannerNode(config) {
|
|
|
687
838
|
includeToolDescriptions = false
|
|
688
839
|
} = config;
|
|
689
840
|
return async (state) => {
|
|
841
|
+
const startTime = Date.now();
|
|
690
842
|
try {
|
|
843
|
+
plannerLogger.debug("Planning started", {
|
|
844
|
+
input: state.input?.substring(0, 100),
|
|
845
|
+
maxSteps,
|
|
846
|
+
includeToolDescriptions
|
|
847
|
+
});
|
|
691
848
|
let toolDescriptions = "";
|
|
692
849
|
if (includeToolDescriptions) {
|
|
693
850
|
toolDescriptions = "";
|
|
@@ -712,6 +869,12 @@ function createPlannerNode(config) {
|
|
|
712
869
|
} catch (parseError) {
|
|
713
870
|
throw new Error(`Failed to parse plan from LLM response: ${parseError}`);
|
|
714
871
|
}
|
|
872
|
+
plannerLogger.info("Plan created", {
|
|
873
|
+
stepCount: plan.steps.length,
|
|
874
|
+
goal: plan.goal.substring(0, 100),
|
|
875
|
+
confidence: plan.confidence,
|
|
876
|
+
duration: Date.now() - startTime
|
|
877
|
+
});
|
|
715
878
|
return {
|
|
716
879
|
plan,
|
|
717
880
|
status: "executing",
|
|
@@ -719,6 +882,10 @@ function createPlannerNode(config) {
|
|
|
719
882
|
iteration: 1
|
|
720
883
|
};
|
|
721
884
|
} catch (error) {
|
|
885
|
+
plannerLogger.error("Planning failed", {
|
|
886
|
+
error: error instanceof Error ? error.message : String(error),
|
|
887
|
+
duration: Date.now() - startTime
|
|
888
|
+
});
|
|
722
889
|
return {
|
|
723
890
|
status: "failed",
|
|
724
891
|
error: error instanceof Error ? error.message : "Unknown error in planner"
|
|
@@ -731,11 +898,18 @@ function createExecutorNode(config) {
|
|
|
731
898
|
tools,
|
|
732
899
|
model,
|
|
733
900
|
parallel = false,
|
|
734
|
-
stepTimeout = 3e4
|
|
901
|
+
stepTimeout = 3e4,
|
|
902
|
+
enableDeduplication = true
|
|
735
903
|
} = config;
|
|
736
904
|
return async (state) => {
|
|
905
|
+
const { plan, currentStepIndex = 0, pastSteps = [], iteration = 0 } = state;
|
|
737
906
|
try {
|
|
738
|
-
|
|
907
|
+
executorLogger.debug("Executor node executing", {
|
|
908
|
+
currentStepIndex,
|
|
909
|
+
totalSteps: plan?.steps?.length || 0,
|
|
910
|
+
iteration,
|
|
911
|
+
deduplicationEnabled: enableDeduplication
|
|
912
|
+
});
|
|
739
913
|
if (!plan || !plan.steps || plan.steps.length === 0) {
|
|
740
914
|
return {
|
|
741
915
|
status: "completed"
|
|
@@ -754,32 +928,80 @@ function createExecutorNode(config) {
|
|
|
754
928
|
throw new Error(`Unmet dependencies for step ${currentStep.id}: ${unmetDependencies.join(", ")}`);
|
|
755
929
|
}
|
|
756
930
|
}
|
|
931
|
+
const executionCache = /* @__PURE__ */ new Map();
|
|
932
|
+
let cacheSize = 0;
|
|
933
|
+
if (enableDeduplication && currentStep.tool) {
|
|
934
|
+
for (const pastStep of pastSteps) {
|
|
935
|
+
if (pastStep.step.tool) {
|
|
936
|
+
const cacheKey = generateToolCallCacheKey(pastStep.step.tool, pastStep.step.args || {});
|
|
937
|
+
executionCache.set(cacheKey, pastStep);
|
|
938
|
+
cacheSize++;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (cacheSize > 0) {
|
|
942
|
+
executorLogger.debug("Deduplication cache built", {
|
|
943
|
+
cacheSize,
|
|
944
|
+
pastStepsCount: pastSteps.length
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
757
948
|
let result;
|
|
758
949
|
let success = true;
|
|
759
950
|
let error;
|
|
951
|
+
let isDuplicate = false;
|
|
760
952
|
try {
|
|
761
953
|
if (currentStep.tool) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
954
|
+
if (enableDeduplication) {
|
|
955
|
+
const cacheKey = generateToolCallCacheKey(currentStep.tool, currentStep.args || {});
|
|
956
|
+
const cachedStep = executionCache.get(cacheKey);
|
|
957
|
+
if (cachedStep) {
|
|
958
|
+
isDuplicate = true;
|
|
959
|
+
result = cachedStep.result;
|
|
960
|
+
success = cachedStep.success;
|
|
961
|
+
error = cachedStep.error;
|
|
962
|
+
executorLogger.info("Duplicate step execution prevented", {
|
|
963
|
+
stepId: currentStep.id,
|
|
964
|
+
toolName: currentStep.tool,
|
|
965
|
+
arguments: currentStep.args,
|
|
966
|
+
iteration,
|
|
967
|
+
cacheHit: true
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (!isDuplicate) {
|
|
972
|
+
const tool = tools.find((t) => t.metadata.name === currentStep.tool);
|
|
973
|
+
if (!tool) {
|
|
974
|
+
throw new Error(`Tool not found: ${currentStep.tool}`);
|
|
975
|
+
}
|
|
976
|
+
const startTime = Date.now();
|
|
977
|
+
const timeoutPromise = new Promise(
|
|
978
|
+
(_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
|
|
979
|
+
);
|
|
980
|
+
result = await Promise.race([
|
|
981
|
+
tool.execute(currentStep.args || {}),
|
|
982
|
+
timeoutPromise
|
|
983
|
+
]);
|
|
984
|
+
const executionTime = Date.now() - startTime;
|
|
985
|
+
executorLogger.debug("Step executed successfully", {
|
|
986
|
+
stepId: currentStep.id,
|
|
987
|
+
toolName: currentStep.tool,
|
|
988
|
+
executionTime,
|
|
989
|
+
iteration
|
|
990
|
+
});
|
|
765
991
|
}
|
|
766
|
-
const timeoutPromise = new Promise(
|
|
767
|
-
(_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
|
|
768
|
-
);
|
|
769
|
-
result = await Promise.race([
|
|
770
|
-
tool.execute(currentStep.args || {}),
|
|
771
|
-
timeoutPromise
|
|
772
|
-
]);
|
|
773
992
|
} else {
|
|
774
993
|
result = { message: "Step completed without tool execution" };
|
|
775
994
|
}
|
|
776
995
|
} catch (execError) {
|
|
777
|
-
|
|
778
|
-
throw execError;
|
|
779
|
-
}
|
|
996
|
+
error = handleNodeError(execError, `executor:${currentStep.description}`, false);
|
|
780
997
|
success = false;
|
|
781
|
-
error = execError instanceof Error ? execError.message : "Unknown execution error";
|
|
782
998
|
result = null;
|
|
999
|
+
executorLogger.warn("Step execution failed", {
|
|
1000
|
+
stepId: currentStep.id,
|
|
1001
|
+
toolName: currentStep.tool,
|
|
1002
|
+
error,
|
|
1003
|
+
iteration
|
|
1004
|
+
});
|
|
783
1005
|
}
|
|
784
1006
|
const completedStep = {
|
|
785
1007
|
step: currentStep,
|
|
@@ -788,11 +1010,23 @@ function createExecutorNode(config) {
|
|
|
788
1010
|
error,
|
|
789
1011
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
790
1012
|
};
|
|
1013
|
+
executorLogger.info("Executor node complete", {
|
|
1014
|
+
stepId: currentStep.id,
|
|
1015
|
+
stepIndex: currentStepIndex,
|
|
1016
|
+
totalSteps: plan.steps.length,
|
|
1017
|
+
success,
|
|
1018
|
+
isDuplicate,
|
|
1019
|
+
iteration
|
|
1020
|
+
});
|
|
791
1021
|
return {
|
|
792
1022
|
pastSteps: [completedStep],
|
|
793
1023
|
currentStepIndex: currentStepIndex + 1
|
|
794
1024
|
};
|
|
795
1025
|
} catch (error) {
|
|
1026
|
+
executorLogger.error("Executor node failed", {
|
|
1027
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1028
|
+
iteration
|
|
1029
|
+
});
|
|
796
1030
|
return {
|
|
797
1031
|
status: "failed",
|
|
798
1032
|
error: error instanceof Error ? error.message : "Unknown error in executor"
|
|
@@ -807,11 +1041,18 @@ function createReplannerNode(config) {
|
|
|
807
1041
|
systemPrompt = DEFAULT_REPLANNER_SYSTEM_PROMPT
|
|
808
1042
|
} = config;
|
|
809
1043
|
return async (state) => {
|
|
1044
|
+
const startTime = Date.now();
|
|
810
1045
|
try {
|
|
811
1046
|
const { plan, pastSteps = [], currentStepIndex = 0 } = state;
|
|
812
1047
|
if (!plan) {
|
|
813
1048
|
return { status: "failed", error: "No plan available for replanning" };
|
|
814
1049
|
}
|
|
1050
|
+
replannerLogger.debug("Evaluating replanning", {
|
|
1051
|
+
completedSteps: pastSteps.length,
|
|
1052
|
+
remainingSteps: plan.steps.length - currentStepIndex,
|
|
1053
|
+
successfulSteps: pastSteps.filter((ps) => ps.success).length,
|
|
1054
|
+
failedSteps: pastSteps.filter((ps) => !ps.success).length
|
|
1055
|
+
});
|
|
815
1056
|
const completedStepsText = pastSteps.map(
|
|
816
1057
|
(ps, idx) => COMPLETED_STEP_TEMPLATE.replace("{stepNumber}", String(idx + 1)).replace("{description}", ps.step.description).replace("{result}", JSON.stringify(ps.result)).replace("{status}", ps.success ? "Success" : `Failed: ${ps.error}`)
|
|
817
1058
|
).join("\n\n");
|
|
@@ -833,17 +1074,30 @@ function createReplannerNode(config) {
|
|
|
833
1074
|
throw new Error(`Failed to parse replan decision from LLM response: ${parseError}`);
|
|
834
1075
|
}
|
|
835
1076
|
if (decision.shouldReplan) {
|
|
1077
|
+
replannerLogger.info("Replanning triggered", {
|
|
1078
|
+
reason: decision.reason,
|
|
1079
|
+
newGoal: decision.newGoal?.substring(0, 100),
|
|
1080
|
+
duration: Date.now() - startTime
|
|
1081
|
+
});
|
|
836
1082
|
return {
|
|
837
1083
|
status: "planning",
|
|
838
1084
|
input: decision.newGoal || plan.goal,
|
|
839
1085
|
iteration: 1
|
|
840
1086
|
};
|
|
841
1087
|
} else {
|
|
1088
|
+
replannerLogger.info("Continuing with current plan", {
|
|
1089
|
+
reason: decision.reason,
|
|
1090
|
+
duration: Date.now() - startTime
|
|
1091
|
+
});
|
|
842
1092
|
return {
|
|
843
1093
|
status: "executing"
|
|
844
1094
|
};
|
|
845
1095
|
}
|
|
846
1096
|
} catch (error) {
|
|
1097
|
+
replannerLogger.error("Replanning evaluation failed", {
|
|
1098
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1099
|
+
duration: Date.now() - startTime
|
|
1100
|
+
});
|
|
847
1101
|
return {
|
|
848
1102
|
status: "failed",
|
|
849
1103
|
error: error instanceof Error ? error.message : "Unknown error in replanner"
|
|
@@ -943,46 +1197,46 @@ function createPlanExecuteAgent(config) {
|
|
|
943
1197
|
}
|
|
944
1198
|
|
|
945
1199
|
// src/reflection/state.ts
|
|
946
|
-
import { z as
|
|
1200
|
+
import { z as z7 } from "zod";
|
|
947
1201
|
import { createStateAnnotation as createStateAnnotation3 } from "@agentforge/core";
|
|
948
1202
|
|
|
949
1203
|
// src/reflection/schemas.ts
|
|
950
|
-
import { z as
|
|
951
|
-
var ReflectionSchema =
|
|
1204
|
+
import { z as z6 } from "zod";
|
|
1205
|
+
var ReflectionSchema = z6.object({
|
|
952
1206
|
/**
|
|
953
1207
|
* The critique or feedback on the current response
|
|
954
1208
|
*/
|
|
955
|
-
critique:
|
|
1209
|
+
critique: z6.string().describe("Critique or feedback on the current response"),
|
|
956
1210
|
/**
|
|
957
1211
|
* Specific issues identified
|
|
958
1212
|
*/
|
|
959
|
-
issues:
|
|
1213
|
+
issues: z6.array(z6.string()).describe("Specific issues or problems identified"),
|
|
960
1214
|
/**
|
|
961
1215
|
* Suggestions for improvement
|
|
962
1216
|
*/
|
|
963
|
-
suggestions:
|
|
1217
|
+
suggestions: z6.array(z6.string()).describe("Suggestions for improving the response"),
|
|
964
1218
|
/**
|
|
965
1219
|
* Quality score (0-10)
|
|
966
1220
|
*/
|
|
967
|
-
score:
|
|
1221
|
+
score: z6.number().min(0).max(10).optional().describe("Quality score from 0 to 10"),
|
|
968
1222
|
/**
|
|
969
1223
|
* Whether the response meets quality standards
|
|
970
1224
|
*/
|
|
971
|
-
meetsStandards:
|
|
1225
|
+
meetsStandards: z6.boolean().describe("Whether the response meets quality standards"),
|
|
972
1226
|
/**
|
|
973
1227
|
* Timestamp of the reflection
|
|
974
1228
|
*/
|
|
975
|
-
timestamp:
|
|
1229
|
+
timestamp: z6.date().optional().describe("When this reflection was created")
|
|
976
1230
|
});
|
|
977
|
-
var RevisionSchema =
|
|
1231
|
+
var RevisionSchema = z6.object({
|
|
978
1232
|
/**
|
|
979
1233
|
* The revised content
|
|
980
1234
|
*/
|
|
981
|
-
content:
|
|
1235
|
+
content: z6.string().describe("The revised content"),
|
|
982
1236
|
/**
|
|
983
1237
|
* Which iteration this revision is from
|
|
984
1238
|
*/
|
|
985
|
-
iteration:
|
|
1239
|
+
iteration: z6.number().int().nonnegative().describe("Iteration number"),
|
|
986
1240
|
/**
|
|
987
1241
|
* The reflection that prompted this revision
|
|
988
1242
|
*/
|
|
@@ -990,9 +1244,9 @@ var RevisionSchema = z5.object({
|
|
|
990
1244
|
/**
|
|
991
1245
|
* Timestamp of the revision
|
|
992
1246
|
*/
|
|
993
|
-
timestamp:
|
|
1247
|
+
timestamp: z6.date().optional().describe("When this revision was created")
|
|
994
1248
|
});
|
|
995
|
-
var ReflectionStatusSchema =
|
|
1249
|
+
var ReflectionStatusSchema = z6.enum([
|
|
996
1250
|
"generating",
|
|
997
1251
|
// Initial generation
|
|
998
1252
|
"reflecting",
|
|
@@ -1004,25 +1258,25 @@ var ReflectionStatusSchema = z5.enum([
|
|
|
1004
1258
|
"failed"
|
|
1005
1259
|
// Max iterations reached without meeting standards
|
|
1006
1260
|
]);
|
|
1007
|
-
var QualityCriteriaSchema =
|
|
1261
|
+
var QualityCriteriaSchema = z6.object({
|
|
1008
1262
|
/**
|
|
1009
1263
|
* Minimum quality score required (0-10)
|
|
1010
1264
|
*/
|
|
1011
|
-
minScore:
|
|
1265
|
+
minScore: z6.number().min(0).max(10).default(7).describe("Minimum quality score required"),
|
|
1012
1266
|
/**
|
|
1013
1267
|
* Specific criteria to evaluate
|
|
1014
1268
|
*/
|
|
1015
|
-
criteria:
|
|
1269
|
+
criteria: z6.array(z6.string()).optional().describe("Specific criteria to evaluate"),
|
|
1016
1270
|
/**
|
|
1017
1271
|
* Whether all criteria must be met
|
|
1018
1272
|
*/
|
|
1019
|
-
requireAll:
|
|
1273
|
+
requireAll: z6.boolean().default(true).describe("Whether all criteria must be met")
|
|
1020
1274
|
});
|
|
1021
|
-
var ReflectionConfigSchema =
|
|
1275
|
+
var ReflectionConfigSchema = z6.object({
|
|
1022
1276
|
/**
|
|
1023
1277
|
* Maximum number of reflection iterations
|
|
1024
1278
|
*/
|
|
1025
|
-
maxIterations:
|
|
1279
|
+
maxIterations: z6.number().int().positive().default(3).describe("Maximum reflection iterations"),
|
|
1026
1280
|
/**
|
|
1027
1281
|
* Quality criteria for completion
|
|
1028
1282
|
*/
|
|
@@ -1030,7 +1284,7 @@ var ReflectionConfigSchema = z5.object({
|
|
|
1030
1284
|
/**
|
|
1031
1285
|
* Whether to include previous reflections in context
|
|
1032
1286
|
*/
|
|
1033
|
-
includeHistory:
|
|
1287
|
+
includeHistory: z6.boolean().default(true).describe("Include previous reflections in context")
|
|
1034
1288
|
});
|
|
1035
1289
|
|
|
1036
1290
|
// src/reflection/state.ts
|
|
@@ -1038,16 +1292,12 @@ var ReflectionStateConfig = {
|
|
|
1038
1292
|
/**
|
|
1039
1293
|
* Original user input/task
|
|
1040
1294
|
*/
|
|
1041
|
-
input:
|
|
1042
|
-
schema: z6.string(),
|
|
1043
|
-
default: () => "",
|
|
1044
|
-
description: "Original user input or task"
|
|
1045
|
-
},
|
|
1295
|
+
input: inputField,
|
|
1046
1296
|
/**
|
|
1047
1297
|
* Current response/output
|
|
1048
1298
|
*/
|
|
1049
1299
|
currentResponse: {
|
|
1050
|
-
schema:
|
|
1300
|
+
schema: z7.string().optional(),
|
|
1051
1301
|
description: "Current response or output"
|
|
1052
1302
|
},
|
|
1053
1303
|
/**
|
|
@@ -1055,7 +1305,7 @@ var ReflectionStateConfig = {
|
|
|
1055
1305
|
* Accumulates all reflections
|
|
1056
1306
|
*/
|
|
1057
1307
|
reflections: {
|
|
1058
|
-
schema:
|
|
1308
|
+
schema: z7.array(ReflectionSchema),
|
|
1059
1309
|
reducer: (left, right) => [...left, ...right],
|
|
1060
1310
|
default: () => [],
|
|
1061
1311
|
description: "History of all reflections and critiques"
|
|
@@ -1065,7 +1315,7 @@ var ReflectionStateConfig = {
|
|
|
1065
1315
|
* Accumulates all revisions
|
|
1066
1316
|
*/
|
|
1067
1317
|
revisions: {
|
|
1068
|
-
schema:
|
|
1318
|
+
schema: z7.array(RevisionSchema),
|
|
1069
1319
|
reducer: (left, right) => [...left, ...right],
|
|
1070
1320
|
default: () => [],
|
|
1071
1321
|
description: "History of all revisions"
|
|
@@ -1073,12 +1323,7 @@ var ReflectionStateConfig = {
|
|
|
1073
1323
|
/**
|
|
1074
1324
|
* Current iteration number
|
|
1075
1325
|
*/
|
|
1076
|
-
iteration:
|
|
1077
|
-
schema: z6.number().int().nonnegative(),
|
|
1078
|
-
reducer: (left, right) => left + right,
|
|
1079
|
-
default: () => 0,
|
|
1080
|
-
description: "Current iteration number"
|
|
1081
|
-
},
|
|
1326
|
+
iteration: iterationField,
|
|
1082
1327
|
/**
|
|
1083
1328
|
* Current status
|
|
1084
1329
|
*/
|
|
@@ -1097,25 +1342,15 @@ var ReflectionStateConfig = {
|
|
|
1097
1342
|
/**
|
|
1098
1343
|
* Maximum iterations allowed
|
|
1099
1344
|
*/
|
|
1100
|
-
maxIterations:
|
|
1101
|
-
schema: z6.number().int().positive(),
|
|
1102
|
-
default: () => 3,
|
|
1103
|
-
description: "Maximum number of reflection iterations allowed"
|
|
1104
|
-
},
|
|
1345
|
+
maxIterations: maxIterationsField(3),
|
|
1105
1346
|
/**
|
|
1106
1347
|
* Final response (when completed)
|
|
1107
1348
|
*/
|
|
1108
|
-
response:
|
|
1109
|
-
schema: z6.string().optional(),
|
|
1110
|
-
description: "Final response after reflection process"
|
|
1111
|
-
},
|
|
1349
|
+
response: responseField,
|
|
1112
1350
|
/**
|
|
1113
1351
|
* Error message if failed
|
|
1114
1352
|
*/
|
|
1115
|
-
error:
|
|
1116
|
-
schema: z6.string().optional(),
|
|
1117
|
-
description: "Error message if reflection failed"
|
|
1118
|
-
}
|
|
1353
|
+
error: errorField
|
|
1119
1354
|
};
|
|
1120
1355
|
var ReflectionState = createStateAnnotation3(ReflectionStateConfig);
|
|
1121
1356
|
|
|
@@ -1221,6 +1456,9 @@ var REVISION_ENTRY_TEMPLATE = `Iteration {iteration}:
|
|
|
1221
1456
|
|
|
1222
1457
|
// src/reflection/nodes.ts
|
|
1223
1458
|
import { HumanMessage as HumanMessage3, SystemMessage as SystemMessage3 } from "@langchain/core/messages";
|
|
1459
|
+
var generatorLogger = createPatternLogger("agentforge:patterns:reflection:generator");
|
|
1460
|
+
var reflectorLogger = createPatternLogger("agentforge:patterns:reflection:reflector");
|
|
1461
|
+
var reviserLogger = createPatternLogger("agentforge:patterns:reflection:reviser");
|
|
1224
1462
|
function createGeneratorNode(config) {
|
|
1225
1463
|
const {
|
|
1226
1464
|
model,
|
|
@@ -1228,10 +1466,13 @@ function createGeneratorNode(config) {
|
|
|
1228
1466
|
verbose = false
|
|
1229
1467
|
} = config;
|
|
1230
1468
|
return async (state) => {
|
|
1469
|
+
const startTime = Date.now();
|
|
1231
1470
|
try {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1471
|
+
generatorLogger.debug("Generating response", {
|
|
1472
|
+
attempt: state.iteration + 1,
|
|
1473
|
+
hasFeedback: state.reflections.length > 0,
|
|
1474
|
+
hasExistingResponse: !!state.currentResponse
|
|
1475
|
+
});
|
|
1235
1476
|
let context = "";
|
|
1236
1477
|
if (state.iteration > 0 && state.reflections.length > 0) {
|
|
1237
1478
|
const lastReflection = state.reflections[state.reflections.length - 1];
|
|
@@ -1246,19 +1487,27 @@ ${lastReflection.critique}`;
|
|
|
1246
1487
|
];
|
|
1247
1488
|
const response = await model.invoke(messages);
|
|
1248
1489
|
const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1490
|
+
generatorLogger.info("Response generated", {
|
|
1491
|
+
attempt: state.iteration + 1,
|
|
1492
|
+
responseLength: content.length,
|
|
1493
|
+
isRevision: state.iteration > 0,
|
|
1494
|
+
duration: Date.now() - startTime
|
|
1495
|
+
});
|
|
1252
1496
|
return {
|
|
1253
1497
|
currentResponse: content,
|
|
1254
1498
|
status: "reflecting",
|
|
1255
1499
|
iteration: 1
|
|
1256
1500
|
};
|
|
1257
1501
|
} catch (error) {
|
|
1258
|
-
|
|
1502
|
+
const errorMessage = handleNodeError(error, "generator", false);
|
|
1503
|
+
generatorLogger.error("Response generation failed", {
|
|
1504
|
+
attempt: state.iteration + 1,
|
|
1505
|
+
error: errorMessage,
|
|
1506
|
+
duration: Date.now() - startTime
|
|
1507
|
+
});
|
|
1259
1508
|
return {
|
|
1260
1509
|
status: "failed",
|
|
1261
|
-
error:
|
|
1510
|
+
error: errorMessage
|
|
1262
1511
|
};
|
|
1263
1512
|
}
|
|
1264
1513
|
};
|
|
@@ -1271,10 +1520,13 @@ function createReflectorNode(config) {
|
|
|
1271
1520
|
verbose = false
|
|
1272
1521
|
} = config;
|
|
1273
1522
|
return async (state) => {
|
|
1523
|
+
const startTime = Date.now();
|
|
1274
1524
|
try {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1525
|
+
reflectorLogger.debug("Reflecting on response", {
|
|
1526
|
+
attempt: state.iteration,
|
|
1527
|
+
responseLength: state.currentResponse?.length || 0,
|
|
1528
|
+
hasCriteria: !!(qualityCriteria || state.qualityCriteria)
|
|
1529
|
+
});
|
|
1278
1530
|
if (!state.currentResponse) {
|
|
1279
1531
|
throw new Error("No current response to reflect on");
|
|
1280
1532
|
}
|
|
@@ -1324,19 +1576,28 @@ function createReflectorNode(config) {
|
|
|
1324
1576
|
meetsStandards: false
|
|
1325
1577
|
};
|
|
1326
1578
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1579
|
+
reflectorLogger.info("Reflection complete", {
|
|
1580
|
+
attempt: state.iteration,
|
|
1581
|
+
score: reflection.score,
|
|
1582
|
+
meetsStandards: reflection.meetsStandards,
|
|
1583
|
+
issueCount: reflection.issues.length,
|
|
1584
|
+
suggestionCount: reflection.suggestions.length,
|
|
1585
|
+
duration: Date.now() - startTime
|
|
1586
|
+
});
|
|
1331
1587
|
return {
|
|
1332
1588
|
reflections: [reflection],
|
|
1333
1589
|
status: reflection.meetsStandards ? "completed" : "revising"
|
|
1334
1590
|
};
|
|
1335
1591
|
} catch (error) {
|
|
1336
|
-
|
|
1592
|
+
const errorMessage = handleNodeError(error, "reflector", false);
|
|
1593
|
+
reflectorLogger.error("Reflection failed", {
|
|
1594
|
+
attempt: state.iteration,
|
|
1595
|
+
error: errorMessage,
|
|
1596
|
+
duration: Date.now() - startTime
|
|
1597
|
+
});
|
|
1337
1598
|
return {
|
|
1338
1599
|
status: "failed",
|
|
1339
|
-
error:
|
|
1600
|
+
error: errorMessage
|
|
1340
1601
|
};
|
|
1341
1602
|
}
|
|
1342
1603
|
};
|
|
@@ -1348,10 +1609,8 @@ function createReviserNode(config) {
|
|
|
1348
1609
|
verbose = false
|
|
1349
1610
|
} = config;
|
|
1350
1611
|
return async (state) => {
|
|
1612
|
+
const startTime = Date.now();
|
|
1351
1613
|
try {
|
|
1352
|
-
if (verbose) {
|
|
1353
|
-
console.log("[Reviser] Revising response...");
|
|
1354
|
-
}
|
|
1355
1614
|
if (!state.currentResponse) {
|
|
1356
1615
|
throw new Error("No current response to revise");
|
|
1357
1616
|
}
|
|
@@ -1359,6 +1618,12 @@ function createReviserNode(config) {
|
|
|
1359
1618
|
throw new Error("No reflections to base revision on");
|
|
1360
1619
|
}
|
|
1361
1620
|
const lastReflection = state.reflections[state.reflections.length - 1];
|
|
1621
|
+
reviserLogger.debug("Revising response", {
|
|
1622
|
+
attempt: state.iteration,
|
|
1623
|
+
previousScore: lastReflection.score,
|
|
1624
|
+
issueCount: lastReflection.issues.length,
|
|
1625
|
+
suggestionCount: lastReflection.suggestions.length
|
|
1626
|
+
});
|
|
1362
1627
|
let historySection = "";
|
|
1363
1628
|
if (state.revisions.length > 0) {
|
|
1364
1629
|
const revisionsText = state.revisions.map(
|
|
@@ -1375,14 +1640,17 @@ ${revisionsText}`;
|
|
|
1375
1640
|
];
|
|
1376
1641
|
const response = await model.invoke(messages);
|
|
1377
1642
|
const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
1378
|
-
if (verbose) {
|
|
1379
|
-
console.log("[Reviser] Created revision:", content.substring(0, 100) + "...");
|
|
1380
|
-
}
|
|
1381
1643
|
const revision = {
|
|
1382
1644
|
content,
|
|
1383
1645
|
iteration: state.iteration,
|
|
1384
1646
|
basedOn: lastReflection
|
|
1385
1647
|
};
|
|
1648
|
+
reviserLogger.info("Revision complete", {
|
|
1649
|
+
attempt: state.iteration,
|
|
1650
|
+
revisionLength: content.length,
|
|
1651
|
+
basedOnScore: lastReflection.score,
|
|
1652
|
+
duration: Date.now() - startTime
|
|
1653
|
+
});
|
|
1386
1654
|
return {
|
|
1387
1655
|
currentResponse: content,
|
|
1388
1656
|
revisions: [revision],
|
|
@@ -1390,10 +1658,15 @@ ${revisionsText}`;
|
|
|
1390
1658
|
iteration: 1
|
|
1391
1659
|
};
|
|
1392
1660
|
} catch (error) {
|
|
1393
|
-
|
|
1661
|
+
const errorMessage = handleNodeError(error, "reviser", false);
|
|
1662
|
+
reviserLogger.error("Revision failed", {
|
|
1663
|
+
attempt: state.iteration,
|
|
1664
|
+
error: errorMessage,
|
|
1665
|
+
duration: Date.now() - startTime
|
|
1666
|
+
});
|
|
1394
1667
|
return {
|
|
1395
1668
|
status: "failed",
|
|
1396
|
-
error:
|
|
1669
|
+
error: errorMessage
|
|
1397
1670
|
};
|
|
1398
1671
|
}
|
|
1399
1672
|
};
|
|
@@ -1479,13 +1752,13 @@ function createReflectionAgent(config) {
|
|
|
1479
1752
|
}
|
|
1480
1753
|
|
|
1481
1754
|
// src/multi-agent/state.ts
|
|
1482
|
-
import { z as
|
|
1755
|
+
import { z as z9 } from "zod";
|
|
1483
1756
|
import { createStateAnnotation as createStateAnnotation4 } from "@agentforge/core";
|
|
1484
1757
|
|
|
1485
1758
|
// src/multi-agent/schemas.ts
|
|
1486
|
-
import { z as
|
|
1487
|
-
var AgentRoleSchema =
|
|
1488
|
-
var MessageTypeSchema =
|
|
1759
|
+
import { z as z8 } from "zod";
|
|
1760
|
+
var AgentRoleSchema = z8.enum(["supervisor", "worker"]);
|
|
1761
|
+
var MessageTypeSchema = z8.enum([
|
|
1489
1762
|
"user_input",
|
|
1490
1763
|
// Initial user message
|
|
1491
1764
|
"task_assignment",
|
|
@@ -1499,11 +1772,11 @@ var MessageTypeSchema = z7.enum([
|
|
|
1499
1772
|
"completion"
|
|
1500
1773
|
// Final completion message
|
|
1501
1774
|
]);
|
|
1502
|
-
var AgentMessageSchema =
|
|
1775
|
+
var AgentMessageSchema = z8.object({
|
|
1503
1776
|
/**
|
|
1504
1777
|
* Unique identifier for the message
|
|
1505
1778
|
*/
|
|
1506
|
-
id:
|
|
1779
|
+
id: z8.string().describe("Unique message identifier"),
|
|
1507
1780
|
/**
|
|
1508
1781
|
* Type of message
|
|
1509
1782
|
*/
|
|
@@ -1511,25 +1784,25 @@ var AgentMessageSchema = z7.object({
|
|
|
1511
1784
|
/**
|
|
1512
1785
|
* Agent that sent the message
|
|
1513
1786
|
*/
|
|
1514
|
-
from:
|
|
1787
|
+
from: z8.string().describe("Agent identifier that sent the message"),
|
|
1515
1788
|
/**
|
|
1516
1789
|
* Agent(s) that should receive the message
|
|
1517
1790
|
*/
|
|
1518
|
-
to:
|
|
1791
|
+
to: z8.union([z8.string(), z8.array(z8.string())]).describe("Target agent(s)"),
|
|
1519
1792
|
/**
|
|
1520
1793
|
* Message content
|
|
1521
1794
|
*/
|
|
1522
|
-
content:
|
|
1795
|
+
content: z8.string().describe("Message content"),
|
|
1523
1796
|
/**
|
|
1524
1797
|
* Optional metadata
|
|
1525
1798
|
*/
|
|
1526
|
-
metadata:
|
|
1799
|
+
metadata: z8.record(z8.any()).optional().describe("Additional message metadata"),
|
|
1527
1800
|
/**
|
|
1528
1801
|
* Timestamp when message was created
|
|
1529
1802
|
*/
|
|
1530
|
-
timestamp:
|
|
1803
|
+
timestamp: z8.number().describe("Timestamp when message was created")
|
|
1531
1804
|
});
|
|
1532
|
-
var RoutingStrategySchema =
|
|
1805
|
+
var RoutingStrategySchema = z8.enum([
|
|
1533
1806
|
"llm-based",
|
|
1534
1807
|
// LLM decides which agent to route to
|
|
1535
1808
|
"rule-based",
|
|
@@ -1541,25 +1814,25 @@ var RoutingStrategySchema = z7.enum([
|
|
|
1541
1814
|
"load-balanced"
|
|
1542
1815
|
// Route based on agent workload
|
|
1543
1816
|
]);
|
|
1544
|
-
var RoutingDecisionSchema =
|
|
1817
|
+
var RoutingDecisionSchema = z8.object({
|
|
1545
1818
|
/**
|
|
1546
1819
|
* Target agent to route to (single agent routing)
|
|
1547
1820
|
* @deprecated Use targetAgents for parallel routing support
|
|
1548
1821
|
*/
|
|
1549
|
-
targetAgent:
|
|
1822
|
+
targetAgent: z8.string().nullable().default(null).describe("Agent to route the task to (single routing)"),
|
|
1550
1823
|
/**
|
|
1551
1824
|
* Target agents to route to (parallel routing)
|
|
1552
1825
|
* When multiple agents are specified, they execute in parallel
|
|
1553
1826
|
*/
|
|
1554
|
-
targetAgents:
|
|
1827
|
+
targetAgents: z8.array(z8.string()).nullable().default(null).describe("Agents to route the task to (parallel routing)"),
|
|
1555
1828
|
/**
|
|
1556
1829
|
* Reasoning for the routing decision
|
|
1557
1830
|
*/
|
|
1558
|
-
reasoning:
|
|
1831
|
+
reasoning: z8.string().default("").describe("Explanation for routing decision"),
|
|
1559
1832
|
/**
|
|
1560
1833
|
* Confidence in the routing decision (0-1)
|
|
1561
1834
|
*/
|
|
1562
|
-
confidence:
|
|
1835
|
+
confidence: z8.number().min(0).max(1).default(0.8).describe("Confidence score"),
|
|
1563
1836
|
/**
|
|
1564
1837
|
* Strategy used for routing
|
|
1565
1838
|
*/
|
|
@@ -1567,86 +1840,86 @@ var RoutingDecisionSchema = z7.object({
|
|
|
1567
1840
|
/**
|
|
1568
1841
|
* Timestamp of the routing decision
|
|
1569
1842
|
*/
|
|
1570
|
-
timestamp:
|
|
1843
|
+
timestamp: z8.number().default(() => Date.now()).describe("Timestamp of the decision")
|
|
1571
1844
|
}).refine(
|
|
1572
1845
|
(data) => data.targetAgent || data.targetAgents && data.targetAgents.length > 0,
|
|
1573
1846
|
{ message: "Either targetAgent or targetAgents must be provided" }
|
|
1574
1847
|
);
|
|
1575
|
-
var WorkerCapabilitiesSchema =
|
|
1848
|
+
var WorkerCapabilitiesSchema = z8.object({
|
|
1576
1849
|
/**
|
|
1577
1850
|
* Skills/capabilities the agent has
|
|
1578
1851
|
*/
|
|
1579
|
-
skills:
|
|
1852
|
+
skills: z8.array(z8.string()).describe("List of agent skills"),
|
|
1580
1853
|
/**
|
|
1581
1854
|
* Tools available to the agent
|
|
1582
1855
|
*/
|
|
1583
|
-
tools:
|
|
1856
|
+
tools: z8.array(z8.string()).describe("List of tool names available to agent"),
|
|
1584
1857
|
/**
|
|
1585
1858
|
* Whether the agent is currently available
|
|
1586
1859
|
*/
|
|
1587
|
-
available:
|
|
1860
|
+
available: z8.boolean().default(true).describe("Whether agent is available"),
|
|
1588
1861
|
/**
|
|
1589
1862
|
* Current workload (number of active tasks)
|
|
1590
1863
|
*/
|
|
1591
|
-
currentWorkload:
|
|
1864
|
+
currentWorkload: z8.number().int().nonnegative().default(0).describe("Current number of active tasks")
|
|
1592
1865
|
});
|
|
1593
|
-
var TaskAssignmentSchema =
|
|
1866
|
+
var TaskAssignmentSchema = z8.object({
|
|
1594
1867
|
/**
|
|
1595
1868
|
* Unique assignment identifier
|
|
1596
1869
|
*/
|
|
1597
|
-
id:
|
|
1870
|
+
id: z8.string().describe("Unique assignment identifier"),
|
|
1598
1871
|
/**
|
|
1599
1872
|
* Worker ID assigned to the task
|
|
1600
1873
|
*/
|
|
1601
|
-
workerId:
|
|
1874
|
+
workerId: z8.string().describe("Worker identifier assigned to task"),
|
|
1602
1875
|
/**
|
|
1603
1876
|
* Task description
|
|
1604
1877
|
*/
|
|
1605
|
-
task:
|
|
1878
|
+
task: z8.string().describe("Description of the task"),
|
|
1606
1879
|
/**
|
|
1607
1880
|
* Task priority (1-10, higher is more urgent)
|
|
1608
1881
|
*/
|
|
1609
|
-
priority:
|
|
1882
|
+
priority: z8.number().int().min(1).max(10).default(5).describe("Task priority"),
|
|
1610
1883
|
/**
|
|
1611
1884
|
* Timestamp when task was assigned
|
|
1612
1885
|
*/
|
|
1613
|
-
assignedAt:
|
|
1886
|
+
assignedAt: z8.number().describe("Timestamp when task was assigned"),
|
|
1614
1887
|
/**
|
|
1615
1888
|
* Optional deadline for task completion
|
|
1616
1889
|
*/
|
|
1617
|
-
deadline:
|
|
1890
|
+
deadline: z8.number().optional().describe("Optional task deadline timestamp")
|
|
1618
1891
|
});
|
|
1619
|
-
var TaskResultSchema =
|
|
1892
|
+
var TaskResultSchema = z8.object({
|
|
1620
1893
|
/**
|
|
1621
1894
|
* Assignment identifier
|
|
1622
1895
|
*/
|
|
1623
|
-
assignmentId:
|
|
1896
|
+
assignmentId: z8.string().describe("Assignment identifier"),
|
|
1624
1897
|
/**
|
|
1625
1898
|
* Worker that completed the task
|
|
1626
1899
|
*/
|
|
1627
|
-
workerId:
|
|
1900
|
+
workerId: z8.string().describe("Worker that completed the task"),
|
|
1628
1901
|
/**
|
|
1629
1902
|
* Whether the task succeeded
|
|
1630
1903
|
*/
|
|
1631
|
-
success:
|
|
1904
|
+
success: z8.boolean().describe("Whether the task succeeded"),
|
|
1632
1905
|
/**
|
|
1633
1906
|
* Task result/output
|
|
1634
1907
|
*/
|
|
1635
|
-
result:
|
|
1908
|
+
result: z8.string().describe("Task result or output"),
|
|
1636
1909
|
/**
|
|
1637
1910
|
* Optional error message if task failed
|
|
1638
1911
|
*/
|
|
1639
|
-
error:
|
|
1912
|
+
error: z8.string().optional().describe("Error message if task failed"),
|
|
1640
1913
|
/**
|
|
1641
1914
|
* Timestamp when task was completed
|
|
1642
1915
|
*/
|
|
1643
|
-
completedAt:
|
|
1916
|
+
completedAt: z8.number().describe("Timestamp when task was completed"),
|
|
1644
1917
|
/**
|
|
1645
1918
|
* Optional metadata about execution
|
|
1646
1919
|
*/
|
|
1647
|
-
metadata:
|
|
1920
|
+
metadata: z8.record(z8.any()).optional().describe("Execution metadata")
|
|
1648
1921
|
});
|
|
1649
|
-
var MultiAgentStatusSchema =
|
|
1922
|
+
var MultiAgentStatusSchema = z8.enum([
|
|
1650
1923
|
"initializing",
|
|
1651
1924
|
// System is initializing
|
|
1652
1925
|
"routing",
|
|
@@ -1662,27 +1935,27 @@ var MultiAgentStatusSchema = z7.enum([
|
|
|
1662
1935
|
"failed"
|
|
1663
1936
|
// Task failed
|
|
1664
1937
|
]);
|
|
1665
|
-
var HandoffRequestSchema =
|
|
1938
|
+
var HandoffRequestSchema = z8.object({
|
|
1666
1939
|
/**
|
|
1667
1940
|
* Agent requesting the handoff
|
|
1668
1941
|
*/
|
|
1669
|
-
from:
|
|
1942
|
+
from: z8.string().describe("Agent requesting handoff"),
|
|
1670
1943
|
/**
|
|
1671
1944
|
* Target agent for handoff
|
|
1672
1945
|
*/
|
|
1673
|
-
to:
|
|
1946
|
+
to: z8.string().describe("Target agent for handoff"),
|
|
1674
1947
|
/**
|
|
1675
1948
|
* Reason for handoff
|
|
1676
1949
|
*/
|
|
1677
|
-
reason:
|
|
1950
|
+
reason: z8.string().describe("Reason for requesting handoff"),
|
|
1678
1951
|
/**
|
|
1679
1952
|
* Context to pass to next agent
|
|
1680
1953
|
*/
|
|
1681
|
-
context:
|
|
1954
|
+
context: z8.any().describe("Context to pass to next agent"),
|
|
1682
1955
|
/**
|
|
1683
1956
|
* Timestamp of handoff request
|
|
1684
1957
|
*/
|
|
1685
|
-
timestamp:
|
|
1958
|
+
timestamp: z8.string().datetime().describe("ISO timestamp of handoff request")
|
|
1686
1959
|
});
|
|
1687
1960
|
|
|
1688
1961
|
// src/multi-agent/state.ts
|
|
@@ -1690,17 +1963,13 @@ var MultiAgentStateConfig = {
|
|
|
1690
1963
|
/**
|
|
1691
1964
|
* Original user input/query
|
|
1692
1965
|
*/
|
|
1693
|
-
input:
|
|
1694
|
-
schema: z8.string(),
|
|
1695
|
-
default: () => "",
|
|
1696
|
-
description: "Original user input or query"
|
|
1697
|
-
},
|
|
1966
|
+
input: inputField,
|
|
1698
1967
|
/**
|
|
1699
1968
|
* All messages in the multi-agent conversation
|
|
1700
1969
|
* Accumulates all messages between agents
|
|
1701
1970
|
*/
|
|
1702
1971
|
messages: {
|
|
1703
|
-
schema:
|
|
1972
|
+
schema: z9.array(AgentMessageSchema),
|
|
1704
1973
|
reducer: (left, right) => [...left, ...right],
|
|
1705
1974
|
default: () => [],
|
|
1706
1975
|
description: "All messages in the multi-agent conversation"
|
|
@@ -1709,7 +1978,7 @@ var MultiAgentStateConfig = {
|
|
|
1709
1978
|
* Available worker agents and their capabilities
|
|
1710
1979
|
*/
|
|
1711
1980
|
workers: {
|
|
1712
|
-
schema:
|
|
1981
|
+
schema: z9.record(z9.string(), WorkerCapabilitiesSchema),
|
|
1713
1982
|
reducer: (left, right) => ({
|
|
1714
1983
|
...left,
|
|
1715
1984
|
...right
|
|
@@ -1721,7 +1990,7 @@ var MultiAgentStateConfig = {
|
|
|
1721
1990
|
* Current active agent
|
|
1722
1991
|
*/
|
|
1723
1992
|
currentAgent: {
|
|
1724
|
-
schema:
|
|
1993
|
+
schema: z9.string().optional(),
|
|
1725
1994
|
description: "Identifier of the currently active agent"
|
|
1726
1995
|
},
|
|
1727
1996
|
/**
|
|
@@ -1729,7 +1998,7 @@ var MultiAgentStateConfig = {
|
|
|
1729
1998
|
* Accumulates all routing decisions
|
|
1730
1999
|
*/
|
|
1731
2000
|
routingHistory: {
|
|
1732
|
-
schema:
|
|
2001
|
+
schema: z9.array(RoutingDecisionSchema),
|
|
1733
2002
|
reducer: (left, right) => [...left, ...right],
|
|
1734
2003
|
default: () => [],
|
|
1735
2004
|
description: "History of routing decisions"
|
|
@@ -1738,7 +2007,7 @@ var MultiAgentStateConfig = {
|
|
|
1738
2007
|
* Active task assignments
|
|
1739
2008
|
*/
|
|
1740
2009
|
activeAssignments: {
|
|
1741
|
-
schema:
|
|
2010
|
+
schema: z9.array(TaskAssignmentSchema),
|
|
1742
2011
|
reducer: (left, right) => [...left, ...right],
|
|
1743
2012
|
default: () => [],
|
|
1744
2013
|
description: "Currently active task assignments"
|
|
@@ -1748,7 +2017,7 @@ var MultiAgentStateConfig = {
|
|
|
1748
2017
|
* Accumulates all completed tasks
|
|
1749
2018
|
*/
|
|
1750
2019
|
completedTasks: {
|
|
1751
|
-
schema:
|
|
2020
|
+
schema: z9.array(TaskResultSchema),
|
|
1752
2021
|
reducer: (left, right) => [...left, ...right],
|
|
1753
2022
|
default: () => [],
|
|
1754
2023
|
description: "Completed task results"
|
|
@@ -1758,7 +2027,7 @@ var MultiAgentStateConfig = {
|
|
|
1758
2027
|
* Accumulates all handoff requests
|
|
1759
2028
|
*/
|
|
1760
2029
|
handoffs: {
|
|
1761
|
-
schema:
|
|
2030
|
+
schema: z9.array(HandoffRequestSchema),
|
|
1762
2031
|
reducer: (left, right) => [...left, ...right],
|
|
1763
2032
|
default: () => [],
|
|
1764
2033
|
description: "Handoff requests between agents"
|
|
@@ -1774,44 +2043,40 @@ var MultiAgentStateConfig = {
|
|
|
1774
2043
|
/**
|
|
1775
2044
|
* Iteration counter
|
|
1776
2045
|
*/
|
|
1777
|
-
iteration:
|
|
1778
|
-
schema: z8.number().int().nonnegative(),
|
|
1779
|
-
reducer: (left, right) => left + right,
|
|
1780
|
-
default: () => 0,
|
|
1781
|
-
description: "Current iteration number"
|
|
1782
|
-
},
|
|
2046
|
+
iteration: iterationField,
|
|
1783
2047
|
/**
|
|
1784
2048
|
* Maximum iterations allowed
|
|
1785
2049
|
*/
|
|
1786
|
-
maxIterations:
|
|
1787
|
-
schema: z8.number().int().positive(),
|
|
1788
|
-
default: () => 10,
|
|
1789
|
-
description: "Maximum number of iterations allowed"
|
|
1790
|
-
},
|
|
2050
|
+
maxIterations: maxIterationsField(10),
|
|
1791
2051
|
/**
|
|
1792
2052
|
* Final aggregated response
|
|
1793
2053
|
*/
|
|
1794
|
-
response:
|
|
1795
|
-
schema: z8.string().optional(),
|
|
1796
|
-
description: "Final aggregated response"
|
|
1797
|
-
},
|
|
2054
|
+
response: responseField,
|
|
1798
2055
|
/**
|
|
1799
2056
|
* Error message if execution failed
|
|
1800
2057
|
*/
|
|
1801
|
-
error:
|
|
1802
|
-
schema: z8.string().optional(),
|
|
1803
|
-
description: "Error message if execution failed"
|
|
1804
|
-
}
|
|
2058
|
+
error: errorField
|
|
1805
2059
|
};
|
|
1806
2060
|
var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
|
|
1807
2061
|
|
|
1808
2062
|
// src/multi-agent/routing.ts
|
|
1809
2063
|
import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4, AIMessage as AIMessage2, ToolMessage as ToolMessage2 } from "@langchain/core/messages";
|
|
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 });
|
|
1810
2067
|
async function executeTools(toolCalls, tools) {
|
|
1811
2068
|
const results = [];
|
|
2069
|
+
logger.debug("Executing tools", {
|
|
2070
|
+
toolCallCount: toolCalls.length,
|
|
2071
|
+
toolNames: toolCalls.map((tc) => tc.name)
|
|
2072
|
+
});
|
|
1812
2073
|
for (const toolCall of toolCalls) {
|
|
1813
2074
|
const tool = tools.find((t) => t.metadata.name === toolCall.name);
|
|
1814
2075
|
if (!tool) {
|
|
2076
|
+
logger.warn("Tool not found", {
|
|
2077
|
+
toolName: toolCall.name,
|
|
2078
|
+
availableTools: tools.map((t) => t.metadata.name)
|
|
2079
|
+
});
|
|
1815
2080
|
results.push(new ToolMessage2({
|
|
1816
2081
|
content: `Error: Tool '${toolCall.name}' not found`,
|
|
1817
2082
|
tool_call_id: toolCall.id
|
|
@@ -1819,19 +2084,41 @@ async function executeTools(toolCalls, tools) {
|
|
|
1819
2084
|
continue;
|
|
1820
2085
|
}
|
|
1821
2086
|
try {
|
|
2087
|
+
logger.debug("Executing tool", {
|
|
2088
|
+
toolName: toolCall.name,
|
|
2089
|
+
args: toolCall.args
|
|
2090
|
+
});
|
|
1822
2091
|
const result = await tool.execute(toolCall.args);
|
|
1823
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
|
+
});
|
|
1824
2097
|
results.push(new ToolMessage2({
|
|
1825
2098
|
content,
|
|
1826
2099
|
tool_call_id: toolCall.id
|
|
1827
2100
|
}));
|
|
1828
2101
|
} catch (error) {
|
|
2102
|
+
logger.error("Tool execution failed", {
|
|
2103
|
+
toolName: toolCall.name,
|
|
2104
|
+
error: error.message
|
|
2105
|
+
});
|
|
1829
2106
|
results.push(new ToolMessage2({
|
|
1830
2107
|
content: `Error executing tool: ${error.message}`,
|
|
1831
2108
|
tool_call_id: toolCall.id
|
|
1832
2109
|
}));
|
|
1833
2110
|
}
|
|
1834
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
|
+
});
|
|
1835
2122
|
return results;
|
|
1836
2123
|
}
|
|
1837
2124
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
@@ -1869,6 +2156,10 @@ Choose parallel routing when the task benefits from multiple perspectives or dat
|
|
|
1869
2156
|
var llmBasedRouting = {
|
|
1870
2157
|
name: "llm-based",
|
|
1871
2158
|
async route(state, config) {
|
|
2159
|
+
logger.info("Starting LLM-based routing", {
|
|
2160
|
+
iteration: state.iteration,
|
|
2161
|
+
availableWorkers: Object.keys(state.workers).length
|
|
2162
|
+
});
|
|
1872
2163
|
if (!config.model) {
|
|
1873
2164
|
throw new Error("LLM-based routing requires a model to be configured");
|
|
1874
2165
|
}
|
|
@@ -1881,8 +2172,20 @@ var llmBasedRouting = {
|
|
|
1881
2172
|
const available = caps.available ? "available" : "busy";
|
|
1882
2173
|
return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
|
|
1883
2174
|
}).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
|
+
});
|
|
1884
2183
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
1885
2184
|
const taskContext = lastMessage?.content || state.input;
|
|
2185
|
+
logger.debug("Task context", {
|
|
2186
|
+
taskLength: taskContext.length,
|
|
2187
|
+
taskPreview: taskContext.substring(0, 100)
|
|
2188
|
+
});
|
|
1886
2189
|
const userPrompt = `Current task: ${taskContext}
|
|
1887
2190
|
|
|
1888
2191
|
Available workers:
|
|
@@ -1892,6 +2195,11 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1892
2195
|
const conversationHistory = [];
|
|
1893
2196
|
let attempt = 0;
|
|
1894
2197
|
while (attempt < maxRetries) {
|
|
2198
|
+
logger.debug("LLM routing attempt", {
|
|
2199
|
+
attempt: attempt + 1,
|
|
2200
|
+
maxRetries,
|
|
2201
|
+
conversationHistoryLength: conversationHistory.length
|
|
2202
|
+
});
|
|
1895
2203
|
const messages = [
|
|
1896
2204
|
new SystemMessage4(systemPrompt),
|
|
1897
2205
|
new HumanMessage4(userPrompt),
|
|
@@ -1899,6 +2207,10 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1899
2207
|
];
|
|
1900
2208
|
const response = await config.model.invoke(messages);
|
|
1901
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
|
+
});
|
|
1902
2214
|
if (tools.length === 0) {
|
|
1903
2215
|
throw new Error("LLM requested tool calls but no tools are configured");
|
|
1904
2216
|
}
|
|
@@ -1908,24 +2220,42 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1908
2220
|
...toolResults
|
|
1909
2221
|
);
|
|
1910
2222
|
attempt++;
|
|
2223
|
+
logger.debug("Retrying routing with tool results", { attempt });
|
|
1911
2224
|
continue;
|
|
1912
2225
|
}
|
|
2226
|
+
logger.debug("Parsing routing decision from LLM response");
|
|
1913
2227
|
let decision;
|
|
1914
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
|
+
});
|
|
1915
2233
|
decision = response;
|
|
1916
2234
|
} else if (response.content) {
|
|
1917
2235
|
if (typeof response.content === "string") {
|
|
1918
2236
|
try {
|
|
1919
2237
|
decision = JSON.parse(response.content);
|
|
2238
|
+
logger.debug("Parsed JSON from string response");
|
|
1920
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
|
+
});
|
|
1921
2244
|
throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
|
|
1922
2245
|
}
|
|
1923
2246
|
} else if (typeof response.content === "object") {
|
|
2247
|
+
logger.debug("Response content is already an object");
|
|
1924
2248
|
decision = response.content;
|
|
1925
2249
|
} else {
|
|
2250
|
+
logger.error("Unexpected response content type", {
|
|
2251
|
+
type: typeof response.content
|
|
2252
|
+
});
|
|
1926
2253
|
throw new Error(`Unexpected response content type: ${typeof response.content}`);
|
|
1927
2254
|
}
|
|
1928
2255
|
} else {
|
|
2256
|
+
logger.error("Unexpected response format", {
|
|
2257
|
+
response: JSON.stringify(response)
|
|
2258
|
+
});
|
|
1929
2259
|
throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
|
|
1930
2260
|
}
|
|
1931
2261
|
const result = {
|
|
@@ -1936,20 +2266,41 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1936
2266
|
strategy: "llm-based",
|
|
1937
2267
|
timestamp: Date.now()
|
|
1938
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
|
+
});
|
|
1939
2276
|
return result;
|
|
1940
2277
|
}
|
|
2278
|
+
logger.error("Max tool retries exceeded", { maxRetries });
|
|
1941
2279
|
throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
|
|
1942
2280
|
}
|
|
1943
2281
|
};
|
|
1944
2282
|
var roundRobinRouting = {
|
|
1945
2283
|
name: "round-robin",
|
|
1946
2284
|
async route(state, config) {
|
|
2285
|
+
logger.info("Starting round-robin routing", {
|
|
2286
|
+
iteration: state.iteration
|
|
2287
|
+
});
|
|
1947
2288
|
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
|
+
});
|
|
1948
2293
|
if (availableWorkers.length === 0) {
|
|
2294
|
+
logger.error("No available workers for round-robin routing");
|
|
1949
2295
|
throw new Error("No available workers for round-robin routing");
|
|
1950
2296
|
}
|
|
1951
2297
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
1952
2298
|
const targetAgent = availableWorkers[lastRoutingIndex];
|
|
2299
|
+
logger.info("Round-robin routing decision", {
|
|
2300
|
+
targetAgent,
|
|
2301
|
+
index: lastRoutingIndex + 1,
|
|
2302
|
+
totalWorkers: availableWorkers.length
|
|
2303
|
+
});
|
|
1953
2304
|
return {
|
|
1954
2305
|
targetAgent,
|
|
1955
2306
|
targetAgents: null,
|
|
@@ -1963,8 +2314,15 @@ var roundRobinRouting = {
|
|
|
1963
2314
|
var skillBasedRouting = {
|
|
1964
2315
|
name: "skill-based",
|
|
1965
2316
|
async route(state, config) {
|
|
2317
|
+
logger.info("Starting skill-based routing", {
|
|
2318
|
+
iteration: state.iteration
|
|
2319
|
+
});
|
|
1966
2320
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
1967
2321
|
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
|
+
});
|
|
1968
2326
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
1969
2327
|
const skillMatches = caps.skills.filter(
|
|
1970
2328
|
(skill) => taskContent.includes(skill.toLowerCase())
|
|
@@ -1975,11 +2333,20 @@ var skillBasedRouting = {
|
|
|
1975
2333
|
const score = skillMatches * 2 + toolMatches;
|
|
1976
2334
|
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
1977
2335
|
}).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
|
+
});
|
|
1978
2339
|
if (workerScores.length === 0) {
|
|
2340
|
+
logger.warn("No skill matches found, using fallback");
|
|
1979
2341
|
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
1980
2342
|
if (!firstAvailable) {
|
|
2343
|
+
logger.error("No available workers for skill-based routing");
|
|
1981
2344
|
throw new Error("No available workers for skill-based routing");
|
|
1982
2345
|
}
|
|
2346
|
+
logger.info("Skill-based routing fallback decision", {
|
|
2347
|
+
targetAgent: firstAvailable[0],
|
|
2348
|
+
confidence: 0.5
|
|
2349
|
+
});
|
|
1983
2350
|
return {
|
|
1984
2351
|
targetAgent: firstAvailable[0],
|
|
1985
2352
|
targetAgents: null,
|
|
@@ -1991,6 +2358,12 @@ var skillBasedRouting = {
|
|
|
1991
2358
|
}
|
|
1992
2359
|
const best = workerScores[0];
|
|
1993
2360
|
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
|
+
});
|
|
1994
2367
|
return {
|
|
1995
2368
|
targetAgent: best.id,
|
|
1996
2369
|
targetAgents: null,
|
|
@@ -2004,13 +2377,26 @@ var skillBasedRouting = {
|
|
|
2004
2377
|
var loadBalancedRouting = {
|
|
2005
2378
|
name: "load-balanced",
|
|
2006
2379
|
async route(state, config) {
|
|
2380
|
+
logger.info("Starting load-balanced routing", {
|
|
2381
|
+
iteration: state.iteration
|
|
2382
|
+
});
|
|
2007
2383
|
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
|
+
});
|
|
2008
2387
|
if (availableWorkers.length === 0) {
|
|
2388
|
+
logger.error("No available workers for load-balanced routing");
|
|
2009
2389
|
throw new Error("No available workers for load-balanced routing");
|
|
2010
2390
|
}
|
|
2011
2391
|
const targetWorker = availableWorkers[0];
|
|
2012
2392
|
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2013
2393
|
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
|
+
});
|
|
2014
2400
|
return {
|
|
2015
2401
|
targetAgent: targetWorker.id,
|
|
2016
2402
|
targetAgents: null,
|
|
@@ -2024,13 +2410,24 @@ var loadBalancedRouting = {
|
|
|
2024
2410
|
var ruleBasedRouting = {
|
|
2025
2411
|
name: "rule-based",
|
|
2026
2412
|
async route(state, config) {
|
|
2413
|
+
logger.info("Starting rule-based routing", {
|
|
2414
|
+
iteration: state.iteration
|
|
2415
|
+
});
|
|
2027
2416
|
if (!config.routingFn) {
|
|
2417
|
+
logger.error("Rule-based routing requires a custom routing function");
|
|
2028
2418
|
throw new Error("Rule-based routing requires a custom routing function");
|
|
2029
2419
|
}
|
|
2030
|
-
|
|
2420
|
+
const decision = await config.routingFn(state);
|
|
2421
|
+
logger.info("Rule-based routing decision", {
|
|
2422
|
+
targetAgent: decision.targetAgent,
|
|
2423
|
+
targetAgents: decision.targetAgents,
|
|
2424
|
+
confidence: decision.confidence
|
|
2425
|
+
});
|
|
2426
|
+
return decision;
|
|
2031
2427
|
}
|
|
2032
2428
|
};
|
|
2033
2429
|
function getRoutingStrategy(name) {
|
|
2430
|
+
logger.debug("Getting routing strategy", { name });
|
|
2034
2431
|
switch (name) {
|
|
2035
2432
|
case "llm-based":
|
|
2036
2433
|
return llmBasedRouting;
|
|
@@ -2043,14 +2440,15 @@ function getRoutingStrategy(name) {
|
|
|
2043
2440
|
case "rule-based":
|
|
2044
2441
|
return ruleBasedRouting;
|
|
2045
2442
|
default:
|
|
2443
|
+
logger.error("Unknown routing strategy", { name });
|
|
2046
2444
|
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2047
2445
|
}
|
|
2048
2446
|
}
|
|
2049
2447
|
|
|
2050
2448
|
// src/multi-agent/utils.ts
|
|
2051
|
-
import { createLogger, LogLevel } from "@agentforge/core";
|
|
2052
|
-
var
|
|
2053
|
-
var
|
|
2449
|
+
import { createLogger as createLogger3, LogLevel as LogLevel2 } from "@agentforge/core";
|
|
2450
|
+
var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel2.INFO;
|
|
2451
|
+
var logger2 = createLogger3("multi-agent", { level: logLevel2 });
|
|
2054
2452
|
function isReActAgent(obj) {
|
|
2055
2453
|
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
|
|
2056
2454
|
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
|
|
@@ -2058,9 +2456,9 @@ function isReActAgent(obj) {
|
|
|
2058
2456
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2059
2457
|
return async (state, config) => {
|
|
2060
2458
|
try {
|
|
2061
|
-
|
|
2459
|
+
logger2.debug("Wrapping ReAct agent execution", { workerId });
|
|
2062
2460
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2063
|
-
|
|
2461
|
+
logger2.debug("Extracted task", {
|
|
2064
2462
|
workerId,
|
|
2065
2463
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2066
2464
|
});
|
|
@@ -2068,7 +2466,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2068
2466
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2069
2467
|
);
|
|
2070
2468
|
if (!currentAssignment) {
|
|
2071
|
-
|
|
2469
|
+
logger2.debug("No active assignment found", { workerId });
|
|
2072
2470
|
return {};
|
|
2073
2471
|
}
|
|
2074
2472
|
const result = await agent.invoke(
|
|
@@ -2079,14 +2477,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2079
2477
|
// Pass through the config for checkpointing and interrupt support
|
|
2080
2478
|
);
|
|
2081
2479
|
const response = result.messages?.[result.messages.length - 1]?.content || "No response";
|
|
2082
|
-
|
|
2480
|
+
logger2.debug("Received response from ReAct agent", {
|
|
2083
2481
|
workerId,
|
|
2084
2482
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2085
2483
|
});
|
|
2086
2484
|
const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
|
|
2087
2485
|
const uniqueTools = [...new Set(toolsUsed)];
|
|
2088
2486
|
if (uniqueTools.length > 0) {
|
|
2089
|
-
|
|
2487
|
+
logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2090
2488
|
}
|
|
2091
2489
|
const taskResult = {
|
|
2092
2490
|
assignmentId: currentAssignment.id,
|
|
@@ -2104,14 +2502,10 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2104
2502
|
completedTasks: [taskResult]
|
|
2105
2503
|
};
|
|
2106
2504
|
} catch (error) {
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
throw error;
|
|
2110
|
-
}
|
|
2111
|
-
logger.error("Error in ReAct agent execution", {
|
|
2505
|
+
const errorMessage = handleNodeError(error, `react-agent:${workerId}`, false);
|
|
2506
|
+
logger2.error("Error in ReAct agent execution", {
|
|
2112
2507
|
workerId,
|
|
2113
|
-
error:
|
|
2114
|
-
stack: error instanceof Error ? error.stack : void 0
|
|
2508
|
+
error: errorMessage
|
|
2115
2509
|
});
|
|
2116
2510
|
const currentAssignment = state.activeAssignments.find(
|
|
2117
2511
|
(assignment) => assignment.workerId === workerId
|
|
@@ -2122,7 +2516,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2122
2516
|
workerId,
|
|
2123
2517
|
success: false,
|
|
2124
2518
|
result: "",
|
|
2125
|
-
error:
|
|
2519
|
+
error: errorMessage,
|
|
2126
2520
|
completedAt: Date.now()
|
|
2127
2521
|
};
|
|
2128
2522
|
return {
|
|
@@ -2133,7 +2527,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2133
2527
|
}
|
|
2134
2528
|
return {
|
|
2135
2529
|
status: "failed",
|
|
2136
|
-
error:
|
|
2530
|
+
error: errorMessage
|
|
2137
2531
|
};
|
|
2138
2532
|
}
|
|
2139
2533
|
};
|
|
@@ -2141,7 +2535,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2141
2535
|
|
|
2142
2536
|
// src/multi-agent/nodes.ts
|
|
2143
2537
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2144
|
-
import { toLangChainTools as toLangChainTools2 } from "@agentforge/core";
|
|
2538
|
+
import { toLangChainTools as toLangChainTools2, createLogger as createLogger4, LogLevel as LogLevel3 } from "@agentforge/core";
|
|
2539
|
+
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel3.INFO;
|
|
2540
|
+
var logger3 = createLogger4("multi-agent:nodes", { level: logLevel3 });
|
|
2145
2541
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2146
2542
|
|
|
2147
2543
|
Your job is to:
|
|
@@ -2159,13 +2555,19 @@ function createSupervisorNode(config) {
|
|
|
2159
2555
|
} = config;
|
|
2160
2556
|
return async (state) => {
|
|
2161
2557
|
try {
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2558
|
+
logger3.info("Supervisor node executing", {
|
|
2559
|
+
iteration: state.iteration,
|
|
2560
|
+
maxIterations,
|
|
2561
|
+
activeAssignments: state.activeAssignments.length,
|
|
2562
|
+
completedTasks: state.completedTasks.length
|
|
2563
|
+
});
|
|
2564
|
+
logger3.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
|
|
2165
2565
|
if (state.iteration >= maxIterations) {
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2566
|
+
logger3.warn("Max iterations reached", {
|
|
2567
|
+
iteration: state.iteration,
|
|
2568
|
+
maxIterations
|
|
2569
|
+
});
|
|
2570
|
+
logger3.debug("Max iterations reached, moving to aggregation");
|
|
2169
2571
|
return {
|
|
2170
2572
|
status: "aggregating",
|
|
2171
2573
|
currentAgent: "aggregator"
|
|
@@ -2174,27 +2576,55 @@ function createSupervisorNode(config) {
|
|
|
2174
2576
|
const allCompleted = state.activeAssignments.every(
|
|
2175
2577
|
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2176
2578
|
);
|
|
2579
|
+
logger3.debug("Checking task completion", {
|
|
2580
|
+
activeAssignments: state.activeAssignments.length,
|
|
2581
|
+
completedTasks: state.completedTasks.length,
|
|
2582
|
+
allCompleted
|
|
2583
|
+
});
|
|
2177
2584
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
}
|
|
2585
|
+
logger3.info("All tasks completed, moving to aggregation", {
|
|
2586
|
+
completedCount: state.completedTasks.length
|
|
2587
|
+
});
|
|
2588
|
+
logger3.debug("All tasks completed, moving to aggregation");
|
|
2181
2589
|
return {
|
|
2182
2590
|
status: "aggregating",
|
|
2183
2591
|
currentAgent: "aggregator"
|
|
2184
2592
|
};
|
|
2185
2593
|
}
|
|
2594
|
+
logger3.debug("Getting routing strategy", { strategy });
|
|
2186
2595
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2187
2596
|
const decision = await routingImpl.route(state, config);
|
|
2188
2597
|
const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
|
|
2598
|
+
logger3.debug("Target agents determined", {
|
|
2599
|
+
targetAgents,
|
|
2600
|
+
isParallel: targetAgents.length > 1,
|
|
2601
|
+
decision: {
|
|
2602
|
+
reasoning: decision.reasoning,
|
|
2603
|
+
confidence: decision.confidence
|
|
2604
|
+
}
|
|
2605
|
+
});
|
|
2189
2606
|
if (targetAgents.length === 0) {
|
|
2607
|
+
logger3.error("No target agents specified in routing decision");
|
|
2190
2608
|
throw new Error("Routing decision must specify at least one target agent");
|
|
2191
2609
|
}
|
|
2192
|
-
if (
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
}
|
|
2610
|
+
if (targetAgents.length === 1) {
|
|
2611
|
+
logger3.info("Routing to single agent", {
|
|
2612
|
+
targetAgent: targetAgents[0],
|
|
2613
|
+
reasoning: decision.reasoning,
|
|
2614
|
+
confidence: decision.confidence
|
|
2615
|
+
});
|
|
2616
|
+
} else {
|
|
2617
|
+
logger3.info("Routing to multiple agents in parallel", {
|
|
2618
|
+
targetAgents,
|
|
2619
|
+
count: targetAgents.length,
|
|
2620
|
+
reasoning: decision.reasoning,
|
|
2621
|
+
confidence: decision.confidence
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
if (targetAgents.length === 1) {
|
|
2625
|
+
logger3.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2626
|
+
} else {
|
|
2627
|
+
logger3.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
|
|
2198
2628
|
}
|
|
2199
2629
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2200
2630
|
const assignments = targetAgents.map((workerId) => ({
|
|
@@ -2204,6 +2634,14 @@ function createSupervisorNode(config) {
|
|
|
2204
2634
|
priority: 5,
|
|
2205
2635
|
assignedAt: Date.now()
|
|
2206
2636
|
}));
|
|
2637
|
+
logger3.debug("Created task assignments", {
|
|
2638
|
+
assignmentCount: assignments.length,
|
|
2639
|
+
assignments: assignments.map((a) => ({
|
|
2640
|
+
id: a.id,
|
|
2641
|
+
workerId: a.workerId,
|
|
2642
|
+
taskLength: a.task.length
|
|
2643
|
+
}))
|
|
2644
|
+
});
|
|
2207
2645
|
const messages = assignments.map((assignment) => ({
|
|
2208
2646
|
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
2209
2647
|
from: "supervisor",
|
|
@@ -2216,6 +2654,12 @@ function createSupervisorNode(config) {
|
|
|
2216
2654
|
priority: assignment.priority
|
|
2217
2655
|
}
|
|
2218
2656
|
}));
|
|
2657
|
+
logger3.info("Supervisor routing complete", {
|
|
2658
|
+
currentAgent: targetAgents.join(","),
|
|
2659
|
+
status: "executing",
|
|
2660
|
+
assignmentCount: assignments.length,
|
|
2661
|
+
nextIteration: state.iteration + 1
|
|
2662
|
+
});
|
|
2219
2663
|
return {
|
|
2220
2664
|
currentAgent: targetAgents.join(","),
|
|
2221
2665
|
// Store all agents (for backward compat)
|
|
@@ -2227,7 +2671,11 @@ function createSupervisorNode(config) {
|
|
|
2227
2671
|
iteration: state.iteration + 1
|
|
2228
2672
|
};
|
|
2229
2673
|
} catch (error) {
|
|
2230
|
-
|
|
2674
|
+
logger3.error("Supervisor node error", {
|
|
2675
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2676
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2677
|
+
iteration: state.iteration
|
|
2678
|
+
});
|
|
2231
2679
|
return {
|
|
2232
2680
|
status: "failed",
|
|
2233
2681
|
error: error instanceof Error ? error.message : "Unknown error in supervisor"
|
|
@@ -2248,40 +2696,52 @@ function createWorkerNode(config) {
|
|
|
2248
2696
|
} = config;
|
|
2249
2697
|
return async (state, runConfig) => {
|
|
2250
2698
|
try {
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2699
|
+
logger3.info("Worker node executing", {
|
|
2700
|
+
workerId: id,
|
|
2701
|
+
iteration: state.iteration,
|
|
2702
|
+
activeAssignments: state.activeAssignments.length
|
|
2703
|
+
});
|
|
2254
2704
|
const currentAssignment = state.activeAssignments.find(
|
|
2255
2705
|
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2256
2706
|
);
|
|
2257
2707
|
if (!currentAssignment) {
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2708
|
+
logger3.debug("No active assignment found for worker", {
|
|
2709
|
+
workerId: id,
|
|
2710
|
+
totalActiveAssignments: state.activeAssignments.length,
|
|
2711
|
+
completedTasks: state.completedTasks.length
|
|
2712
|
+
});
|
|
2261
2713
|
return {};
|
|
2262
2714
|
}
|
|
2715
|
+
logger3.info("Worker processing assignment", {
|
|
2716
|
+
workerId: id,
|
|
2717
|
+
assignmentId: currentAssignment.id,
|
|
2718
|
+
taskLength: currentAssignment.task.length,
|
|
2719
|
+
taskPreview: currentAssignment.task.substring(0, 100)
|
|
2720
|
+
});
|
|
2263
2721
|
if (executeFn) {
|
|
2264
|
-
|
|
2265
|
-
console.log(`[Worker:${id}] Using custom executeFn`);
|
|
2266
|
-
}
|
|
2722
|
+
logger3.debug("Using custom execution function", { workerId: id });
|
|
2267
2723
|
return await executeFn(state, runConfig);
|
|
2268
2724
|
}
|
|
2269
2725
|
if (agent) {
|
|
2270
2726
|
if (isReActAgent(agent)) {
|
|
2271
|
-
|
|
2272
|
-
console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
|
|
2273
|
-
}
|
|
2727
|
+
logger3.debug("Using ReAct agent", { workerId: id });
|
|
2274
2728
|
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
2275
2729
|
return await wrappedFn(state, runConfig);
|
|
2276
2730
|
} else {
|
|
2277
|
-
|
|
2731
|
+
logger3.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
|
|
2278
2732
|
}
|
|
2279
2733
|
}
|
|
2280
2734
|
if (!model) {
|
|
2735
|
+
logger3.error("Worker missing required configuration", { workerId: id });
|
|
2281
2736
|
throw new Error(
|
|
2282
2737
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2283
2738
|
);
|
|
2284
2739
|
}
|
|
2740
|
+
logger3.debug("Using default LLM execution", {
|
|
2741
|
+
workerId: id,
|
|
2742
|
+
hasTools: tools.length > 0,
|
|
2743
|
+
toolCount: tools.length
|
|
2744
|
+
});
|
|
2285
2745
|
const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
|
|
2286
2746
|
Skills: ${capabilities.skills.join(", ")}
|
|
2287
2747
|
Tools: ${capabilities.tools.join(", ")}
|
|
@@ -2293,14 +2753,23 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2293
2753
|
];
|
|
2294
2754
|
let modelToUse = model;
|
|
2295
2755
|
if (tools.length > 0 && model.bindTools) {
|
|
2756
|
+
logger3.debug("Binding tools to model", {
|
|
2757
|
+
workerId: id,
|
|
2758
|
+
toolCount: tools.length,
|
|
2759
|
+
toolNames: tools.map((t) => t.metadata.name)
|
|
2760
|
+
});
|
|
2296
2761
|
const langchainTools = toLangChainTools2(tools);
|
|
2297
2762
|
modelToUse = model.bindTools(langchainTools);
|
|
2298
2763
|
}
|
|
2764
|
+
logger3.debug("Invoking LLM", { workerId: id });
|
|
2299
2765
|
const response = await modelToUse.invoke(messages);
|
|
2300
2766
|
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2767
|
+
logger3.info("Worker task completed", {
|
|
2768
|
+
workerId: id,
|
|
2769
|
+
assignmentId: currentAssignment.id,
|
|
2770
|
+
resultLength: result.length,
|
|
2771
|
+
resultPreview: result.substring(0, 100)
|
|
2772
|
+
});
|
|
2304
2773
|
const taskResult = {
|
|
2305
2774
|
assignmentId: currentAssignment.id,
|
|
2306
2775
|
workerId: id,
|
|
@@ -2330,26 +2799,35 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2330
2799
|
currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
|
|
2331
2800
|
}
|
|
2332
2801
|
};
|
|
2802
|
+
logger3.debug("Worker state update", {
|
|
2803
|
+
workerId: id,
|
|
2804
|
+
newWorkload: updatedWorkers[id].currentWorkload
|
|
2805
|
+
});
|
|
2333
2806
|
return {
|
|
2334
2807
|
completedTasks: [taskResult],
|
|
2335
2808
|
messages: [message],
|
|
2336
2809
|
workers: updatedWorkers
|
|
2337
2810
|
};
|
|
2338
2811
|
} catch (error) {
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2812
|
+
const errorMessage = handleNodeError(error, `worker:${id}`, false);
|
|
2813
|
+
logger3.error("Worker node error", {
|
|
2814
|
+
workerId: id,
|
|
2815
|
+
error: errorMessage
|
|
2816
|
+
});
|
|
2343
2817
|
const currentAssignment = state.activeAssignments.find(
|
|
2344
2818
|
(assignment) => assignment.workerId === id
|
|
2345
2819
|
);
|
|
2346
2820
|
if (currentAssignment) {
|
|
2821
|
+
logger3.warn("Creating error result for assignment", {
|
|
2822
|
+
workerId: id,
|
|
2823
|
+
assignmentId: currentAssignment.id
|
|
2824
|
+
});
|
|
2347
2825
|
const errorResult = {
|
|
2348
2826
|
assignmentId: currentAssignment.id,
|
|
2349
2827
|
workerId: id,
|
|
2350
2828
|
success: false,
|
|
2351
2829
|
result: "",
|
|
2352
|
-
error:
|
|
2830
|
+
error: errorMessage,
|
|
2353
2831
|
completedAt: Date.now()
|
|
2354
2832
|
};
|
|
2355
2833
|
return {
|
|
@@ -2358,9 +2836,10 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2358
2836
|
status: "routing"
|
|
2359
2837
|
};
|
|
2360
2838
|
}
|
|
2839
|
+
logger3.error("No assignment found for error handling", { workerId: id });
|
|
2361
2840
|
return {
|
|
2362
2841
|
status: "failed",
|
|
2363
|
-
error:
|
|
2842
|
+
error: errorMessage
|
|
2364
2843
|
};
|
|
2365
2844
|
}
|
|
2366
2845
|
};
|
|
@@ -2374,29 +2853,44 @@ function createAggregatorNode(config = {}) {
|
|
|
2374
2853
|
} = config;
|
|
2375
2854
|
return async (state) => {
|
|
2376
2855
|
try {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2856
|
+
logger3.info("Aggregator node executing", {
|
|
2857
|
+
completedTasks: state.completedTasks.length,
|
|
2858
|
+
successfulTasks: state.completedTasks.filter((t) => t.success).length,
|
|
2859
|
+
failedTasks: state.completedTasks.filter((t) => !t.success).length
|
|
2860
|
+
});
|
|
2861
|
+
logger3.debug("Combining results from workers");
|
|
2380
2862
|
if (aggregateFn) {
|
|
2863
|
+
logger3.debug("Using custom aggregation function");
|
|
2381
2864
|
const response2 = await aggregateFn(state);
|
|
2865
|
+
logger3.info("Custom aggregation complete", {
|
|
2866
|
+
responseLength: response2.length
|
|
2867
|
+
});
|
|
2382
2868
|
return {
|
|
2383
2869
|
response: response2,
|
|
2384
2870
|
status: "completed"
|
|
2385
2871
|
};
|
|
2386
2872
|
}
|
|
2387
2873
|
if (state.completedTasks.length === 0) {
|
|
2874
|
+
logger3.warn("No completed tasks to aggregate");
|
|
2388
2875
|
return {
|
|
2389
2876
|
response: "No tasks were completed.",
|
|
2390
2877
|
status: "completed"
|
|
2391
2878
|
};
|
|
2392
2879
|
}
|
|
2393
2880
|
if (!model) {
|
|
2881
|
+
logger3.debug("No model provided, concatenating results");
|
|
2394
2882
|
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2883
|
+
logger3.info("Simple concatenation complete", {
|
|
2884
|
+
resultLength: combinedResults.length
|
|
2885
|
+
});
|
|
2395
2886
|
return {
|
|
2396
2887
|
response: combinedResults || "No successful results to aggregate.",
|
|
2397
2888
|
status: "completed"
|
|
2398
2889
|
};
|
|
2399
2890
|
}
|
|
2891
|
+
logger3.debug("Using LLM for intelligent aggregation", {
|
|
2892
|
+
taskCount: state.completedTasks.length
|
|
2893
|
+
});
|
|
2400
2894
|
const taskResults = state.completedTasks.map((task, idx) => {
|
|
2401
2895
|
const status = task.success ? "\u2713" : "\u2717";
|
|
2402
2896
|
const result = task.success ? task.result : `Error: ${task.error}`;
|
|
@@ -2413,17 +2907,24 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2413
2907
|
new SystemMessage5(systemPrompt),
|
|
2414
2908
|
new HumanMessage5(userPrompt)
|
|
2415
2909
|
];
|
|
2910
|
+
logger3.debug("Invoking aggregation LLM");
|
|
2416
2911
|
const response = await model.invoke(messages);
|
|
2417
2912
|
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2913
|
+
logger3.info("Aggregation complete", {
|
|
2914
|
+
responseLength: aggregatedResponse.length,
|
|
2915
|
+
responsePreview: aggregatedResponse.substring(0, 100)
|
|
2916
|
+
});
|
|
2917
|
+
logger3.debug("Aggregation complete");
|
|
2421
2918
|
return {
|
|
2422
2919
|
response: aggregatedResponse,
|
|
2423
2920
|
status: "completed"
|
|
2424
2921
|
};
|
|
2425
2922
|
} catch (error) {
|
|
2426
|
-
|
|
2923
|
+
logger3.error("Aggregator node error", {
|
|
2924
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2925
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2926
|
+
completedTasks: state.completedTasks.length
|
|
2927
|
+
});
|
|
2427
2928
|
return {
|
|
2428
2929
|
status: "failed",
|
|
2429
2930
|
error: error instanceof Error ? error.message : "Unknown error in aggregator"
|
|
@@ -2434,7 +2935,9 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2434
2935
|
|
|
2435
2936
|
// src/multi-agent/agent.ts
|
|
2436
2937
|
import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
|
|
2437
|
-
import { toLangChainTools as toLangChainTools3 } from "@agentforge/core";
|
|
2938
|
+
import { toLangChainTools as toLangChainTools3, createLogger as createLogger5, LogLevel as LogLevel4 } from "@agentforge/core";
|
|
2939
|
+
var logLevel4 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel4.INFO;
|
|
2940
|
+
var logger4 = createLogger5("multi-agent:system", { level: logLevel4 });
|
|
2438
2941
|
function createMultiAgentSystem(config) {
|
|
2439
2942
|
const {
|
|
2440
2943
|
supervisor,
|
|
@@ -2476,25 +2979,49 @@ function createMultiAgentSystem(config) {
|
|
|
2476
2979
|
});
|
|
2477
2980
|
workflow.addNode("aggregator", aggregatorNode);
|
|
2478
2981
|
const supervisorRouter = (state) => {
|
|
2982
|
+
logger4.debug("Supervisor router executing", {
|
|
2983
|
+
status: state.status,
|
|
2984
|
+
currentAgent: state.currentAgent,
|
|
2985
|
+
iteration: state.iteration
|
|
2986
|
+
});
|
|
2479
2987
|
if (state.status === "completed" || state.status === "failed") {
|
|
2988
|
+
logger4.info("Supervisor router: ending workflow", { status: state.status });
|
|
2480
2989
|
return END4;
|
|
2481
2990
|
}
|
|
2482
2991
|
if (state.status === "aggregating") {
|
|
2992
|
+
logger4.info("Supervisor router: routing to aggregator");
|
|
2483
2993
|
return "aggregator";
|
|
2484
2994
|
}
|
|
2485
2995
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
2486
2996
|
if (state.currentAgent.includes(",")) {
|
|
2487
2997
|
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
2998
|
+
logger4.info("Supervisor router: parallel routing", {
|
|
2999
|
+
agents,
|
|
3000
|
+
count: agents.length
|
|
3001
|
+
});
|
|
2488
3002
|
return agents;
|
|
2489
3003
|
}
|
|
3004
|
+
logger4.info("Supervisor router: single agent routing", {
|
|
3005
|
+
targetAgent: state.currentAgent
|
|
3006
|
+
});
|
|
2490
3007
|
return state.currentAgent;
|
|
2491
3008
|
}
|
|
3009
|
+
logger4.debug("Supervisor router: staying at supervisor");
|
|
2492
3010
|
return "supervisor";
|
|
2493
3011
|
};
|
|
2494
3012
|
const workerRouter = (state) => {
|
|
3013
|
+
logger4.debug("Worker router executing", {
|
|
3014
|
+
iteration: state.iteration,
|
|
3015
|
+
completedTasks: state.completedTasks.length
|
|
3016
|
+
});
|
|
3017
|
+
logger4.debug("Worker router: returning to supervisor");
|
|
2495
3018
|
return "supervisor";
|
|
2496
3019
|
};
|
|
2497
3020
|
const aggregatorRouter = (state) => {
|
|
3021
|
+
logger4.info("Aggregator router: ending workflow", {
|
|
3022
|
+
completedTasks: state.completedTasks.length,
|
|
3023
|
+
status: state.status
|
|
3024
|
+
});
|
|
2498
3025
|
return END4;
|
|
2499
3026
|
};
|
|
2500
3027
|
workflow.setEntryPoint("supervisor");
|
|
@@ -2687,11 +3214,14 @@ export {
|
|
|
2687
3214
|
ToolCallSchema,
|
|
2688
3215
|
ToolResultSchema,
|
|
2689
3216
|
WorkerCapabilitiesSchema,
|
|
3217
|
+
buildDeduplicationMetrics,
|
|
3218
|
+
calculateDeduplicationSavings,
|
|
2690
3219
|
createAggregatorNode,
|
|
2691
3220
|
createExecutorNode,
|
|
2692
3221
|
createFinisherNode,
|
|
2693
3222
|
createGeneratorNode,
|
|
2694
3223
|
createMultiAgentSystem,
|
|
3224
|
+
createPatternLogger,
|
|
2695
3225
|
createPlanExecuteAgent,
|
|
2696
3226
|
createPlannerNode,
|
|
2697
3227
|
createReActAgent,
|
|
@@ -2703,6 +3233,7 @@ export {
|
|
|
2703
3233
|
createReviserNode,
|
|
2704
3234
|
createSupervisorNode,
|
|
2705
3235
|
createWorkerNode,
|
|
3236
|
+
generateToolCallCacheKey,
|
|
2706
3237
|
getRoutingStrategy,
|
|
2707
3238
|
llmBasedRouting,
|
|
2708
3239
|
loadBalancedRouting,
|