@agentforge/patterns 0.6.2 → 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 +783 -166
- package/dist/index.d.cts +168 -26
- package/dist/index.d.ts +168 -26
- package/dist/index.js +769 -156
- 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"
|
|
@@ -1543,26 +1816,35 @@ var RoutingStrategySchema = z7.enum([
|
|
|
1543
1816
|
]);
|
|
1544
1817
|
var RoutingDecisionSchema = z7.object({
|
|
1545
1818
|
/**
|
|
1546
|
-
* Target agent to route to
|
|
1819
|
+
* Target agent to route to (single agent routing)
|
|
1820
|
+
* @deprecated Use targetAgents for parallel routing support
|
|
1821
|
+
*/
|
|
1822
|
+
targetAgent: z7.string().nullable().default(null).describe("Agent to route the task to (single routing)"),
|
|
1823
|
+
/**
|
|
1824
|
+
* Target agents to route to (parallel routing)
|
|
1825
|
+
* When multiple agents are specified, they execute in parallel
|
|
1547
1826
|
*/
|
|
1548
|
-
|
|
1827
|
+
targetAgents: z7.array(z7.string()).nullable().default(null).describe("Agents to route the task to (parallel routing)"),
|
|
1549
1828
|
/**
|
|
1550
1829
|
* Reasoning for the routing decision
|
|
1551
1830
|
*/
|
|
1552
|
-
reasoning: z7.string().
|
|
1831
|
+
reasoning: z7.string().default("").describe("Explanation for routing decision"),
|
|
1553
1832
|
/**
|
|
1554
1833
|
* Confidence in the routing decision (0-1)
|
|
1555
1834
|
*/
|
|
1556
|
-
confidence: z7.number().min(0).max(1).
|
|
1835
|
+
confidence: z7.number().min(0).max(1).default(0.8).describe("Confidence score"),
|
|
1557
1836
|
/**
|
|
1558
1837
|
* Strategy used for routing
|
|
1559
1838
|
*/
|
|
1560
|
-
strategy: RoutingStrategySchema.describe("Strategy used for this decision"),
|
|
1839
|
+
strategy: RoutingStrategySchema.default("llm-based").describe("Strategy used for this decision"),
|
|
1561
1840
|
/**
|
|
1562
1841
|
* Timestamp of the routing decision
|
|
1563
1842
|
*/
|
|
1564
|
-
timestamp: z7.number().
|
|
1565
|
-
})
|
|
1843
|
+
timestamp: z7.number().default(() => Date.now()).describe("Timestamp of the decision")
|
|
1844
|
+
}).refine(
|
|
1845
|
+
(data) => data.targetAgent || data.targetAgents && data.targetAgents.length > 0,
|
|
1846
|
+
{ message: "Either targetAgent or targetAgents must be provided" }
|
|
1847
|
+
);
|
|
1566
1848
|
var WorkerCapabilitiesSchema = z7.object({
|
|
1567
1849
|
/**
|
|
1568
1850
|
* Skills/capabilities the agent has
|
|
@@ -1798,11 +2080,22 @@ var MultiAgentState = createStateAnnotation4(MultiAgentStateConfig);
|
|
|
1798
2080
|
|
|
1799
2081
|
// src/multi-agent/routing.ts
|
|
1800
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 });
|
|
1801
2086
|
async function executeTools(toolCalls, tools) {
|
|
1802
2087
|
const results = [];
|
|
2088
|
+
logger.debug("Executing tools", {
|
|
2089
|
+
toolCallCount: toolCalls.length,
|
|
2090
|
+
toolNames: toolCalls.map((tc) => tc.name)
|
|
2091
|
+
});
|
|
1803
2092
|
for (const toolCall of toolCalls) {
|
|
1804
2093
|
const tool = tools.find((t) => t.metadata.name === toolCall.name);
|
|
1805
2094
|
if (!tool) {
|
|
2095
|
+
logger.warn("Tool not found", {
|
|
2096
|
+
toolName: toolCall.name,
|
|
2097
|
+
availableTools: tools.map((t) => t.metadata.name)
|
|
2098
|
+
});
|
|
1806
2099
|
results.push(new ToolMessage2({
|
|
1807
2100
|
content: `Error: Tool '${toolCall.name}' not found`,
|
|
1808
2101
|
tool_call_id: toolCall.id
|
|
@@ -1810,19 +2103,41 @@ async function executeTools(toolCalls, tools) {
|
|
|
1810
2103
|
continue;
|
|
1811
2104
|
}
|
|
1812
2105
|
try {
|
|
2106
|
+
logger.debug("Executing tool", {
|
|
2107
|
+
toolName: toolCall.name,
|
|
2108
|
+
args: toolCall.args
|
|
2109
|
+
});
|
|
1813
2110
|
const result = await tool.execute(toolCall.args);
|
|
1814
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
|
+
});
|
|
1815
2116
|
results.push(new ToolMessage2({
|
|
1816
2117
|
content,
|
|
1817
2118
|
tool_call_id: toolCall.id
|
|
1818
2119
|
}));
|
|
1819
2120
|
} catch (error) {
|
|
2121
|
+
logger.error("Tool execution failed", {
|
|
2122
|
+
toolName: toolCall.name,
|
|
2123
|
+
error: error.message
|
|
2124
|
+
});
|
|
1820
2125
|
results.push(new ToolMessage2({
|
|
1821
2126
|
content: `Error executing tool: ${error.message}`,
|
|
1822
2127
|
tool_call_id: toolCall.id
|
|
1823
2128
|
}));
|
|
1824
2129
|
}
|
|
1825
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
|
+
});
|
|
1826
2141
|
return results;
|
|
1827
2142
|
}
|
|
1828
2143
|
var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible for routing tasks to specialized worker agents.
|
|
@@ -1830,19 +2145,40 @@ var DEFAULT_SUPERVISOR_SYSTEM_PROMPT = `You are a supervisor agent responsible f
|
|
|
1830
2145
|
Your job is to:
|
|
1831
2146
|
1. Analyze the current task and context
|
|
1832
2147
|
2. Review available worker capabilities
|
|
1833
|
-
3. Select the most appropriate worker for the task
|
|
2148
|
+
3. Select the most appropriate worker(s) for the task
|
|
1834
2149
|
4. Provide clear reasoning for your decision
|
|
1835
2150
|
|
|
1836
|
-
|
|
2151
|
+
**IMPORTANT: You can route to MULTIPLE workers for parallel execution when:**
|
|
2152
|
+
- The task requires information from multiple domains (e.g., code + documentation)
|
|
2153
|
+
- Multiple workers have complementary expertise
|
|
2154
|
+
- Parallel execution would provide a more comprehensive answer
|
|
2155
|
+
|
|
2156
|
+
**Response Format:**
|
|
2157
|
+
|
|
2158
|
+
For SINGLE worker routing:
|
|
1837
2159
|
{
|
|
1838
2160
|
"targetAgent": "worker_id",
|
|
1839
2161
|
"reasoning": "explanation of why this worker is best suited",
|
|
1840
2162
|
"confidence": 0.0-1.0,
|
|
1841
2163
|
"strategy": "llm-based"
|
|
1842
|
-
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
For PARALLEL multi-worker routing:
|
|
2167
|
+
{
|
|
2168
|
+
"targetAgents": ["worker_id_1", "worker_id_2", ...],
|
|
2169
|
+
"reasoning": "explanation of why these workers should work in parallel",
|
|
2170
|
+
"confidence": 0.0-1.0,
|
|
2171
|
+
"strategy": "llm-based"
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
Choose parallel routing when the task benefits from multiple perspectives or data sources.`;
|
|
1843
2175
|
var llmBasedRouting = {
|
|
1844
2176
|
name: "llm-based",
|
|
1845
2177
|
async route(state, config) {
|
|
2178
|
+
logger.info("Starting LLM-based routing", {
|
|
2179
|
+
iteration: state.iteration,
|
|
2180
|
+
availableWorkers: Object.keys(state.workers).length
|
|
2181
|
+
});
|
|
1846
2182
|
if (!config.model) {
|
|
1847
2183
|
throw new Error("LLM-based routing requires a model to be configured");
|
|
1848
2184
|
}
|
|
@@ -1855,17 +2191,34 @@ var llmBasedRouting = {
|
|
|
1855
2191
|
const available = caps.available ? "available" : "busy";
|
|
1856
2192
|
return `- ${id}: Skills: [${skills}], Tools: [${tools2}], Status: ${available}, Workload: ${caps.currentWorkload}`;
|
|
1857
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
|
+
});
|
|
1858
2202
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
1859
2203
|
const taskContext = lastMessage?.content || state.input;
|
|
2204
|
+
logger.debug("Task context", {
|
|
2205
|
+
taskLength: taskContext.length,
|
|
2206
|
+
taskPreview: taskContext.substring(0, 100)
|
|
2207
|
+
});
|
|
1860
2208
|
const userPrompt = `Current task: ${taskContext}
|
|
1861
2209
|
|
|
1862
2210
|
Available workers:
|
|
1863
2211
|
${workerInfo}
|
|
1864
2212
|
|
|
1865
|
-
Select the best worker for this task and explain your reasoning.`;
|
|
2213
|
+
Select the best worker(s) for this task and explain your reasoning.`;
|
|
1866
2214
|
const conversationHistory = [];
|
|
1867
2215
|
let attempt = 0;
|
|
1868
2216
|
while (attempt < maxRetries) {
|
|
2217
|
+
logger.debug("LLM routing attempt", {
|
|
2218
|
+
attempt: attempt + 1,
|
|
2219
|
+
maxRetries,
|
|
2220
|
+
conversationHistoryLength: conversationHistory.length
|
|
2221
|
+
});
|
|
1869
2222
|
const messages = [
|
|
1870
2223
|
new SystemMessage4(systemPrompt),
|
|
1871
2224
|
new HumanMessage4(userPrompt),
|
|
@@ -1873,6 +2226,10 @@ Select the best worker for this task and explain your reasoning.`;
|
|
|
1873
2226
|
];
|
|
1874
2227
|
const response = await config.model.invoke(messages);
|
|
1875
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
|
+
});
|
|
1876
2233
|
if (tools.length === 0) {
|
|
1877
2234
|
throw new Error("LLM requested tool calls but no tools are configured");
|
|
1878
2235
|
}
|
|
@@ -1882,36 +2239,90 @@ Select the best worker for this task and explain your reasoning.`;
|
|
|
1882
2239
|
...toolResults
|
|
1883
2240
|
);
|
|
1884
2241
|
attempt++;
|
|
2242
|
+
logger.debug("Retrying routing with tool results", { attempt });
|
|
1885
2243
|
continue;
|
|
1886
2244
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2245
|
+
logger.debug("Parsing routing decision from LLM response");
|
|
2246
|
+
let decision;
|
|
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
|
+
});
|
|
2252
|
+
decision = response;
|
|
2253
|
+
} else if (response.content) {
|
|
2254
|
+
if (typeof response.content === "string") {
|
|
2255
|
+
try {
|
|
2256
|
+
decision = JSON.parse(response.content);
|
|
2257
|
+
logger.debug("Parsed JSON from string response");
|
|
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
|
+
});
|
|
2263
|
+
throw new Error(`Failed to parse routing decision from LLM. Expected JSON but got: ${response.content}`);
|
|
2264
|
+
}
|
|
2265
|
+
} else if (typeof response.content === "object") {
|
|
2266
|
+
logger.debug("Response content is already an object");
|
|
2267
|
+
decision = response.content;
|
|
2268
|
+
} else {
|
|
2269
|
+
logger.error("Unexpected response content type", {
|
|
2270
|
+
type: typeof response.content
|
|
2271
|
+
});
|
|
2272
|
+
throw new Error(`Unexpected response content type: ${typeof response.content}`);
|
|
2273
|
+
}
|
|
2274
|
+
} else {
|
|
2275
|
+
logger.error("Unexpected response format", {
|
|
2276
|
+
response: JSON.stringify(response)
|
|
2277
|
+
});
|
|
2278
|
+
throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
|
|
1899
2279
|
}
|
|
2280
|
+
const result = {
|
|
2281
|
+
targetAgent: decision.targetAgent,
|
|
2282
|
+
targetAgents: decision.targetAgents,
|
|
2283
|
+
reasoning: decision.reasoning,
|
|
2284
|
+
confidence: decision.confidence,
|
|
2285
|
+
strategy: "llm-based",
|
|
2286
|
+
timestamp: Date.now()
|
|
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
|
+
});
|
|
2295
|
+
return result;
|
|
1900
2296
|
}
|
|
2297
|
+
logger.error("Max tool retries exceeded", { maxRetries });
|
|
1901
2298
|
throw new Error(`Max tool retries (${maxRetries}) exceeded without routing decision`);
|
|
1902
2299
|
}
|
|
1903
2300
|
};
|
|
1904
2301
|
var roundRobinRouting = {
|
|
1905
2302
|
name: "round-robin",
|
|
1906
2303
|
async route(state, config) {
|
|
2304
|
+
logger.info("Starting round-robin routing", {
|
|
2305
|
+
iteration: state.iteration
|
|
2306
|
+
});
|
|
1907
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
|
+
});
|
|
1908
2312
|
if (availableWorkers.length === 0) {
|
|
2313
|
+
logger.error("No available workers for round-robin routing");
|
|
1909
2314
|
throw new Error("No available workers for round-robin routing");
|
|
1910
2315
|
}
|
|
1911
2316
|
const lastRoutingIndex = state.routingHistory.length % availableWorkers.length;
|
|
1912
2317
|
const targetAgent = availableWorkers[lastRoutingIndex];
|
|
2318
|
+
logger.info("Round-robin routing decision", {
|
|
2319
|
+
targetAgent,
|
|
2320
|
+
index: lastRoutingIndex + 1,
|
|
2321
|
+
totalWorkers: availableWorkers.length
|
|
2322
|
+
});
|
|
1913
2323
|
return {
|
|
1914
2324
|
targetAgent,
|
|
2325
|
+
targetAgents: null,
|
|
1915
2326
|
reasoning: `Round-robin selection: worker ${lastRoutingIndex + 1} of ${availableWorkers.length}`,
|
|
1916
2327
|
confidence: 1,
|
|
1917
2328
|
strategy: "round-robin",
|
|
@@ -1922,8 +2333,15 @@ var roundRobinRouting = {
|
|
|
1922
2333
|
var skillBasedRouting = {
|
|
1923
2334
|
name: "skill-based",
|
|
1924
2335
|
async route(state, config) {
|
|
2336
|
+
logger.info("Starting skill-based routing", {
|
|
2337
|
+
iteration: state.iteration
|
|
2338
|
+
});
|
|
1925
2339
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
1926
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
|
+
});
|
|
1927
2345
|
const workerScores = Object.entries(state.workers).filter(([_, caps]) => caps.available).map(([id, caps]) => {
|
|
1928
2346
|
const skillMatches = caps.skills.filter(
|
|
1929
2347
|
(skill) => taskContent.includes(skill.toLowerCase())
|
|
@@ -1934,13 +2352,23 @@ var skillBasedRouting = {
|
|
|
1934
2352
|
const score = skillMatches * 2 + toolMatches;
|
|
1935
2353
|
return { id, score, skills: caps.skills, tools: caps.tools };
|
|
1936
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
|
+
});
|
|
1937
2358
|
if (workerScores.length === 0) {
|
|
2359
|
+
logger.warn("No skill matches found, using fallback");
|
|
1938
2360
|
const firstAvailable = Object.entries(state.workers).find(([_, caps]) => caps.available);
|
|
1939
2361
|
if (!firstAvailable) {
|
|
2362
|
+
logger.error("No available workers for skill-based routing");
|
|
1940
2363
|
throw new Error("No available workers for skill-based routing");
|
|
1941
2364
|
}
|
|
2365
|
+
logger.info("Skill-based routing fallback decision", {
|
|
2366
|
+
targetAgent: firstAvailable[0],
|
|
2367
|
+
confidence: 0.5
|
|
2368
|
+
});
|
|
1942
2369
|
return {
|
|
1943
2370
|
targetAgent: firstAvailable[0],
|
|
2371
|
+
targetAgents: null,
|
|
1944
2372
|
reasoning: "No skill matches found, using first available worker",
|
|
1945
2373
|
confidence: 0.5,
|
|
1946
2374
|
strategy: "skill-based",
|
|
@@ -1949,8 +2377,15 @@ var skillBasedRouting = {
|
|
|
1949
2377
|
}
|
|
1950
2378
|
const best = workerScores[0];
|
|
1951
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
|
+
});
|
|
1952
2386
|
return {
|
|
1953
2387
|
targetAgent: best.id,
|
|
2388
|
+
targetAgents: null,
|
|
1954
2389
|
reasoning: `Best skill match with score ${best.score} (skills: ${best.skills.join(", ")})`,
|
|
1955
2390
|
confidence,
|
|
1956
2391
|
strategy: "skill-based",
|
|
@@ -1961,15 +2396,29 @@ var skillBasedRouting = {
|
|
|
1961
2396
|
var loadBalancedRouting = {
|
|
1962
2397
|
name: "load-balanced",
|
|
1963
2398
|
async route(state, config) {
|
|
2399
|
+
logger.info("Starting load-balanced routing", {
|
|
2400
|
+
iteration: state.iteration
|
|
2401
|
+
});
|
|
1964
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
|
+
});
|
|
1965
2406
|
if (availableWorkers.length === 0) {
|
|
2407
|
+
logger.error("No available workers for load-balanced routing");
|
|
1966
2408
|
throw new Error("No available workers for load-balanced routing");
|
|
1967
2409
|
}
|
|
1968
2410
|
const targetWorker = availableWorkers[0];
|
|
1969
2411
|
const avgWorkload = availableWorkers.reduce((sum, w) => sum + w.workload, 0) / availableWorkers.length;
|
|
1970
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
|
+
});
|
|
1971
2419
|
return {
|
|
1972
2420
|
targetAgent: targetWorker.id,
|
|
2421
|
+
targetAgents: null,
|
|
1973
2422
|
reasoning: `Lowest workload: ${targetWorker.workload} tasks (avg: ${avgWorkload.toFixed(1)})`,
|
|
1974
2423
|
confidence,
|
|
1975
2424
|
strategy: "load-balanced",
|
|
@@ -1980,13 +2429,24 @@ var loadBalancedRouting = {
|
|
|
1980
2429
|
var ruleBasedRouting = {
|
|
1981
2430
|
name: "rule-based",
|
|
1982
2431
|
async route(state, config) {
|
|
2432
|
+
logger.info("Starting rule-based routing", {
|
|
2433
|
+
iteration: state.iteration
|
|
2434
|
+
});
|
|
1983
2435
|
if (!config.routingFn) {
|
|
2436
|
+
logger.error("Rule-based routing requires a custom routing function");
|
|
1984
2437
|
throw new Error("Rule-based routing requires a custom routing function");
|
|
1985
2438
|
}
|
|
1986
|
-
|
|
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;
|
|
1987
2446
|
}
|
|
1988
2447
|
};
|
|
1989
2448
|
function getRoutingStrategy(name) {
|
|
2449
|
+
logger.debug("Getting routing strategy", { name });
|
|
1990
2450
|
switch (name) {
|
|
1991
2451
|
case "llm-based":
|
|
1992
2452
|
return llmBasedRouting;
|
|
@@ -1999,14 +2459,15 @@ function getRoutingStrategy(name) {
|
|
|
1999
2459
|
case "rule-based":
|
|
2000
2460
|
return ruleBasedRouting;
|
|
2001
2461
|
default:
|
|
2462
|
+
logger.error("Unknown routing strategy", { name });
|
|
2002
2463
|
throw new Error(`Unknown routing strategy: ${name}`);
|
|
2003
2464
|
}
|
|
2004
2465
|
}
|
|
2005
2466
|
|
|
2006
2467
|
// src/multi-agent/utils.ts
|
|
2007
|
-
import { createLogger, LogLevel } from "@agentforge/core";
|
|
2008
|
-
var
|
|
2009
|
-
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 });
|
|
2010
2471
|
function isReActAgent(obj) {
|
|
2011
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
|
|
2012
2473
|
(obj.constructor?.name === "CompiledGraph" || obj.constructor?.name === "CompiledStateGraph");
|
|
@@ -2014,9 +2475,9 @@ function isReActAgent(obj) {
|
|
|
2014
2475
|
function wrapReActAgent(workerId, agent, verbose = false) {
|
|
2015
2476
|
return async (state, config) => {
|
|
2016
2477
|
try {
|
|
2017
|
-
|
|
2478
|
+
logger2.debug("Wrapping ReAct agent execution", { workerId });
|
|
2018
2479
|
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2019
|
-
|
|
2480
|
+
logger2.debug("Extracted task", {
|
|
2020
2481
|
workerId,
|
|
2021
2482
|
taskPreview: task.substring(0, 100) + (task.length > 100 ? "..." : "")
|
|
2022
2483
|
});
|
|
@@ -2024,11 +2485,8 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2024
2485
|
(assignment) => assignment.workerId === workerId && !state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2025
2486
|
);
|
|
2026
2487
|
if (!currentAssignment) {
|
|
2027
|
-
|
|
2028
|
-
return {
|
|
2029
|
-
currentAgent: "supervisor",
|
|
2030
|
-
status: "routing"
|
|
2031
|
-
};
|
|
2488
|
+
logger2.debug("No active assignment found", { workerId });
|
|
2489
|
+
return {};
|
|
2032
2490
|
}
|
|
2033
2491
|
const result = await agent.invoke(
|
|
2034
2492
|
{
|
|
@@ -2038,14 +2496,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2038
2496
|
// Pass through the config for checkpointing and interrupt support
|
|
2039
2497
|
);
|
|
2040
2498
|
const response = result.messages?.[result.messages.length - 1]?.content || "No response";
|
|
2041
|
-
|
|
2499
|
+
logger2.debug("Received response from ReAct agent", {
|
|
2042
2500
|
workerId,
|
|
2043
2501
|
responsePreview: response.substring(0, 100) + (response.length > 100 ? "..." : "")
|
|
2044
2502
|
});
|
|
2045
2503
|
const toolsUsed = result.actions?.map((action) => action.name).filter(Boolean) || [];
|
|
2046
2504
|
const uniqueTools = [...new Set(toolsUsed)];
|
|
2047
2505
|
if (uniqueTools.length > 0) {
|
|
2048
|
-
|
|
2506
|
+
logger2.debug("Tools used by ReAct agent", { workerId, tools: uniqueTools });
|
|
2049
2507
|
}
|
|
2050
2508
|
const taskResult = {
|
|
2051
2509
|
assignmentId: currentAssignment.id,
|
|
@@ -2060,16 +2518,14 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2060
2518
|
}
|
|
2061
2519
|
};
|
|
2062
2520
|
return {
|
|
2063
|
-
completedTasks: [taskResult]
|
|
2064
|
-
currentAgent: "supervisor",
|
|
2065
|
-
status: "routing"
|
|
2521
|
+
completedTasks: [taskResult]
|
|
2066
2522
|
};
|
|
2067
2523
|
} catch (error) {
|
|
2068
2524
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
2069
|
-
|
|
2525
|
+
logger2.debug("GraphInterrupt detected - re-throwing", { workerId });
|
|
2070
2526
|
throw error;
|
|
2071
2527
|
}
|
|
2072
|
-
|
|
2528
|
+
logger2.error("Error in ReAct agent execution", {
|
|
2073
2529
|
workerId,
|
|
2074
2530
|
error: error instanceof Error ? error.message : String(error),
|
|
2075
2531
|
stack: error instanceof Error ? error.stack : void 0
|
|
@@ -2102,7 +2558,9 @@ function wrapReActAgent(workerId, agent, verbose = false) {
|
|
|
2102
2558
|
|
|
2103
2559
|
// src/multi-agent/nodes.ts
|
|
2104
2560
|
import { HumanMessage as HumanMessage5, SystemMessage as SystemMessage5 } from "@langchain/core/messages";
|
|
2105
|
-
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 });
|
|
2106
2564
|
var DEFAULT_AGGREGATOR_SYSTEM_PROMPT = `You are an aggregator agent responsible for combining results from multiple worker agents.
|
|
2107
2565
|
|
|
2108
2566
|
Your job is to:
|
|
@@ -2120,46 +2578,97 @@ function createSupervisorNode(config) {
|
|
|
2120
2578
|
} = config;
|
|
2121
2579
|
return async (state) => {
|
|
2122
2580
|
try {
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
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}`);
|
|
2126
2588
|
if (state.iteration >= maxIterations) {
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2589
|
+
logger3.warn("Max iterations reached", {
|
|
2590
|
+
iteration: state.iteration,
|
|
2591
|
+
maxIterations
|
|
2592
|
+
});
|
|
2593
|
+
logger3.debug("Max iterations reached, moving to aggregation");
|
|
2130
2594
|
return {
|
|
2131
2595
|
status: "aggregating",
|
|
2132
2596
|
currentAgent: "aggregator"
|
|
2133
2597
|
};
|
|
2134
2598
|
}
|
|
2135
2599
|
const allCompleted = state.activeAssignments.every(
|
|
2136
|
-
(
|
|
2600
|
+
(assignment) => state.completedTasks.some((task2) => task2.assignmentId === assignment.id)
|
|
2137
2601
|
);
|
|
2602
|
+
logger3.debug("Checking task completion", {
|
|
2603
|
+
activeAssignments: state.activeAssignments.length,
|
|
2604
|
+
completedTasks: state.completedTasks.length,
|
|
2605
|
+
allCompleted
|
|
2606
|
+
});
|
|
2138
2607
|
if (allCompleted && state.activeAssignments.length > 0) {
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
}
|
|
2608
|
+
logger3.info("All tasks completed, moving to aggregation", {
|
|
2609
|
+
completedCount: state.completedTasks.length
|
|
2610
|
+
});
|
|
2611
|
+
logger3.debug("All tasks completed, moving to aggregation");
|
|
2142
2612
|
return {
|
|
2143
2613
|
status: "aggregating",
|
|
2144
2614
|
currentAgent: "aggregator"
|
|
2145
2615
|
};
|
|
2146
2616
|
}
|
|
2617
|
+
logger3.debug("Getting routing strategy", { strategy });
|
|
2147
2618
|
const routingImpl = getRoutingStrategy(strategy);
|
|
2148
2619
|
const decision = await routingImpl.route(state, config);
|
|
2149
|
-
|
|
2150
|
-
|
|
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
|
+
});
|
|
2629
|
+
if (targetAgents.length === 0) {
|
|
2630
|
+
logger3.error("No target agents specified in routing decision");
|
|
2631
|
+
throw new Error("Routing decision must specify at least one target agent");
|
|
2151
2632
|
}
|
|
2152
|
-
|
|
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}`);
|
|
2651
|
+
}
|
|
2652
|
+
const task = state.messages[state.messages.length - 1]?.content || state.input;
|
|
2653
|
+
const assignments = targetAgents.map((workerId) => ({
|
|
2153
2654
|
id: `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
2154
|
-
workerId
|
|
2155
|
-
task
|
|
2655
|
+
workerId,
|
|
2656
|
+
task,
|
|
2156
2657
|
priority: 5,
|
|
2157
2658
|
assignedAt: Date.now()
|
|
2158
|
-
};
|
|
2159
|
-
|
|
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
|
+
});
|
|
2668
|
+
const messages = assignments.map((assignment) => ({
|
|
2160
2669
|
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
2161
2670
|
from: "supervisor",
|
|
2162
|
-
to: [
|
|
2671
|
+
to: [assignment.workerId],
|
|
2163
2672
|
type: "task_assignment",
|
|
2164
2673
|
content: assignment.task,
|
|
2165
2674
|
timestamp: Date.now(),
|
|
@@ -2167,17 +2676,29 @@ function createSupervisorNode(config) {
|
|
|
2167
2676
|
assignmentId: assignment.id,
|
|
2168
2677
|
priority: assignment.priority
|
|
2169
2678
|
}
|
|
2170
|
-
};
|
|
2679
|
+
}));
|
|
2680
|
+
logger3.info("Supervisor routing complete", {
|
|
2681
|
+
currentAgent: targetAgents.join(","),
|
|
2682
|
+
status: "executing",
|
|
2683
|
+
assignmentCount: assignments.length,
|
|
2684
|
+
nextIteration: state.iteration + 1
|
|
2685
|
+
});
|
|
2171
2686
|
return {
|
|
2172
|
-
currentAgent:
|
|
2687
|
+
currentAgent: targetAgents.join(","),
|
|
2688
|
+
// Store all agents (for backward compat)
|
|
2173
2689
|
status: "executing",
|
|
2174
2690
|
routingHistory: [decision],
|
|
2175
|
-
activeAssignments:
|
|
2176
|
-
|
|
2691
|
+
activeAssignments: assignments,
|
|
2692
|
+
// Multiple assignments for parallel execution!
|
|
2693
|
+
messages,
|
|
2177
2694
|
iteration: state.iteration + 1
|
|
2178
2695
|
};
|
|
2179
2696
|
} catch (error) {
|
|
2180
|
-
|
|
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
|
+
});
|
|
2181
2702
|
return {
|
|
2182
2703
|
status: "failed",
|
|
2183
2704
|
error: error instanceof Error ? error.message : "Unknown error in supervisor"
|
|
@@ -2198,43 +2719,52 @@ function createWorkerNode(config) {
|
|
|
2198
2719
|
} = config;
|
|
2199
2720
|
return async (state, runConfig) => {
|
|
2200
2721
|
try {
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2722
|
+
logger3.info("Worker node executing", {
|
|
2723
|
+
workerId: id,
|
|
2724
|
+
iteration: state.iteration,
|
|
2725
|
+
activeAssignments: state.activeAssignments.length
|
|
2726
|
+
});
|
|
2204
2727
|
const currentAssignment = state.activeAssignments.find(
|
|
2205
2728
|
(assignment) => assignment.workerId === id && !state.completedTasks.some((task) => task.assignmentId === assignment.id)
|
|
2206
2729
|
);
|
|
2207
2730
|
if (!currentAssignment) {
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
};
|
|
2731
|
+
logger3.debug("No active assignment found for worker", {
|
|
2732
|
+
workerId: id,
|
|
2733
|
+
totalActiveAssignments: state.activeAssignments.length,
|
|
2734
|
+
completedTasks: state.completedTasks.length
|
|
2735
|
+
});
|
|
2736
|
+
return {};
|
|
2215
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
|
+
});
|
|
2216
2744
|
if (executeFn) {
|
|
2217
|
-
|
|
2218
|
-
console.log(`[Worker:${id}] Using custom executeFn`);
|
|
2219
|
-
}
|
|
2745
|
+
logger3.debug("Using custom execution function", { workerId: id });
|
|
2220
2746
|
return await executeFn(state, runConfig);
|
|
2221
2747
|
}
|
|
2222
2748
|
if (agent) {
|
|
2223
2749
|
if (isReActAgent(agent)) {
|
|
2224
|
-
|
|
2225
|
-
console.log(`[Worker:${id}] Using ReAct agent (auto-wrapped)`);
|
|
2226
|
-
}
|
|
2750
|
+
logger3.debug("Using ReAct agent", { workerId: id });
|
|
2227
2751
|
const wrappedFn = wrapReActAgent(id, agent, verbose);
|
|
2228
2752
|
return await wrappedFn(state, runConfig);
|
|
2229
2753
|
} else {
|
|
2230
|
-
|
|
2754
|
+
logger3.warn("Agent provided but not a ReAct agent, falling back", { workerId: id });
|
|
2231
2755
|
}
|
|
2232
2756
|
}
|
|
2233
2757
|
if (!model) {
|
|
2758
|
+
logger3.error("Worker missing required configuration", { workerId: id });
|
|
2234
2759
|
throw new Error(
|
|
2235
2760
|
`Worker ${id} requires either a model, an agent, or a custom execution function. Provide one of: config.model, config.agent, or config.executeFn`
|
|
2236
2761
|
);
|
|
2237
2762
|
}
|
|
2763
|
+
logger3.debug("Using default LLM execution", {
|
|
2764
|
+
workerId: id,
|
|
2765
|
+
hasTools: tools.length > 0,
|
|
2766
|
+
toolCount: tools.length
|
|
2767
|
+
});
|
|
2238
2768
|
const defaultSystemPrompt = `You are a specialized worker agent with the following capabilities:
|
|
2239
2769
|
Skills: ${capabilities.skills.join(", ")}
|
|
2240
2770
|
Tools: ${capabilities.tools.join(", ")}
|
|
@@ -2246,14 +2776,23 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2246
2776
|
];
|
|
2247
2777
|
let modelToUse = model;
|
|
2248
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
|
+
});
|
|
2249
2784
|
const langchainTools = toLangChainTools2(tools);
|
|
2250
2785
|
modelToUse = model.bindTools(langchainTools);
|
|
2251
2786
|
}
|
|
2787
|
+
logger3.debug("Invoking LLM", { workerId: id });
|
|
2252
2788
|
const response = await modelToUse.invoke(messages);
|
|
2253
2789
|
const result = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2790
|
+
logger3.info("Worker task completed", {
|
|
2791
|
+
workerId: id,
|
|
2792
|
+
assignmentId: currentAssignment.id,
|
|
2793
|
+
resultLength: result.length,
|
|
2794
|
+
resultPreview: result.substring(0, 100)
|
|
2795
|
+
});
|
|
2257
2796
|
const taskResult = {
|
|
2258
2797
|
assignmentId: currentAssignment.id,
|
|
2259
2798
|
workerId: id,
|
|
@@ -2283,22 +2822,33 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2283
2822
|
currentWorkload: Math.max(0, capabilities.currentWorkload - 1)
|
|
2284
2823
|
}
|
|
2285
2824
|
};
|
|
2825
|
+
logger3.debug("Worker state update", {
|
|
2826
|
+
workerId: id,
|
|
2827
|
+
newWorkload: updatedWorkers[id].currentWorkload
|
|
2828
|
+
});
|
|
2286
2829
|
return {
|
|
2287
2830
|
completedTasks: [taskResult],
|
|
2288
2831
|
messages: [message],
|
|
2289
|
-
workers: updatedWorkers
|
|
2290
|
-
currentAgent: "supervisor",
|
|
2291
|
-
status: "routing"
|
|
2832
|
+
workers: updatedWorkers
|
|
2292
2833
|
};
|
|
2293
2834
|
} catch (error) {
|
|
2294
2835
|
if (error && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt") {
|
|
2836
|
+
logger3.info("GraphInterrupt detected, re-throwing", { workerId: id });
|
|
2295
2837
|
throw error;
|
|
2296
2838
|
}
|
|
2297
|
-
|
|
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
|
+
});
|
|
2298
2844
|
const currentAssignment = state.activeAssignments.find(
|
|
2299
2845
|
(assignment) => assignment.workerId === id
|
|
2300
2846
|
);
|
|
2301
2847
|
if (currentAssignment) {
|
|
2848
|
+
logger3.warn("Creating error result for assignment", {
|
|
2849
|
+
workerId: id,
|
|
2850
|
+
assignmentId: currentAssignment.id
|
|
2851
|
+
});
|
|
2302
2852
|
const errorResult = {
|
|
2303
2853
|
assignmentId: currentAssignment.id,
|
|
2304
2854
|
workerId: id,
|
|
@@ -2313,6 +2863,7 @@ Execute the assigned task using your skills and tools. Provide a clear, actionab
|
|
|
2313
2863
|
status: "routing"
|
|
2314
2864
|
};
|
|
2315
2865
|
}
|
|
2866
|
+
logger3.error("No assignment found for error handling", { workerId: id });
|
|
2316
2867
|
return {
|
|
2317
2868
|
status: "failed",
|
|
2318
2869
|
error: error instanceof Error ? error.message : `Unknown error in worker ${id}`
|
|
@@ -2329,29 +2880,44 @@ function createAggregatorNode(config = {}) {
|
|
|
2329
2880
|
} = config;
|
|
2330
2881
|
return async (state) => {
|
|
2331
2882
|
try {
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
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");
|
|
2335
2889
|
if (aggregateFn) {
|
|
2890
|
+
logger3.debug("Using custom aggregation function");
|
|
2336
2891
|
const response2 = await aggregateFn(state);
|
|
2892
|
+
logger3.info("Custom aggregation complete", {
|
|
2893
|
+
responseLength: response2.length
|
|
2894
|
+
});
|
|
2337
2895
|
return {
|
|
2338
2896
|
response: response2,
|
|
2339
2897
|
status: "completed"
|
|
2340
2898
|
};
|
|
2341
2899
|
}
|
|
2342
2900
|
if (state.completedTasks.length === 0) {
|
|
2901
|
+
logger3.warn("No completed tasks to aggregate");
|
|
2343
2902
|
return {
|
|
2344
2903
|
response: "No tasks were completed.",
|
|
2345
2904
|
status: "completed"
|
|
2346
2905
|
};
|
|
2347
2906
|
}
|
|
2348
2907
|
if (!model) {
|
|
2908
|
+
logger3.debug("No model provided, concatenating results");
|
|
2349
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
|
+
});
|
|
2350
2913
|
return {
|
|
2351
2914
|
response: combinedResults || "No successful results to aggregate.",
|
|
2352
2915
|
status: "completed"
|
|
2353
2916
|
};
|
|
2354
2917
|
}
|
|
2918
|
+
logger3.debug("Using LLM for intelligent aggregation", {
|
|
2919
|
+
taskCount: state.completedTasks.length
|
|
2920
|
+
});
|
|
2355
2921
|
const taskResults = state.completedTasks.map((task, idx) => {
|
|
2356
2922
|
const status = task.success ? "\u2713" : "\u2717";
|
|
2357
2923
|
const result = task.success ? task.result : `Error: ${task.error}`;
|
|
@@ -2368,17 +2934,24 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2368
2934
|
new SystemMessage5(systemPrompt),
|
|
2369
2935
|
new HumanMessage5(userPrompt)
|
|
2370
2936
|
];
|
|
2937
|
+
logger3.debug("Invoking aggregation LLM");
|
|
2371
2938
|
const response = await model.invoke(messages);
|
|
2372
2939
|
const aggregatedResponse = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2940
|
+
logger3.info("Aggregation complete", {
|
|
2941
|
+
responseLength: aggregatedResponse.length,
|
|
2942
|
+
responsePreview: aggregatedResponse.substring(0, 100)
|
|
2943
|
+
});
|
|
2944
|
+
logger3.debug("Aggregation complete");
|
|
2376
2945
|
return {
|
|
2377
2946
|
response: aggregatedResponse,
|
|
2378
2947
|
status: "completed"
|
|
2379
2948
|
};
|
|
2380
2949
|
} catch (error) {
|
|
2381
|
-
|
|
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
|
+
});
|
|
2382
2955
|
return {
|
|
2383
2956
|
status: "failed",
|
|
2384
2957
|
error: error instanceof Error ? error.message : "Unknown error in aggregator"
|
|
@@ -2389,7 +2962,9 @@ Please synthesize these results into a comprehensive response that addresses the
|
|
|
2389
2962
|
|
|
2390
2963
|
// src/multi-agent/agent.ts
|
|
2391
2964
|
import { StateGraph as StateGraph4, END as END4 } from "@langchain/langgraph";
|
|
2392
|
-
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 });
|
|
2393
2968
|
function createMultiAgentSystem(config) {
|
|
2394
2969
|
const {
|
|
2395
2970
|
supervisor,
|
|
@@ -2401,10 +2976,16 @@ function createMultiAgentSystem(config) {
|
|
|
2401
2976
|
} = config;
|
|
2402
2977
|
const workflow = new StateGraph4(MultiAgentState);
|
|
2403
2978
|
let supervisorConfig = { ...supervisor, maxIterations, verbose };
|
|
2404
|
-
if (supervisor.model
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2979
|
+
if (supervisor.model) {
|
|
2980
|
+
let configuredModel = supervisor.model;
|
|
2981
|
+
if (supervisor.strategy === "llm-based") {
|
|
2982
|
+
configuredModel = configuredModel.withStructuredOutput(RoutingDecisionSchema);
|
|
2983
|
+
}
|
|
2984
|
+
if (supervisor.tools && supervisor.tools.length > 0) {
|
|
2985
|
+
const langchainTools = toLangChainTools3(supervisor.tools);
|
|
2986
|
+
configuredModel = configuredModel.bindTools(langchainTools);
|
|
2987
|
+
}
|
|
2988
|
+
supervisorConfig.model = configuredModel;
|
|
2408
2989
|
}
|
|
2409
2990
|
const supervisorNode = createSupervisorNode(supervisorConfig);
|
|
2410
2991
|
workflow.addNode("supervisor", supervisorNode);
|
|
@@ -2425,21 +3006,49 @@ function createMultiAgentSystem(config) {
|
|
|
2425
3006
|
});
|
|
2426
3007
|
workflow.addNode("aggregator", aggregatorNode);
|
|
2427
3008
|
const supervisorRouter = (state) => {
|
|
3009
|
+
logger4.debug("Supervisor router executing", {
|
|
3010
|
+
status: state.status,
|
|
3011
|
+
currentAgent: state.currentAgent,
|
|
3012
|
+
iteration: state.iteration
|
|
3013
|
+
});
|
|
2428
3014
|
if (state.status === "completed" || state.status === "failed") {
|
|
3015
|
+
logger4.info("Supervisor router: ending workflow", { status: state.status });
|
|
2429
3016
|
return END4;
|
|
2430
3017
|
}
|
|
2431
3018
|
if (state.status === "aggregating") {
|
|
3019
|
+
logger4.info("Supervisor router: routing to aggregator");
|
|
2432
3020
|
return "aggregator";
|
|
2433
3021
|
}
|
|
2434
3022
|
if (state.currentAgent && state.currentAgent !== "supervisor") {
|
|
3023
|
+
if (state.currentAgent.includes(",")) {
|
|
3024
|
+
const agents = state.currentAgent.split(",").map((a) => a.trim());
|
|
3025
|
+
logger4.info("Supervisor router: parallel routing", {
|
|
3026
|
+
agents,
|
|
3027
|
+
count: agents.length
|
|
3028
|
+
});
|
|
3029
|
+
return agents;
|
|
3030
|
+
}
|
|
3031
|
+
logger4.info("Supervisor router: single agent routing", {
|
|
3032
|
+
targetAgent: state.currentAgent
|
|
3033
|
+
});
|
|
2435
3034
|
return state.currentAgent;
|
|
2436
3035
|
}
|
|
3036
|
+
logger4.debug("Supervisor router: staying at supervisor");
|
|
2437
3037
|
return "supervisor";
|
|
2438
3038
|
};
|
|
2439
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");
|
|
2440
3045
|
return "supervisor";
|
|
2441
3046
|
};
|
|
2442
3047
|
const aggregatorRouter = (state) => {
|
|
3048
|
+
logger4.info("Aggregator router: ending workflow", {
|
|
3049
|
+
completedTasks: state.completedTasks.length,
|
|
3050
|
+
status: state.status
|
|
3051
|
+
});
|
|
2443
3052
|
return END4;
|
|
2444
3053
|
};
|
|
2445
3054
|
workflow.setEntryPoint("supervisor");
|
|
@@ -2632,11 +3241,14 @@ export {
|
|
|
2632
3241
|
ToolCallSchema,
|
|
2633
3242
|
ToolResultSchema,
|
|
2634
3243
|
WorkerCapabilitiesSchema,
|
|
3244
|
+
buildDeduplicationMetrics,
|
|
3245
|
+
calculateDeduplicationSavings,
|
|
2635
3246
|
createAggregatorNode,
|
|
2636
3247
|
createExecutorNode,
|
|
2637
3248
|
createFinisherNode,
|
|
2638
3249
|
createGeneratorNode,
|
|
2639
3250
|
createMultiAgentSystem,
|
|
3251
|
+
createPatternLogger,
|
|
2640
3252
|
createPlanExecuteAgent,
|
|
2641
3253
|
createPlannerNode,
|
|
2642
3254
|
createReActAgent,
|
|
@@ -2648,6 +3260,7 @@ export {
|
|
|
2648
3260
|
createReviserNode,
|
|
2649
3261
|
createSupervisorNode,
|
|
2650
3262
|
createWorkerNode,
|
|
3263
|
+
generateToolCallCacheKey,
|
|
2651
3264
|
getRoutingStrategy,
|
|
2652
3265
|
llmBasedRouting,
|
|
2653
3266
|
loadBalancedRouting,
|