@agentforge/patterns 0.6.3 → 0.6.4
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 +681 -119
- package/dist/index.d.cts +96 -2
- package/dist/index.d.ts +96 -2
- package/dist/index.js +666 -108
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -71,11 +71,14 @@ __export(index_exports, {
|
|
|
71
71
|
ToolCallSchema: () => ToolCallSchema,
|
|
72
72
|
ToolResultSchema: () => ToolResultSchema,
|
|
73
73
|
WorkerCapabilitiesSchema: () => WorkerCapabilitiesSchema,
|
|
74
|
+
buildDeduplicationMetrics: () => buildDeduplicationMetrics,
|
|
75
|
+
calculateDeduplicationSavings: () => calculateDeduplicationSavings,
|
|
74
76
|
createAggregatorNode: () => createAggregatorNode,
|
|
75
77
|
createExecutorNode: () => createExecutorNode,
|
|
76
78
|
createFinisherNode: () => createFinisherNode,
|
|
77
79
|
createGeneratorNode: () => createGeneratorNode,
|
|
78
80
|
createMultiAgentSystem: () => createMultiAgentSystem,
|
|
81
|
+
createPatternLogger: () => createPatternLogger,
|
|
79
82
|
createPlanExecuteAgent: () => createPlanExecuteAgent,
|
|
80
83
|
createPlannerNode: () => createPlannerNode,
|
|
81
84
|
createReActAgent: () => createReActAgent,
|
|
@@ -87,6 +90,7 @@ __export(index_exports, {
|
|
|
87
90
|
createReviserNode: () => createReviserNode,
|
|
88
91
|
createSupervisorNode: () => createSupervisorNode,
|
|
89
92
|
createWorkerNode: () => createWorkerNode,
|
|
93
|
+
generateToolCallCacheKey: () => generateToolCallCacheKey,
|
|
90
94
|
getRoutingStrategy: () => getRoutingStrategy,
|
|
91
95
|
llmBasedRouting: () => llmBasedRouting,
|
|
92
96
|
loadBalancedRouting: () => loadBalancedRouting,
|
|
@@ -121,7 +125,9 @@ var ToolResultSchema = import_zod.z.object({
|
|
|
121
125
|
toolCallId: import_zod.z.string(),
|
|
122
126
|
result: import_zod.z.any(),
|
|
123
127
|
error: import_zod.z.string().optional(),
|
|
124
|
-
timestamp: import_zod.z.number().optional()
|
|
128
|
+
timestamp: import_zod.z.number().optional(),
|
|
129
|
+
isDuplicate: import_zod.z.boolean().optional()
|
|
130
|
+
// Flag indicating this was a duplicate tool call
|
|
125
131
|
});
|
|
126
132
|
var ScratchpadEntrySchema = import_zod.z.object({
|
|
127
133
|
step: import_zod.z.number(),
|
|
@@ -245,14 +251,50 @@ function formatScratchpad(scratchpad) {
|
|
|
245
251
|
|
|
246
252
|
// src/react/nodes.ts
|
|
247
253
|
var import_messages = require("@langchain/core/messages");
|
|
254
|
+
var import_core3 = require("@agentforge/core");
|
|
255
|
+
|
|
256
|
+
// src/shared/deduplication.ts
|
|
248
257
|
var import_core2 = require("@agentforge/core");
|
|
258
|
+
function generateToolCallCacheKey(toolName, args) {
|
|
259
|
+
const sortedArgs = JSON.stringify(args, Object.keys(args || {}).sort());
|
|
260
|
+
return `${toolName}:${sortedArgs}`;
|
|
261
|
+
}
|
|
262
|
+
function createPatternLogger(name, defaultLevel = "info") {
|
|
263
|
+
const logLevel5 = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
|
|
264
|
+
return (0, import_core2.createLogger)(name, { level: logLevel5 });
|
|
265
|
+
}
|
|
266
|
+
function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
|
|
267
|
+
if (duplicatesSkipped === 0) {
|
|
268
|
+
return "0%";
|
|
269
|
+
}
|
|
270
|
+
const total = toolsExecuted + duplicatesSkipped;
|
|
271
|
+
return `${Math.round(duplicatesSkipped / total * 100)}%`;
|
|
272
|
+
}
|
|
273
|
+
function buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, totalObservations) {
|
|
274
|
+
return {
|
|
275
|
+
toolsExecuted,
|
|
276
|
+
duplicatesSkipped,
|
|
277
|
+
totalObservations,
|
|
278
|
+
deduplicationSavings: calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted)
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/react/nodes.ts
|
|
283
|
+
var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
|
|
284
|
+
var actionLogger = createPatternLogger("agentforge:patterns:react:action");
|
|
285
|
+
var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
|
|
249
286
|
function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
|
|
250
|
-
const langchainTools = (0,
|
|
287
|
+
const langchainTools = (0, import_core3.toLangChainTools)(tools);
|
|
251
288
|
const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
|
|
252
289
|
return async (state) => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
290
|
+
const currentIteration = state.iteration || 0;
|
|
291
|
+
const startTime = Date.now();
|
|
292
|
+
reasoningLogger.debug("Reasoning iteration started", {
|
|
293
|
+
iteration: currentIteration + 1,
|
|
294
|
+
maxIterations,
|
|
295
|
+
observationCount: state.observations?.length || 0,
|
|
296
|
+
hasActions: !!state.actions?.length
|
|
297
|
+
});
|
|
256
298
|
const stateMessages = state.messages || [];
|
|
257
299
|
const messages = [
|
|
258
300
|
new import_messages.SystemMessage(systemPrompt),
|
|
@@ -282,8 +324,15 @@ ${scratchpadText}`));
|
|
|
282
324
|
});
|
|
283
325
|
}
|
|
284
326
|
}
|
|
285
|
-
const currentIteration = state.iteration || 0;
|
|
286
327
|
const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
|
|
328
|
+
reasoningLogger.info("Reasoning complete", {
|
|
329
|
+
iteration: currentIteration + 1,
|
|
330
|
+
thoughtGenerated: !!thought,
|
|
331
|
+
actionCount: toolCalls.length,
|
|
332
|
+
shouldContinue,
|
|
333
|
+
isFinalResponse: toolCalls.length === 0,
|
|
334
|
+
duration: Date.now() - startTime
|
|
335
|
+
});
|
|
287
336
|
return {
|
|
288
337
|
messages: [{ role: "assistant", content: thought }],
|
|
289
338
|
thoughts: thought ? [{ content: thought, timestamp: Date.now() }] : [],
|
|
@@ -296,16 +345,71 @@ ${scratchpadText}`));
|
|
|
296
345
|
};
|
|
297
346
|
};
|
|
298
347
|
}
|
|
299
|
-
function createActionNode(tools, verbose = false) {
|
|
348
|
+
function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
300
349
|
const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
|
|
301
350
|
return async (state) => {
|
|
302
351
|
const actions = state.actions || [];
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
352
|
+
const allObservations = state.observations || [];
|
|
353
|
+
const iteration = state.iteration || 0;
|
|
354
|
+
const startTime = Date.now();
|
|
355
|
+
actionLogger.debug("Action node started", {
|
|
356
|
+
actionCount: actions.length,
|
|
357
|
+
iteration,
|
|
358
|
+
cacheEnabled: enableDeduplication
|
|
359
|
+
});
|
|
306
360
|
const recentActions = actions.slice(-10);
|
|
307
361
|
const observations = [];
|
|
362
|
+
const executionCache = /* @__PURE__ */ new Map();
|
|
363
|
+
let cacheSize = 0;
|
|
364
|
+
if (enableDeduplication) {
|
|
365
|
+
for (const observation of allObservations) {
|
|
366
|
+
const correspondingAction = actions.find((a) => a.id === observation.toolCallId);
|
|
367
|
+
if (correspondingAction) {
|
|
368
|
+
const cacheKey = generateToolCallCacheKey(correspondingAction.name, correspondingAction.arguments);
|
|
369
|
+
executionCache.set(cacheKey, observation);
|
|
370
|
+
cacheSize++;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (cacheSize > 0) {
|
|
374
|
+
actionLogger.debug("Deduplication cache built", {
|
|
375
|
+
cacheSize,
|
|
376
|
+
totalObservations: allObservations.length
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
let duplicatesSkipped = 0;
|
|
381
|
+
let toolsExecuted = 0;
|
|
308
382
|
for (const action of recentActions) {
|
|
383
|
+
const existingObservation = allObservations.find((obs) => obs.toolCallId === action.id);
|
|
384
|
+
if (existingObservation) {
|
|
385
|
+
actionLogger.debug("Skipping already-processed action", {
|
|
386
|
+
toolName: action.name,
|
|
387
|
+
toolCallId: action.id,
|
|
388
|
+
iteration
|
|
389
|
+
});
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (enableDeduplication) {
|
|
393
|
+
const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
|
|
394
|
+
const cachedResult = executionCache.get(cacheKey);
|
|
395
|
+
if (cachedResult) {
|
|
396
|
+
duplicatesSkipped++;
|
|
397
|
+
actionLogger.info("Duplicate tool call prevented", {
|
|
398
|
+
toolName: action.name,
|
|
399
|
+
arguments: action.arguments,
|
|
400
|
+
iteration,
|
|
401
|
+
cacheHit: true
|
|
402
|
+
});
|
|
403
|
+
observations.push({
|
|
404
|
+
toolCallId: action.id,
|
|
405
|
+
result: cachedResult.result,
|
|
406
|
+
error: cachedResult.error,
|
|
407
|
+
timestamp: Date.now(),
|
|
408
|
+
isDuplicate: true
|
|
409
|
+
});
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
309
413
|
const tool = toolMap.get(action.name);
|
|
310
414
|
if (!tool) {
|
|
311
415
|
observations.push({
|
|
@@ -317,31 +421,51 @@ function createActionNode(tools, verbose = false) {
|
|
|
317
421
|
continue;
|
|
318
422
|
}
|
|
319
423
|
try {
|
|
424
|
+
const startTime2 = Date.now();
|
|
320
425
|
const result = await tool.execute(action.arguments);
|
|
321
|
-
|
|
426
|
+
const executionTime = Date.now() - startTime2;
|
|
427
|
+
toolsExecuted++;
|
|
428
|
+
actionLogger.debug("Tool executed successfully", {
|
|
429
|
+
toolName: action.name,
|
|
430
|
+
executionTime,
|
|
431
|
+
iteration
|
|
432
|
+
});
|
|
433
|
+
const observation = {
|
|
322
434
|
toolCallId: action.id,
|
|
323
435
|
result,
|
|
324
436
|
timestamp: Date.now()
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
437
|
+
};
|
|
438
|
+
observations.push(observation);
|
|
439
|
+
if (enableDeduplication) {
|
|
440
|
+
const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
|
|
441
|
+
executionCache.set(cacheKey, observation);
|
|
328
442
|
}
|
|
329
443
|
} catch (error) {
|
|
330
444
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
331
445
|
throw error;
|
|
332
446
|
}
|
|
333
447
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
448
|
+
actionLogger.error("Tool execution failed", {
|
|
449
|
+
toolName: action.name,
|
|
450
|
+
error: errorMessage,
|
|
451
|
+
iteration
|
|
452
|
+
});
|
|
334
453
|
observations.push({
|
|
335
454
|
toolCallId: action.id,
|
|
336
455
|
result: null,
|
|
337
456
|
error: errorMessage,
|
|
338
457
|
timestamp: Date.now()
|
|
339
458
|
});
|
|
340
|
-
if (verbose) {
|
|
341
|
-
console.error(`[action] Tool '${action.name}' failed:`, errorMessage);
|
|
342
|
-
}
|
|
343
459
|
}
|
|
344
460
|
}
|
|
461
|
+
if (duplicatesSkipped > 0 || toolsExecuted > 0) {
|
|
462
|
+
const metrics = buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, observations.length);
|
|
463
|
+
actionLogger.info("Action node complete", {
|
|
464
|
+
iteration,
|
|
465
|
+
...metrics,
|
|
466
|
+
duration: Date.now() - startTime
|
|
467
|
+
});
|
|
468
|
+
}
|
|
345
469
|
return {
|
|
346
470
|
observations
|
|
347
471
|
};
|
|
@@ -352,9 +476,11 @@ function createObservationNode(verbose = false) {
|
|
|
352
476
|
const observations = state.observations || [];
|
|
353
477
|
const thoughts = state.thoughts || [];
|
|
354
478
|
const actions = state.actions || [];
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
479
|
+
const iteration = state.iteration || 0;
|
|
480
|
+
observationLogger.debug("Processing observations", {
|
|
481
|
+
observationCount: observations.length,
|
|
482
|
+
iteration
|
|
483
|
+
});
|
|
358
484
|
const recentObservations = observations.slice(-10);
|
|
359
485
|
const currentStep = state.iteration;
|
|
360
486
|
const latestThought = thoughts[thoughts.length - 1]?.content || "";
|
|
@@ -380,6 +506,11 @@ function createObservationNode(verbose = false) {
|
|
|
380
506
|
name: latestActions.find((a) => a.id === obs.toolCallId)?.name
|
|
381
507
|
};
|
|
382
508
|
});
|
|
509
|
+
observationLogger.debug("Observation node complete", {
|
|
510
|
+
iteration,
|
|
511
|
+
scratchpadUpdated: true,
|
|
512
|
+
messageCount: observationMessages.length
|
|
513
|
+
});
|
|
383
514
|
return {
|
|
384
515
|
scratchpad: [scratchpadEntry],
|
|
385
516
|
messages: observationMessages
|
|
@@ -389,7 +520,7 @@ function createObservationNode(verbose = false) {
|
|
|
389
520
|
|
|
390
521
|
// src/react/agent.ts
|
|
391
522
|
var import_langgraph = require("@langchain/langgraph");
|
|
392
|
-
var
|
|
523
|
+
var import_core4 = require("@agentforge/core");
|
|
393
524
|
function createReActAgent(config, options) {
|
|
394
525
|
const {
|
|
395
526
|
model,
|
|
@@ -398,13 +529,15 @@ function createReActAgent(config, options) {
|
|
|
398
529
|
maxIterations = 10,
|
|
399
530
|
returnIntermediateSteps = false,
|
|
400
531
|
stopCondition,
|
|
401
|
-
checkpointer
|
|
532
|
+
checkpointer,
|
|
533
|
+
enableDeduplication = true
|
|
534
|
+
// Enable by default
|
|
402
535
|
} = config;
|
|
403
536
|
const {
|
|
404
537
|
verbose = false,
|
|
405
538
|
nodeNames = {}
|
|
406
539
|
} = options || {};
|
|
407
|
-
const toolArray = tools instanceof
|
|
540
|
+
const toolArray = tools instanceof import_core4.ToolRegistry ? tools.getAll() : tools;
|
|
408
541
|
const REASONING_NODE = nodeNames.reasoning || "reasoning";
|
|
409
542
|
const ACTION_NODE = nodeNames.action || "action";
|
|
410
543
|
const OBSERVATION_NODE = nodeNames.observation || "observation";
|
|
@@ -415,7 +548,7 @@ function createReActAgent(config, options) {
|
|
|
415
548
|
maxIterations,
|
|
416
549
|
verbose
|
|
417
550
|
);
|
|
418
|
-
const actionNode = createActionNode(toolArray, verbose);
|
|
551
|
+
const actionNode = createActionNode(toolArray, verbose, enableDeduplication);
|
|
419
552
|
const observationNode = createObservationNode(verbose);
|
|
420
553
|
const shouldContinue = (state) => {
|
|
421
554
|
if (stopCondition && stopCondition(state)) {
|
|
@@ -551,7 +684,7 @@ var import_langgraph2 = require("@langchain/langgraph");
|
|
|
551
684
|
|
|
552
685
|
// src/plan-execute/state.ts
|
|
553
686
|
var import_zod4 = require("zod");
|
|
554
|
-
var
|
|
687
|
+
var import_core5 = require("@agentforge/core");
|
|
555
688
|
|
|
556
689
|
// src/plan-execute/schemas.ts
|
|
557
690
|
var import_zod3 = require("zod");
|
|
@@ -713,7 +846,7 @@ var PlanExecuteStateConfig = {
|
|
|
713
846
|
description: "Maximum number of planning iterations allowed"
|
|
714
847
|
}
|
|
715
848
|
};
|
|
716
|
-
var PlanExecuteState = (0,
|
|
849
|
+
var PlanExecuteState = (0, import_core5.createStateAnnotation)(PlanExecuteStateConfig);
|
|
717
850
|
|
|
718
851
|
// src/plan-execute/nodes.ts
|
|
719
852
|
var import_messages2 = require("@langchain/core/messages");
|
|
@@ -778,6 +911,9 @@ var REMAINING_STEP_TEMPLATE = `Step {stepNumber}: {description}
|
|
|
778
911
|
{dependencies}`;
|
|
779
912
|
|
|
780
913
|
// src/plan-execute/nodes.ts
|
|
914
|
+
var plannerLogger = createPatternLogger("agentforge:patterns:plan-execute:planner");
|
|
915
|
+
var executorLogger = createPatternLogger("agentforge:patterns:plan-execute:executor");
|
|
916
|
+
var replannerLogger = createPatternLogger("agentforge:patterns:plan-execute:replanner");
|
|
781
917
|
function createPlannerNode(config) {
|
|
782
918
|
const {
|
|
783
919
|
model,
|
|
@@ -786,7 +922,13 @@ function createPlannerNode(config) {
|
|
|
786
922
|
includeToolDescriptions = false
|
|
787
923
|
} = config;
|
|
788
924
|
return async (state) => {
|
|
925
|
+
const startTime = Date.now();
|
|
789
926
|
try {
|
|
927
|
+
plannerLogger.debug("Planning started", {
|
|
928
|
+
input: state.input?.substring(0, 100),
|
|
929
|
+
maxSteps,
|
|
930
|
+
includeToolDescriptions
|
|
931
|
+
});
|
|
790
932
|
let toolDescriptions = "";
|
|
791
933
|
if (includeToolDescriptions) {
|
|
792
934
|
toolDescriptions = "";
|
|
@@ -811,6 +953,12 @@ function createPlannerNode(config) {
|
|
|
811
953
|
} catch (parseError) {
|
|
812
954
|
throw new Error(`Failed to parse plan from LLM response: ${parseError}`);
|
|
813
955
|
}
|
|
956
|
+
plannerLogger.info("Plan created", {
|
|
957
|
+
stepCount: plan.steps.length,
|
|
958
|
+
goal: plan.goal.substring(0, 100),
|
|
959
|
+
confidence: plan.confidence,
|
|
960
|
+
duration: Date.now() - startTime
|
|
961
|
+
});
|
|
814
962
|
return {
|
|
815
963
|
plan,
|
|
816
964
|
status: "executing",
|
|
@@ -818,6 +966,10 @@ function createPlannerNode(config) {
|
|
|
818
966
|
iteration: 1
|
|
819
967
|
};
|
|
820
968
|
} catch (error) {
|
|
969
|
+
plannerLogger.error("Planning failed", {
|
|
970
|
+
error: error instanceof Error ? error.message : String(error),
|
|
971
|
+
duration: Date.now() - startTime
|
|
972
|
+
});
|
|
821
973
|
return {
|
|
822
974
|
status: "failed",
|
|
823
975
|
error: error instanceof Error ? error.message : "Unknown error in planner"
|
|
@@ -830,11 +982,18 @@ function createExecutorNode(config) {
|
|
|
830
982
|
tools,
|
|
831
983
|
model,
|
|
832
984
|
parallel = false,
|
|
833
|
-
stepTimeout = 3e4
|
|
985
|
+
stepTimeout = 3e4,
|
|
986
|
+
enableDeduplication = true
|
|
834
987
|
} = config;
|
|
835
988
|
return async (state) => {
|
|
989
|
+
const { plan, currentStepIndex = 0, pastSteps = [], iteration = 0 } = state;
|
|
836
990
|
try {
|
|
837
|
-
|
|
991
|
+
executorLogger.debug("Executor node executing", {
|
|
992
|
+
currentStepIndex,
|
|
993
|
+
totalSteps: plan?.steps?.length || 0,
|
|
994
|
+
iteration,
|
|
995
|
+
deduplicationEnabled: enableDeduplication
|
|
996
|
+
});
|
|
838
997
|
if (!plan || !plan.steps || plan.steps.length === 0) {
|
|
839
998
|
return {
|
|
840
999
|
status: "completed"
|
|
@@ -853,22 +1012,67 @@ function createExecutorNode(config) {
|
|
|
853
1012
|
throw new Error(`Unmet dependencies for step ${currentStep.id}: ${unmetDependencies.join(", ")}`);
|
|
854
1013
|
}
|
|
855
1014
|
}
|
|
1015
|
+
const executionCache = /* @__PURE__ */ new Map();
|
|
1016
|
+
let cacheSize = 0;
|
|
1017
|
+
if (enableDeduplication && currentStep.tool) {
|
|
1018
|
+
for (const pastStep of pastSteps) {
|
|
1019
|
+
if (pastStep.step.tool) {
|
|
1020
|
+
const cacheKey = generateToolCallCacheKey(pastStep.step.tool, pastStep.step.args || {});
|
|
1021
|
+
executionCache.set(cacheKey, pastStep);
|
|
1022
|
+
cacheSize++;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (cacheSize > 0) {
|
|
1026
|
+
executorLogger.debug("Deduplication cache built", {
|
|
1027
|
+
cacheSize,
|
|
1028
|
+
pastStepsCount: pastSteps.length
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
856
1032
|
let result;
|
|
857
1033
|
let success = true;
|
|
858
1034
|
let error;
|
|
1035
|
+
let isDuplicate = false;
|
|
859
1036
|
try {
|
|
860
1037
|
if (currentStep.tool) {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1038
|
+
if (enableDeduplication) {
|
|
1039
|
+
const cacheKey = generateToolCallCacheKey(currentStep.tool, currentStep.args || {});
|
|
1040
|
+
const cachedStep = executionCache.get(cacheKey);
|
|
1041
|
+
if (cachedStep) {
|
|
1042
|
+
isDuplicate = true;
|
|
1043
|
+
result = cachedStep.result;
|
|
1044
|
+
success = cachedStep.success;
|
|
1045
|
+
error = cachedStep.error;
|
|
1046
|
+
executorLogger.info("Duplicate step execution prevented", {
|
|
1047
|
+
stepId: currentStep.id,
|
|
1048
|
+
toolName: currentStep.tool,
|
|
1049
|
+
arguments: currentStep.args,
|
|
1050
|
+
iteration,
|
|
1051
|
+
cacheHit: true
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (!isDuplicate) {
|
|
1056
|
+
const tool = tools.find((t) => t.metadata.name === currentStep.tool);
|
|
1057
|
+
if (!tool) {
|
|
1058
|
+
throw new Error(`Tool not found: ${currentStep.tool}`);
|
|
1059
|
+
}
|
|
1060
|
+
const startTime = Date.now();
|
|
1061
|
+
const timeoutPromise = new Promise(
|
|
1062
|
+
(_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
|
|
1063
|
+
);
|
|
1064
|
+
result = await Promise.race([
|
|
1065
|
+
tool.execute(currentStep.args || {}),
|
|
1066
|
+
timeoutPromise
|
|
1067
|
+
]);
|
|
1068
|
+
const executionTime = Date.now() - startTime;
|
|
1069
|
+
executorLogger.debug("Step executed successfully", {
|
|
1070
|
+
stepId: currentStep.id,
|
|
1071
|
+
toolName: currentStep.tool,
|
|
1072
|
+
executionTime,
|
|
1073
|
+
iteration
|
|
1074
|
+
});
|
|
864
1075
|
}
|
|
865
|
-
const timeoutPromise = new Promise(
|
|
866
|
-
(_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
|
|
867
|
-
);
|
|
868
|
-
result = await Promise.race([
|
|
869
|
-
tool.execute(currentStep.args || {}),
|
|
870
|
-
timeoutPromise
|
|
871
|
-
]);
|
|
872
1076
|
} else {
|
|
873
1077
|
result = { message: "Step completed without tool execution" };
|
|
874
1078
|
}
|
|
@@ -879,6 +1083,12 @@ function createExecutorNode(config) {
|
|
|
879
1083
|
success = false;
|
|
880
1084
|
error = execError instanceof Error ? execError.message : "Unknown execution error";
|
|
881
1085
|
result = null;
|
|
1086
|
+
executorLogger.warn("Step execution failed", {
|
|
1087
|
+
stepId: currentStep.id,
|
|
1088
|
+
toolName: currentStep.tool,
|
|
1089
|
+
error,
|
|
1090
|
+
iteration
|
|
1091
|
+
});
|
|
882
1092
|
}
|
|
883
1093
|
const completedStep = {
|
|
884
1094
|
step: currentStep,
|
|
@@ -887,11 +1097,23 @@ function createExecutorNode(config) {
|
|
|
887
1097
|
error,
|
|
888
1098
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
889
1099
|
};
|
|
1100
|
+
executorLogger.info("Executor node complete", {
|
|
1101
|
+
stepId: currentStep.id,
|
|
1102
|
+
stepIndex: currentStepIndex,
|
|
1103
|
+
totalSteps: plan.steps.length,
|
|
1104
|
+
success,
|
|
1105
|
+
isDuplicate,
|
|
1106
|
+
iteration
|
|
1107
|
+
});
|
|
890
1108
|
return {
|
|
891
1109
|
pastSteps: [completedStep],
|
|
892
1110
|
currentStepIndex: currentStepIndex + 1
|
|
893
1111
|
};
|
|
894
1112
|
} catch (error) {
|
|
1113
|
+
executorLogger.error("Executor node failed", {
|
|
1114
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1115
|
+
iteration
|
|
1116
|
+
});
|
|
895
1117
|
return {
|
|
896
1118
|
status: "failed",
|
|
897
1119
|
error: error instanceof Error ? error.message : "Unknown error in executor"
|
|
@@ -906,11 +1128,18 @@ function createReplannerNode(config) {
|
|
|
906
1128
|
systemPrompt = DEFAULT_REPLANNER_SYSTEM_PROMPT
|
|
907
1129
|
} = config;
|
|
908
1130
|
return async (state) => {
|
|
1131
|
+
const startTime = Date.now();
|
|
909
1132
|
try {
|
|
910
1133
|
const { plan, pastSteps = [], currentStepIndex = 0 } = state;
|
|
911
1134
|
if (!plan) {
|
|
912
1135
|
return { status: "failed", error: "No plan available for replanning" };
|
|
913
1136
|
}
|
|
1137
|
+
replannerLogger.debug("Evaluating replanning", {
|
|
1138
|
+
completedSteps: pastSteps.length,
|
|
1139
|
+
remainingSteps: plan.steps.length - currentStepIndex,
|
|
1140
|
+
successfulSteps: pastSteps.filter((ps) => ps.success).length,
|
|
1141
|
+
failedSteps: pastSteps.filter((ps) => !ps.success).length
|
|
1142
|
+
});
|
|
914
1143
|
const completedStepsText = pastSteps.map(
|
|
915
1144
|
(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}`)
|
|
916
1145
|
).join("\n\n");
|
|
@@ -932,17 +1161,30 @@ function createReplannerNode(config) {
|
|
|
932
1161
|
throw new Error(`Failed to parse replan decision from LLM response: ${parseError}`);
|
|
933
1162
|
}
|
|
934
1163
|
if (decision.shouldReplan) {
|
|
1164
|
+
replannerLogger.info("Replanning triggered", {
|
|
1165
|
+
reason: decision.reason,
|
|
1166
|
+
newGoal: decision.newGoal?.substring(0, 100),
|
|
1167
|
+
duration: Date.now() - startTime
|
|
1168
|
+
});
|
|
935
1169
|
return {
|
|
936
1170
|
status: "planning",
|
|
937
1171
|
input: decision.newGoal || plan.goal,
|
|
938
1172
|
iteration: 1
|
|
939
1173
|
};
|
|
940
1174
|
} else {
|
|
1175
|
+
replannerLogger.info("Continuing with current plan", {
|
|
1176
|
+
reason: decision.reason,
|
|
1177
|
+
duration: Date.now() - startTime
|
|
1178
|
+
});
|
|
941
1179
|
return {
|
|
942
1180
|
status: "executing"
|
|
943
1181
|
};
|
|
944
1182
|
}
|
|
945
1183
|
} catch (error) {
|
|
1184
|
+
replannerLogger.error("Replanning evaluation failed", {
|
|
1185
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1186
|
+
duration: Date.now() - startTime
|
|
1187
|
+
});
|
|
946
1188
|
return {
|
|
947
1189
|
status: "failed",
|
|
948
1190
|
error: error instanceof Error ? error.message : "Unknown error in replanner"
|
|
@@ -1043,7 +1285,7 @@ function createPlanExecuteAgent(config) {
|
|
|
1043
1285
|
|
|
1044
1286
|
// src/reflection/state.ts
|
|
1045
1287
|
var import_zod6 = require("zod");
|
|
1046
|
-
var
|
|
1288
|
+
var import_core6 = require("@agentforge/core");
|
|
1047
1289
|
|
|
1048
1290
|
// src/reflection/schemas.ts
|
|
1049
1291
|
var import_zod5 = require("zod");
|
|
@@ -1216,7 +1458,7 @@ var ReflectionStateConfig = {
|
|
|
1216
1458
|
description: "Error message if reflection failed"
|
|
1217
1459
|
}
|
|
1218
1460
|
};
|
|
1219
|
-
var ReflectionState = (0,
|
|
1461
|
+
var ReflectionState = (0, import_core6.createStateAnnotation)(ReflectionStateConfig);
|
|
1220
1462
|
|
|
1221
1463
|
// src/reflection/prompts.ts
|
|
1222
1464
|
var DEFAULT_GENERATOR_SYSTEM_PROMPT = `You are an expert content generator. Your task is to create high-quality responses to user requests.
|
|
@@ -1320,6 +1562,9 @@ var REVISION_ENTRY_TEMPLATE = `Iteration {iteration}:
|
|
|
1320
1562
|
|
|
1321
1563
|
// src/reflection/nodes.ts
|
|
1322
1564
|
var import_messages3 = require("@langchain/core/messages");
|
|
1565
|
+
var generatorLogger = createPatternLogger("agentforge:patterns:reflection:generator");
|
|
1566
|
+
var reflectorLogger = createPatternLogger("agentforge:patterns:reflection:reflector");
|
|
1567
|
+
var reviserLogger = createPatternLogger("agentforge:patterns:reflection:reviser");
|
|
1323
1568
|
function createGeneratorNode(config) {
|
|
1324
1569
|
const {
|
|
1325
1570
|
model,
|
|
@@ -1327,10 +1572,13 @@ function createGeneratorNode(config) {
|
|
|
1327
1572
|
verbose = false
|
|
1328
1573
|
} = config;
|
|
1329
1574
|
return async (state) => {
|
|
1575
|
+
const startTime = Date.now();
|
|
1330
1576
|
try {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1577
|
+
generatorLogger.debug("Generating response", {
|
|
1578
|
+
attempt: state.iteration + 1,
|
|
1579
|
+
hasFeedback: state.reflections.length > 0,
|
|
1580
|
+
hasExistingResponse: !!state.currentResponse
|
|
1581
|
+
});
|
|
1334
1582
|
let context = "";
|
|
1335
1583
|
if (state.iteration > 0 && state.reflections.length > 0) {
|
|
1336
1584
|
const lastReflection = state.reflections[state.reflections.length - 1];
|
|
@@ -1345,16 +1593,23 @@ ${lastReflection.critique}`;
|
|
|
1345
1593
|
];
|
|
1346
1594
|
const response = await model.invoke(messages);
|
|
1347
1595
|
const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1596
|
+
generatorLogger.info("Response generated", {
|
|
1597
|
+
attempt: state.iteration + 1,
|
|
1598
|
+
responseLength: content.length,
|
|
1599
|
+
isRevision: state.iteration > 0,
|
|
1600
|
+
duration: Date.now() - startTime
|
|
1601
|
+
});
|
|
1351
1602
|
return {
|
|
1352
1603
|
currentResponse: content,
|
|
1353
1604
|
status: "reflecting",
|
|
1354
1605
|
iteration: 1
|
|
1355
1606
|
};
|
|
1356
1607
|
} catch (error) {
|
|
1357
|
-
|
|
1608
|
+
generatorLogger.error("Response generation failed", {
|
|
1609
|
+
attempt: state.iteration + 1,
|
|
1610
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1611
|
+
duration: Date.now() - startTime
|
|
1612
|
+
});
|
|
1358
1613
|
return {
|
|
1359
1614
|
status: "failed",
|
|
1360
1615
|
error: error instanceof Error ? error.message : "Unknown error in generator"
|
|
@@ -1370,10 +1625,13 @@ function createReflectorNode(config) {
|
|
|
1370
1625
|
verbose = false
|
|
1371
1626
|
} = config;
|
|
1372
1627
|
return async (state) => {
|
|
1628
|
+
const startTime = Date.now();
|
|
1373
1629
|
try {
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1630
|
+
reflectorLogger.debug("Reflecting on response", {
|
|
1631
|
+
attempt: state.iteration,
|
|
1632
|
+
responseLength: state.currentResponse?.length || 0,
|
|
1633
|
+
hasCriteria: !!(qualityCriteria || state.qualityCriteria)
|
|
1634
|
+
});
|
|
1377
1635
|
if (!state.currentResponse) {
|
|
1378
1636
|
throw new Error("No current response to reflect on");
|
|
1379
1637
|
}
|
|
@@ -1423,16 +1681,24 @@ function createReflectorNode(config) {
|
|
|
1423
1681
|
meetsStandards: false
|
|
1424
1682
|
};
|
|
1425
1683
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1684
|
+
reflectorLogger.info("Reflection complete", {
|
|
1685
|
+
attempt: state.iteration,
|
|
1686
|
+
score: reflection.score,
|
|
1687
|
+
meetsStandards: reflection.meetsStandards,
|
|
1688
|
+
issueCount: reflection.issues.length,
|
|
1689
|
+
suggestionCount: reflection.suggestions.length,
|
|
1690
|
+
duration: Date.now() - startTime
|
|
1691
|
+
});
|
|
1430
1692
|
return {
|
|
1431
1693
|
reflections: [reflection],
|
|
1432
1694
|
status: reflection.meetsStandards ? "completed" : "revising"
|
|
1433
1695
|
};
|
|
1434
1696
|
} catch (error) {
|
|
1435
|
-
|
|
1697
|
+
reflectorLogger.error("Reflection failed", {
|
|
1698
|
+
attempt: state.iteration,
|
|
1699
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1700
|
+
duration: Date.now() - startTime
|
|
1701
|
+
});
|
|
1436
1702
|
return {
|
|
1437
1703
|
status: "failed",
|
|
1438
1704
|
error: error instanceof Error ? error.message : "Unknown error in reflector"
|
|
@@ -1447,10 +1713,8 @@ function createReviserNode(config) {
|
|
|
1447
1713
|
verbose = false
|
|
1448
1714
|
} = config;
|
|
1449
1715
|
return async (state) => {
|
|
1716
|
+
const startTime = Date.now();
|
|
1450
1717
|
try {
|
|
1451
|
-
if (verbose) {
|
|
1452
|
-
console.log("[Reviser] Revising response...");
|
|
1453
|
-
}
|
|
1454
1718
|
if (!state.currentResponse) {
|
|
1455
1719
|
throw new Error("No current response to revise");
|
|
1456
1720
|
}
|
|
@@ -1458,6 +1722,12 @@ function createReviserNode(config) {
|
|
|
1458
1722
|
throw new Error("No reflections to base revision on");
|
|
1459
1723
|
}
|
|
1460
1724
|
const lastReflection = state.reflections[state.reflections.length - 1];
|
|
1725
|
+
reviserLogger.debug("Revising response", {
|
|
1726
|
+
attempt: state.iteration,
|
|
1727
|
+
previousScore: lastReflection.score,
|
|
1728
|
+
issueCount: lastReflection.issues.length,
|
|
1729
|
+
suggestionCount: lastReflection.suggestions.length
|
|
1730
|
+
});
|
|
1461
1731
|
let historySection = "";
|
|
1462
1732
|
if (state.revisions.length > 0) {
|
|
1463
1733
|
const revisionsText = state.revisions.map(
|
|
@@ -1474,14 +1744,17 @@ ${revisionsText}`;
|
|
|
1474
1744
|
];
|
|
1475
1745
|
const response = await model.invoke(messages);
|
|
1476
1746
|
const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
1477
|
-
if (verbose) {
|
|
1478
|
-
console.log("[Reviser] Created revision:", content.substring(0, 100) + "...");
|
|
1479
|
-
}
|
|
1480
1747
|
const revision = {
|
|
1481
1748
|
content,
|
|
1482
1749
|
iteration: state.iteration,
|
|
1483
1750
|
basedOn: lastReflection
|
|
1484
1751
|
};
|
|
1752
|
+
reviserLogger.info("Revision complete", {
|
|
1753
|
+
attempt: state.iteration,
|
|
1754
|
+
revisionLength: content.length,
|
|
1755
|
+
basedOnScore: lastReflection.score,
|
|
1756
|
+
duration: Date.now() - startTime
|
|
1757
|
+
});
|
|
1485
1758
|
return {
|
|
1486
1759
|
currentResponse: content,
|
|
1487
1760
|
revisions: [revision],
|
|
@@ -1489,7 +1762,11 @@ ${revisionsText}`;
|
|
|
1489
1762
|
iteration: 1
|
|
1490
1763
|
};
|
|
1491
1764
|
} catch (error) {
|
|
1492
|
-
|
|
1765
|
+
reviserLogger.error("Revision failed", {
|
|
1766
|
+
attempt: state.iteration,
|
|
1767
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1768
|
+
duration: Date.now() - startTime
|
|
1769
|
+
});
|
|
1493
1770
|
return {
|
|
1494
1771
|
status: "failed",
|
|
1495
1772
|
error: error instanceof Error ? error.message : "Unknown error in reviser"
|
|
@@ -1579,7 +1856,7 @@ function createReflectionAgent(config) {
|
|
|
1579
1856
|
|
|
1580
1857
|
// src/multi-agent/state.ts
|
|
1581
1858
|
var import_zod8 = require("zod");
|
|
1582
|
-
var
|
|
1859
|
+
var import_core7 = require("@agentforge/core");
|
|
1583
1860
|
|
|
1584
1861
|
// src/multi-agent/schemas.ts
|
|
1585
1862
|
var import_zod7 = require("zod");
|
|
@@ -1902,15 +2179,26 @@ var MultiAgentStateConfig = {
|
|
|
1902
2179
|
description: "Error message if execution failed"
|
|
1903
2180
|
}
|
|
1904
2181
|
};
|
|
1905
|
-
var MultiAgentState = (0,
|
|
2182
|
+
var MultiAgentState = (0, import_core7.createStateAnnotation)(MultiAgentStateConfig);
|
|
1906
2183
|
|
|
1907
2184
|
// src/multi-agent/routing.ts
|
|
1908
2185
|
var import_messages4 = require("@langchain/core/messages");
|
|
2186
|
+
var import_core8 = require("@agentforge/core");
|
|
2187
|
+
var logLevel = process.env.LOG_LEVEL?.toLowerCase() || import_core8.LogLevel.INFO;
|
|
2188
|
+
var logger = (0, import_core8.createLogger)("multi-agent:routing", { level: logLevel });
|
|
1909
2189
|
async function executeTools(toolCalls, tools) {
|
|
1910
2190
|
const results = [];
|
|
2191
|
+
logger.debug("Executing tools", {
|
|
2192
|
+
toolCallCount: toolCalls.length,
|
|
2193
|
+
toolNames: toolCalls.map((tc) => tc.name)
|
|
2194
|
+
});
|
|
1911
2195
|
for (const toolCall of toolCalls) {
|
|
1912
2196
|
const tool = tools.find((t) => t.metadata.name === toolCall.name);
|
|
1913
2197
|
if (!tool) {
|
|
2198
|
+
logger.warn("Tool not found", {
|
|
2199
|
+
toolName: toolCall.name,
|
|
2200
|
+
availableTools: tools.map((t) => t.metadata.name)
|
|
2201
|
+
});
|
|
1914
2202
|
results.push(new import_messages4.ToolMessage({
|
|
1915
2203
|
content: `Error: Tool '${toolCall.name}' not found`,
|
|
1916
2204
|
tool_call_id: toolCall.id
|
|
@@ -1918,19 +2206,41 @@ async function executeTools(toolCalls, tools) {
|
|
|
1918
2206
|
continue;
|
|
1919
2207
|
}
|
|
1920
2208
|
try {
|
|
2209
|
+
logger.debug("Executing tool", {
|
|
2210
|
+
toolName: toolCall.name,
|
|
2211
|
+
args: toolCall.args
|
|
2212
|
+
});
|
|
1921
2213
|
const result = await tool.execute(toolCall.args);
|
|
1922
2214
|
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
2215
|
+
logger.debug("Tool execution successful", {
|
|
2216
|
+
toolName: toolCall.name,
|
|
2217
|
+
resultLength: content.length
|
|
2218
|
+
});
|
|
1923
2219
|
results.push(new import_messages4.ToolMessage({
|
|
1924
2220
|
content,
|
|
1925
2221
|
tool_call_id: toolCall.id
|
|
1926
2222
|
}));
|
|
1927
2223
|
} catch (error) {
|
|
2224
|
+
logger.error("Tool execution failed", {
|
|
2225
|
+
toolName: toolCall.name,
|
|
2226
|
+
error: error.message
|
|
2227
|
+
});
|
|
1928
2228
|
results.push(new import_messages4.ToolMessage({
|
|
1929
2229
|
content: `Error executing tool: ${error.message}`,
|
|
1930
2230
|
tool_call_id: toolCall.id
|
|
1931
2231
|
}));
|
|
1932
2232
|
}
|
|
1933
2233
|
}
|
|
2234
|
+
logger.debug("Tool execution complete", {
|
|
2235
|
+
successCount: results.filter((r) => {
|
|
2236
|
+
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2237
|
+
return !content.startsWith("Error");
|
|
2238
|
+
}).length,
|
|
2239
|
+
errorCount: results.filter((r) => {
|
|
2240
|
+
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2241
|
+
return content.startsWith("Error");
|
|
2242
|
+
}).length
|
|
2243
|
+
});
|
|
1934
2244
|
return results;
|
|
1935
2245
|
}
|
|
1936
2246
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
@@ -1968,6 +2278,10 @@ Choose parallel routing when the task benefits from multiple perspectives or dat
|
|
|
1968
2278
|
var llmBasedRouting = {
|
|
1969
2279
|
name: "llm-based",
|
|
1970
2280
|
async route(state, config) {
|
|
2281
|
+
logger.info("Starting LLM-based routing", {
|
|
2282
|
+
iteration: state.iteration,
|
|
2283
|
+
availableWorkers: Object.keys(state.workers).length
|
|
2284
|
+
});
|
|
1971
2285
|
if (!config.model) {
|
|
1972
2286
|
throw new Error("LLM-based routing requires a model to be configured");
|
|
1973
2287
|
}
|
|
@@ -1980,8 +2294,20 @@ var llmBasedRouting = {
|
|
|
1980
2294
|
const available = caps.available ? "available" : "busy";
|
|
1981
2295
|
return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
|
|
1982
2296
|
}).join("\n");
|
|
2297
|
+
logger.debug("Worker capabilities", {
|
|
2298
|
+
workers: Object.entries(state.workers).map(([id, caps]) => ({
|
|
2299
|
+
id,
|
|
2300
|
+
skills: caps.skills,
|
|
2301
|
+
available: caps.available,
|
|
2302
|
+
workload: caps.currentWorkload
|
|
2303
|
+
}))
|
|
2304
|
+
});
|
|
1983
2305
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
1984
2306
|
const taskContext = lastMessage?.content || state.input;
|
|
2307
|
+
logger.debug("Task context", {
|
|
2308
|
+
taskLength: taskContext.length,
|
|
2309
|
+
taskPreview: taskContext.substring(0, 100)
|
|
2310
|
+
});
|
|
1985
2311
|
const userPrompt = `Current task: ${taskContext}
|
|
1986
2312
|
|
|
1987
2313
|
Available workers:
|
|
@@ -1991,6 +2317,11 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1991
2317
|
const conversationHistory = [];
|
|
1992
2318
|
let attempt = 0;
|
|
1993
2319
|
while (attempt < maxRetries) {
|
|
2320
|
+
logger.debug("LLM routing attempt", {
|
|
2321
|
+
attempt: attempt + 1,
|
|
2322
|
+
maxRetries,
|
|
2323
|
+
conversationHistoryLength: conversationHistory.length
|
|
2324
|
+
});
|
|
1994
2325
|
const messages = [
|
|
1995
2326
|
new import_messages4.SystemMessage(systemPrompt),
|
|
1996
2327
|
new import_messages4.HumanMessage(userPrompt),
|
|
@@ -1998,6 +2329,10 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1998
2329
|
];
|
|
1999
2330
|
const response = await config.model.invoke(messages);
|
|
2000
2331
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
2332
|
+
logger.info("LLM requested tool calls", {
|
|
2333
|
+
toolCount: response.tool_calls.length,
|
|
2334
|
+
toolNames: response.tool_calls.map((tc) => tc.name)
|
|
2335
|
+
});
|
|
2001
2336
|
if (tools.length === 0) {
|
|
2002
2337
|
throw new Error("LLM requested tool calls but no tools are configured");
|
|
2003
2338
|
}
|
|
@@ -2007,24 +2342,42 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
2007
2342
|
...toolResults
|
|
2008
2343
|
);
|
|
2009
2344
|
attempt++;
|
|
2345
|
+
logger.debug("Retrying routing with tool results", { attempt });
|
|
2010
2346
|
continue;
|
|
2011
2347
|
}
|
|
2348
|
+
logger.debug("Parsing routing decision from LLM response");
|
|
2012
2349
|
let decision;
|
|
2013
2350
|
if (response && typeof response === "object" && ("targetAgent" in response || "targetAgents" in response)) {
|
|
2351
|
+
logger.debug("Response is structured output", {
|
|
2352
|
+
hasTargetAgent: "targetAgent" in response,
|
|
2353
|
+
hasTargetAgents: "targetAgents" in response
|
|
2354
|
+
});
|
|
2014
2355
|
decision = response;
|
|
2015
2356
|
} else if (response.content) {
|
|
2016
2357
|
if (typeof response.content === "string") {
|
|
2017
2358
|
try {
|
|
2018
2359
|
decision = JSON.parse(response.content);
|
|
2360
|
+
logger.debug("Parsed JSON from string response");
|
|
2019
2361
|
} catch (error) {
|
|
2362
|
+
logger.error("Failed to parse routing decision", {
|
|
2363
|
+
content: response.content,
|
|
2364
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2365
|
+
});
|
|
2020
2366
|
throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
|
|
2021
2367
|
}
|
|
2022
2368
|
} else if (typeof response.content === "object") {
|
|
2369
|
+
logger.debug("Response content is already an object");
|
|
2023
2370
|
decision = response.content;
|
|
2024
2371
|
} else {
|
|
2372
|
+
logger.error("Unexpected response content type", {
|
|
2373
|
+
type: typeof response.content
|
|
2374
|
+
});
|
|
2025
2375
|
throw new Error(`Unexpected response content type: ${typeof response.content}`);
|
|
2026
2376
|
}
|
|
2027
2377
|
} else {
|
|
2378
|
+
logger.error("Unexpected response format", {
|
|
2379
|
+
response: JSON.stringify(response)
|
|
2380
|
+
});
|
|
2028
2381
|
throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
|
|
2029
2382
|
}
|
|
2030
2383
|
const result = {
|
|
@@ -2035,20 +2388,41 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
2035
2388
|
strategy: "llm-based",
|
|
2036
2389
|
timestamp: Date.now()
|
|
2037
2390
|
};
|
|
2391
|
+
logger.info("LLM routing decision made", {
|
|
2392
|
+
targetAgent: result.targetAgent,
|
|
2393
|
+
targetAgents: result.targetAgents,
|
|
2394
|
+
isParallel: result.targetAgents && result.targetAgents.length > 1,
|
|
2395
|
+
confidence: result.confidence,
|
|
2396
|
+
reasoning: result.reasoning
|
|
2397
|
+
});
|
|
2038
2398
|
return result;
|
|
2039
2399
|
}
|
|
2400
|
+
logger.error("Max tool retries exceeded", { maxRetries });
|
|
2040
2401
|
throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
|
|
2041
2402
|
}
|
|
2042
2403
|
};
|
|
2043
2404
|
var roundRobinRouting = {
|
|
2044
2405
|
name: "round-robin",
|
|
2045
2406
|
async route(state, config) {
|
|
2407
|
+
logger.info("Starting round-robin routing", {
|
|
2408
|
+
iteration: state.iteration
|
|
2409
|
+
});
|
|
2046
2410
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
|
|
2411
|
+
logger.debug("Available workers for round-robin", {
|
|
2412
|
+
count: availableWorkers.length,
|
|
2413
|
+
workers: availableWorkers
|
|
2414
|
+
});
|
|
2047
2415
|
if (availableWorkers.length === 0) {
|
|
2416
|
+
logger.error("No available workers for round-robin routing");
|
|
2048
2417
|
throw new Error("No available workers for round-robin routing");
|
|
2049
2418
|
}
|
|
2050
2419
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
2051
2420
|
const targetAgent = availableWorkers[lastRoutingIndex];
|
|
2421
|
+
logger.info("Round-robin routing decision", {
|
|
2422
|
+
targetAgent,
|
|
2423
|
+
index: lastRoutingIndex + 1,
|
|
2424
|
+
totalWorkers: availableWorkers.length
|
|
2425
|
+
});
|
|
2052
2426
|
return {
|
|
2053
2427
|
targetAgent,
|
|
2054
2428
|
targetAgents: null,
|
|
@@ -2062,8 +2436,15 @@ var roundRobinRouting = {
|
|
|
2062
2436
|
var skillBasedRouting = {
|
|
2063
2437
|
name: "skill-based",
|
|
2064
2438
|
async route(state, config) {
|
|
2439
|
+
logger.info("Starting skill-based routing", {
|
|
2440
|
+
iteration: state.iteration
|
|
2441
|
+
});
|
|
2065
2442
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
2066
2443
|
const taskContent = (lastMessage?.content || state.input).toLowerCase();
|
|
2444
|
+
logger.debug("Task content for skill matching", {
|
|
2445
|
+
taskLength: taskContent.length,
|
|
2446
|
+
taskPreview: taskContent.substring(0, 100)
|
|
2447
|
+
});
|
|
2067
2448
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
2068
2449
|
const skillMatches = caps.skills.filter(
|
|
2069
2450
|
(skill) => taskContent.includes(skill.toLowerCase())
|
|
@@ -2074,11 +2455,20 @@ var skillBasedRouting = {
|
|
|
2074
2455
|
const score = skillMatches * 2 + toolMatches;
|
|
2075
2456
|
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
2076
2457
|
}).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
|
|
2458
|
+
logger.debug("Worker skill scores", {
|
|
2459
|
+
scoredWorkers: workerScores.map((w) => ({ id: w.id, score: w.score }))
|
|
2460
|
+
});
|
|
2077
2461
|
if (workerScores.length === 0) {
|
|
2462
|
+
logger.warn("No skill matches found, using fallback");
|
|
2078
2463
|
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
2079
2464
|
if (!firstAvailable) {
|
|
2465
|
+
logger.error("No available workers for skill-based routing");
|
|
2080
2466
|
throw new Error("No available workers for skill-based routing");
|
|
2081
2467
|
}
|
|
2468
|
+
logger.info("Skill-based routing fallback decision", {
|
|
2469
|
+
targetAgent: firstAvailable[0],
|
|
2470
|
+
confidence: 0.5
|
|
2471
|
+
});
|
|
2082
2472
|
return {
|
|
2083
2473
|
targetAgent: firstAvailable[0],
|
|
2084
2474
|
targetAgents: null,
|
|
@@ -2090,6 +2480,12 @@ var skillBasedRouting = {
|
|
|
2090
2480
|
}
|
|
2091
2481
|
const best = workerScores[0];
|
|
2092
2482
|
const confidence = Math.min(best.score / 5, 1);
|
|
2483
|
+
logger.info("Skill-based routing decision", {
|
|
2484
|
+
targetAgent: best.id,
|
|
2485
|
+
score: best.score,
|
|
2486
|
+
confidence,
|
|
2487
|
+
matchedSkills: best.skills
|
|
2488
|
+
});
|
|
2093
2489
|
return {
|
|
2094
2490
|
targetAgent: best.id,
|
|
2095
2491
|
targetAgents: null,
|
|
@@ -2103,13 +2499,26 @@ var skillBasedRouting = {
|
|
|
2103
2499
|
var loadBalancedRouting = {
|
|
2104
2500
|
name: "load-balanced",
|
|
2105
2501
|
async route(state, config) {
|
|
2502
|
+
logger.info("Starting load-balanced routing", {
|
|
2503
|
+
iteration: state.iteration
|
|
2504
|
+
});
|
|
2106
2505
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
|
|
2506
|
+
logger.debug("Worker workloads", {
|
|
2507
|
+
workers: availableWorkers.map((w) => ({ id: w.id, workload: w.workload }))
|
|
2508
|
+
});
|
|
2107
2509
|
if (availableWorkers.length === 0) {
|
|
2510
|
+
logger.error("No available workers for load-balanced routing");
|
|
2108
2511
|
throw new Error("No available workers for load-balanced routing");
|
|
2109
2512
|
}
|
|
2110
2513
|
const targetWorker = availableWorkers[0];
|
|
2111
2514
|
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2112
2515
|
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2516
|
+
logger.info("Load-balanced routing decision", {
|
|
2517
|
+
targetAgent: targetWorker.id,
|
|
2518
|
+
workload: targetWorker.workload,
|
|
2519
|
+
avgWorkload: avgWorkload.toFixed(1),
|
|
2520
|
+
confidence
|
|
2521
|
+
});
|
|
2113
2522
|
return {
|
|
2114
2523
|
targetAgent: targetWorker.id,
|
|
2115
2524
|
targetAgents: null,
|
|
@@ -2123,13 +2532,24 @@ var loadBalancedRouting = {
|
|
|
2123
2532
|
var ruleBasedRouting = {
|
|
2124
2533
|
name: "rule-based",
|
|
2125
2534
|
async route(state, config) {
|
|
2535
|
+
logger.info("Starting rule-based routing", {
|
|
2536
|
+
iteration: state.iteration
|
|
2537
|
+
});
|
|
2126
2538
|
if (!config.routingFn) {
|
|
2539
|
+
logger.error("Rule-based routing requires a custom routing function");
|
|
2127
2540
|
throw new Error("Rule-based routing requires a custom routing function");
|
|
2128
2541
|
}
|
|
2129
|
-
|
|
2542
|
+
const decision = await config.routingFn(state);
|
|
2543
|
+
logger.info("Rule-based routing decision", {
|
|
2544
|
+
targetAgent: decision.targetAgent,
|
|
2545
|
+
targetAgents: decision.targetAgents,
|
|
2546
|
+
confidence: decision.confidence
|
|
2547
|
+
});
|
|
2548
|
+
return decision;
|
|
2130
2549
|
}
|
|
2131
2550
|
};
|
|
2132
2551
|
function getRoutingStrategy(name) {
|
|
2552
|
+
logger.debug("Getting routing strategy", { name });
|
|
2133
2553
|
switch (name) {
|
|
2134
2554
|
case "llm-based":
|
|
2135
2555
|
return llmBasedRouting;
|
|
@@ -2142,14 +2562,15 @@ function getRoutingStrategy(name) {
|
|
|
2142
2562
|
case "rule-based":
|
|
2143
2563
|
return ruleBasedRouting;
|
|
2144
2564
|
default:
|
|
2565
|
+
logger.error("Unknown routing strategy", { name });
|
|
2145
2566
|
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2146
2567
|
}
|
|
2147
2568
|
}
|
|
2148
2569
|
|
|
2149
2570
|
// src/multi-agent/utils.ts
|
|
2150
|
-
var
|
|
2151
|
-
var
|
|
2152
|
-
var
|
|
2571
|
+
var import_core9 = require("@agentforge/core");
|
|
2572
|
+
var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || import_core9.LogLevel.INFO;
|
|
2573
|
+
var logger2 = (0, import_core9.createLogger)("multi-agent", { level: logLevel2 });
|
|
2153
2574
|
function isReActAgent(obj) {
|
|
2154
2575
|
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
|
|
2155
2576
|
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
|
|
@@ -2157,9 +2578,9 @@ function isReActAgent(obj) {
|
|
|
2157
2578
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2158
2579
|
return async (state, config) => {
|
|
2159
2580
|
try {
|
|
2160
|
-
|
|
2581
|
+
logger2.debug("Wrapping ReAct agent execution", { workerId });
|
|
2161
2582
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2162
|
-
|
|
2583
|
+
logger2.debug("Extracted task", {
|
|
2163
2584
|
workerId,
|
|
2164
2585
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2165
2586
|
});
|
|
@@ -2167,7 +2588,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2167
2588
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2168
2589
|
);
|
|
2169
2590
|
if (!currentAssignment) {
|
|
2170
|
-
|
|
2591
|
+
logger2.debug("No active assignment found", { workerId });
|
|
2171
2592
|
return {};
|
|
2172
2593
|
}
|
|
2173
2594
|
const result = await agent.invoke(
|
|
@@ -2178,14 +2599,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2178
2599
|
// Pass through the config for checkpointing and interrupt support
|
|
2179
2600
|
);
|
|
2180
2601
|
const response = result.messages?.[result.messages.length - 1]?.content || "No response";
|
|
2181
|
-
|
|
2602
|
+
logger2.debug("Received response from ReAct agent", {
|
|
2182
2603
|
workerId,
|
|
2183
2604
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2184
2605
|
});
|
|
2185
2606
|
const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
|
|
2186
2607
|
const uniqueTools = [...new Set(toolsUsed)];
|
|
2187
2608
|
if (uniqueTools.length > 0) {
|
|
2188
|
-
|
|
2609
|
+
logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2189
2610
|
}
|
|
2190
2611
|
const taskResult = {
|
|
2191
2612
|
assignmentId: currentAssignment.id,
|
|
@@ -2204,10 +2625,10 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2204
2625
|
};
|
|
2205
2626
|
} catch (error) {
|
|
2206
2627
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
2207
|
-
|
|
2628
|
+
logger2.debug("GraphInterrupt detected - re-throwing", { workerId });
|
|
2208
2629
|
throw error;
|
|
2209
2630
|
}
|
|
2210
|
-
|
|
2631
|
+
logger2.error("Error in ReAct agent execution", {
|
|
2211
2632
|
workerId,
|
|
2212
2633
|
error: error instanceof Error ? error.message : String(error),
|
|
2213
2634
|
stack: error instanceof Error ? error.stack : void 0
|
|
@@ -2240,7 +2661,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2240
2661
|
|
|
2241
2662
|
// src/multi-agent/nodes.ts
|
|
2242
2663
|
var import_messages5 = require("@langchain/core/messages");
|
|
2243
|
-
var
|
|
2664
|
+
var import_core10 = require("@agentforge/core");
|
|
2665
|
+
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || import_core10.LogLevel.INFO;
|
|
2666
|
+
var logger3 = (0, import_core10.createLogger)("multi-agent:nodes", { level: logLevel3 });
|
|
2244
2667
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2245
2668
|
|
|
2246
2669
|
Your job is to:
|
|
@@ -2258,13 +2681,19 @@ function createSupervisorNode(config) {
|
|
|
2258
2681
|
} = config;
|
|
2259
2682
|
return async (state) => {
|
|
2260
2683
|
try {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2684
|
+
logger3.info("Supervisor node executing", {
|
|
2685
|
+
iteration: state.iteration,
|
|
2686
|
+
maxIterations,
|
|
2687
|
+
activeAssignments: state.activeAssignments.length,
|
|
2688
|
+
completedTasks: state.completedTasks.length
|
|
2689
|
+
});
|
|
2690
|
+
logger3.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
|
|
2264
2691
|
if (state.iteration >= maxIterations) {
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2692
|
+
logger3.warn("Max iterations reached", {
|
|
2693
|
+
iteration: state.iteration,
|
|
2694
|
+
maxIterations
|
|
2695
|
+
});
|
|
2696
|
+
logger3.debug("Max iterations reached, moving to aggregation");
|
|
2268
2697
|
return {
|
|
2269
2698
|
status: "aggregating",
|
|
2270
2699
|
currentAgent: "aggregator"
|
|
@@ -2273,27 +2702,55 @@ function createSupervisorNode(config) {
|
|
|
2273
2702
|
const allCompleted = state.activeAssignments.every(
|
|
2274
2703
|
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2275
2704
|
);
|
|
2705
|
+
logger3.debug("Checking task completion", {
|
|
2706
|
+
activeAssignments: state.activeAssignments.length,
|
|
2707
|
+
completedTasks: state.completedTasks.length,
|
|
2708
|
+
allCompleted
|
|
2709
|
+
});
|
|
2276
2710
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
}
|
|
2711
|
+
logger3.info("All tasks completed, moving to aggregation", {
|
|
2712
|
+
completedCount: state.completedTasks.length
|
|
2713
|
+
});
|
|
2714
|
+
logger3.debug("All tasks completed, moving to aggregation");
|
|
2280
2715
|
return {
|
|
2281
2716
|
status: "aggregating",
|
|
2282
2717
|
currentAgent: "aggregator"
|
|
2283
2718
|
};
|
|
2284
2719
|
}
|
|
2720
|
+
logger3.debug("Getting routing strategy", { strategy });
|
|
2285
2721
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2286
2722
|
const decision = await routingImpl.route(state, config);
|
|
2287
2723
|
const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
|
|
2724
|
+
logger3.debug("Target agents determined", {
|
|
2725
|
+
targetAgents,
|
|
2726
|
+
isParallel: targetAgents.length > 1,
|
|
2727
|
+
decision: {
|
|
2728
|
+
reasoning: decision.reasoning,
|
|
2729
|
+
confidence: decision.confidence
|
|
2730
|
+
}
|
|
2731
|
+
});
|
|
2288
2732
|
if (targetAgents.length === 0) {
|
|
2733
|
+
logger3.error("No target agents specified in routing decision");
|
|
2289
2734
|
throw new Error("Routing decision must specify at least one target agent");
|
|
2290
2735
|
}
|
|
2291
|
-
if (
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
}
|
|
2736
|
+
if (targetAgents.length === 1) {
|
|
2737
|
+
logger3.info("Routing to single agent", {
|
|
2738
|
+
targetAgent: targetAgents[0],
|
|
2739
|
+
reasoning: decision.reasoning,
|
|
2740
|
+
confidence: decision.confidence
|
|
2741
|
+
});
|
|
2742
|
+
} else {
|
|
2743
|
+
logger3.info("Routing to multiple agents in parallel", {
|
|
2744
|
+
targetAgents,
|
|
2745
|
+
count: targetAgents.length,
|
|
2746
|
+
reasoning: decision.reasoning,
|
|
2747
|
+
confidence: decision.confidence
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
if (targetAgents.length === 1) {
|
|
2751
|
+
logger3.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2752
|
+
} else {
|
|
2753
|
+
logger3.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
|
|
2297
2754
|
}
|
|
2298
2755
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2299
2756
|
const assignments = targetAgents.map((workerId) => ({
|
|
@@ -2303,6 +2760,14 @@ function createSupervisorNode(config) {
|
|
|
2303
2760
|
priority: 5,
|
|
2304
2761
|
assignedAt: Date.now()
|
|
2305
2762
|
}));
|
|
2763
|
+
logger3.debug("Created task assignments", {
|
|
2764
|
+
assignmentCount: assignments.length,
|
|
2765
|
+
assignments: assignments.map((a) => ({
|
|
2766
|
+
id: a.id,
|
|
2767
|
+
workerId: a.workerId,
|
|
2768
|
+
taskLength: a.task.length
|
|
2769
|
+
}))
|
|
2770
|
+
});
|
|
2306
2771
|
const messages = assignments.map((assignment) => ({
|
|
2307
2772
|
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
2308
2773
|
from: "supervisor",
|
|
@@ -2315,6 +2780,12 @@ function createSupervisorNode(config) {
|
|
|
2315
2780
|
priority: assignment.priority
|
|
2316
2781
|
}
|
|
2317
2782
|
}));
|
|
2783
|
+
logger3.info("Supervisor routing complete", {
|
|
2784
|
+
currentAgent: targetAgents.join(","),
|
|
2785
|
+
status: "executing",
|
|
2786
|
+
assignmentCount: assignments.length,
|
|
2787
|
+
nextIteration: state.iteration + 1
|
|
2788
|
+
});
|
|
2318
2789
|
return {
|
|
2319
2790
|
currentAgent: targetAgents.join(","),
|
|
2320
2791
|
// Store all agents (for backward compat)
|
|
@@ -2326,7 +2797,11 @@ function createSupervisorNode(config) {
|
|
|
2326
2797
|
iteration: state.iteration + 1
|
|
2327
2798
|
};
|
|
2328
2799
|
} catch (error) {
|
|
2329
|
-
|
|
2800
|
+
logger3.error("Supervisor node error", {
|
|
2801
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2802
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2803
|
+
iteration: state.iteration
|
|
2804
|
+
});
|
|
2330
2805
|
return {
|
|
2331
2806
|
status: "failed",
|
|
2332
2807
|
error: error instanceof Error ? error.message : "Unknown error in supervisor"
|
|
@@ -2347,40 +2822,52 @@ function createWorkerNode(config) {
|
|
|
2347
2822
|
} = config;
|
|
2348
2823
|
return async (state, runConfig) => {
|
|
2349
2824
|
try {
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2825
|
+
logger3.info("Worker node executing", {
|
|
2826
|
+
workerId: id,
|
|
2827
|
+
iteration: state.iteration,
|
|
2828
|
+
activeAssignments: state.activeAssignments.length
|
|
2829
|
+
});
|
|
2353
2830
|
const currentAssignment = state.activeAssignments.find(
|
|
2354
2831
|
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2355
2832
|
);
|
|
2356
2833
|
if (!currentAssignment) {
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2834
|
+
logger3.debug("No active assignment found for worker", {
|
|
2835
|
+
workerId: id,
|
|
2836
|
+
totalActiveAssignments: state.activeAssignments.length,
|
|
2837
|
+
completedTasks: state.completedTasks.length
|
|
2838
|
+
});
|
|
2360
2839
|
return {};
|
|
2361
2840
|
}
|
|
2841
|
+
logger3.info("Worker processing assignment", {
|
|
2842
|
+
workerId: id,
|
|
2843
|
+
assignmentId: currentAssignment.id,
|
|
2844
|
+
taskLength: currentAssignment.task.length,
|
|
2845
|
+
taskPreview: currentAssignment.task.substring(0, 100)
|
|
2846
|
+
});
|
|
2362
2847
|
if (executeFn) {
|
|
2363
|
-
|
|
2364
|
-
console.log(`[Worker:${id}] Using custom executeFn`);
|
|
2365
|
-
}
|
|
2848
|
+
logger3.debug("Using custom execution function", { workerId: id });
|
|
2366
2849
|
return await executeFn(state, runConfig);
|
|
2367
2850
|
}
|
|
2368
2851
|
if (agent) {
|
|
2369
2852
|
if (isReActAgent(agent)) {
|
|
2370
|
-
|
|
2371
|
-
console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
|
|
2372
|
-
}
|
|
2853
|
+
logger3.debug("Using ReAct agent", { workerId: id });
|
|
2373
2854
|
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
2374
2855
|
return await wrappedFn(state, runConfig);
|
|
2375
2856
|
} else {
|
|
2376
|
-
|
|
2857
|
+
logger3.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
|
|
2377
2858
|
}
|
|
2378
2859
|
}
|
|
2379
2860
|
if (!model) {
|
|
2861
|
+
logger3.error("Worker missing required configuration", { workerId: id });
|
|
2380
2862
|
throw new Error(
|
|
2381
2863
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2382
2864
|
);
|
|
2383
2865
|
}
|
|
2866
|
+
logger3.debug("Using default LLM execution", {
|
|
2867
|
+
workerId: id,
|
|
2868
|
+
hasTools: tools.length > 0,
|
|
2869
|
+
toolCount: tools.length
|
|
2870
|
+
});
|
|
2384
2871
|
const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
|
|
2385
2872
|
Skills: ${capabilities.skills.join(", ")}
|
|
2386
2873
|
Tools: ${capabilities.tools.join(", ")}
|
|
@@ -2392,14 +2879,23 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2392
2879
|
];
|
|
2393
2880
|
let modelToUse = model;
|
|
2394
2881
|
if (tools.length > 0 && model.bindTools) {
|
|
2395
|
-
|
|
2882
|
+
logger3.debug("Binding tools to model", {
|
|
2883
|
+
workerId: id,
|
|
2884
|
+
toolCount: tools.length,
|
|
2885
|
+
toolNames: tools.map((t) => t.metadata.name)
|
|
2886
|
+
});
|
|
2887
|
+
const langchainTools = (0, import_core10.toLangChainTools)(tools);
|
|
2396
2888
|
modelToUse = model.bindTools(langchainTools);
|
|
2397
2889
|
}
|
|
2890
|
+
logger3.debug("Invoking LLM", { workerId: id });
|
|
2398
2891
|
const response = await modelToUse.invoke(messages);
|
|
2399
2892
|
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2893
|
+
logger3.info("Worker task completed", {
|
|
2894
|
+
workerId: id,
|
|
2895
|
+
assignmentId: currentAssignment.id,
|
|
2896
|
+
resultLength: result.length,
|
|
2897
|
+
resultPreview: result.substring(0, 100)
|
|
2898
|
+
});
|
|
2403
2899
|
const taskResult = {
|
|
2404
2900
|
assignmentId: currentAssignment.id,
|
|
2405
2901
|
workerId: id,
|
|
@@ -2429,6 +2925,10 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2429
2925
|
currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
|
|
2430
2926
|
}
|
|
2431
2927
|
};
|
|
2928
|
+
logger3.debug("Worker state update", {
|
|
2929
|
+
workerId: id,
|
|
2930
|
+
newWorkload: updatedWorkers[id].currentWorkload
|
|
2931
|
+
});
|
|
2432
2932
|
return {
|
|
2433
2933
|
completedTasks: [taskResult],
|
|
2434
2934
|
messages: [message],
|
|
@@ -2436,13 +2936,22 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2436
2936
|
};
|
|
2437
2937
|
} catch (error) {
|
|
2438
2938
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
2939
|
+
logger3.info("GraphInterrupt detected, re-throwing", { workerId: id });
|
|
2439
2940
|
throw error;
|
|
2440
2941
|
}
|
|
2441
|
-
|
|
2942
|
+
logger3.error("Worker node error", {
|
|
2943
|
+
workerId: id,
|
|
2944
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2945
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
2946
|
+
});
|
|
2442
2947
|
const currentAssignment = state.activeAssignments.find(
|
|
2443
2948
|
(assignment) => assignment.workerId === id
|
|
2444
2949
|
);
|
|
2445
2950
|
if (currentAssignment) {
|
|
2951
|
+
logger3.warn("Creating error result for assignment", {
|
|
2952
|
+
workerId: id,
|
|
2953
|
+
assignmentId: currentAssignment.id
|
|
2954
|
+
});
|
|
2446
2955
|
const errorResult = {
|
|
2447
2956
|
assignmentId: currentAssignment.id,
|
|
2448
2957
|
workerId: id,
|
|
@@ -2457,6 +2966,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2457
2966
|
status: "routing"
|
|
2458
2967
|
};
|
|
2459
2968
|
}
|
|
2969
|
+
logger3.error("No assignment found for error handling", { workerId: id });
|
|
2460
2970
|
return {
|
|
2461
2971
|
status: "failed",
|
|
2462
2972
|
error: error instanceof Error ? error.message : `Unknown error in worker ${id}`
|
|
@@ -2473,29 +2983,44 @@ function createAggregatorNode(config = {}) {
|
|
|
2473
2983
|
} = config;
|
|
2474
2984
|
return async (state) => {
|
|
2475
2985
|
try {
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2986
|
+
logger3.info("Aggregator node executing", {
|
|
2987
|
+
completedTasks: state.completedTasks.length,
|
|
2988
|
+
successfulTasks: state.completedTasks.filter((t) => t.success).length,
|
|
2989
|
+
failedTasks: state.completedTasks.filter((t) => !t.success).length
|
|
2990
|
+
});
|
|
2991
|
+
logger3.debug("Combining results from workers");
|
|
2479
2992
|
if (aggregateFn) {
|
|
2993
|
+
logger3.debug("Using custom aggregation function");
|
|
2480
2994
|
const response2 = await aggregateFn(state);
|
|
2995
|
+
logger3.info("Custom aggregation complete", {
|
|
2996
|
+
responseLength: response2.length
|
|
2997
|
+
});
|
|
2481
2998
|
return {
|
|
2482
2999
|
response: response2,
|
|
2483
3000
|
status: "completed"
|
|
2484
3001
|
};
|
|
2485
3002
|
}
|
|
2486
3003
|
if (state.completedTasks.length === 0) {
|
|
3004
|
+
logger3.warn("No completed tasks to aggregate");
|
|
2487
3005
|
return {
|
|
2488
3006
|
response: "No tasks were completed.",
|
|
2489
3007
|
status: "completed"
|
|
2490
3008
|
};
|
|
2491
3009
|
}
|
|
2492
3010
|
if (!model) {
|
|
3011
|
+
logger3.debug("No model provided, concatenating results");
|
|
2493
3012
|
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
3013
|
+
logger3.info("Simple concatenation complete", {
|
|
3014
|
+
resultLength: combinedResults.length
|
|
3015
|
+
});
|
|
2494
3016
|
return {
|
|
2495
3017
|
response: combinedResults || "No successful results to aggregate.",
|
|
2496
3018
|
status: "completed"
|
|
2497
3019
|
};
|
|
2498
3020
|
}
|
|
3021
|
+
logger3.debug("Using LLM for intelligent aggregation", {
|
|
3022
|
+
taskCount: state.completedTasks.length
|
|
3023
|
+
});
|
|
2499
3024
|
const taskResults = state.completedTasks.map((task, idx) => {
|
|
2500
3025
|
const status = task.success ? "\u2713" : "\u2717";
|
|
2501
3026
|
const result = task.success ? task.result : `Error: ${task.error}`;
|
|
@@ -2512,17 +3037,24 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2512
3037
|
new import_messages5.SystemMessage(systemPrompt),
|
|
2513
3038
|
new import_messages5.HumanMessage(userPrompt)
|
|
2514
3039
|
];
|
|
3040
|
+
logger3.debug("Invoking aggregation LLM");
|
|
2515
3041
|
const response = await model.invoke(messages);
|
|
2516
3042
|
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
3043
|
+
logger3.info("Aggregation complete", {
|
|
3044
|
+
responseLength: aggregatedResponse.length,
|
|
3045
|
+
responsePreview: aggregatedResponse.substring(0, 100)
|
|
3046
|
+
});
|
|
3047
|
+
logger3.debug("Aggregation complete");
|
|
2520
3048
|
return {
|
|
2521
3049
|
response: aggregatedResponse,
|
|
2522
3050
|
status: "completed"
|
|
2523
3051
|
};
|
|
2524
3052
|
} catch (error) {
|
|
2525
|
-
|
|
3053
|
+
logger3.error("Aggregator node error", {
|
|
3054
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3055
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
3056
|
+
completedTasks: state.completedTasks.length
|
|
3057
|
+
});
|
|
2526
3058
|
return {
|
|
2527
3059
|
status: "failed",
|
|
2528
3060
|
error: error instanceof Error ? error.message : "Unknown error in aggregator"
|
|
@@ -2533,7 +3065,9 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2533
3065
|
|
|
2534
3066
|
// src/multi-agent/agent.ts
|
|
2535
3067
|
var import_langgraph4 = require("@langchain/langgraph");
|
|
2536
|
-
var
|
|
3068
|
+
var import_core11 = require("@agentforge/core");
|
|
3069
|
+
var logLevel4 = process.env.LOG_LEVEL?.toLowerCase() || import_core11.LogLevel.INFO;
|
|
3070
|
+
var logger4 = (0, import_core11.createLogger)("multi-agent:system", { level: logLevel4 });
|
|
2537
3071
|
function createMultiAgentSystem(config) {
|
|
2538
3072
|
const {
|
|
2539
3073
|
supervisor,
|
|
@@ -2551,7 +3085,7 @@ function createMultiAgentSystem(config) {
|
|
|
2551
3085
|
configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
|
|
2552
3086
|
}
|
|
2553
3087
|
if (supervisor.tools && supervisor.tools.length > 0) {
|
|
2554
|
-
const langchainTools = (0,
|
|
3088
|
+
const langchainTools = (0, import_core11.toLangChainTools)(supervisor.tools);
|
|
2555
3089
|
configuredModel = configuredModel.bindTools(langchainTools);
|
|
2556
3090
|
}
|
|
2557
3091
|
supervisorConfig.model = configuredModel;
|
|
@@ -2575,25 +3109,49 @@ function createMultiAgentSystem(config) {
|
|
|
2575
3109
|
});
|
|
2576
3110
|
workflow.addNode("aggregator", aggregatorNode);
|
|
2577
3111
|
const supervisorRouter = (state) => {
|
|
3112
|
+
logger4.debug("Supervisor router executing", {
|
|
3113
|
+
status: state.status,
|
|
3114
|
+
currentAgent: state.currentAgent,
|
|
3115
|
+
iteration: state.iteration
|
|
3116
|
+
});
|
|
2578
3117
|
if (state.status === "completed" || state.status === "failed") {
|
|
3118
|
+
logger4.info("Supervisor router: ending workflow", { status: state.status });
|
|
2579
3119
|
return import_langgraph4.END;
|
|
2580
3120
|
}
|
|
2581
3121
|
if (state.status === "aggregating") {
|
|
3122
|
+
logger4.info("Supervisor router: routing to aggregator");
|
|
2582
3123
|
return "aggregator";
|
|
2583
3124
|
}
|
|
2584
3125
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
2585
3126
|
if (state.currentAgent.includes(",")) {
|
|
2586
3127
|
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
3128
|
+
logger4.info("Supervisor router: parallel routing", {
|
|
3129
|
+
agents,
|
|
3130
|
+
count: agents.length
|
|
3131
|
+
});
|
|
2587
3132
|
return agents;
|
|
2588
3133
|
}
|
|
3134
|
+
logger4.info("Supervisor router: single agent routing", {
|
|
3135
|
+
targetAgent: state.currentAgent
|
|
3136
|
+
});
|
|
2589
3137
|
return state.currentAgent;
|
|
2590
3138
|
}
|
|
3139
|
+
logger4.debug("Supervisor router: staying at supervisor");
|
|
2591
3140
|
return "supervisor";
|
|
2592
3141
|
};
|
|
2593
3142
|
const workerRouter = (state) => {
|
|
3143
|
+
logger4.debug("Worker router executing", {
|
|
3144
|
+
iteration: state.iteration,
|
|
3145
|
+
completedTasks: state.completedTasks.length
|
|
3146
|
+
});
|
|
3147
|
+
logger4.debug("Worker router: returning to supervisor");
|
|
2594
3148
|
return "supervisor";
|
|
2595
3149
|
};
|
|
2596
3150
|
const aggregatorRouter = (state) => {
|
|
3151
|
+
logger4.info("Aggregator router: ending workflow", {
|
|
3152
|
+
completedTasks: state.completedTasks.length,
|
|
3153
|
+
status: state.status
|
|
3154
|
+
});
|
|
2597
3155
|
return import_langgraph4.END;
|
|
2598
3156
|
};
|
|
2599
3157
|
workflow.setEntryPoint("supervisor");
|
|
@@ -2787,11 +3345,14 @@ function registerWorkers(system, workers) {
|
|
|
2787
3345
|
ToolCallSchema,
|
|
2788
3346
|
ToolResultSchema,
|
|
2789
3347
|
WorkerCapabilitiesSchema,
|
|
3348
|
+
buildDeduplicationMetrics,
|
|
3349
|
+
calculateDeduplicationSavings,
|
|
2790
3350
|
createAggregatorNode,
|
|
2791
3351
|
createExecutorNode,
|
|
2792
3352
|
createFinisherNode,
|
|
2793
3353
|
createGeneratorNode,
|
|
2794
3354
|
createMultiAgentSystem,
|
|
3355
|
+
createPatternLogger,
|
|
2795
3356
|
createPlanExecuteAgent,
|
|
2796
3357
|
createPlannerNode,
|
|
2797
3358
|
createReActAgent,
|
|
@@ -2803,6 +3364,7 @@ function registerWorkers(system, workers) {
|
|
|
2803
3364
|
createReviserNode,
|
|
2804
3365
|
createSupervisorNode,
|
|
2805
3366
|
createWorkerNode,
|
|
3367
|
+
generateToolCallCacheKey,
|
|
2806
3368
|
getRoutingStrategy,
|
|
2807
3369
|
llmBasedRouting,
|
|
2808
3370
|
loadBalancedRouting,
|