@agentforge/patterns 0.15.9 → 0.15.11
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 +183 -105
- package/dist/index.js +188 -105
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -273,10 +273,12 @@ function formatScratchpad(scratchpad) {
|
|
|
273
273
|
}).join("\n\n");
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
// src/react/nodes.ts
|
|
277
|
-
var import_messages = require("@langchain/core/messages");
|
|
276
|
+
// src/react/nodes/reasoning.ts
|
|
278
277
|
var import_core3 = require("@agentforge/core");
|
|
279
278
|
|
|
279
|
+
// src/react/nodes/shared.ts
|
|
280
|
+
var import_messages = require("@langchain/core/messages");
|
|
281
|
+
|
|
280
282
|
// src/shared/deduplication.ts
|
|
281
283
|
var import_core2 = require("@agentforge/core");
|
|
282
284
|
function normalizeObject(obj) {
|
|
@@ -321,76 +323,114 @@ function buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, totalObserv
|
|
|
321
323
|
};
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
// src/shared
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
// src/react/nodes/shared.ts
|
|
327
|
+
var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
|
|
328
|
+
var actionLogger = createPatternLogger("agentforge:patterns:react:action");
|
|
329
|
+
var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
|
|
330
|
+
function normalizeConversationMessage(message) {
|
|
331
|
+
switch (message.role) {
|
|
332
|
+
case "user":
|
|
333
|
+
return new import_messages.HumanMessage(message.content);
|
|
334
|
+
case "assistant":
|
|
335
|
+
return new import_messages.AIMessage(message.content);
|
|
336
|
+
case "system":
|
|
337
|
+
return new import_messages.SystemMessage(message.content);
|
|
338
|
+
case "tool":
|
|
339
|
+
if (!message.tool_call_id) {
|
|
340
|
+
reasoningLogger.warn(
|
|
341
|
+
"Tool message missing tool_call_id; falling back to human message",
|
|
342
|
+
message.name ? { name: message.name } : void 0
|
|
343
|
+
);
|
|
344
|
+
return new import_messages.HumanMessage(message.content);
|
|
345
|
+
}
|
|
346
|
+
return new import_messages.ToolMessage({
|
|
347
|
+
content: message.content,
|
|
348
|
+
tool_call_id: message.tool_call_id,
|
|
349
|
+
name: message.name
|
|
350
|
+
});
|
|
351
|
+
default:
|
|
352
|
+
return new import_messages.HumanMessage(message.content);
|
|
353
|
+
}
|
|
327
354
|
}
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
355
|
+
function buildReasoningMessages(systemPrompt, stateMessages, scratchpad) {
|
|
356
|
+
const messages = [
|
|
357
|
+
new import_messages.SystemMessage(systemPrompt),
|
|
358
|
+
...stateMessages.map(normalizeConversationMessage)
|
|
359
|
+
];
|
|
360
|
+
if (scratchpad.length > 0) {
|
|
361
|
+
messages.push(new import_messages.SystemMessage(`Previous steps:
|
|
362
|
+
${formatScratchpad(scratchpad)}`));
|
|
331
363
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
364
|
+
return messages;
|
|
365
|
+
}
|
|
366
|
+
function extractToolCalls(response) {
|
|
367
|
+
if (!response.tool_calls || response.tool_calls.length === 0) {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
return response.tool_calls.map((toolCall) => ({
|
|
371
|
+
id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
|
|
372
|
+
name: toolCall.name,
|
|
373
|
+
arguments: toolCall.args ?? {},
|
|
374
|
+
timestamp: Date.now()
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
function formatObservationContent(observation) {
|
|
378
|
+
if (observation.error) {
|
|
379
|
+
return `Error: ${observation.error}`;
|
|
380
|
+
}
|
|
381
|
+
return stringifyObservationResult(observation.result, 2);
|
|
382
|
+
}
|
|
383
|
+
function formatActionSummary(actions) {
|
|
384
|
+
return actions.map((action) => `${action.name}(${stringifyActionArguments(action.arguments)})`).join(", ");
|
|
385
|
+
}
|
|
386
|
+
function formatObservationSummary(observations) {
|
|
387
|
+
return observations.map((observation) => {
|
|
388
|
+
if (observation.error) {
|
|
389
|
+
return `Error: ${observation.error}`;
|
|
337
390
|
}
|
|
391
|
+
return stringifyObservationResult(observation.result);
|
|
392
|
+
}).join("; ");
|
|
393
|
+
}
|
|
394
|
+
function stringifyObservationResult(result, space) {
|
|
395
|
+
if (typeof result === "string") {
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
const stringified = JSON.stringify(result, null, space);
|
|
400
|
+
return stringified ?? String(result);
|
|
401
|
+
} catch {
|
|
402
|
+
return String(result);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function stringifyActionArguments(arguments_) {
|
|
406
|
+
return stringifyObservationResult(arguments_);
|
|
407
|
+
}
|
|
408
|
+
function getLatestThought(thoughts) {
|
|
409
|
+
return thoughts[thoughts.length - 1]?.content ?? "";
|
|
410
|
+
}
|
|
411
|
+
function debugIfVerbose(logger4, verbose, message, data) {
|
|
412
|
+
if (verbose) {
|
|
413
|
+
logger4.debug(message, data);
|
|
338
414
|
}
|
|
339
|
-
return errorMessage;
|
|
340
415
|
}
|
|
341
416
|
|
|
342
|
-
// src/react/nodes.ts
|
|
343
|
-
var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
|
|
344
|
-
var actionLogger = createPatternLogger("agentforge:patterns:react:action");
|
|
345
|
-
var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
|
|
417
|
+
// src/react/nodes/reasoning.ts
|
|
346
418
|
function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
|
|
347
419
|
const langchainTools = (0, import_core3.toLangChainTools)(tools);
|
|
348
420
|
const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
|
|
349
421
|
return async (state) => {
|
|
350
422
|
const currentIteration = state.iteration || 0;
|
|
351
423
|
const startTime = Date.now();
|
|
352
|
-
reasoningLogger
|
|
424
|
+
debugIfVerbose(reasoningLogger, verbose, "Reasoning iteration started", {
|
|
353
425
|
iteration: currentIteration + 1,
|
|
354
426
|
maxIterations,
|
|
355
|
-
observationCount: state.observations
|
|
356
|
-
hasActions:
|
|
427
|
+
observationCount: state.observations.length,
|
|
428
|
+
hasActions: state.actions.length > 0
|
|
357
429
|
});
|
|
358
|
-
const
|
|
359
|
-
const messages = [
|
|
360
|
-
new import_messages.SystemMessage(systemPrompt),
|
|
361
|
-
...stateMessages.map((msg) => {
|
|
362
|
-
if (msg.role === "user") return new import_messages.HumanMessage(msg.content);
|
|
363
|
-
if (msg.role === "assistant") return new import_messages.AIMessage(msg.content);
|
|
364
|
-
if (msg.role === "system") return new import_messages.SystemMessage(msg.content);
|
|
365
|
-
if (msg.role === "tool") {
|
|
366
|
-
return new import_messages.ToolMessage({
|
|
367
|
-
content: msg.content,
|
|
368
|
-
tool_call_id: msg.tool_call_id,
|
|
369
|
-
name: msg.name
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
return new import_messages.HumanMessage(msg.content);
|
|
373
|
-
})
|
|
374
|
-
];
|
|
375
|
-
const scratchpad = state.scratchpad || [];
|
|
376
|
-
if (scratchpad.length > 0) {
|
|
377
|
-
const scratchpadText = formatScratchpad(scratchpad);
|
|
378
|
-
messages.push(new import_messages.SystemMessage(`Previous steps:
|
|
379
|
-
${scratchpadText}`));
|
|
380
|
-
}
|
|
430
|
+
const messages = buildReasoningMessages(systemPrompt, state.messages, state.scratchpad);
|
|
381
431
|
const response = await llmWithTools.invoke(messages);
|
|
382
432
|
const thought = typeof response.content === "string" ? response.content : "";
|
|
383
|
-
const toolCalls =
|
|
384
|
-
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
385
|
-
for (const toolCall of response.tool_calls) {
|
|
386
|
-
toolCalls.push({
|
|
387
|
-
id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
|
|
388
|
-
name: toolCall.name,
|
|
389
|
-
arguments: toolCall.args || {},
|
|
390
|
-
timestamp: Date.now()
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
}
|
|
433
|
+
const toolCalls = extractToolCalls(response);
|
|
394
434
|
const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
|
|
395
435
|
reasoningLogger.info("Reasoning complete", {
|
|
396
436
|
iteration: currentIteration + 1,
|
|
@@ -405,21 +445,49 @@ ${scratchpadText}`));
|
|
|
405
445
|
thoughts: thought ? [{ content: thought, timestamp: Date.now() }] : [],
|
|
406
446
|
actions: toolCalls,
|
|
407
447
|
iteration: 1,
|
|
408
|
-
// Add 1 to iteration counter (uses additive reducer)
|
|
409
448
|
shouldContinue,
|
|
410
449
|
response: toolCalls.length === 0 ? thought : void 0
|
|
411
|
-
// Final response if no tool calls
|
|
412
450
|
};
|
|
413
451
|
};
|
|
414
452
|
}
|
|
453
|
+
|
|
454
|
+
// src/shared/error-handling.ts
|
|
455
|
+
function isGraphInterrupt(error) {
|
|
456
|
+
return error !== null && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt";
|
|
457
|
+
}
|
|
458
|
+
function handleNodeError(error, context, verbose = false) {
|
|
459
|
+
if (isGraphInterrupt(error)) {
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
463
|
+
if (verbose) {
|
|
464
|
+
console.error(`[${context}] Error:`, errorMessage);
|
|
465
|
+
if (error instanceof Error && error.stack) {
|
|
466
|
+
console.error(`[${context}] Stack:`, error.stack);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return errorMessage;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/react/nodes/action.ts
|
|
415
473
|
function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
416
474
|
const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
|
|
475
|
+
function buildCacheKey(toolName, args) {
|
|
476
|
+
try {
|
|
477
|
+
return generateToolCallCacheKey(toolName, args);
|
|
478
|
+
} catch {
|
|
479
|
+
debugIfVerbose(actionLogger, verbose, "Skipping deduplication for unserializable tool call", {
|
|
480
|
+
toolName
|
|
481
|
+
});
|
|
482
|
+
return void 0;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
417
485
|
return async (state) => {
|
|
418
|
-
const actions = state.actions
|
|
419
|
-
const allObservations = state.observations
|
|
486
|
+
const actions = state.actions;
|
|
487
|
+
const allObservations = state.observations;
|
|
420
488
|
const iteration = state.iteration || 0;
|
|
421
489
|
const startTime = Date.now();
|
|
422
|
-
actionLogger
|
|
490
|
+
debugIfVerbose(actionLogger, verbose, "Action node started", {
|
|
423
491
|
actionCount: actions.length,
|
|
424
492
|
iteration,
|
|
425
493
|
cacheEnabled: enableDeduplication
|
|
@@ -427,18 +495,27 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
427
495
|
const recentActions = actions.slice(-10);
|
|
428
496
|
const observations = [];
|
|
429
497
|
const executionCache = /* @__PURE__ */ new Map();
|
|
498
|
+
const actionsById = new Map(actions.map((action) => [action.id, action]));
|
|
499
|
+
const observedToolCallIds = new Set(
|
|
500
|
+
allObservations.map((observation) => observation.toolCallId)
|
|
501
|
+
);
|
|
430
502
|
let cacheSize = 0;
|
|
431
503
|
if (enableDeduplication) {
|
|
432
504
|
for (const observation of allObservations) {
|
|
433
|
-
const correspondingAction =
|
|
505
|
+
const correspondingAction = actionsById.get(observation.toolCallId);
|
|
434
506
|
if (correspondingAction) {
|
|
435
|
-
const cacheKey =
|
|
436
|
-
|
|
437
|
-
|
|
507
|
+
const cacheKey = buildCacheKey(
|
|
508
|
+
correspondingAction.name,
|
|
509
|
+
correspondingAction.arguments
|
|
510
|
+
);
|
|
511
|
+
if (cacheKey) {
|
|
512
|
+
executionCache.set(cacheKey, observation);
|
|
513
|
+
cacheSize++;
|
|
514
|
+
}
|
|
438
515
|
}
|
|
439
516
|
}
|
|
440
517
|
if (cacheSize > 0) {
|
|
441
|
-
actionLogger
|
|
518
|
+
debugIfVerbose(actionLogger, verbose, "Deduplication cache built", {
|
|
442
519
|
cacheSize,
|
|
443
520
|
totalObservations: allObservations.length
|
|
444
521
|
});
|
|
@@ -447,9 +524,8 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
447
524
|
let duplicatesSkipped = 0;
|
|
448
525
|
let toolsExecuted = 0;
|
|
449
526
|
for (const action of recentActions) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
actionLogger.debug("Skipping already-processed action", {
|
|
527
|
+
if (observedToolCallIds.has(action.id)) {
|
|
528
|
+
debugIfVerbose(actionLogger, verbose, "Skipping already-processed action", {
|
|
453
529
|
toolName: action.name,
|
|
454
530
|
toolCallId: action.id,
|
|
455
531
|
iteration
|
|
@@ -457,13 +533,13 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
457
533
|
continue;
|
|
458
534
|
}
|
|
459
535
|
if (enableDeduplication) {
|
|
460
|
-
const cacheKey =
|
|
461
|
-
const cachedResult = executionCache.get(cacheKey);
|
|
536
|
+
const cacheKey = buildCacheKey(action.name, action.arguments);
|
|
537
|
+
const cachedResult = cacheKey ? executionCache.get(cacheKey) : void 0;
|
|
462
538
|
if (cachedResult) {
|
|
463
539
|
duplicatesSkipped++;
|
|
464
540
|
actionLogger.info("Duplicate tool call prevented", {
|
|
465
541
|
toolName: action.name,
|
|
466
|
-
|
|
542
|
+
argumentKeys: Object.keys(action.arguments),
|
|
467
543
|
iteration,
|
|
468
544
|
cacheHit: true
|
|
469
545
|
});
|
|
@@ -488,11 +564,11 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
488
564
|
continue;
|
|
489
565
|
}
|
|
490
566
|
try {
|
|
491
|
-
const
|
|
567
|
+
const toolStartTime = Date.now();
|
|
492
568
|
const result = await tool.invoke(action.arguments);
|
|
493
|
-
const executionTime = Date.now() -
|
|
569
|
+
const executionTime = Date.now() - toolStartTime;
|
|
494
570
|
toolsExecuted++;
|
|
495
|
-
actionLogger
|
|
571
|
+
debugIfVerbose(actionLogger, verbose, "Tool executed successfully", {
|
|
496
572
|
toolName: action.name,
|
|
497
573
|
executionTime,
|
|
498
574
|
iteration
|
|
@@ -504,11 +580,13 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
504
580
|
};
|
|
505
581
|
observations.push(observation);
|
|
506
582
|
if (enableDeduplication) {
|
|
507
|
-
const cacheKey =
|
|
508
|
-
|
|
583
|
+
const cacheKey = buildCacheKey(action.name, action.arguments);
|
|
584
|
+
if (cacheKey) {
|
|
585
|
+
executionCache.set(cacheKey, observation);
|
|
586
|
+
}
|
|
509
587
|
}
|
|
510
588
|
} catch (error) {
|
|
511
|
-
const errorMessage = handleNodeError(error, `action:${action.name}`,
|
|
589
|
+
const errorMessage = handleNodeError(error, `action:${action.name}`, verbose);
|
|
512
590
|
actionLogger.error("Tool execution failed", {
|
|
513
591
|
toolName: action.name,
|
|
514
592
|
error: errorMessage,
|
|
@@ -523,7 +601,11 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
523
601
|
}
|
|
524
602
|
}
|
|
525
603
|
if (duplicatesSkipped > 0 || toolsExecuted > 0) {
|
|
526
|
-
const metrics = buildDeduplicationMetrics(
|
|
604
|
+
const metrics = buildDeduplicationMetrics(
|
|
605
|
+
toolsExecuted,
|
|
606
|
+
duplicatesSkipped,
|
|
607
|
+
observations.length
|
|
608
|
+
);
|
|
527
609
|
actionLogger.info("Action node complete", {
|
|
528
610
|
iteration,
|
|
529
611
|
...metrics,
|
|
@@ -535,41 +617,37 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
535
617
|
};
|
|
536
618
|
};
|
|
537
619
|
}
|
|
620
|
+
|
|
621
|
+
// src/react/nodes/observation.ts
|
|
538
622
|
function createObservationNode(verbose = false, returnIntermediateSteps = false) {
|
|
539
623
|
return async (state) => {
|
|
540
|
-
const observations = state.observations
|
|
541
|
-
const thoughts = state.thoughts
|
|
542
|
-
const actions = state.actions
|
|
624
|
+
const observations = state.observations;
|
|
625
|
+
const thoughts = state.thoughts;
|
|
626
|
+
const actions = state.actions;
|
|
543
627
|
const iteration = state.iteration || 0;
|
|
544
|
-
observationLogger
|
|
628
|
+
debugIfVerbose(observationLogger, verbose, "Processing observations", {
|
|
545
629
|
observationCount: observations.length,
|
|
546
630
|
iteration
|
|
547
631
|
});
|
|
548
632
|
const recentObservations = observations.slice(-10);
|
|
549
633
|
const latestActions = actions.slice(-10);
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
return typeof obs.result === "string" ? obs.result : JSON.stringify(obs.result);
|
|
569
|
-
}).join("; "),
|
|
570
|
-
timestamp: Date.now()
|
|
571
|
-
}] : [];
|
|
572
|
-
observationLogger.debug("Observation node complete", {
|
|
634
|
+
const actionNamesById = new Map(latestActions.map((action) => [action.id, action.name]));
|
|
635
|
+
const observationMessages = recentObservations.map((observation) => ({
|
|
636
|
+
role: "tool",
|
|
637
|
+
content: formatObservationContent(observation),
|
|
638
|
+
name: actionNamesById.get(observation.toolCallId),
|
|
639
|
+
tool_call_id: observation.toolCallId
|
|
640
|
+
}));
|
|
641
|
+
const scratchpadEntries = returnIntermediateSteps ? [
|
|
642
|
+
{
|
|
643
|
+
step: iteration,
|
|
644
|
+
thought: getLatestThought(thoughts),
|
|
645
|
+
action: formatActionSummary(latestActions),
|
|
646
|
+
observation: formatObservationSummary(recentObservations),
|
|
647
|
+
timestamp: Date.now()
|
|
648
|
+
}
|
|
649
|
+
] : [];
|
|
650
|
+
debugIfVerbose(observationLogger, verbose, "Observation node complete", {
|
|
573
651
|
iteration,
|
|
574
652
|
scratchpadUpdated: returnIntermediateSteps,
|
|
575
653
|
messageCount: observationMessages.length
|
package/dist/index.js
CHANGED
|
@@ -170,10 +170,17 @@ function formatScratchpad(scratchpad) {
|
|
|
170
170
|
}).join("\n\n");
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
// src/react/nodes.ts
|
|
174
|
-
import { HumanMessage, AIMessage, SystemMessage, ToolMessage } from "@langchain/core/messages";
|
|
173
|
+
// src/react/nodes/reasoning.ts
|
|
175
174
|
import { toLangChainTools } from "@agentforge/core";
|
|
176
175
|
|
|
176
|
+
// src/react/nodes/shared.ts
|
|
177
|
+
import {
|
|
178
|
+
HumanMessage,
|
|
179
|
+
AIMessage,
|
|
180
|
+
SystemMessage,
|
|
181
|
+
ToolMessage
|
|
182
|
+
} from "@langchain/core/messages";
|
|
183
|
+
|
|
177
184
|
// src/shared/deduplication.ts
|
|
178
185
|
import { createLogger } from "@agentforge/core";
|
|
179
186
|
function normalizeObject(obj) {
|
|
@@ -218,76 +225,114 @@ function buildDeduplicationMetrics(toolsExecuted, duplicatesSkipped, totalObserv
|
|
|
218
225
|
};
|
|
219
226
|
}
|
|
220
227
|
|
|
221
|
-
// src/shared
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
// src/react/nodes/shared.ts
|
|
229
|
+
var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
|
|
230
|
+
var actionLogger = createPatternLogger("agentforge:patterns:react:action");
|
|
231
|
+
var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
|
|
232
|
+
function normalizeConversationMessage(message) {
|
|
233
|
+
switch (message.role) {
|
|
234
|
+
case "user":
|
|
235
|
+
return new HumanMessage(message.content);
|
|
236
|
+
case "assistant":
|
|
237
|
+
return new AIMessage(message.content);
|
|
238
|
+
case "system":
|
|
239
|
+
return new SystemMessage(message.content);
|
|
240
|
+
case "tool":
|
|
241
|
+
if (!message.tool_call_id) {
|
|
242
|
+
reasoningLogger.warn(
|
|
243
|
+
"Tool message missing tool_call_id; falling back to human message",
|
|
244
|
+
message.name ? { name: message.name } : void 0
|
|
245
|
+
);
|
|
246
|
+
return new HumanMessage(message.content);
|
|
247
|
+
}
|
|
248
|
+
return new ToolMessage({
|
|
249
|
+
content: message.content,
|
|
250
|
+
tool_call_id: message.tool_call_id,
|
|
251
|
+
name: message.name
|
|
252
|
+
});
|
|
253
|
+
default:
|
|
254
|
+
return new HumanMessage(message.content);
|
|
255
|
+
}
|
|
224
256
|
}
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
257
|
+
function buildReasoningMessages(systemPrompt, stateMessages, scratchpad) {
|
|
258
|
+
const messages = [
|
|
259
|
+
new SystemMessage(systemPrompt),
|
|
260
|
+
...stateMessages.map(normalizeConversationMessage)
|
|
261
|
+
];
|
|
262
|
+
if (scratchpad.length > 0) {
|
|
263
|
+
messages.push(new SystemMessage(`Previous steps:
|
|
264
|
+
${formatScratchpad(scratchpad)}`));
|
|
228
265
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
266
|
+
return messages;
|
|
267
|
+
}
|
|
268
|
+
function extractToolCalls(response) {
|
|
269
|
+
if (!response.tool_calls || response.tool_calls.length === 0) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
return response.tool_calls.map((toolCall) => ({
|
|
273
|
+
id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
|
|
274
|
+
name: toolCall.name,
|
|
275
|
+
arguments: toolCall.args ?? {},
|
|
276
|
+
timestamp: Date.now()
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
function formatObservationContent(observation) {
|
|
280
|
+
if (observation.error) {
|
|
281
|
+
return `Error: ${observation.error}`;
|
|
282
|
+
}
|
|
283
|
+
return stringifyObservationResult(observation.result, 2);
|
|
284
|
+
}
|
|
285
|
+
function formatActionSummary(actions) {
|
|
286
|
+
return actions.map((action) => `${action.name}(${stringifyActionArguments(action.arguments)})`).join(", ");
|
|
287
|
+
}
|
|
288
|
+
function formatObservationSummary(observations) {
|
|
289
|
+
return observations.map((observation) => {
|
|
290
|
+
if (observation.error) {
|
|
291
|
+
return `Error: ${observation.error}`;
|
|
234
292
|
}
|
|
293
|
+
return stringifyObservationResult(observation.result);
|
|
294
|
+
}).join("; ");
|
|
295
|
+
}
|
|
296
|
+
function stringifyObservationResult(result, space) {
|
|
297
|
+
if (typeof result === "string") {
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const stringified = JSON.stringify(result, null, space);
|
|
302
|
+
return stringified ?? String(result);
|
|
303
|
+
} catch {
|
|
304
|
+
return String(result);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function stringifyActionArguments(arguments_) {
|
|
308
|
+
return stringifyObservationResult(arguments_);
|
|
309
|
+
}
|
|
310
|
+
function getLatestThought(thoughts) {
|
|
311
|
+
return thoughts[thoughts.length - 1]?.content ?? "";
|
|
312
|
+
}
|
|
313
|
+
function debugIfVerbose(logger4, verbose, message, data) {
|
|
314
|
+
if (verbose) {
|
|
315
|
+
logger4.debug(message, data);
|
|
235
316
|
}
|
|
236
|
-
return errorMessage;
|
|
237
317
|
}
|
|
238
318
|
|
|
239
|
-
// src/react/nodes.ts
|
|
240
|
-
var reasoningLogger = createPatternLogger("agentforge:patterns:react:reasoning");
|
|
241
|
-
var actionLogger = createPatternLogger("agentforge:patterns:react:action");
|
|
242
|
-
var observationLogger = createPatternLogger("agentforge:patterns:react:observation");
|
|
319
|
+
// src/react/nodes/reasoning.ts
|
|
243
320
|
function createReasoningNode(llm, tools, systemPrompt, maxIterations, verbose = false) {
|
|
244
321
|
const langchainTools = toLangChainTools(tools);
|
|
245
322
|
const llmWithTools = llm.bindTools ? llm.bindTools(langchainTools) : llm;
|
|
246
323
|
return async (state) => {
|
|
247
324
|
const currentIteration = state.iteration || 0;
|
|
248
325
|
const startTime = Date.now();
|
|
249
|
-
reasoningLogger
|
|
326
|
+
debugIfVerbose(reasoningLogger, verbose, "Reasoning iteration started", {
|
|
250
327
|
iteration: currentIteration + 1,
|
|
251
328
|
maxIterations,
|
|
252
|
-
observationCount: state.observations
|
|
253
|
-
hasActions:
|
|
329
|
+
observationCount: state.observations.length,
|
|
330
|
+
hasActions: state.actions.length > 0
|
|
254
331
|
});
|
|
255
|
-
const
|
|
256
|
-
const messages = [
|
|
257
|
-
new SystemMessage(systemPrompt),
|
|
258
|
-
...stateMessages.map((msg) => {
|
|
259
|
-
if (msg.role === "user") return new HumanMessage(msg.content);
|
|
260
|
-
if (msg.role === "assistant") return new AIMessage(msg.content);
|
|
261
|
-
if (msg.role === "system") return new SystemMessage(msg.content);
|
|
262
|
-
if (msg.role === "tool") {
|
|
263
|
-
return new ToolMessage({
|
|
264
|
-
content: msg.content,
|
|
265
|
-
tool_call_id: msg.tool_call_id,
|
|
266
|
-
name: msg.name
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
return new HumanMessage(msg.content);
|
|
270
|
-
})
|
|
271
|
-
];
|
|
272
|
-
const scratchpad = state.scratchpad || [];
|
|
273
|
-
if (scratchpad.length > 0) {
|
|
274
|
-
const scratchpadText = formatScratchpad(scratchpad);
|
|
275
|
-
messages.push(new SystemMessage(`Previous steps:
|
|
276
|
-
${scratchpadText}`));
|
|
277
|
-
}
|
|
332
|
+
const messages = buildReasoningMessages(systemPrompt, state.messages, state.scratchpad);
|
|
278
333
|
const response = await llmWithTools.invoke(messages);
|
|
279
334
|
const thought = typeof response.content === "string" ? response.content : "";
|
|
280
|
-
const toolCalls =
|
|
281
|
-
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
282
|
-
for (const toolCall of response.tool_calls) {
|
|
283
|
-
toolCalls.push({
|
|
284
|
-
id: toolCall.id || `call_${Date.now()}_${Math.random()}`,
|
|
285
|
-
name: toolCall.name,
|
|
286
|
-
arguments: toolCall.args || {},
|
|
287
|
-
timestamp: Date.now()
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
335
|
+
const toolCalls = extractToolCalls(response);
|
|
291
336
|
const shouldContinue = toolCalls.length > 0 && currentIteration + 1 < maxIterations;
|
|
292
337
|
reasoningLogger.info("Reasoning complete", {
|
|
293
338
|
iteration: currentIteration + 1,
|
|
@@ -302,21 +347,49 @@ ${scratchpadText}`));
|
|
|
302
347
|
thoughts: thought ? [{ content: thought, timestamp: Date.now() }] : [],
|
|
303
348
|
actions: toolCalls,
|
|
304
349
|
iteration: 1,
|
|
305
|
-
// Add 1 to iteration counter (uses additive reducer)
|
|
306
350
|
shouldContinue,
|
|
307
351
|
response: toolCalls.length === 0 ? thought : void 0
|
|
308
|
-
// Final response if no tool calls
|
|
309
352
|
};
|
|
310
353
|
};
|
|
311
354
|
}
|
|
355
|
+
|
|
356
|
+
// src/shared/error-handling.ts
|
|
357
|
+
function isGraphInterrupt(error) {
|
|
358
|
+
return error !== null && typeof error === "object" && "constructor" in error && error.constructor.name === "GraphInterrupt";
|
|
359
|
+
}
|
|
360
|
+
function handleNodeError(error, context, verbose = false) {
|
|
361
|
+
if (isGraphInterrupt(error)) {
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
365
|
+
if (verbose) {
|
|
366
|
+
console.error(`[${context}] Error:`, errorMessage);
|
|
367
|
+
if (error instanceof Error && error.stack) {
|
|
368
|
+
console.error(`[${context}] Stack:`, error.stack);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return errorMessage;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/react/nodes/action.ts
|
|
312
375
|
function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
313
376
|
const toolMap = new Map(tools.map((tool) => [tool.metadata.name, tool]));
|
|
377
|
+
function buildCacheKey(toolName, args) {
|
|
378
|
+
try {
|
|
379
|
+
return generateToolCallCacheKey(toolName, args);
|
|
380
|
+
} catch {
|
|
381
|
+
debugIfVerbose(actionLogger, verbose, "Skipping deduplication for unserializable tool call", {
|
|
382
|
+
toolName
|
|
383
|
+
});
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
314
387
|
return async (state) => {
|
|
315
|
-
const actions = state.actions
|
|
316
|
-
const allObservations = state.observations
|
|
388
|
+
const actions = state.actions;
|
|
389
|
+
const allObservations = state.observations;
|
|
317
390
|
const iteration = state.iteration || 0;
|
|
318
391
|
const startTime = Date.now();
|
|
319
|
-
actionLogger
|
|
392
|
+
debugIfVerbose(actionLogger, verbose, "Action node started", {
|
|
320
393
|
actionCount: actions.length,
|
|
321
394
|
iteration,
|
|
322
395
|
cacheEnabled: enableDeduplication
|
|
@@ -324,18 +397,27 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
324
397
|
const recentActions = actions.slice(-10);
|
|
325
398
|
const observations = [];
|
|
326
399
|
const executionCache = /* @__PURE__ */ new Map();
|
|
400
|
+
const actionsById = new Map(actions.map((action) => [action.id, action]));
|
|
401
|
+
const observedToolCallIds = new Set(
|
|
402
|
+
allObservations.map((observation) => observation.toolCallId)
|
|
403
|
+
);
|
|
327
404
|
let cacheSize = 0;
|
|
328
405
|
if (enableDeduplication) {
|
|
329
406
|
for (const observation of allObservations) {
|
|
330
|
-
const correspondingAction =
|
|
407
|
+
const correspondingAction = actionsById.get(observation.toolCallId);
|
|
331
408
|
if (correspondingAction) {
|
|
332
|
-
const cacheKey =
|
|
333
|
-
|
|
334
|
-
|
|
409
|
+
const cacheKey = buildCacheKey(
|
|
410
|
+
correspondingAction.name,
|
|
411
|
+
correspondingAction.arguments
|
|
412
|
+
);
|
|
413
|
+
if (cacheKey) {
|
|
414
|
+
executionCache.set(cacheKey, observation);
|
|
415
|
+
cacheSize++;
|
|
416
|
+
}
|
|
335
417
|
}
|
|
336
418
|
}
|
|
337
419
|
if (cacheSize > 0) {
|
|
338
|
-
actionLogger
|
|
420
|
+
debugIfVerbose(actionLogger, verbose, "Deduplication cache built", {
|
|
339
421
|
cacheSize,
|
|
340
422
|
totalObservations: allObservations.length
|
|
341
423
|
});
|
|
@@ -344,9 +426,8 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
344
426
|
let duplicatesSkipped = 0;
|
|
345
427
|
let toolsExecuted = 0;
|
|
346
428
|
for (const action of recentActions) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
actionLogger.debug("Skipping already-processed action", {
|
|
429
|
+
if (observedToolCallIds.has(action.id)) {
|
|
430
|
+
debugIfVerbose(actionLogger, verbose, "Skipping already-processed action", {
|
|
350
431
|
toolName: action.name,
|
|
351
432
|
toolCallId: action.id,
|
|
352
433
|
iteration
|
|
@@ -354,13 +435,13 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
354
435
|
continue;
|
|
355
436
|
}
|
|
356
437
|
if (enableDeduplication) {
|
|
357
|
-
const cacheKey =
|
|
358
|
-
const cachedResult = executionCache.get(cacheKey);
|
|
438
|
+
const cacheKey = buildCacheKey(action.name, action.arguments);
|
|
439
|
+
const cachedResult = cacheKey ? executionCache.get(cacheKey) : void 0;
|
|
359
440
|
if (cachedResult) {
|
|
360
441
|
duplicatesSkipped++;
|
|
361
442
|
actionLogger.info("Duplicate tool call prevented", {
|
|
362
443
|
toolName: action.name,
|
|
363
|
-
|
|
444
|
+
argumentKeys: Object.keys(action.arguments),
|
|
364
445
|
iteration,
|
|
365
446
|
cacheHit: true
|
|
366
447
|
});
|
|
@@ -385,11 +466,11 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
385
466
|
continue;
|
|
386
467
|
}
|
|
387
468
|
try {
|
|
388
|
-
const
|
|
469
|
+
const toolStartTime = Date.now();
|
|
389
470
|
const result = await tool.invoke(action.arguments);
|
|
390
|
-
const executionTime = Date.now() -
|
|
471
|
+
const executionTime = Date.now() - toolStartTime;
|
|
391
472
|
toolsExecuted++;
|
|
392
|
-
actionLogger
|
|
473
|
+
debugIfVerbose(actionLogger, verbose, "Tool executed successfully", {
|
|
393
474
|
toolName: action.name,
|
|
394
475
|
executionTime,
|
|
395
476
|
iteration
|
|
@@ -401,11 +482,13 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
401
482
|
};
|
|
402
483
|
observations.push(observation);
|
|
403
484
|
if (enableDeduplication) {
|
|
404
|
-
const cacheKey =
|
|
405
|
-
|
|
485
|
+
const cacheKey = buildCacheKey(action.name, action.arguments);
|
|
486
|
+
if (cacheKey) {
|
|
487
|
+
executionCache.set(cacheKey, observation);
|
|
488
|
+
}
|
|
406
489
|
}
|
|
407
490
|
} catch (error) {
|
|
408
|
-
const errorMessage = handleNodeError(error, `action:${action.name}`,
|
|
491
|
+
const errorMessage = handleNodeError(error, `action:${action.name}`, verbose);
|
|
409
492
|
actionLogger.error("Tool execution failed", {
|
|
410
493
|
toolName: action.name,
|
|
411
494
|
error: errorMessage,
|
|
@@ -420,7 +503,11 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
420
503
|
}
|
|
421
504
|
}
|
|
422
505
|
if (duplicatesSkipped > 0 || toolsExecuted > 0) {
|
|
423
|
-
const metrics = buildDeduplicationMetrics(
|
|
506
|
+
const metrics = buildDeduplicationMetrics(
|
|
507
|
+
toolsExecuted,
|
|
508
|
+
duplicatesSkipped,
|
|
509
|
+
observations.length
|
|
510
|
+
);
|
|
424
511
|
actionLogger.info("Action node complete", {
|
|
425
512
|
iteration,
|
|
426
513
|
...metrics,
|
|
@@ -432,41 +519,37 @@ function createActionNode(tools, verbose = false, enableDeduplication = true) {
|
|
|
432
519
|
};
|
|
433
520
|
};
|
|
434
521
|
}
|
|
522
|
+
|
|
523
|
+
// src/react/nodes/observation.ts
|
|
435
524
|
function createObservationNode(verbose = false, returnIntermediateSteps = false) {
|
|
436
525
|
return async (state) => {
|
|
437
|
-
const observations = state.observations
|
|
438
|
-
const thoughts = state.thoughts
|
|
439
|
-
const actions = state.actions
|
|
526
|
+
const observations = state.observations;
|
|
527
|
+
const thoughts = state.thoughts;
|
|
528
|
+
const actions = state.actions;
|
|
440
529
|
const iteration = state.iteration || 0;
|
|
441
|
-
observationLogger
|
|
530
|
+
debugIfVerbose(observationLogger, verbose, "Processing observations", {
|
|
442
531
|
observationCount: observations.length,
|
|
443
532
|
iteration
|
|
444
533
|
});
|
|
445
534
|
const recentObservations = observations.slice(-10);
|
|
446
535
|
const latestActions = actions.slice(-10);
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
return typeof obs.result === "string" ? obs.result : JSON.stringify(obs.result);
|
|
466
|
-
}).join("; "),
|
|
467
|
-
timestamp: Date.now()
|
|
468
|
-
}] : [];
|
|
469
|
-
observationLogger.debug("Observation node complete", {
|
|
536
|
+
const actionNamesById = new Map(latestActions.map((action) => [action.id, action.name]));
|
|
537
|
+
const observationMessages = recentObservations.map((observation) => ({
|
|
538
|
+
role: "tool",
|
|
539
|
+
content: formatObservationContent(observation),
|
|
540
|
+
name: actionNamesById.get(observation.toolCallId),
|
|
541
|
+
tool_call_id: observation.toolCallId
|
|
542
|
+
}));
|
|
543
|
+
const scratchpadEntries = returnIntermediateSteps ? [
|
|
544
|
+
{
|
|
545
|
+
step: iteration,
|
|
546
|
+
thought: getLatestThought(thoughts),
|
|
547
|
+
action: formatActionSummary(latestActions),
|
|
548
|
+
observation: formatObservationSummary(recentObservations),
|
|
549
|
+
timestamp: Date.now()
|
|
550
|
+
}
|
|
551
|
+
] : [];
|
|
552
|
+
debugIfVerbose(observationLogger, verbose, "Observation node complete", {
|
|
470
553
|
iteration,
|
|
471
554
|
scratchpadUpdated: returnIntermediateSteps,
|
|
472
555
|
messageCount: observationMessages.length
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentforge/patterns",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.11",
|
|
4
4
|
"description": "Production-ready agent workflow patterns for TypeScript including ReAct and Planner-Executor, built on LangGraph.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"url": "https://github.com/TVScoundrel/agentforge/issues"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@agentforge/core": "0.15.
|
|
44
|
+
"@agentforge/core": "0.15.11",
|
|
45
45
|
"@langchain/core": "^1.1.17",
|
|
46
46
|
"@langchain/langgraph": "^1.1.2",
|
|
47
47
|
"zod": "^3.23.8"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@agentforge/testing": "0.15.
|
|
50
|
+
"@agentforge/testing": "0.15.11",
|
|
51
51
|
"@eslint/js": "^9.17.0",
|
|
52
52
|
"@types/node": "^22.10.2",
|
|
53
53
|
"eslint": "^9.17.0",
|