@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.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(),
|
|
@@ -147,13 +149,49 @@ function formatScratchpad(scratchpad) {
|
|
|
147
149
|
// src/react/nodes.ts
|
|
148
150
|
import { HumanMessage, AIMessage, SystemMessage } from "@langchain/core/messages";
|
|
149
151
|
import { toLangChainTools } from "@agentforge/core";
|
|
152
|
+
|
|
153
|
+
// src/shared/deduplication.ts
|
|
154
|
+
import { createLogger } from "@agentforge/core";
|
|
155
|
+
function generateToolCallCacheKey(toolName, args) {
|
|
156
|
+
const sortedArgs = JSON.stringify(args, Object.keys(args || {}).sort());
|
|
157
|
+
return `${toolName}:${sortedArgs}`;
|
|
158
|
+
}
|
|
159
|
+
function createPatternLogger(name, defaultLevel = "info") {
|
|
160
|
+
const logLevel5 = process.env.LOG_LEVEL?.toLowerCase() || defaultLevel;
|
|
161
|
+
return createLogger(name, { level: logLevel5 });
|
|
162
|
+
}
|
|
163
|
+
function calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted) {
|
|
164
|
+
if (duplicatesSkipped === 0) {
|
|
165
|
+
return "0%";
|
|
166
|
+
}
|
|
167
|
+
const total = toolsExecuted + duplicatesSkipped;
|
|
168
|
+
return `${Math.round(duplicatesSkipped / total * 100)}%`;
|
|
169
|
+
}
|
|
170
|
+
function buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, totalObservations) {
|
|
171
|
+
return {
|
|
172
|
+
toolsExecuted,
|
|
173
|
+
duplicatesSkipped,
|
|
174
|
+
totalObservations,
|
|
175
|
+
deduplicationSavings: calculateDeduplicationSavings(duplicatesSkipped, toolsExecuted)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/react/nodes.ts
|
|
180
|
+
var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
|
|
181
|
+
var actionLogger = createPatternLogger("agentforge:patterns:react:action");
|
|
182
|
+
var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
|
|
150
183
|
function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
|
|
151
184
|
const langchainTools = toLangChainTools(tools);
|
|
152
185
|
const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
|
|
153
186
|
return async (state) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
187
|
+
const currentIteration = state.iteration || 0;
|
|
188
|
+
const startTime = Date.now();
|
|
189
|
+
reasoningLogger.debug("Reasoning iteration started", {
|
|
190
|
+
iteration: currentIteration + 1,
|
|
191
|
+
maxIterations,
|
|
192
|
+
observationCount: state.observations?.length || 0,
|
|
193
|
+
hasActions: !!state.actions?.length
|
|
194
|
+
});
|
|
157
195
|
const stateMessages = state.messages || [];
|
|
158
196
|
const messages = [
|
|
159
197
|
new SystemMessage(systemPrompt),
|
|
@@ -183,8 +221,15 @@ ${scratchpadText}`));
|
|
|
183
221
|
});
|
|
184
222
|
}
|
|
185
223
|
}
|
|
186
|
-
const currentIteration = state.iteration || 0;
|
|
187
224
|
const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
|
|
225
|
+
reasoningLogger.info("Reasoning complete", {
|
|
226
|
+
iteration: currentIteration + 1,
|
|
227
|
+
thoughtGenerated: !!thought,
|
|
228
|
+
actionCount: toolCalls.length,
|
|
229
|
+
shouldContinue,
|
|
230
|
+
isFinalResponse: toolCalls.length === 0,
|
|
231
|
+
duration: Date.now() - startTime
|
|
232
|
+
});
|
|
188
233
|
return {
|
|
189
234
|
messages: [{ role: "assistant", content: thought }],
|
|
190
235
|
thoughts: thought ? [{ content: thought, timestamp: Date.now() }] : [],
|
|
@@ -197,16 +242,71 @@ ${scratchpadText}`));
|
|
|
197
242
|
};
|
|
198
243
|
};
|
|
199
244
|
}
|
|
200
|
-
function createActionNode(tools, verbose = false) {
|
|
245
|
+
function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
201
246
|
const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
|
|
202
247
|
return async (state) => {
|
|
203
248
|
const actions = state.actions || [];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
249
|
+
const allObservations = state.observations || [];
|
|
250
|
+
const iteration = state.iteration || 0;
|
|
251
|
+
const startTime = Date.now();
|
|
252
|
+
actionLogger.debug("Action node started", {
|
|
253
|
+
actionCount: actions.length,
|
|
254
|
+
iteration,
|
|
255
|
+
cacheEnabled: enableDeduplication
|
|
256
|
+
});
|
|
207
257
|
const recentActions = actions.slice(-10);
|
|
208
258
|
const observations = [];
|
|
259
|
+
const executionCache = /* @__PURE__ */ new Map();
|
|
260
|
+
let cacheSize = 0;
|
|
261
|
+
if (enableDeduplication) {
|
|
262
|
+
for (const observation of allObservations) {
|
|
263
|
+
const correspondingAction = actions.find((a) => a.id === observation.toolCallId);
|
|
264
|
+
if (correspondingAction) {
|
|
265
|
+
const cacheKey = generateToolCallCacheKey(correspondingAction.name, correspondingAction.arguments);
|
|
266
|
+
executionCache.set(cacheKey, observation);
|
|
267
|
+
cacheSize++;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (cacheSize > 0) {
|
|
271
|
+
actionLogger.debug("Deduplication cache built", {
|
|
272
|
+
cacheSize,
|
|
273
|
+
totalObservations: allObservations.length
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
let duplicatesSkipped = 0;
|
|
278
|
+
let toolsExecuted = 0;
|
|
209
279
|
for (const action of recentActions) {
|
|
280
|
+
const existingObservation = allObservations.find((obs) => obs.toolCallId === action.id);
|
|
281
|
+
if (existingObservation) {
|
|
282
|
+
actionLogger.debug("Skipping already-processed action", {
|
|
283
|
+
toolName: action.name,
|
|
284
|
+
toolCallId: action.id,
|
|
285
|
+
iteration
|
|
286
|
+
});
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (enableDeduplication) {
|
|
290
|
+
const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
|
|
291
|
+
const cachedResult = executionCache.get(cacheKey);
|
|
292
|
+
if (cachedResult) {
|
|
293
|
+
duplicatesSkipped++;
|
|
294
|
+
actionLogger.info("Duplicate tool call prevented", {
|
|
295
|
+
toolName: action.name,
|
|
296
|
+
arguments: action.arguments,
|
|
297
|
+
iteration,
|
|
298
|
+
cacheHit: true
|
|
299
|
+
});
|
|
300
|
+
observations.push({
|
|
301
|
+
toolCallId: action.id,
|
|
302
|
+
result: cachedResult.result,
|
|
303
|
+
error: cachedResult.error,
|
|
304
|
+
timestamp: Date.now(),
|
|
305
|
+
isDuplicate: true
|
|
306
|
+
});
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
210
310
|
const tool = toolMap.get(action.name);
|
|
211
311
|
if (!tool) {
|
|
212
312
|
observations.push({
|
|
@@ -218,31 +318,51 @@ function createActionNode(tools, verbose = false) {
|
|
|
218
318
|
continue;
|
|
219
319
|
}
|
|
220
320
|
try {
|
|
321
|
+
const startTime2 = Date.now();
|
|
221
322
|
const result = await tool.execute(action.arguments);
|
|
222
|
-
|
|
323
|
+
const executionTime = Date.now() - startTime2;
|
|
324
|
+
toolsExecuted++;
|
|
325
|
+
actionLogger.debug("Tool executed successfully", {
|
|
326
|
+
toolName: action.name,
|
|
327
|
+
executionTime,
|
|
328
|
+
iteration
|
|
329
|
+
});
|
|
330
|
+
const observation = {
|
|
223
331
|
toolCallId: action.id,
|
|
224
332
|
result,
|
|
225
333
|
timestamp: Date.now()
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
334
|
+
};
|
|
335
|
+
observations.push(observation);
|
|
336
|
+
if (enableDeduplication) {
|
|
337
|
+
const cacheKey = generateToolCallCacheKey(action.name, action.arguments);
|
|
338
|
+
executionCache.set(cacheKey, observation);
|
|
229
339
|
}
|
|
230
340
|
} catch (error) {
|
|
231
341
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
232
342
|
throw error;
|
|
233
343
|
}
|
|
234
344
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
345
|
+
actionLogger.error("Tool execution failed", {
|
|
346
|
+
toolName: action.name,
|
|
347
|
+
error: errorMessage,
|
|
348
|
+
iteration
|
|
349
|
+
});
|
|
235
350
|
observations.push({
|
|
236
351
|
toolCallId: action.id,
|
|
237
352
|
result: null,
|
|
238
353
|
error: errorMessage,
|
|
239
354
|
timestamp: Date.now()
|
|
240
355
|
});
|
|
241
|
-
if (verbose) {
|
|
242
|
-
console.error(`[action] Tool '${action.name}' failed:`, errorMessage);
|
|
243
|
-
}
|
|
244
356
|
}
|
|
245
357
|
}
|
|
358
|
+
if (duplicatesSkipped > 0 || toolsExecuted > 0) {
|
|
359
|
+
const metrics = buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, observations.length);
|
|
360
|
+
actionLogger.info("Action node complete", {
|
|
361
|
+
iteration,
|
|
362
|
+
...metrics,
|
|
363
|
+
duration: Date.now() - startTime
|
|
364
|
+
});
|
|
365
|
+
}
|
|
246
366
|
return {
|
|
247
367
|
observations
|
|
248
368
|
};
|
|
@@ -253,9 +373,11 @@ function createObservationNode(verbose = false) {
|
|
|
253
373
|
const observations = state.observations || [];
|
|
254
374
|
const thoughts = state.thoughts || [];
|
|
255
375
|
const actions = state.actions || [];
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
376
|
+
const iteration = state.iteration || 0;
|
|
377
|
+
observationLogger.debug("Processing observations", {
|
|
378
|
+
observationCount: observations.length,
|
|
379
|
+
iteration
|
|
380
|
+
});
|
|
259
381
|
const recentObservations = observations.slice(-10);
|
|
260
382
|
const currentStep = state.iteration;
|
|
261
383
|
const latestThought = thoughts[thoughts.length - 1]?.content || "";
|
|
@@ -281,6 +403,11 @@ function createObservationNode(verbose = false) {
|
|
|
281
403
|
name: latestActions.find((a) => a.id === obs.toolCallId)?.name
|
|
282
404
|
};
|
|
283
405
|
});
|
|
406
|
+
observationLogger.debug("Observation node complete", {
|
|
407
|
+
iteration,
|
|
408
|
+
scratchpadUpdated: true,
|
|
409
|
+
messageCount: observationMessages.length
|
|
410
|
+
});
|
|
284
411
|
return {
|
|
285
412
|
scratchpad: [scratchpadEntry],
|
|
286
413
|
messages: observationMessages
|
|
@@ -299,7 +426,9 @@ function createReActAgent(config, options) {
|
|
|
299
426
|
maxIterations = 10,
|
|
300
427
|
returnIntermediateSteps = false,
|
|
301
428
|
stopCondition,
|
|
302
|
-
checkpointer
|
|
429
|
+
checkpointer,
|
|
430
|
+
enableDeduplication = true
|
|
431
|
+
// Enable by default
|
|
303
432
|
} = config;
|
|
304
433
|
const {
|
|
305
434
|
verbose = false,
|
|
@@ -316,7 +445,7 @@ function createReActAgent(config, options) {
|
|
|
316
445
|
maxIterations,
|
|
317
446
|
verbose
|
|
318
447
|
);
|
|
319
|
-
const actionNode = createActionNode(toolArray, verbose);
|
|
448
|
+
const actionNode = createActionNode(toolArray, verbose, enableDeduplication);
|
|
320
449
|
const observationNode = createObservationNode(verbose);
|
|
321
450
|
const shouldContinue = (state) => {
|
|
322
451
|
if (stopCondition && stopCondition(state)) {
|
|
@@ -679,6 +808,9 @@ var REMAINING_STEP_TEMPLATE = `Step {stepNumber}: {description}
|
|
|
679
808
|
{dependencies}`;
|
|
680
809
|
|
|
681
810
|
// src/plan-execute/nodes.ts
|
|
811
|
+
var plannerLogger = createPatternLogger("agentforge:patterns:plan-execute:planner");
|
|
812
|
+
var executorLogger = createPatternLogger("agentforge:patterns:plan-execute:executor");
|
|
813
|
+
var replannerLogger = createPatternLogger("agentforge:patterns:plan-execute:replanner");
|
|
682
814
|
function createPlannerNode(config) {
|
|
683
815
|
const {
|
|
684
816
|
model,
|
|
@@ -687,7 +819,13 @@ function createPlannerNode(config) {
|
|
|
687
819
|
includeToolDescriptions = false
|
|
688
820
|
} = config;
|
|
689
821
|
return async (state) => {
|
|
822
|
+
const startTime = Date.now();
|
|
690
823
|
try {
|
|
824
|
+
plannerLogger.debug("Planning started", {
|
|
825
|
+
input: state.input?.substring(0, 100),
|
|
826
|
+
maxSteps,
|
|
827
|
+
includeToolDescriptions
|
|
828
|
+
});
|
|
691
829
|
let toolDescriptions = "";
|
|
692
830
|
if (includeToolDescriptions) {
|
|
693
831
|
toolDescriptions = "";
|
|
@@ -712,6 +850,12 @@ function createPlannerNode(config) {
|
|
|
712
850
|
} catch (parseError) {
|
|
713
851
|
throw new Error(`Failed to parse plan from LLM response: ${parseError}`);
|
|
714
852
|
}
|
|
853
|
+
plannerLogger.info("Plan created", {
|
|
854
|
+
stepCount: plan.steps.length,
|
|
855
|
+
goal: plan.goal.substring(0, 100),
|
|
856
|
+
confidence: plan.confidence,
|
|
857
|
+
duration: Date.now() - startTime
|
|
858
|
+
});
|
|
715
859
|
return {
|
|
716
860
|
plan,
|
|
717
861
|
status: "executing",
|
|
@@ -719,6 +863,10 @@ function createPlannerNode(config) {
|
|
|
719
863
|
iteration: 1
|
|
720
864
|
};
|
|
721
865
|
} catch (error) {
|
|
866
|
+
plannerLogger.error("Planning failed", {
|
|
867
|
+
error: error instanceof Error ? error.message : String(error),
|
|
868
|
+
duration: Date.now() - startTime
|
|
869
|
+
});
|
|
722
870
|
return {
|
|
723
871
|
status: "failed",
|
|
724
872
|
error: error instanceof Error ? error.message : "Unknown error in planner"
|
|
@@ -731,11 +879,18 @@ function createExecutorNode(config) {
|
|
|
731
879
|
tools,
|
|
732
880
|
model,
|
|
733
881
|
parallel = false,
|
|
734
|
-
stepTimeout = 3e4
|
|
882
|
+
stepTimeout = 3e4,
|
|
883
|
+
enableDeduplication = true
|
|
735
884
|
} = config;
|
|
736
885
|
return async (state) => {
|
|
886
|
+
const { plan, currentStepIndex = 0, pastSteps = [], iteration = 0 } = state;
|
|
737
887
|
try {
|
|
738
|
-
|
|
888
|
+
executorLogger.debug("Executor node executing", {
|
|
889
|
+
currentStepIndex,
|
|
890
|
+
totalSteps: plan?.steps?.length || 0,
|
|
891
|
+
iteration,
|
|
892
|
+
deduplicationEnabled: enableDeduplication
|
|
893
|
+
});
|
|
739
894
|
if (!plan || !plan.steps || plan.steps.length === 0) {
|
|
740
895
|
return {
|
|
741
896
|
status: "completed"
|
|
@@ -754,22 +909,67 @@ function createExecutorNode(config) {
|
|
|
754
909
|
throw new Error(`Unmet dependencies for step ${currentStep.id}: ${unmetDependencies.join(", ")}`);
|
|
755
910
|
}
|
|
756
911
|
}
|
|
912
|
+
const executionCache = /* @__PURE__ */ new Map();
|
|
913
|
+
let cacheSize = 0;
|
|
914
|
+
if (enableDeduplication && currentStep.tool) {
|
|
915
|
+
for (const pastStep of pastSteps) {
|
|
916
|
+
if (pastStep.step.tool) {
|
|
917
|
+
const cacheKey = generateToolCallCacheKey(pastStep.step.tool, pastStep.step.args || {});
|
|
918
|
+
executionCache.set(cacheKey, pastStep);
|
|
919
|
+
cacheSize++;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
if (cacheSize > 0) {
|
|
923
|
+
executorLogger.debug("Deduplication cache built", {
|
|
924
|
+
cacheSize,
|
|
925
|
+
pastStepsCount: pastSteps.length
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
757
929
|
let result;
|
|
758
930
|
let success = true;
|
|
759
931
|
let error;
|
|
932
|
+
let isDuplicate = false;
|
|
760
933
|
try {
|
|
761
934
|
if (currentStep.tool) {
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
935
|
+
if (enableDeduplication) {
|
|
936
|
+
const cacheKey = generateToolCallCacheKey(currentStep.tool, currentStep.args || {});
|
|
937
|
+
const cachedStep = executionCache.get(cacheKey);
|
|
938
|
+
if (cachedStep) {
|
|
939
|
+
isDuplicate = true;
|
|
940
|
+
result = cachedStep.result;
|
|
941
|
+
success = cachedStep.success;
|
|
942
|
+
error = cachedStep.error;
|
|
943
|
+
executorLogger.info("Duplicate step execution prevented", {
|
|
944
|
+
stepId: currentStep.id,
|
|
945
|
+
toolName: currentStep.tool,
|
|
946
|
+
arguments: currentStep.args,
|
|
947
|
+
iteration,
|
|
948
|
+
cacheHit: true
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (!isDuplicate) {
|
|
953
|
+
const tool = tools.find((t) => t.metadata.name === currentStep.tool);
|
|
954
|
+
if (!tool) {
|
|
955
|
+
throw new Error(`Tool not found: ${currentStep.tool}`);
|
|
956
|
+
}
|
|
957
|
+
const startTime = Date.now();
|
|
958
|
+
const timeoutPromise = new Promise(
|
|
959
|
+
(_, reject) => setTimeout(() => reject(new Error("Step execution timeout")), stepTimeout)
|
|
960
|
+
);
|
|
961
|
+
result = await Promise.race([
|
|
962
|
+
tool.execute(currentStep.args || {}),
|
|
963
|
+
timeoutPromise
|
|
964
|
+
]);
|
|
965
|
+
const executionTime = Date.now() - startTime;
|
|
966
|
+
executorLogger.debug("Step executed successfully", {
|
|
967
|
+
stepId: currentStep.id,
|
|
968
|
+
toolName: currentStep.tool,
|
|
969
|
+
executionTime,
|
|
970
|
+
iteration
|
|
971
|
+
});
|
|
765
972
|
}
|
|
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
973
|
} else {
|
|
774
974
|
result = { message: "Step completed without tool execution" };
|
|
775
975
|
}
|
|
@@ -780,6 +980,12 @@ function createExecutorNode(config) {
|
|
|
780
980
|
success = false;
|
|
781
981
|
error = execError instanceof Error ? execError.message : "Unknown execution error";
|
|
782
982
|
result = null;
|
|
983
|
+
executorLogger.warn("Step execution failed", {
|
|
984
|
+
stepId: currentStep.id,
|
|
985
|
+
toolName: currentStep.tool,
|
|
986
|
+
error,
|
|
987
|
+
iteration
|
|
988
|
+
});
|
|
783
989
|
}
|
|
784
990
|
const completedStep = {
|
|
785
991
|
step: currentStep,
|
|
@@ -788,11 +994,23 @@ function createExecutorNode(config) {
|
|
|
788
994
|
error,
|
|
789
995
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
790
996
|
};
|
|
997
|
+
executorLogger.info("Executor node complete", {
|
|
998
|
+
stepId: currentStep.id,
|
|
999
|
+
stepIndex: currentStepIndex,
|
|
1000
|
+
totalSteps: plan.steps.length,
|
|
1001
|
+
success,
|
|
1002
|
+
isDuplicate,
|
|
1003
|
+
iteration
|
|
1004
|
+
});
|
|
791
1005
|
return {
|
|
792
1006
|
pastSteps: [completedStep],
|
|
793
1007
|
currentStepIndex: currentStepIndex + 1
|
|
794
1008
|
};
|
|
795
1009
|
} catch (error) {
|
|
1010
|
+
executorLogger.error("Executor node failed", {
|
|
1011
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1012
|
+
iteration
|
|
1013
|
+
});
|
|
796
1014
|
return {
|
|
797
1015
|
status: "failed",
|
|
798
1016
|
error: error instanceof Error ? error.message : "Unknown error in executor"
|
|
@@ -807,11 +1025,18 @@ function createReplannerNode(config) {
|
|
|
807
1025
|
systemPrompt = DEFAULT_REPLANNER_SYSTEM_PROMPT
|
|
808
1026
|
} = config;
|
|
809
1027
|
return async (state) => {
|
|
1028
|
+
const startTime = Date.now();
|
|
810
1029
|
try {
|
|
811
1030
|
const { plan, pastSteps = [], currentStepIndex = 0 } = state;
|
|
812
1031
|
if (!plan) {
|
|
813
1032
|
return { status: "failed", error: "No plan available for replanning" };
|
|
814
1033
|
}
|
|
1034
|
+
replannerLogger.debug("Evaluating replanning", {
|
|
1035
|
+
completedSteps: pastSteps.length,
|
|
1036
|
+
remainingSteps: plan.steps.length - currentStepIndex,
|
|
1037
|
+
successfulSteps: pastSteps.filter((ps) => ps.success).length,
|
|
1038
|
+
failedSteps: pastSteps.filter((ps) => !ps.success).length
|
|
1039
|
+
});
|
|
815
1040
|
const completedStepsText = pastSteps.map(
|
|
816
1041
|
(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
1042
|
).join("\n\n");
|
|
@@ -833,17 +1058,30 @@ function createReplannerNode(config) {
|
|
|
833
1058
|
throw new Error(`Failed to parse replan decision from LLM response: ${parseError}`);
|
|
834
1059
|
}
|
|
835
1060
|
if (decision.shouldReplan) {
|
|
1061
|
+
replannerLogger.info("Replanning triggered", {
|
|
1062
|
+
reason: decision.reason,
|
|
1063
|
+
newGoal: decision.newGoal?.substring(0, 100),
|
|
1064
|
+
duration: Date.now() - startTime
|
|
1065
|
+
});
|
|
836
1066
|
return {
|
|
837
1067
|
status: "planning",
|
|
838
1068
|
input: decision.newGoal || plan.goal,
|
|
839
1069
|
iteration: 1
|
|
840
1070
|
};
|
|
841
1071
|
} else {
|
|
1072
|
+
replannerLogger.info("Continuing with current plan", {
|
|
1073
|
+
reason: decision.reason,
|
|
1074
|
+
duration: Date.now() - startTime
|
|
1075
|
+
});
|
|
842
1076
|
return {
|
|
843
1077
|
status: "executing"
|
|
844
1078
|
};
|
|
845
1079
|
}
|
|
846
1080
|
} catch (error) {
|
|
1081
|
+
replannerLogger.error("Replanning evaluation failed", {
|
|
1082
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1083
|
+
duration: Date.now() - startTime
|
|
1084
|
+
});
|
|
847
1085
|
return {
|
|
848
1086
|
status: "failed",
|
|
849
1087
|
error: error instanceof Error ? error.message : "Unknown error in replanner"
|
|
@@ -1221,6 +1459,9 @@ var REVISION_ENTRY_TEMPLATE = `Iteration {iteration}:
|
|
|
1221
1459
|
|
|
1222
1460
|
// src/reflection/nodes.ts
|
|
1223
1461
|
import { HumanMessage as HumanMessage3, SystemMessage as SystemMessage3 } from "@langchain/core/messages";
|
|
1462
|
+
var generatorLogger = createPatternLogger("agentforge:patterns:reflection:generator");
|
|
1463
|
+
var reflectorLogger = createPatternLogger("agentforge:patterns:reflection:reflector");
|
|
1464
|
+
var reviserLogger = createPatternLogger("agentforge:patterns:reflection:reviser");
|
|
1224
1465
|
function createGeneratorNode(config) {
|
|
1225
1466
|
const {
|
|
1226
1467
|
model,
|
|
@@ -1228,10 +1469,13 @@ function createGeneratorNode(config) {
|
|
|
1228
1469
|
verbose = false
|
|
1229
1470
|
} = config;
|
|
1230
1471
|
return async (state) => {
|
|
1472
|
+
const startTime = Date.now();
|
|
1231
1473
|
try {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1474
|
+
generatorLogger.debug("Generating response", {
|
|
1475
|
+
attempt: state.iteration + 1,
|
|
1476
|
+
hasFeedback: state.reflections.length > 0,
|
|
1477
|
+
hasExistingResponse: !!state.currentResponse
|
|
1478
|
+
});
|
|
1235
1479
|
let context = "";
|
|
1236
1480
|
if (state.iteration > 0 && state.reflections.length > 0) {
|
|
1237
1481
|
const lastReflection = state.reflections[state.reflections.length - 1];
|
|
@@ -1246,16 +1490,23 @@ ${lastReflection.critique}`;
|
|
|
1246
1490
|
];
|
|
1247
1491
|
const response = await model.invoke(messages);
|
|
1248
1492
|
const content = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1493
|
+
generatorLogger.info("Response generated", {
|
|
1494
|
+
attempt: state.iteration + 1,
|
|
1495
|
+
responseLength: content.length,
|
|
1496
|
+
isRevision: state.iteration > 0,
|
|
1497
|
+
duration: Date.now() - startTime
|
|
1498
|
+
});
|
|
1252
1499
|
return {
|
|
1253
1500
|
currentResponse: content,
|
|
1254
1501
|
status: "reflecting",
|
|
1255
1502
|
iteration: 1
|
|
1256
1503
|
};
|
|
1257
1504
|
} catch (error) {
|
|
1258
|
-
|
|
1505
|
+
generatorLogger.error("Response generation failed", {
|
|
1506
|
+
attempt: state.iteration + 1,
|
|
1507
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1508
|
+
duration: Date.now() - startTime
|
|
1509
|
+
});
|
|
1259
1510
|
return {
|
|
1260
1511
|
status: "failed",
|
|
1261
1512
|
error: error instanceof Error ? error.message : "Unknown error in generator"
|
|
@@ -1271,10 +1522,13 @@ function createReflectorNode(config) {
|
|
|
1271
1522
|
verbose = false
|
|
1272
1523
|
} = config;
|
|
1273
1524
|
return async (state) => {
|
|
1525
|
+
const startTime = Date.now();
|
|
1274
1526
|
try {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1527
|
+
reflectorLogger.debug("Reflecting on response", {
|
|
1528
|
+
attempt: state.iteration,
|
|
1529
|
+
responseLength: state.currentResponse?.length || 0,
|
|
1530
|
+
hasCriteria: !!(qualityCriteria || state.qualityCriteria)
|
|
1531
|
+
});
|
|
1278
1532
|
if (!state.currentResponse) {
|
|
1279
1533
|
throw new Error("No current response to reflect on");
|
|
1280
1534
|
}
|
|
@@ -1324,16 +1578,24 @@ function createReflectorNode(config) {
|
|
|
1324
1578
|
meetsStandards: false
|
|
1325
1579
|
};
|
|
1326
1580
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1581
|
+
reflectorLogger.info("Reflection complete", {
|
|
1582
|
+
attempt: state.iteration,
|
|
1583
|
+
score: reflection.score,
|
|
1584
|
+
meetsStandards: reflection.meetsStandards,
|
|
1585
|
+
issueCount: reflection.issues.length,
|
|
1586
|
+
suggestionCount: reflection.suggestions.length,
|
|
1587
|
+
duration: Date.now() - startTime
|
|
1588
|
+
});
|
|
1331
1589
|
return {
|
|
1332
1590
|
reflections: [reflection],
|
|
1333
1591
|
status: reflection.meetsStandards ? "completed" : "revising"
|
|
1334
1592
|
};
|
|
1335
1593
|
} catch (error) {
|
|
1336
|
-
|
|
1594
|
+
reflectorLogger.error("Reflection failed", {
|
|
1595
|
+
attempt: state.iteration,
|
|
1596
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1597
|
+
duration: Date.now() - startTime
|
|
1598
|
+
});
|
|
1337
1599
|
return {
|
|
1338
1600
|
status: "failed",
|
|
1339
1601
|
error: error instanceof Error ? error.message : "Unknown error in reflector"
|
|
@@ -1348,10 +1610,8 @@ function createReviserNode(config) {
|
|
|
1348
1610
|
verbose = false
|
|
1349
1611
|
} = config;
|
|
1350
1612
|
return async (state) => {
|
|
1613
|
+
const startTime = Date.now();
|
|
1351
1614
|
try {
|
|
1352
|
-
if (verbose) {
|
|
1353
|
-
console.log("[Reviser] Revising response...");
|
|
1354
|
-
}
|
|
1355
1615
|
if (!state.currentResponse) {
|
|
1356
1616
|
throw new Error("No current response to revise");
|
|
1357
1617
|
}
|
|
@@ -1359,6 +1619,12 @@ function createReviserNode(config) {
|
|
|
1359
1619
|
throw new Error("No reflections to base revision on");
|
|
1360
1620
|
}
|
|
1361
1621
|
const lastReflection = state.reflections[state.reflections.length - 1];
|
|
1622
|
+
reviserLogger.debug("Revising response", {
|
|
1623
|
+
attempt: state.iteration,
|
|
1624
|
+
previousScore: lastReflection.score,
|
|
1625
|
+
issueCount: lastReflection.issues.length,
|
|
1626
|
+
suggestionCount: lastReflection.suggestions.length
|
|
1627
|
+
});
|
|
1362
1628
|
let historySection = "";
|
|
1363
1629
|
if (state.revisions.length > 0) {
|
|
1364
1630
|
const revisionsText = state.revisions.map(
|
|
@@ -1375,14 +1641,17 @@ ${revisionsText}`;
|
|
|
1375
1641
|
];
|
|
1376
1642
|
const response = await model.invoke(messages);
|
|
1377
1643
|
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
1644
|
const revision = {
|
|
1382
1645
|
content,
|
|
1383
1646
|
iteration: state.iteration,
|
|
1384
1647
|
basedOn: lastReflection
|
|
1385
1648
|
};
|
|
1649
|
+
reviserLogger.info("Revision complete", {
|
|
1650
|
+
attempt: state.iteration,
|
|
1651
|
+
revisionLength: content.length,
|
|
1652
|
+
basedOnScore: lastReflection.score,
|
|
1653
|
+
duration: Date.now() - startTime
|
|
1654
|
+
});
|
|
1386
1655
|
return {
|
|
1387
1656
|
currentResponse: content,
|
|
1388
1657
|
revisions: [revision],
|
|
@@ -1390,7 +1659,11 @@ ${revisionsText}`;
|
|
|
1390
1659
|
iteration: 1
|
|
1391
1660
|
};
|
|
1392
1661
|
} catch (error) {
|
|
1393
|
-
|
|
1662
|
+
reviserLogger.error("Revision failed", {
|
|
1663
|
+
attempt: state.iteration,
|
|
1664
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1665
|
+
duration: Date.now() - startTime
|
|
1666
|
+
});
|
|
1394
1667
|
return {
|
|
1395
1668
|
status: "failed",
|
|
1396
1669
|
error: error instanceof Error ? error.message : "Unknown error in reviser"
|
|
@@ -1807,11 +2080,22 @@ var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
|
|
|
1807
2080
|
|
|
1808
2081
|
// src/multi-agent/routing.ts
|
|
1809
2082
|
import { HumanMessage as HumanMessage4, SystemMessage as SystemMessage4, AIMessage as AIMessage2, ToolMessage as ToolMessage2 } from "@langchain/core/messages";
|
|
2083
|
+
import { createLogger as createLogger2, LogLevel } from "@agentforge/core";
|
|
2084
|
+
var logLevel = process.env.LOG_LEVEL?.toLowerCase() || LogLevel.INFO;
|
|
2085
|
+
var logger = createLogger2("multi-agent:routing", { level: logLevel });
|
|
1810
2086
|
async function executeTools(toolCalls, tools) {
|
|
1811
2087
|
const results = [];
|
|
2088
|
+
logger.debug("Executing tools", {
|
|
2089
|
+
toolCallCount: toolCalls.length,
|
|
2090
|
+
toolNames: toolCalls.map((tc) => tc.name)
|
|
2091
|
+
});
|
|
1812
2092
|
for (const toolCall of toolCalls) {
|
|
1813
2093
|
const tool = tools.find((t) => t.metadata.name === toolCall.name);
|
|
1814
2094
|
if (!tool) {
|
|
2095
|
+
logger.warn("Tool not found", {
|
|
2096
|
+
toolName: toolCall.name,
|
|
2097
|
+
availableTools: tools.map((t) => t.metadata.name)
|
|
2098
|
+
});
|
|
1815
2099
|
results.push(new ToolMessage2({
|
|
1816
2100
|
content: `Error: Tool '${toolCall.name}' not found`,
|
|
1817
2101
|
tool_call_id: toolCall.id
|
|
@@ -1819,19 +2103,41 @@ async function executeTools(toolCalls, tools) {
|
|
|
1819
2103
|
continue;
|
|
1820
2104
|
}
|
|
1821
2105
|
try {
|
|
2106
|
+
logger.debug("Executing tool", {
|
|
2107
|
+
toolName: toolCall.name,
|
|
2108
|
+
args: toolCall.args
|
|
2109
|
+
});
|
|
1822
2110
|
const result = await tool.execute(toolCall.args);
|
|
1823
2111
|
const content = typeof result === "string" ? result : JSON.stringify(result);
|
|
2112
|
+
logger.debug("Tool execution successful", {
|
|
2113
|
+
toolName: toolCall.name,
|
|
2114
|
+
resultLength: content.length
|
|
2115
|
+
});
|
|
1824
2116
|
results.push(new ToolMessage2({
|
|
1825
2117
|
content,
|
|
1826
2118
|
tool_call_id: toolCall.id
|
|
1827
2119
|
}));
|
|
1828
2120
|
} catch (error) {
|
|
2121
|
+
logger.error("Tool execution failed", {
|
|
2122
|
+
toolName: toolCall.name,
|
|
2123
|
+
error: error.message
|
|
2124
|
+
});
|
|
1829
2125
|
results.push(new ToolMessage2({
|
|
1830
2126
|
content: `Error executing tool: ${error.message}`,
|
|
1831
2127
|
tool_call_id: toolCall.id
|
|
1832
2128
|
}));
|
|
1833
2129
|
}
|
|
1834
2130
|
}
|
|
2131
|
+
logger.debug("Tool execution complete", {
|
|
2132
|
+
successCount: results.filter((r) => {
|
|
2133
|
+
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2134
|
+
return !content.startsWith("Error");
|
|
2135
|
+
}).length,
|
|
2136
|
+
errorCount: results.filter((r) => {
|
|
2137
|
+
const content = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
2138
|
+
return content.startsWith("Error");
|
|
2139
|
+
}).length
|
|
2140
|
+
});
|
|
1835
2141
|
return results;
|
|
1836
2142
|
}
|
|
1837
2143
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
@@ -1869,6 +2175,10 @@ Choose parallel routing when the task benefits from multiple perspectives or dat
|
|
|
1869
2175
|
var llmBasedRouting = {
|
|
1870
2176
|
name: "llm-based",
|
|
1871
2177
|
async route(state, config) {
|
|
2178
|
+
logger.info("Starting LLM-based routing", {
|
|
2179
|
+
iteration: state.iteration,
|
|
2180
|
+
availableWorkers: Object.keys(state.workers).length
|
|
2181
|
+
});
|
|
1872
2182
|
if (!config.model) {
|
|
1873
2183
|
throw new Error("LLM-based routing requires a model to be configured");
|
|
1874
2184
|
}
|
|
@@ -1881,8 +2191,20 @@ var llmBasedRouting = {
|
|
|
1881
2191
|
const available = caps.available ? "available" : "busy";
|
|
1882
2192
|
return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
|
|
1883
2193
|
}).join("\n");
|
|
2194
|
+
logger.debug("Worker capabilities", {
|
|
2195
|
+
workers: Object.entries(state.workers).map(([id, caps]) => ({
|
|
2196
|
+
id,
|
|
2197
|
+
skills: caps.skills,
|
|
2198
|
+
available: caps.available,
|
|
2199
|
+
workload: caps.currentWorkload
|
|
2200
|
+
}))
|
|
2201
|
+
});
|
|
1884
2202
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
1885
2203
|
const taskContext = lastMessage?.content || state.input;
|
|
2204
|
+
logger.debug("Task context", {
|
|
2205
|
+
taskLength: taskContext.length,
|
|
2206
|
+
taskPreview: taskContext.substring(0, 100)
|
|
2207
|
+
});
|
|
1886
2208
|
const userPrompt = `Current task: ${taskContext}
|
|
1887
2209
|
|
|
1888
2210
|
Available workers:
|
|
@@ -1892,6 +2214,11 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1892
2214
|
const conversationHistory = [];
|
|
1893
2215
|
let attempt = 0;
|
|
1894
2216
|
while (attempt < maxRetries) {
|
|
2217
|
+
logger.debug("LLM routing attempt", {
|
|
2218
|
+
attempt: attempt + 1,
|
|
2219
|
+
maxRetries,
|
|
2220
|
+
conversationHistoryLength: conversationHistory.length
|
|
2221
|
+
});
|
|
1895
2222
|
const messages = [
|
|
1896
2223
|
new SystemMessage4(systemPrompt),
|
|
1897
2224
|
new HumanMessage4(userPrompt),
|
|
@@ -1899,6 +2226,10 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1899
2226
|
];
|
|
1900
2227
|
const response = await config.model.invoke(messages);
|
|
1901
2228
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
2229
|
+
logger.info("LLM requested tool calls", {
|
|
2230
|
+
toolCount: response.tool_calls.length,
|
|
2231
|
+
toolNames: response.tool_calls.map((tc) => tc.name)
|
|
2232
|
+
});
|
|
1902
2233
|
if (tools.length === 0) {
|
|
1903
2234
|
throw new Error("LLM requested tool calls but no tools are configured");
|
|
1904
2235
|
}
|
|
@@ -1908,24 +2239,42 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1908
2239
|
...toolResults
|
|
1909
2240
|
);
|
|
1910
2241
|
attempt++;
|
|
2242
|
+
logger.debug("Retrying routing with tool results", { attempt });
|
|
1911
2243
|
continue;
|
|
1912
2244
|
}
|
|
2245
|
+
logger.debug("Parsing routing decision from LLM response");
|
|
1913
2246
|
let decision;
|
|
1914
2247
|
if (response && typeof response === "object" && ("targetAgent" in response || "targetAgents" in response)) {
|
|
2248
|
+
logger.debug("Response is structured output", {
|
|
2249
|
+
hasTargetAgent: "targetAgent" in response,
|
|
2250
|
+
hasTargetAgents: "targetAgents" in response
|
|
2251
|
+
});
|
|
1915
2252
|
decision = response;
|
|
1916
2253
|
} else if (response.content) {
|
|
1917
2254
|
if (typeof response.content === "string") {
|
|
1918
2255
|
try {
|
|
1919
2256
|
decision = JSON.parse(response.content);
|
|
2257
|
+
logger.debug("Parsed JSON from string response");
|
|
1920
2258
|
} catch (error) {
|
|
2259
|
+
logger.error("Failed to parse routing decision", {
|
|
2260
|
+
content: response.content,
|
|
2261
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2262
|
+
});
|
|
1921
2263
|
throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
|
|
1922
2264
|
}
|
|
1923
2265
|
} else if (typeof response.content === "object") {
|
|
2266
|
+
logger.debug("Response content is already an object");
|
|
1924
2267
|
decision = response.content;
|
|
1925
2268
|
} else {
|
|
2269
|
+
logger.error("Unexpected response content type", {
|
|
2270
|
+
type: typeof response.content
|
|
2271
|
+
});
|
|
1926
2272
|
throw new Error(`Unexpected response content type: ${typeof response.content}`);
|
|
1927
2273
|
}
|
|
1928
2274
|
} else {
|
|
2275
|
+
logger.error("Unexpected response format", {
|
|
2276
|
+
response: JSON.stringify(response)
|
|
2277
|
+
});
|
|
1929
2278
|
throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
|
|
1930
2279
|
}
|
|
1931
2280
|
const result = {
|
|
@@ -1936,20 +2285,41 @@ Select the best worker(s) for this task and explain your reasoning.`;
|
|
|
1936
2285
|
strategy: "llm-based",
|
|
1937
2286
|
timestamp: Date.now()
|
|
1938
2287
|
};
|
|
2288
|
+
logger.info("LLM routing decision made", {
|
|
2289
|
+
targetAgent: result.targetAgent,
|
|
2290
|
+
targetAgents: result.targetAgents,
|
|
2291
|
+
isParallel: result.targetAgents && result.targetAgents.length > 1,
|
|
2292
|
+
confidence: result.confidence,
|
|
2293
|
+
reasoning: result.reasoning
|
|
2294
|
+
});
|
|
1939
2295
|
return result;
|
|
1940
2296
|
}
|
|
2297
|
+
logger.error("Max tool retries exceeded", { maxRetries });
|
|
1941
2298
|
throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
|
|
1942
2299
|
}
|
|
1943
2300
|
};
|
|
1944
2301
|
var roundRobinRouting = {
|
|
1945
2302
|
name: "round-robin",
|
|
1946
2303
|
async route(state, config) {
|
|
2304
|
+
logger.info("Starting round-robin routing", {
|
|
2305
|
+
iteration: state.iteration
|
|
2306
|
+
});
|
|
1947
2307
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id]) => id);
|
|
2308
|
+
logger.debug("Available workers for round-robin", {
|
|
2309
|
+
count: availableWorkers.length,
|
|
2310
|
+
workers: availableWorkers
|
|
2311
|
+
});
|
|
1948
2312
|
if (availableWorkers.length === 0) {
|
|
2313
|
+
logger.error("No available workers for round-robin routing");
|
|
1949
2314
|
throw new Error("No available workers for round-robin routing");
|
|
1950
2315
|
}
|
|
1951
2316
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
1952
2317
|
const targetAgent = availableWorkers[lastRoutingIndex];
|
|
2318
|
+
logger.info("Round-robin routing decision", {
|
|
2319
|
+
targetAgent,
|
|
2320
|
+
index: lastRoutingIndex + 1,
|
|
2321
|
+
totalWorkers: availableWorkers.length
|
|
2322
|
+
});
|
|
1953
2323
|
return {
|
|
1954
2324
|
targetAgent,
|
|
1955
2325
|
targetAgents: null,
|
|
@@ -1963,8 +2333,15 @@ var roundRobinRouting = {
|
|
|
1963
2333
|
var skillBasedRouting = {
|
|
1964
2334
|
name: "skill-based",
|
|
1965
2335
|
async route(state, config) {
|
|
2336
|
+
logger.info("Starting skill-based routing", {
|
|
2337
|
+
iteration: state.iteration
|
|
2338
|
+
});
|
|
1966
2339
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
1967
2340
|
const taskContent = (lastMessage?.content || state.input).toLowerCase();
|
|
2341
|
+
logger.debug("Task content for skill matching", {
|
|
2342
|
+
taskLength: taskContent.length,
|
|
2343
|
+
taskPreview: taskContent.substring(0, 100)
|
|
2344
|
+
});
|
|
1968
2345
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
1969
2346
|
const skillMatches = caps.skills.filter(
|
|
1970
2347
|
(skill) => taskContent.includes(skill.toLowerCase())
|
|
@@ -1975,11 +2352,20 @@ var skillBasedRouting = {
|
|
|
1975
2352
|
const score = skillMatches * 2 + toolMatches;
|
|
1976
2353
|
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
1977
2354
|
}).filter((w) => w.score > 0).sort((a, b) => b.score - a.score);
|
|
2355
|
+
logger.debug("Worker skill scores", {
|
|
2356
|
+
scoredWorkers: workerScores.map((w) => ({ id: w.id, score: w.score }))
|
|
2357
|
+
});
|
|
1978
2358
|
if (workerScores.length === 0) {
|
|
2359
|
+
logger.warn("No skill matches found, using fallback");
|
|
1979
2360
|
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
1980
2361
|
if (!firstAvailable) {
|
|
2362
|
+
logger.error("No available workers for skill-based routing");
|
|
1981
2363
|
throw new Error("No available workers for skill-based routing");
|
|
1982
2364
|
}
|
|
2365
|
+
logger.info("Skill-based routing fallback decision", {
|
|
2366
|
+
targetAgent: firstAvailable[0],
|
|
2367
|
+
confidence: 0.5
|
|
2368
|
+
});
|
|
1983
2369
|
return {
|
|
1984
2370
|
targetAgent: firstAvailable[0],
|
|
1985
2371
|
targetAgents: null,
|
|
@@ -1991,6 +2377,12 @@ var skillBasedRouting = {
|
|
|
1991
2377
|
}
|
|
1992
2378
|
const best = workerScores[0];
|
|
1993
2379
|
const confidence = Math.min(best.score / 5, 1);
|
|
2380
|
+
logger.info("Skill-based routing decision", {
|
|
2381
|
+
targetAgent: best.id,
|
|
2382
|
+
score: best.score,
|
|
2383
|
+
confidence,
|
|
2384
|
+
matchedSkills: best.skills
|
|
2385
|
+
});
|
|
1994
2386
|
return {
|
|
1995
2387
|
targetAgent: best.id,
|
|
1996
2388
|
targetAgents: null,
|
|
@@ -2004,13 +2396,26 @@ var skillBasedRouting = {
|
|
|
2004
2396
|
var loadBalancedRouting = {
|
|
2005
2397
|
name: "load-balanced",
|
|
2006
2398
|
async route(state, config) {
|
|
2399
|
+
logger.info("Starting load-balanced routing", {
|
|
2400
|
+
iteration: state.iteration
|
|
2401
|
+
});
|
|
2007
2402
|
const availableWorkers = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => ({ id, workload: caps.currentWorkload })).sort((a, b) => a.workload - b.workload);
|
|
2403
|
+
logger.debug("Worker workloads", {
|
|
2404
|
+
workers: availableWorkers.map((w) => ({ id: w.id, workload: w.workload }))
|
|
2405
|
+
});
|
|
2008
2406
|
if (availableWorkers.length === 0) {
|
|
2407
|
+
logger.error("No available workers for load-balanced routing");
|
|
2009
2408
|
throw new Error("No available workers for load-balanced routing");
|
|
2010
2409
|
}
|
|
2011
2410
|
const targetWorker = availableWorkers[0];
|
|
2012
2411
|
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
2013
2412
|
const confidence = targetWorker.workload === 0 ? 1 : Math.max(0.5, 1 - targetWorker.workload / (avgWorkload * 2));
|
|
2413
|
+
logger.info("Load-balanced routing decision", {
|
|
2414
|
+
targetAgent: targetWorker.id,
|
|
2415
|
+
workload: targetWorker.workload,
|
|
2416
|
+
avgWorkload: avgWorkload.toFixed(1),
|
|
2417
|
+
confidence
|
|
2418
|
+
});
|
|
2014
2419
|
return {
|
|
2015
2420
|
targetAgent: targetWorker.id,
|
|
2016
2421
|
targetAgents: null,
|
|
@@ -2024,13 +2429,24 @@ var loadBalancedRouting = {
|
|
|
2024
2429
|
var ruleBasedRouting = {
|
|
2025
2430
|
name: "rule-based",
|
|
2026
2431
|
async route(state, config) {
|
|
2432
|
+
logger.info("Starting rule-based routing", {
|
|
2433
|
+
iteration: state.iteration
|
|
2434
|
+
});
|
|
2027
2435
|
if (!config.routingFn) {
|
|
2436
|
+
logger.error("Rule-based routing requires a custom routing function");
|
|
2028
2437
|
throw new Error("Rule-based routing requires a custom routing function");
|
|
2029
2438
|
}
|
|
2030
|
-
|
|
2439
|
+
const decision = await config.routingFn(state);
|
|
2440
|
+
logger.info("Rule-based routing decision", {
|
|
2441
|
+
targetAgent: decision.targetAgent,
|
|
2442
|
+
targetAgents: decision.targetAgents,
|
|
2443
|
+
confidence: decision.confidence
|
|
2444
|
+
});
|
|
2445
|
+
return decision;
|
|
2031
2446
|
}
|
|
2032
2447
|
};
|
|
2033
2448
|
function getRoutingStrategy(name) {
|
|
2449
|
+
logger.debug("Getting routing strategy", { name });
|
|
2034
2450
|
switch (name) {
|
|
2035
2451
|
case "llm-based":
|
|
2036
2452
|
return llmBasedRouting;
|
|
@@ -2043,14 +2459,15 @@ function getRoutingStrategy(name) {
|
|
|
2043
2459
|
case "rule-based":
|
|
2044
2460
|
return ruleBasedRouting;
|
|
2045
2461
|
default:
|
|
2462
|
+
logger.error("Unknown routing strategy", { name });
|
|
2046
2463
|
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2047
2464
|
}
|
|
2048
2465
|
}
|
|
2049
2466
|
|
|
2050
2467
|
// src/multi-agent/utils.ts
|
|
2051
|
-
import { createLogger, LogLevel } from "@agentforge/core";
|
|
2052
|
-
var
|
|
2053
|
-
var
|
|
2468
|
+
import { createLogger as createLogger3, LogLevel as LogLevel2 } from "@agentforge/core";
|
|
2469
|
+
var logLevel2 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel2.INFO;
|
|
2470
|
+
var logger2 = createLogger3("multi-agent", { level: logLevel2 });
|
|
2054
2471
|
function isReActAgent(obj) {
|
|
2055
2472
|
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
2473
|
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
|
|
@@ -2058,9 +2475,9 @@ function isReActAgent(obj) {
|
|
|
2058
2475
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2059
2476
|
return async (state, config) => {
|
|
2060
2477
|
try {
|
|
2061
|
-
|
|
2478
|
+
logger2.debug("Wrapping ReAct agent execution", { workerId });
|
|
2062
2479
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2063
|
-
|
|
2480
|
+
logger2.debug("Extracted task", {
|
|
2064
2481
|
workerId,
|
|
2065
2482
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2066
2483
|
});
|
|
@@ -2068,7 +2485,7 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2068
2485
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2069
2486
|
);
|
|
2070
2487
|
if (!currentAssignment) {
|
|
2071
|
-
|
|
2488
|
+
logger2.debug("No active assignment found", { workerId });
|
|
2072
2489
|
return {};
|
|
2073
2490
|
}
|
|
2074
2491
|
const result = await agent.invoke(
|
|
@@ -2079,14 +2496,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2079
2496
|
// Pass through the config for checkpointing and interrupt support
|
|
2080
2497
|
);
|
|
2081
2498
|
const response = result.messages?.[result.messages.length - 1]?.content || "No response";
|
|
2082
|
-
|
|
2499
|
+
logger2.debug("Received response from ReAct agent", {
|
|
2083
2500
|
workerId,
|
|
2084
2501
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2085
2502
|
});
|
|
2086
2503
|
const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
|
|
2087
2504
|
const uniqueTools = [...new Set(toolsUsed)];
|
|
2088
2505
|
if (uniqueTools.length > 0) {
|
|
2089
|
-
|
|
2506
|
+
logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2090
2507
|
}
|
|
2091
2508
|
const taskResult = {
|
|
2092
2509
|
assignmentId: currentAssignment.id,
|
|
@@ -2105,10 +2522,10 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2105
2522
|
};
|
|
2106
2523
|
} catch (error) {
|
|
2107
2524
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
2108
|
-
|
|
2525
|
+
logger2.debug("GraphInterrupt detected - re-throwing", { workerId });
|
|
2109
2526
|
throw error;
|
|
2110
2527
|
}
|
|
2111
|
-
|
|
2528
|
+
logger2.error("Error in ReAct agent execution", {
|
|
2112
2529
|
workerId,
|
|
2113
2530
|
error: error instanceof Error ? error.message : String(error),
|
|
2114
2531
|
stack: error instanceof Error ? error.stack : void 0
|
|
@@ -2141,7 +2558,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2141
2558
|
|
|
2142
2559
|
// src/multi-agent/nodes.ts
|
|
2143
2560
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2144
|
-
import { toLangChainTools as toLangChainTools2 } from "@agentforge/core";
|
|
2561
|
+
import { toLangChainTools as toLangChainTools2, createLogger as createLogger4, LogLevel as LogLevel3 } from "@agentforge/core";
|
|
2562
|
+
var logLevel3 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel3.INFO;
|
|
2563
|
+
var logger3 = createLogger4("multi-agent:nodes", { level: logLevel3 });
|
|
2145
2564
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2146
2565
|
|
|
2147
2566
|
Your job is to:
|
|
@@ -2159,13 +2578,19 @@ function createSupervisorNode(config) {
|
|
|
2159
2578
|
} = config;
|
|
2160
2579
|
return async (state) => {
|
|
2161
2580
|
try {
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2581
|
+
logger3.info("Supervisor node executing", {
|
|
2582
|
+
iteration: state.iteration,
|
|
2583
|
+
maxIterations,
|
|
2584
|
+
activeAssignments: state.activeAssignments.length,
|
|
2585
|
+
completedTasks: state.completedTasks.length
|
|
2586
|
+
});
|
|
2587
|
+
logger3.debug(`Routing iteration ${state.iteration}/${maxIterations}`);
|
|
2165
2588
|
if (state.iteration >= maxIterations) {
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2589
|
+
logger3.warn("Max iterations reached", {
|
|
2590
|
+
iteration: state.iteration,
|
|
2591
|
+
maxIterations
|
|
2592
|
+
});
|
|
2593
|
+
logger3.debug("Max iterations reached, moving to aggregation");
|
|
2169
2594
|
return {
|
|
2170
2595
|
status: "aggregating",
|
|
2171
2596
|
currentAgent: "aggregator"
|
|
@@ -2174,27 +2599,55 @@ function createSupervisorNode(config) {
|
|
|
2174
2599
|
const allCompleted = state.activeAssignments.every(
|
|
2175
2600
|
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2176
2601
|
);
|
|
2602
|
+
logger3.debug("Checking task completion", {
|
|
2603
|
+
activeAssignments: state.activeAssignments.length,
|
|
2604
|
+
completedTasks: state.completedTasks.length,
|
|
2605
|
+
allCompleted
|
|
2606
|
+
});
|
|
2177
2607
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
}
|
|
2608
|
+
logger3.info("All tasks completed, moving to aggregation", {
|
|
2609
|
+
completedCount: state.completedTasks.length
|
|
2610
|
+
});
|
|
2611
|
+
logger3.debug("All tasks completed, moving to aggregation");
|
|
2181
2612
|
return {
|
|
2182
2613
|
status: "aggregating",
|
|
2183
2614
|
currentAgent: "aggregator"
|
|
2184
2615
|
};
|
|
2185
2616
|
}
|
|
2617
|
+
logger3.debug("Getting routing strategy", { strategy });
|
|
2186
2618
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2187
2619
|
const decision = await routingImpl.route(state, config);
|
|
2188
2620
|
const targetAgents = decision.targetAgents && decision.targetAgents.length > 0 ? decision.targetAgents : decision.targetAgent ? [decision.targetAgent] : [];
|
|
2621
|
+
logger3.debug("Target agents determined", {
|
|
2622
|
+
targetAgents,
|
|
2623
|
+
isParallel: targetAgents.length > 1,
|
|
2624
|
+
decision: {
|
|
2625
|
+
reasoning: decision.reasoning,
|
|
2626
|
+
confidence: decision.confidence
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2189
2629
|
if (targetAgents.length === 0) {
|
|
2630
|
+
logger3.error("No target agents specified in routing decision");
|
|
2190
2631
|
throw new Error("Routing decision must specify at least one target agent");
|
|
2191
2632
|
}
|
|
2192
|
-
if (
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
}
|
|
2633
|
+
if (targetAgents.length === 1) {
|
|
2634
|
+
logger3.info("Routing to single agent", {
|
|
2635
|
+
targetAgent: targetAgents[0],
|
|
2636
|
+
reasoning: decision.reasoning,
|
|
2637
|
+
confidence: decision.confidence
|
|
2638
|
+
});
|
|
2639
|
+
} else {
|
|
2640
|
+
logger3.info("Routing to multiple agents in parallel", {
|
|
2641
|
+
targetAgents,
|
|
2642
|
+
count: targetAgents.length,
|
|
2643
|
+
reasoning: decision.reasoning,
|
|
2644
|
+
confidence: decision.confidence
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
if (targetAgents.length === 1) {
|
|
2648
|
+
logger3.debug(`Routing to ${targetAgents[0]}: ${decision.reasoning}`);
|
|
2649
|
+
} else {
|
|
2650
|
+
logger3.debug(`Routing to ${targetAgents.length} agents in parallel [${targetAgents.join(", ")}]: ${decision.reasoning}`);
|
|
2198
2651
|
}
|
|
2199
2652
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2200
2653
|
const assignments = targetAgents.map((workerId) => ({
|
|
@@ -2204,6 +2657,14 @@ function createSupervisorNode(config) {
|
|
|
2204
2657
|
priority: 5,
|
|
2205
2658
|
assignedAt: Date.now()
|
|
2206
2659
|
}));
|
|
2660
|
+
logger3.debug("Created task assignments", {
|
|
2661
|
+
assignmentCount: assignments.length,
|
|
2662
|
+
assignments: assignments.map((a) => ({
|
|
2663
|
+
id: a.id,
|
|
2664
|
+
workerId: a.workerId,
|
|
2665
|
+
taskLength: a.task.length
|
|
2666
|
+
}))
|
|
2667
|
+
});
|
|
2207
2668
|
const messages = assignments.map((assignment) => ({
|
|
2208
2669
|
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
2209
2670
|
from: "supervisor",
|
|
@@ -2216,6 +2677,12 @@ function createSupervisorNode(config) {
|
|
|
2216
2677
|
priority: assignment.priority
|
|
2217
2678
|
}
|
|
2218
2679
|
}));
|
|
2680
|
+
logger3.info("Supervisor routing complete", {
|
|
2681
|
+
currentAgent: targetAgents.join(","),
|
|
2682
|
+
status: "executing",
|
|
2683
|
+
assignmentCount: assignments.length,
|
|
2684
|
+
nextIteration: state.iteration + 1
|
|
2685
|
+
});
|
|
2219
2686
|
return {
|
|
2220
2687
|
currentAgent: targetAgents.join(","),
|
|
2221
2688
|
// Store all agents (for backward compat)
|
|
@@ -2227,7 +2694,11 @@ function createSupervisorNode(config) {
|
|
|
2227
2694
|
iteration: state.iteration + 1
|
|
2228
2695
|
};
|
|
2229
2696
|
} catch (error) {
|
|
2230
|
-
|
|
2697
|
+
logger3.error("Supervisor node error", {
|
|
2698
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2699
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2700
|
+
iteration: state.iteration
|
|
2701
|
+
});
|
|
2231
2702
|
return {
|
|
2232
2703
|
status: "failed",
|
|
2233
2704
|
error: error instanceof Error ? error.message : "Unknown error in supervisor"
|
|
@@ -2248,40 +2719,52 @@ function createWorkerNode(config) {
|
|
|
2248
2719
|
} = config;
|
|
2249
2720
|
return async (state, runConfig) => {
|
|
2250
2721
|
try {
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2722
|
+
logger3.info("Worker node executing", {
|
|
2723
|
+
workerId: id,
|
|
2724
|
+
iteration: state.iteration,
|
|
2725
|
+
activeAssignments: state.activeAssignments.length
|
|
2726
|
+
});
|
|
2254
2727
|
const currentAssignment = state.activeAssignments.find(
|
|
2255
2728
|
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2256
2729
|
);
|
|
2257
2730
|
if (!currentAssignment) {
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2731
|
+
logger3.debug("No active assignment found for worker", {
|
|
2732
|
+
workerId: id,
|
|
2733
|
+
totalActiveAssignments: state.activeAssignments.length,
|
|
2734
|
+
completedTasks: state.completedTasks.length
|
|
2735
|
+
});
|
|
2261
2736
|
return {};
|
|
2262
2737
|
}
|
|
2738
|
+
logger3.info("Worker processing assignment", {
|
|
2739
|
+
workerId: id,
|
|
2740
|
+
assignmentId: currentAssignment.id,
|
|
2741
|
+
taskLength: currentAssignment.task.length,
|
|
2742
|
+
taskPreview: currentAssignment.task.substring(0, 100)
|
|
2743
|
+
});
|
|
2263
2744
|
if (executeFn) {
|
|
2264
|
-
|
|
2265
|
-
console.log(`[Worker:${id}] Using custom executeFn`);
|
|
2266
|
-
}
|
|
2745
|
+
logger3.debug("Using custom execution function", { workerId: id });
|
|
2267
2746
|
return await executeFn(state, runConfig);
|
|
2268
2747
|
}
|
|
2269
2748
|
if (agent) {
|
|
2270
2749
|
if (isReActAgent(agent)) {
|
|
2271
|
-
|
|
2272
|
-
console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
|
|
2273
|
-
}
|
|
2750
|
+
logger3.debug("Using ReAct agent", { workerId: id });
|
|
2274
2751
|
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
2275
2752
|
return await wrappedFn(state, runConfig);
|
|
2276
2753
|
} else {
|
|
2277
|
-
|
|
2754
|
+
logger3.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
|
|
2278
2755
|
}
|
|
2279
2756
|
}
|
|
2280
2757
|
if (!model) {
|
|
2758
|
+
logger3.error("Worker missing required configuration", { workerId: id });
|
|
2281
2759
|
throw new Error(
|
|
2282
2760
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2283
2761
|
);
|
|
2284
2762
|
}
|
|
2763
|
+
logger3.debug("Using default LLM execution", {
|
|
2764
|
+
workerId: id,
|
|
2765
|
+
hasTools: tools.length > 0,
|
|
2766
|
+
toolCount: tools.length
|
|
2767
|
+
});
|
|
2285
2768
|
const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
|
|
2286
2769
|
Skills: ${capabilities.skills.join(", ")}
|
|
2287
2770
|
Tools: ${capabilities.tools.join(", ")}
|
|
@@ -2293,14 +2776,23 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2293
2776
|
];
|
|
2294
2777
|
let modelToUse = model;
|
|
2295
2778
|
if (tools.length > 0 && model.bindTools) {
|
|
2779
|
+
logger3.debug("Binding tools to model", {
|
|
2780
|
+
workerId: id,
|
|
2781
|
+
toolCount: tools.length,
|
|
2782
|
+
toolNames: tools.map((t) => t.metadata.name)
|
|
2783
|
+
});
|
|
2296
2784
|
const langchainTools = toLangChainTools2(tools);
|
|
2297
2785
|
modelToUse = model.bindTools(langchainTools);
|
|
2298
2786
|
}
|
|
2787
|
+
logger3.debug("Invoking LLM", { workerId: id });
|
|
2299
2788
|
const response = await modelToUse.invoke(messages);
|
|
2300
2789
|
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2790
|
+
logger3.info("Worker task completed", {
|
|
2791
|
+
workerId: id,
|
|
2792
|
+
assignmentId: currentAssignment.id,
|
|
2793
|
+
resultLength: result.length,
|
|
2794
|
+
resultPreview: result.substring(0, 100)
|
|
2795
|
+
});
|
|
2304
2796
|
const taskResult = {
|
|
2305
2797
|
assignmentId: currentAssignment.id,
|
|
2306
2798
|
workerId: id,
|
|
@@ -2330,6 +2822,10 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2330
2822
|
currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
|
|
2331
2823
|
}
|
|
2332
2824
|
};
|
|
2825
|
+
logger3.debug("Worker state update", {
|
|
2826
|
+
workerId: id,
|
|
2827
|
+
newWorkload: updatedWorkers[id].currentWorkload
|
|
2828
|
+
});
|
|
2333
2829
|
return {
|
|
2334
2830
|
completedTasks: [taskResult],
|
|
2335
2831
|
messages: [message],
|
|
@@ -2337,13 +2833,22 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2337
2833
|
};
|
|
2338
2834
|
} catch (error) {
|
|
2339
2835
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
2836
|
+
logger3.info("GraphInterrupt detected, re-throwing", { workerId: id });
|
|
2340
2837
|
throw error;
|
|
2341
2838
|
}
|
|
2342
|
-
|
|
2839
|
+
logger3.error("Worker node error", {
|
|
2840
|
+
workerId: id,
|
|
2841
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2842
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
2843
|
+
});
|
|
2343
2844
|
const currentAssignment = state.activeAssignments.find(
|
|
2344
2845
|
(assignment) => assignment.workerId === id
|
|
2345
2846
|
);
|
|
2346
2847
|
if (currentAssignment) {
|
|
2848
|
+
logger3.warn("Creating error result for assignment", {
|
|
2849
|
+
workerId: id,
|
|
2850
|
+
assignmentId: currentAssignment.id
|
|
2851
|
+
});
|
|
2347
2852
|
const errorResult = {
|
|
2348
2853
|
assignmentId: currentAssignment.id,
|
|
2349
2854
|
workerId: id,
|
|
@@ -2358,6 +2863,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2358
2863
|
status: "routing"
|
|
2359
2864
|
};
|
|
2360
2865
|
}
|
|
2866
|
+
logger3.error("No assignment found for error handling", { workerId: id });
|
|
2361
2867
|
return {
|
|
2362
2868
|
status: "failed",
|
|
2363
2869
|
error: error instanceof Error ? error.message : `Unknown error in worker ${id}`
|
|
@@ -2374,29 +2880,44 @@ function createAggregatorNode(config = {}) {
|
|
|
2374
2880
|
} = config;
|
|
2375
2881
|
return async (state) => {
|
|
2376
2882
|
try {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2883
|
+
logger3.info("Aggregator node executing", {
|
|
2884
|
+
completedTasks: state.completedTasks.length,
|
|
2885
|
+
successfulTasks: state.completedTasks.filter((t) => t.success).length,
|
|
2886
|
+
failedTasks: state.completedTasks.filter((t) => !t.success).length
|
|
2887
|
+
});
|
|
2888
|
+
logger3.debug("Combining results from workers");
|
|
2380
2889
|
if (aggregateFn) {
|
|
2890
|
+
logger3.debug("Using custom aggregation function");
|
|
2381
2891
|
const response2 = await aggregateFn(state);
|
|
2892
|
+
logger3.info("Custom aggregation complete", {
|
|
2893
|
+
responseLength: response2.length
|
|
2894
|
+
});
|
|
2382
2895
|
return {
|
|
2383
2896
|
response: response2,
|
|
2384
2897
|
status: "completed"
|
|
2385
2898
|
};
|
|
2386
2899
|
}
|
|
2387
2900
|
if (state.completedTasks.length === 0) {
|
|
2901
|
+
logger3.warn("No completed tasks to aggregate");
|
|
2388
2902
|
return {
|
|
2389
2903
|
response: "No tasks were completed.",
|
|
2390
2904
|
status: "completed"
|
|
2391
2905
|
};
|
|
2392
2906
|
}
|
|
2393
2907
|
if (!model) {
|
|
2908
|
+
logger3.debug("No model provided, concatenating results");
|
|
2394
2909
|
const combinedResults = state.completedTasks.filter((task) => task.success).map((task) => task.result).join("\n\n");
|
|
2910
|
+
logger3.info("Simple concatenation complete", {
|
|
2911
|
+
resultLength: combinedResults.length
|
|
2912
|
+
});
|
|
2395
2913
|
return {
|
|
2396
2914
|
response: combinedResults || "No successful results to aggregate.",
|
|
2397
2915
|
status: "completed"
|
|
2398
2916
|
};
|
|
2399
2917
|
}
|
|
2918
|
+
logger3.debug("Using LLM for intelligent aggregation", {
|
|
2919
|
+
taskCount: state.completedTasks.length
|
|
2920
|
+
});
|
|
2400
2921
|
const taskResults = state.completedTasks.map((task, idx) => {
|
|
2401
2922
|
const status = task.success ? "\u2713" : "\u2717";
|
|
2402
2923
|
const result = task.success ? task.result : `Error: ${task.error}`;
|
|
@@ -2413,17 +2934,24 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2413
2934
|
new SystemMessage5(systemPrompt),
|
|
2414
2935
|
new HumanMessage5(userPrompt)
|
|
2415
2936
|
];
|
|
2937
|
+
logger3.debug("Invoking aggregation LLM");
|
|
2416
2938
|
const response = await model.invoke(messages);
|
|
2417
2939
|
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2940
|
+
logger3.info("Aggregation complete", {
|
|
2941
|
+
responseLength: aggregatedResponse.length,
|
|
2942
|
+
responsePreview: aggregatedResponse.substring(0, 100)
|
|
2943
|
+
});
|
|
2944
|
+
logger3.debug("Aggregation complete");
|
|
2421
2945
|
return {
|
|
2422
2946
|
response: aggregatedResponse,
|
|
2423
2947
|
status: "completed"
|
|
2424
2948
|
};
|
|
2425
2949
|
} catch (error) {
|
|
2426
|
-
|
|
2950
|
+
logger3.error("Aggregator node error", {
|
|
2951
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2952
|
+
stack: error instanceof Error ? error.stack : void 0,
|
|
2953
|
+
completedTasks: state.completedTasks.length
|
|
2954
|
+
});
|
|
2427
2955
|
return {
|
|
2428
2956
|
status: "failed",
|
|
2429
2957
|
error: error instanceof Error ? error.message : "Unknown error in aggregator"
|
|
@@ -2434,7 +2962,9 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2434
2962
|
|
|
2435
2963
|
// src/multi-agent/agent.ts
|
|
2436
2964
|
import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
|
|
2437
|
-
import { toLangChainTools as toLangChainTools3 } from "@agentforge/core";
|
|
2965
|
+
import { toLangChainTools as toLangChainTools3, createLogger as createLogger5, LogLevel as LogLevel4 } from "@agentforge/core";
|
|
2966
|
+
var logLevel4 = process.env.LOG_LEVEL?.toLowerCase() || LogLevel4.INFO;
|
|
2967
|
+
var logger4 = createLogger5("multi-agent:system", { level: logLevel4 });
|
|
2438
2968
|
function createMultiAgentSystem(config) {
|
|
2439
2969
|
const {
|
|
2440
2970
|
supervisor,
|
|
@@ -2476,25 +3006,49 @@ function createMultiAgentSystem(config) {
|
|
|
2476
3006
|
});
|
|
2477
3007
|
workflow.addNode("aggregator", aggregatorNode);
|
|
2478
3008
|
const supervisorRouter = (state) => {
|
|
3009
|
+
logger4.debug("Supervisor router executing", {
|
|
3010
|
+
status: state.status,
|
|
3011
|
+
currentAgent: state.currentAgent,
|
|
3012
|
+
iteration: state.iteration
|
|
3013
|
+
});
|
|
2479
3014
|
if (state.status === "completed" || state.status === "failed") {
|
|
3015
|
+
logger4.info("Supervisor router: ending workflow", { status: state.status });
|
|
2480
3016
|
return END4;
|
|
2481
3017
|
}
|
|
2482
3018
|
if (state.status === "aggregating") {
|
|
3019
|
+
logger4.info("Supervisor router: routing to aggregator");
|
|
2483
3020
|
return "aggregator";
|
|
2484
3021
|
}
|
|
2485
3022
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
2486
3023
|
if (state.currentAgent.includes(",")) {
|
|
2487
3024
|
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
3025
|
+
logger4.info("Supervisor router: parallel routing", {
|
|
3026
|
+
agents,
|
|
3027
|
+
count: agents.length
|
|
3028
|
+
});
|
|
2488
3029
|
return agents;
|
|
2489
3030
|
}
|
|
3031
|
+
logger4.info("Supervisor router: single agent routing", {
|
|
3032
|
+
targetAgent: state.currentAgent
|
|
3033
|
+
});
|
|
2490
3034
|
return state.currentAgent;
|
|
2491
3035
|
}
|
|
3036
|
+
logger4.debug("Supervisor router: staying at supervisor");
|
|
2492
3037
|
return "supervisor";
|
|
2493
3038
|
};
|
|
2494
3039
|
const workerRouter = (state) => {
|
|
3040
|
+
logger4.debug("Worker router executing", {
|
|
3041
|
+
iteration: state.iteration,
|
|
3042
|
+
completedTasks: state.completedTasks.length
|
|
3043
|
+
});
|
|
3044
|
+
logger4.debug("Worker router: returning to supervisor");
|
|
2495
3045
|
return "supervisor";
|
|
2496
3046
|
};
|
|
2497
3047
|
const aggregatorRouter = (state) => {
|
|
3048
|
+
logger4.info("Aggregator router: ending workflow", {
|
|
3049
|
+
completedTasks: state.completedTasks.length,
|
|
3050
|
+
status: state.status
|
|
3051
|
+
});
|
|
2498
3052
|
return END4;
|
|
2499
3053
|
};
|
|
2500
3054
|
workflow.setEntryPoint("supervisor");
|
|
@@ -2687,11 +3241,14 @@ export {
|
|
|
2687
3241
|
ToolCallSchema,
|
|
2688
3242
|
ToolResultSchema,
|
|
2689
3243
|
WorkerCapabilitiesSchema,
|
|
3244
|
+
buildDeduplicationMetrics,
|
|
3245
|
+
calculateDeduplicationSavings,
|
|
2690
3246
|
createAggregatorNode,
|
|
2691
3247
|
createExecutorNode,
|
|
2692
3248
|
createFinisherNode,
|
|
2693
3249
|
createGeneratorNode,
|
|
2694
3250
|
createMultiAgentSystem,
|
|
3251
|
+
createPatternLogger,
|
|
2695
3252
|
createPlanExecuteAgent,
|
|
2696
3253
|
createPlannerNode,
|
|
2697
3254
|
createReActAgent,
|
|
@@ -2703,6 +3260,7 @@ export {
|
|
|
2703
3260
|
createReviserNode,
|
|
2704
3261
|
createSupervisorNode,
|
|
2705
3262
|
createWorkerNode,
|
|
3263
|
+
generateToolCallCacheKey,
|
|
2706
3264
|
getRoutingStrategy,
|
|
2707
3265
|
llmBasedRouting,
|
|
2708
3266
|
loadBalancedRouting,
|