@librechat/agents 3.2.68 → 3.3.1
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/cjs/agents/AgentContext.cjs +117 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +4 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +243 -49
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +103 -28
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +4 -0
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +11 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/contextOverflowRecovery.cjs +132 -0
- package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -0
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +60 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/main.cjs +21 -7
- package/dist/cjs/messages/format.cjs +136 -4
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +16 -5
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +2 -0
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/prompts/activityLabel.cjs +101 -0
- package/dist/cjs/prompts/activityLabel.cjs.map +1 -0
- package/dist/cjs/run.cjs +177 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +205 -49
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +55 -7
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +2 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +69 -8
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +19 -15
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -1
- package/dist/cjs/utils/errors.cjs +317 -52
- package/dist/cjs/utils/errors.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +118 -4
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +4 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +242 -48
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +103 -28
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +4 -1
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +11 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/contextOverflowRecovery.mjs +130 -0
- package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -0
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +59 -4
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/main.mjs +10 -10
- package/dist/esm/messages/format.mjs +136 -5
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +14 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +2 -1
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/prompts/activityLabel.mjs +100 -0
- package/dist/esm/prompts/activityLabel.mjs.map +1 -0
- package/dist/esm/run.mjs +178 -4
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +205 -49
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +55 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -3
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +62 -9
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +20 -16
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -1
- package/dist/esm/utils/errors.mjs +317 -53
- package/dist/esm/utils/errors.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +62 -3
- package/dist/types/common/enum.d.ts +5 -1
- package/dist/types/graphs/Graph.d.ts +16 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +4 -0
- package/dist/types/llm/contextOverflowRecovery.d.ts +85 -0
- package/dist/types/llm/invoke.d.ts +35 -6
- package/dist/types/messages/format.d.ts +22 -0
- package/dist/types/messages/prune.d.ts +10 -2
- package/dist/types/messages/recency.d.ts +1 -0
- package/dist/types/prompts/activityLabel.d.ts +31 -0
- package/dist/types/run.d.ts +16 -0
- package/dist/types/tools/CodeExecutor.d.ts +14 -1
- package/dist/types/types/activityLabel.d.ts +53 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/llm.d.ts +7 -4
- package/dist/types/types/stream.d.ts +7 -4
- package/dist/types/types/summarize.d.ts +22 -0
- package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +40 -0
- package/dist/types/utils/errors.d.ts +65 -16
- package/dist/types/utils/redactSecrets.d.ts +3 -0
- package/package.json +7 -8
- package/src/agents/AgentContext.ts +188 -7
- package/src/agents/__tests__/AgentContext.overflow.test.ts +205 -0
- package/src/common/enum.ts +4 -0
- package/src/graphs/Graph.ts +409 -58
- package/src/graphs/MultiAgentGraph.ts +184 -46
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +631 -0
- package/src/langfuseToolOutputTracing.ts +4 -1
- package/src/llm/__tests__/contextOverflowRecovery.test.ts +401 -0
- package/src/llm/__tests__/fallbackOverflow.test.ts +287 -0
- package/src/llm/bedrock/index.ts +25 -12
- package/src/llm/contextOverflowRecovery.ts +292 -0
- package/src/llm/invoke.ts +119 -4
- package/src/messages/foldToollessToolBlocks.test.ts +438 -0
- package/src/messages/format.ts +233 -5
- package/src/messages/prune.ts +24 -11
- package/src/messages/recency.ts +3 -1
- package/src/prompts/activityLabel.ts +177 -0
- package/src/run.ts +322 -3
- package/src/scripts/context-overflow-probe.ts +997 -0
- package/src/specs/activity-label-prompt.test.ts +128 -0
- package/src/specs/activity-label-trace-seed.test.ts +47 -0
- package/src/specs/agent-handoffs.test.ts +903 -1
- package/src/specs/bedrock-toolless.live.test.ts +123 -0
- package/src/specs/context-overflow-recovery.live.test.ts +213 -0
- package/src/splitStream.test.ts +882 -0
- package/src/stream.ts +315 -51
- package/src/summarization/__tests__/aggregator.test.ts +83 -0
- package/src/summarization/__tests__/node.test.ts +139 -0
- package/src/summarization/node.ts +99 -14
- package/src/tools/BashExecutor.ts +4 -2
- package/src/tools/BashProgrammaticToolCalling.ts +4 -7
- package/src/tools/CodeExecutor.ts +119 -8
- package/src/tools/ProgrammaticToolCalling.ts +29 -27
- package/src/tools/ToolNode.ts +50 -8
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +297 -3
- package/src/types/activityLabel.ts +55 -0
- package/src/types/index.ts +1 -0
- package/src/types/llm.ts +8 -1
- package/src/types/stream.ts +7 -4
- package/src/types/summarize.ts +22 -0
- package/src/utils/__tests__/errors.test.ts +270 -0
- package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +336 -0
- package/src/utils/__tests__/redactSecrets.test.ts +56 -0
- package/src/utils/errors.ts +484 -66
- package/src/utils/redactSecrets.ts +61 -0
package/src/graphs/Graph.ts
CHANGED
|
@@ -11,19 +11,22 @@ import type {
|
|
|
11
11
|
MessageContent,
|
|
12
12
|
} from '@langchain/core/messages';
|
|
13
13
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
14
|
+
import type { OverflowRecoveryPlan } from '@/llm/contextOverflowRecovery';
|
|
15
|
+
import type { FallbackErrorContext } from '@/llm/invoke';
|
|
14
16
|
import type { HookRegistry } from '@/hooks';
|
|
15
17
|
import type * as t from '@/types';
|
|
16
18
|
import {
|
|
17
19
|
formatAnthropicArtifactContent,
|
|
18
20
|
ensureThinkingBlockInMessages,
|
|
21
|
+
foldToolBlocksForToollessAgent,
|
|
19
22
|
convertMessagesToContent,
|
|
20
23
|
sanitizeOrphanToolBlocks,
|
|
21
24
|
extractToolDiscoveries,
|
|
22
25
|
addBedrockTailCacheControl,
|
|
23
26
|
formatArtifactPayload,
|
|
24
|
-
enforceOriginalContentCap,
|
|
25
27
|
formatContentStrings,
|
|
26
28
|
isLegacyConvertible,
|
|
29
|
+
CALIBRATION_RATIO_MAX,
|
|
27
30
|
createPruneMessages,
|
|
28
31
|
syncBudgetDerivedFields,
|
|
29
32
|
addTailCacheControl,
|
|
@@ -33,6 +36,8 @@ import {
|
|
|
33
36
|
getMessageId,
|
|
34
37
|
makeIsDeferred,
|
|
35
38
|
partitionAndMarkAnthropicToolCache,
|
|
39
|
+
DEFAULT_RETAIN_RECENT_TURNS,
|
|
40
|
+
splitAtRecencyBoundary,
|
|
36
41
|
} from '@/messages';
|
|
37
42
|
import {
|
|
38
43
|
createLangfuseHandler,
|
|
@@ -50,16 +55,28 @@ import {
|
|
|
50
55
|
sleep,
|
|
51
56
|
} from '@/utils';
|
|
52
57
|
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
getBlindRecoveryBudget,
|
|
59
|
+
planContextOverflowRecovery,
|
|
60
|
+
translateRecoveryBudget,
|
|
61
|
+
} from '@/llm/contextOverflowRecovery';
|
|
62
|
+
import {
|
|
63
|
+
attemptInvoke,
|
|
64
|
+
tryFallbackProviders,
|
|
65
|
+
getFallbackErrorContext,
|
|
66
|
+
getFallbackOverflowCandidates,
|
|
67
|
+
} from '@/llm/invoke';
|
|
56
68
|
import {
|
|
69
|
+
Constants,
|
|
57
70
|
GraphNodeKeys,
|
|
58
71
|
ContentTypes,
|
|
59
72
|
GraphEvents,
|
|
60
73
|
Providers,
|
|
61
74
|
StepTypes,
|
|
62
75
|
} from '@/common';
|
|
76
|
+
import {
|
|
77
|
+
resolveLangfuseRuntimeScope,
|
|
78
|
+
withLangfuseRuntimeScope,
|
|
79
|
+
} from '@/langfuseRuntimeScope';
|
|
63
80
|
import {
|
|
64
81
|
appendCallbacks,
|
|
65
82
|
findCallback,
|
|
@@ -74,7 +91,6 @@ import { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
|
74
91
|
import { partitionAndMarkBedrockToolCache } from '@/llm/bedrock/toolCache';
|
|
75
92
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
76
93
|
import { createCloudflareCodingToolBundle } from '@/tools/cloudflare';
|
|
77
|
-
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
78
94
|
import { buildSubagentToolParams } from '@/tools/SubagentTool';
|
|
79
95
|
import { initializeLangfuseTracing } from '@/instrumentation';
|
|
80
96
|
import { shouldTriggerSummarization } from '@/summarization';
|
|
@@ -369,6 +385,61 @@ function clearCurrentDeltaStepMarkers({
|
|
|
369
385
|
}
|
|
370
386
|
}
|
|
371
387
|
|
|
388
|
+
/**
|
|
389
|
+
* The completion allowance the caller configured, under whichever key the
|
|
390
|
+
* provider's client uses. Providers count it against the same ceiling as the
|
|
391
|
+
* prompt, so overflow recovery has to reserve it when the error did not
|
|
392
|
+
* itemize the total.
|
|
393
|
+
*/
|
|
394
|
+
function getConfiguredCompletionTokens(
|
|
395
|
+
clientOptions: t.ClientOptions | undefined
|
|
396
|
+
): number | undefined {
|
|
397
|
+
const options = clientOptions as
|
|
398
|
+
| { maxTokens?: unknown; maxOutputTokens?: unknown }
|
|
399
|
+
| undefined;
|
|
400
|
+
for (const value of [options?.maxTokens, options?.maxOutputTokens]) {
|
|
401
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
402
|
+
return value;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Our own estimate of the prompt that was actually sent, derived from the
|
|
410
|
+
* pre-invoke usage snapshot. Used to corroborate ambiguous provider errors
|
|
411
|
+
* and to measure how far our token accounting sits from the provider's.
|
|
412
|
+
*/
|
|
413
|
+
function getEstimatedPromptTokens(
|
|
414
|
+
contextUsage: t.ContextUsageEvent | null
|
|
415
|
+
): number | undefined {
|
|
416
|
+
const budget = contextUsage?.contextBudget;
|
|
417
|
+
const remaining = contextUsage?.remainingContextTokens;
|
|
418
|
+
if (
|
|
419
|
+
budget == null ||
|
|
420
|
+
remaining == null ||
|
|
421
|
+
!Number.isFinite(budget) ||
|
|
422
|
+
!Number.isFinite(remaining)
|
|
423
|
+
) {
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
const used = budget - remaining;
|
|
427
|
+
return used > 0 ? used : undefined;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function minDefined(
|
|
431
|
+
left: number | undefined,
|
|
432
|
+
right: number | undefined
|
|
433
|
+
): number | undefined {
|
|
434
|
+
if (left == null) {
|
|
435
|
+
return right;
|
|
436
|
+
}
|
|
437
|
+
if (right == null) {
|
|
438
|
+
return left;
|
|
439
|
+
}
|
|
440
|
+
return Math.min(left, right);
|
|
441
|
+
}
|
|
442
|
+
|
|
372
443
|
async function dispatchMessageCreationStep({
|
|
373
444
|
graph,
|
|
374
445
|
stepKey,
|
|
@@ -519,7 +590,7 @@ export abstract class Graph<
|
|
|
519
590
|
T extends t.BaseGraphState = t.BaseGraphState,
|
|
520
591
|
_TNodeName extends string = string,
|
|
521
592
|
> {
|
|
522
|
-
abstract resetValues(): void;
|
|
593
|
+
abstract resetValues(keepContent?: boolean, checkpointScope?: string): void;
|
|
523
594
|
abstract initializeTools({
|
|
524
595
|
currentTools,
|
|
525
596
|
currentToolMap,
|
|
@@ -861,9 +932,13 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
861
932
|
overrideModel?: t.ChatModel;
|
|
862
933
|
/** Optional compile options passed into workflow.compile() */
|
|
863
934
|
compileOptions?: t.CompileOptions | undefined;
|
|
935
|
+
/** Whether the workflow was actually compiled with a checkpointer. */
|
|
936
|
+
hasCompiledCheckpointer: boolean = false;
|
|
864
937
|
messages: BaseMessage[] = [];
|
|
865
938
|
/** Cached run messages preserved before clearHeavyState() so getRunMessages() works after cleanup. */
|
|
866
939
|
private cachedRunMessages?: BaseMessage[];
|
|
940
|
+
/** Checkpoint scope whose messages match index-keyed tool snapshots. */
|
|
941
|
+
private originalToolContentCheckpointScope?: string;
|
|
867
942
|
runId: string | undefined;
|
|
868
943
|
/**
|
|
869
944
|
* Boundary between historical messages (loaded from conversation state)
|
|
@@ -928,7 +1003,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
928
1003
|
|
|
929
1004
|
/* Init */
|
|
930
1005
|
|
|
931
|
-
resetValues(keepContent?: boolean): void {
|
|
1006
|
+
resetValues(keepContent?: boolean, checkpointScope?: string): void {
|
|
932
1007
|
this.messages = [];
|
|
933
1008
|
this.cachedRunMessages = undefined;
|
|
934
1009
|
this.config = resetIfNotEmpty(this.config, undefined);
|
|
@@ -976,9 +1051,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
976
1051
|
new Map()
|
|
977
1052
|
);
|
|
978
1053
|
this.invokedToolIds = resetIfNotEmpty(this.invokedToolIds, undefined);
|
|
1054
|
+
const hasScopedCheckpoint =
|
|
1055
|
+
this.hasCompiledCheckpointer &&
|
|
1056
|
+
checkpointScope != null &&
|
|
1057
|
+
checkpointScope !== '';
|
|
1058
|
+
const preserveOriginalToolContent =
|
|
1059
|
+
hasScopedCheckpoint &&
|
|
1060
|
+
this.originalToolContentCheckpointScope === checkpointScope;
|
|
979
1061
|
for (const context of this.agentContexts.values()) {
|
|
980
|
-
context.reset();
|
|
1062
|
+
context.reset({ preserveOriginalToolContent });
|
|
981
1063
|
}
|
|
1064
|
+
this.originalToolContentCheckpointScope = hasScopedCheckpoint
|
|
1065
|
+
? checkpointScope
|
|
1066
|
+
: undefined;
|
|
982
1067
|
}
|
|
983
1068
|
|
|
984
1069
|
override clearHeavyState(): void {
|
|
@@ -986,8 +1071,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
986
1071
|
super.clearHeavyState();
|
|
987
1072
|
this.messages = [];
|
|
988
1073
|
this.overrideModel = undefined;
|
|
1074
|
+
const preserveOriginalToolContent =
|
|
1075
|
+
this.hasCompiledCheckpointer &&
|
|
1076
|
+
this.originalToolContentCheckpointScope != null;
|
|
989
1077
|
for (const context of this.agentContexts.values()) {
|
|
990
|
-
context.reset();
|
|
1078
|
+
context.reset({ preserveOriginalToolContent });
|
|
991
1079
|
}
|
|
992
1080
|
}
|
|
993
1081
|
|
|
@@ -1444,6 +1532,82 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1444
1532
|
client.abortHandler = undefined;
|
|
1445
1533
|
}
|
|
1446
1534
|
|
|
1535
|
+
/**
|
|
1536
|
+
* Applies a context-overflow recovery plan and hands control to the
|
|
1537
|
+
* summarize node, which compacts and then routes straight back here for a
|
|
1538
|
+
* retry against the corrected budget.
|
|
1539
|
+
*
|
|
1540
|
+
* Returning the detour rather than rethrowing is the whole point: the
|
|
1541
|
+
* caller never sees the provider's rejection, only a slightly longer turn.
|
|
1542
|
+
*/
|
|
1543
|
+
private beginOverflowRecovery({
|
|
1544
|
+
recovery,
|
|
1545
|
+
agentContext,
|
|
1546
|
+
agentId,
|
|
1547
|
+
config,
|
|
1548
|
+
originalToolContent,
|
|
1549
|
+
estimatedPromptTokens,
|
|
1550
|
+
}: {
|
|
1551
|
+
recovery: OverflowRecoveryPlan;
|
|
1552
|
+
agentContext: AgentContext;
|
|
1553
|
+
agentId: string;
|
|
1554
|
+
config?: RunnableConfig;
|
|
1555
|
+
/** Masking record from the prune pass that built the rejected prompt. */
|
|
1556
|
+
originalToolContent?: Map<number, string>;
|
|
1557
|
+
/** Size of the rejected prompt, recorded to detect a correction that changed nothing. */
|
|
1558
|
+
estimatedPromptTokens?: number;
|
|
1559
|
+
}): Partial<t.AgentSubgraphState> {
|
|
1560
|
+
const previousBudget = agentContext.maxContextTokens;
|
|
1561
|
+
/**
|
|
1562
|
+
* Deterministic compaction first. Re-pruning against the corrected budget
|
|
1563
|
+
* raises context pressure, which is what drives the pruner's tool-output
|
|
1564
|
+
* truncation and observation masking — no model call, no cost, and no
|
|
1565
|
+
* message content lost. A summarization call is held back until that has
|
|
1566
|
+
* been tried and the provider rejected the prompt again.
|
|
1567
|
+
*/
|
|
1568
|
+
const allowSummarization = agentContext.shouldSummarizeOverflow();
|
|
1569
|
+
|
|
1570
|
+
agentContext.preserveOriginalToolContent(originalToolContent);
|
|
1571
|
+
agentContext.applyContextBudgetCorrection(
|
|
1572
|
+
recovery.budgetTokens,
|
|
1573
|
+
estimatedPromptTokens
|
|
1574
|
+
);
|
|
1575
|
+
agentContext.applyObservedOverflowCalibration(
|
|
1576
|
+
recovery.info.provider,
|
|
1577
|
+
recovery.observedCalibrationRatio
|
|
1578
|
+
);
|
|
1579
|
+
|
|
1580
|
+
emitAgentLog(
|
|
1581
|
+
config,
|
|
1582
|
+
'warn',
|
|
1583
|
+
'graph',
|
|
1584
|
+
'Provider rejected the prompt as too large — compacting and retrying',
|
|
1585
|
+
{
|
|
1586
|
+
kind: recovery.info.kind,
|
|
1587
|
+
previousBudget,
|
|
1588
|
+
recoveredBudget: recovery.budgetTokens,
|
|
1589
|
+
providerReportedLimit: recovery.info.limitTokens,
|
|
1590
|
+
providerReportedTokens: recovery.info.requestedTokens,
|
|
1591
|
+
providerReportedPromptTokens: recovery.info.promptTokens,
|
|
1592
|
+
observedCalibrationRatio: recovery.observedCalibrationRatio,
|
|
1593
|
+
detectedBy: recovery.info.source,
|
|
1594
|
+
attempt: agentContext.overflowRecoveryAttempts,
|
|
1595
|
+
compaction: allowSummarization ? 'summarize' : 'compress',
|
|
1596
|
+
},
|
|
1597
|
+
{ runId: this.runId, agentId },
|
|
1598
|
+
{ force: true }
|
|
1599
|
+
);
|
|
1600
|
+
|
|
1601
|
+
return {
|
|
1602
|
+
summarizationRequest: {
|
|
1603
|
+
remainingContextTokens: 0,
|
|
1604
|
+
agentId: agentId || agentContext.agentId,
|
|
1605
|
+
reason: 'overflow',
|
|
1606
|
+
allowSummarization,
|
|
1607
|
+
},
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1447
1611
|
createCallModel(agentId = 'default') {
|
|
1448
1612
|
return async (
|
|
1449
1613
|
state: t.AgentSubgraphState,
|
|
@@ -1566,6 +1730,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1566
1730
|
|
|
1567
1731
|
let messagesToUse = messages;
|
|
1568
1732
|
let contextUsage: t.ContextUsageEvent | null = null;
|
|
1733
|
+
/**
|
|
1734
|
+
* Held outside the prune block so overflow recovery — which detours to
|
|
1735
|
+
* the summarize node from the invoke catch below — can preserve the
|
|
1736
|
+
* same masking record the configured trigger preserves.
|
|
1737
|
+
*/
|
|
1738
|
+
let prunedOriginalToolContent: Map<number, string> | undefined;
|
|
1569
1739
|
if (
|
|
1570
1740
|
!agentContext.pruneMessages &&
|
|
1571
1741
|
agentContext.tokenCounter &&
|
|
@@ -1602,7 +1772,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1602
1772
|
messagesToRefine,
|
|
1603
1773
|
prePruneContextTokens,
|
|
1604
1774
|
remainingContextTokens,
|
|
1605
|
-
|
|
1775
|
+
newOriginalToolContent,
|
|
1606
1776
|
calibrationRatio,
|
|
1607
1777
|
resolvedInstructionOverhead,
|
|
1608
1778
|
contextBudget,
|
|
@@ -1613,6 +1783,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1613
1783
|
lastCallUsage: agentContext.lastCallUsage,
|
|
1614
1784
|
totalTokensFresh: agentContext.totalTokensFresh,
|
|
1615
1785
|
});
|
|
1786
|
+
prunedOriginalToolContent = newOriginalToolContent;
|
|
1787
|
+
/**
|
|
1788
|
+
* Masking rewrites tool content in `state.messages` in place, so this
|
|
1789
|
+
* map is the only surviving copy of the full output. Persist it on
|
|
1790
|
+
* every prune, not just when a summary is about to be written — the
|
|
1791
|
+
* pruner closure that produced it is discarded on the next reset, and
|
|
1792
|
+
* with it any chance of a later summary restoring the real content.
|
|
1793
|
+
* AgentContext bounds what accumulates.
|
|
1794
|
+
*/
|
|
1795
|
+
agentContext.preserveOriginalToolContent(newOriginalToolContent);
|
|
1616
1796
|
agentContext.indexTokenCountMap = indexTokenCountMap;
|
|
1617
1797
|
if (calibrationRatio != null && calibrationRatio > 0) {
|
|
1618
1798
|
agentContext.calibrationRatio = calibrationRatio;
|
|
@@ -1693,38 +1873,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1693
1873
|
});
|
|
1694
1874
|
|
|
1695
1875
|
if (triggerResult) {
|
|
1696
|
-
if (originalToolContent != null && originalToolContent.size > 0) {
|
|
1697
|
-
/**
|
|
1698
|
-
* Merge — never overwrite — the pruner's masking record
|
|
1699
|
-
* into pendingOriginalToolContent. Carry-over entries
|
|
1700
|
-
* from a prior summarize (preserved by the recency
|
|
1701
|
-
* window for masked tool messages still in the tail) and
|
|
1702
|
-
* the current pruner's new entries are both keyed by
|
|
1703
|
-
* indices in the current `state.messages`, so a key-wise
|
|
1704
|
-
* union is correct. Overwriting would discard the
|
|
1705
|
-
* carry-over and reduce summary fidelity when those
|
|
1706
|
-
* masked tail messages eventually move into the head.
|
|
1707
|
-
*/
|
|
1708
|
-
if (agentContext.pendingOriginalToolContent == null) {
|
|
1709
|
-
agentContext.pendingOriginalToolContent = originalToolContent;
|
|
1710
|
-
} else {
|
|
1711
|
-
for (const [idx, content] of originalToolContent) {
|
|
1712
|
-
agentContext.pendingOriginalToolContent.set(idx, content);
|
|
1713
|
-
}
|
|
1714
|
-
/**
|
|
1715
|
-
* Re-apply the per-store char cap after the union. The
|
|
1716
|
-
* pruner enforces ORIGINAL_CONTENT_MAX_CHARS inside its
|
|
1717
|
-
* own map via the onContentStored callback, but a
|
|
1718
|
-
* key-wise merge with recency carry-over bypasses that
|
|
1719
|
-
* accounting and could let the merged map grow without
|
|
1720
|
-
* bound across long sessions.
|
|
1721
|
-
*/
|
|
1722
|
-
enforceOriginalContentCap(
|
|
1723
|
-
agentContext.pendingOriginalToolContent
|
|
1724
|
-
);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
1876
|
emitAgentLog(
|
|
1729
1877
|
config,
|
|
1730
1878
|
'info',
|
|
@@ -1864,6 +2012,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1864
2012
|
);
|
|
1865
2013
|
}
|
|
1866
2014
|
|
|
2015
|
+
/**
|
|
2016
|
+
* A destination that binds no tools is invoked without a tool schema, but
|
|
2017
|
+
* in a multi-agent graph it can still inherit a prior agent's toolUse/
|
|
2018
|
+
* toolResult history. Bedrock's Converse API (and other tool-schema-strict
|
|
2019
|
+
* providers) reject such a request when no top-level toolConfig is sent.
|
|
2020
|
+
* Fold that historical tool content into plain text so the tool-less agent
|
|
2021
|
+
* receives valid, context-preserving messages. Handoff tools count as
|
|
2022
|
+
* bound tools, so a tool-less router mid-handoff is not affected.
|
|
2023
|
+
*/
|
|
2024
|
+
if (toolsForBinding == null || toolsForBinding.length === 0) {
|
|
2025
|
+
finalMessages = foldToolBlocksForToollessAgent(finalMessages, config);
|
|
2026
|
+
// The fold emits structured (array) content; re-flatten for agents that
|
|
2027
|
+
// opted into string-only messages (`useLegacyContent`, run earlier at
|
|
2028
|
+
// the top of this block) so the folded turn isn't the lone exception.
|
|
2029
|
+
if (agentContext.useLegacyContent) {
|
|
2030
|
+
finalMessages = formatContentStrings(finalMessages);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
1867
2034
|
// Determine the prompt-cache strategy up front. Two distinct facts:
|
|
1868
2035
|
//
|
|
1869
2036
|
// `providerPromptCacheEnabled` — prompt caching is on for this provider
|
|
@@ -2200,21 +2367,165 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2200
2367
|
graph: this,
|
|
2201
2368
|
metadata,
|
|
2202
2369
|
});
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2370
|
+
/**
|
|
2371
|
+
* A context overflow is a deterministic consequence of the payload,
|
|
2372
|
+
* not a provider being unavailable — so it is answered by compacting
|
|
2373
|
+
* and retrying rather than by re-sending the same oversized prompt
|
|
2374
|
+
* down the fallback chain. Fallbacks still run for every other
|
|
2375
|
+
* failure, and for an overflow whose recovery budget is spent.
|
|
2376
|
+
*/
|
|
2377
|
+
/**
|
|
2378
|
+
* Compaction has to have something to work with. Without a token
|
|
2379
|
+
* counter there is no pruner, and with summarization disabled the
|
|
2380
|
+
* summarize node deliberately no-ops — so in that combination the
|
|
2381
|
+
* retry would resend a byte-identical prompt. Skipping the detour
|
|
2382
|
+
* keeps the original error and one round trip instead of three.
|
|
2383
|
+
*/
|
|
2384
|
+
const estimatedPromptTokens = getEstimatedPromptTokens(contextUsage);
|
|
2385
|
+
|
|
2386
|
+
/**
|
|
2387
|
+
* A previous correction that left the prompt no smaller proves this
|
|
2388
|
+
* state has nothing left to compact — an emptied message list whose
|
|
2389
|
+
* content rides along in an injected summary, for instance. Measuring
|
|
2390
|
+
* that beats trying to predict every such configuration.
|
|
2391
|
+
*/
|
|
2392
|
+
const recoveryStalled = agentContext.overflowRecoveryStalled(
|
|
2393
|
+
estimatedPromptTokens
|
|
2217
2394
|
);
|
|
2395
|
+
const canSummarizeOverflow =
|
|
2396
|
+
agentContext.summarizationEnabled === true &&
|
|
2397
|
+
splitAtRecencyBoundary(messages, {
|
|
2398
|
+
turns:
|
|
2399
|
+
agentContext.summarizationConfig?.retainRecent?.turns ??
|
|
2400
|
+
DEFAULT_RETAIN_RECENT_TURNS,
|
|
2401
|
+
tokens: agentContext.summarizationConfig?.retainRecent?.tokens,
|
|
2402
|
+
tokenCounter: agentContext.tokenCounter,
|
|
2403
|
+
}).head.length > 0;
|
|
2404
|
+
|
|
2405
|
+
const planRecovery = (
|
|
2406
|
+
error: unknown,
|
|
2407
|
+
attributedFallbackContext?: FallbackErrorContext
|
|
2408
|
+
): OverflowRecoveryPlan | null => {
|
|
2409
|
+
if (recoveryStalled) {
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* When the rejection came from a fallback, plan against *that*
|
|
2414
|
+
* client: its window and output allowance are why it was configured
|
|
2415
|
+
* as an alternative in the first place.
|
|
2416
|
+
*/
|
|
2417
|
+
const fallbackContext =
|
|
2418
|
+
attributedFallbackContext ?? getFallbackErrorContext(error);
|
|
2419
|
+
const recovery = planContextOverflowRecovery({
|
|
2420
|
+
error,
|
|
2421
|
+
provider: fallbackContext?.provider ?? agentContext.provider,
|
|
2422
|
+
maxContextTokens:
|
|
2423
|
+
fallbackContext?.maxContextTokens ??
|
|
2424
|
+
agentContext.maxContextTokens,
|
|
2425
|
+
estimatedPromptTokens,
|
|
2426
|
+
calibrationRatio: agentContext.calibrationRatio,
|
|
2427
|
+
instructionTokens: agentContext.instructionTokens,
|
|
2428
|
+
canSummarize: agentContext.summarizationEnabled === true,
|
|
2429
|
+
configuredCompletionTokens: getConfiguredCompletionTokens(
|
|
2430
|
+
fallbackContext?.clientOptions ?? agentContext.clientOptions
|
|
2431
|
+
),
|
|
2432
|
+
attemptsSoFar: agentContext.overflowRecoveryAttempts,
|
|
2433
|
+
});
|
|
2434
|
+
if (recovery == null) {
|
|
2435
|
+
return null;
|
|
2436
|
+
}
|
|
2437
|
+
const translatedRecovery =
|
|
2438
|
+
fallbackContext != null
|
|
2439
|
+
? {
|
|
2440
|
+
...recovery,
|
|
2441
|
+
budgetTokens: minDefined(
|
|
2442
|
+
getBlindRecoveryBudget(agentContext.maxContextTokens),
|
|
2443
|
+
translateRecoveryBudget(
|
|
2444
|
+
recovery.budgetTokens,
|
|
2445
|
+
recovery.observedCalibrationRatio ??
|
|
2446
|
+
CALIBRATION_RATIO_MAX,
|
|
2447
|
+
agentContext.calibrationRatio
|
|
2448
|
+
)
|
|
2449
|
+
),
|
|
2450
|
+
observedCalibrationRatio: undefined,
|
|
2451
|
+
}
|
|
2452
|
+
: recovery;
|
|
2453
|
+
const canReduceContext =
|
|
2454
|
+
canSummarizeOverflow ||
|
|
2455
|
+
(agentContext.tokenCounter != null &&
|
|
2456
|
+
translatedRecovery.budgetTokens != null);
|
|
2457
|
+
return canReduceContext ? translatedRecovery : null;
|
|
2458
|
+
};
|
|
2459
|
+
|
|
2460
|
+
const recovery = planRecovery(primaryError);
|
|
2461
|
+
if (recovery != null) {
|
|
2462
|
+
return this.beginOverflowRecovery({
|
|
2463
|
+
recovery,
|
|
2464
|
+
agentContext,
|
|
2465
|
+
agentId,
|
|
2466
|
+
config,
|
|
2467
|
+
originalToolContent: prunedOriginalToolContent,
|
|
2468
|
+
estimatedPromptTokens,
|
|
2469
|
+
});
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
/**
|
|
2473
|
+
* A fallback can reject the same prompt as too large even when the
|
|
2474
|
+
* primary failed for an unrelated reason — a fallback with a smaller
|
|
2475
|
+
* window is the obvious case. Planning against the exhausted-chain
|
|
2476
|
+
* error keeps that path recoverable instead of surfacing it.
|
|
2477
|
+
*/
|
|
2478
|
+
try {
|
|
2479
|
+
result = await withLangfuseRuntimeScope(
|
|
2480
|
+
resolveLangfuseRuntimeScope({
|
|
2481
|
+
runLangfuse: this.langfuse,
|
|
2482
|
+
langfuseOverlay: agentContext.langfuse,
|
|
2483
|
+
}),
|
|
2484
|
+
() =>
|
|
2485
|
+
tryFallbackProviders({
|
|
2486
|
+
fallbacks,
|
|
2487
|
+
tools: agentContext.tools,
|
|
2488
|
+
messages: finalMessages,
|
|
2489
|
+
config: invokeConfig,
|
|
2490
|
+
primaryError,
|
|
2491
|
+
context: this,
|
|
2492
|
+
/**
|
|
2493
|
+
* Lets the chain recognise a fallback overflow whose signature
|
|
2494
|
+
* carries no reason of its own (Vertex AI's bare 400) and
|
|
2495
|
+
* surface it rather than a later unrelated failure.
|
|
2496
|
+
*/
|
|
2497
|
+
overflowContext: {
|
|
2498
|
+
provider: agentContext.provider,
|
|
2499
|
+
estimatedPromptTokens: getEstimatedPromptTokens(contextUsage),
|
|
2500
|
+
maxContextTokens: agentContext.maxContextTokens,
|
|
2501
|
+
},
|
|
2502
|
+
})
|
|
2503
|
+
);
|
|
2504
|
+
} catch (fallbackError) {
|
|
2505
|
+
const overflowCandidates =
|
|
2506
|
+
getFallbackOverflowCandidates(fallbackError);
|
|
2507
|
+
let fallbackRecovery: OverflowRecoveryPlan | null = null;
|
|
2508
|
+
for (const candidate of overflowCandidates) {
|
|
2509
|
+
fallbackRecovery = planRecovery(candidate.error, candidate.context);
|
|
2510
|
+
if (fallbackRecovery != null) {
|
|
2511
|
+
break;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
if (overflowCandidates.length === 0) {
|
|
2515
|
+
fallbackRecovery = planRecovery(fallbackError);
|
|
2516
|
+
}
|
|
2517
|
+
if (fallbackRecovery == null) {
|
|
2518
|
+
throw fallbackError;
|
|
2519
|
+
}
|
|
2520
|
+
return this.beginOverflowRecovery({
|
|
2521
|
+
recovery: fallbackRecovery,
|
|
2522
|
+
agentContext,
|
|
2523
|
+
agentId,
|
|
2524
|
+
config,
|
|
2525
|
+
originalToolContent: prunedOriginalToolContent,
|
|
2526
|
+
estimatedPromptTokens,
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2218
2529
|
} finally {
|
|
2219
2530
|
await disposeLangfuseHandler(langfuseHandler);
|
|
2220
2531
|
}
|
|
@@ -2573,10 +2884,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2573
2884
|
isMultiAgent: this.isMultiAgentGraph(),
|
|
2574
2885
|
hookRegistry: this.hookRegistry,
|
|
2575
2886
|
dispatchRunStep: async (runStep, nodeConfig) => {
|
|
2887
|
+
const resolvedConfig = nodeConfig ?? this.config;
|
|
2888
|
+
if (runStep.agentId != null) {
|
|
2889
|
+
const groupId = this.resolveParallelGroupId(
|
|
2890
|
+
runStep.agentId,
|
|
2891
|
+
resolvedConfig?.metadata
|
|
2892
|
+
);
|
|
2893
|
+
if (groupId != null) {
|
|
2894
|
+
runStep.groupId = groupId;
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2576
2897
|
this.contentData.push(runStep);
|
|
2577
2898
|
this.contentIndexMap.set(runStep.id, runStep.index);
|
|
2578
2899
|
|
|
2579
|
-
const resolvedConfig = nodeConfig ?? this.config;
|
|
2580
2900
|
const handler = this.handlerRegistry?.getHandler(
|
|
2581
2901
|
GraphEvents.ON_RUN_STEP
|
|
2582
2902
|
);
|
|
@@ -2646,6 +2966,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2646
2966
|
}
|
|
2647
2967
|
|
|
2648
2968
|
createWorkflow(): t.CompiledStateWorkflow {
|
|
2969
|
+
this.hasCompiledCheckpointer = this.compileOptions?.checkpointer != null;
|
|
2649
2970
|
const agentNode = this.createAgentNode(this.defaultAgentId);
|
|
2650
2971
|
const StateAnnotation = Annotation.Root({
|
|
2651
2972
|
messages: Annotation<BaseMessage[]>({
|
|
@@ -2696,6 +3017,33 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2696
3017
|
return undefined;
|
|
2697
3018
|
}
|
|
2698
3019
|
|
|
3020
|
+
protected resolveParallelGroupId(
|
|
3021
|
+
agentId: string,
|
|
3022
|
+
metadata?: Record<string, unknown>
|
|
3023
|
+
): number | undefined {
|
|
3024
|
+
if (
|
|
3025
|
+
metadata == null ||
|
|
3026
|
+
!Object.prototype.hasOwnProperty.call(
|
|
3027
|
+
metadata,
|
|
3028
|
+
Constants.HANDOFF_GROUP_ID
|
|
3029
|
+
)
|
|
3030
|
+
) {
|
|
3031
|
+
return this.getParallelGroupIdForAgent(agentId);
|
|
3032
|
+
}
|
|
3033
|
+
const runtimeGroupId = metadata[Constants.HANDOFF_GROUP_ID];
|
|
3034
|
+
if (runtimeGroupId === null) {
|
|
3035
|
+
return undefined;
|
|
3036
|
+
}
|
|
3037
|
+
if (
|
|
3038
|
+
typeof runtimeGroupId === 'number' &&
|
|
3039
|
+
Number.isSafeInteger(runtimeGroupId) &&
|
|
3040
|
+
runtimeGroupId > 0
|
|
3041
|
+
) {
|
|
3042
|
+
return runtimeGroupId;
|
|
3043
|
+
}
|
|
3044
|
+
return this.getParallelGroupIdForAgent(agentId);
|
|
3045
|
+
}
|
|
3046
|
+
|
|
2699
3047
|
/* Dispatchers */
|
|
2700
3048
|
|
|
2701
3049
|
/**
|
|
@@ -2740,7 +3088,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2740
3088
|
const agentContext = this.getAgentContext(metadata);
|
|
2741
3089
|
if (this.isMultiAgentGraph() && agentContext.agentId) {
|
|
2742
3090
|
runStep.agentId = agentContext.agentId;
|
|
2743
|
-
const groupId = this.
|
|
3091
|
+
const groupId = this.resolveParallelGroupId(
|
|
3092
|
+
agentContext.agentId,
|
|
3093
|
+
metadata
|
|
3094
|
+
);
|
|
2744
3095
|
if (groupId != null) {
|
|
2745
3096
|
runStep.groupId = groupId;
|
|
2746
3097
|
}
|