@librechat/agents 3.7.7 → 3.7.9
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 +26 -5
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +12 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +56 -11
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +8 -0
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +2 -0
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +43 -3
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +75 -0
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +1 -1
- package/dist/cjs/llm/fake.cjs +9 -5
- package/dist/cjs/llm/fake.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +3 -7
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +2 -2
- package/dist/cjs/llm/providers.cjs +1 -1
- package/dist/cjs/llm/request.cjs +6 -0
- package/dist/cjs/llm/request.cjs.map +1 -1
- package/dist/cjs/llm/truncation.cjs +1 -0
- package/dist/cjs/main.cjs +22 -13
- package/dist/cjs/messages/format.cjs +375 -3
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +1 -1
- package/dist/cjs/run.cjs +217 -52
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/summarization/index.cjs +1 -0
- package/dist/cjs/summarization/index.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +58 -85
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/summarization/semanticIndex.cjs +366 -0
- package/dist/cjs/summarization/semanticIndex.cjs.map +1 -0
- package/dist/cjs/summarization/shared.cjs +83 -0
- package/dist/cjs/summarization/shared.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +5 -5
- package/dist/cjs/tools/local/CompileCheckTool.cjs +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +1 -1
- package/dist/cjs/tools/runStepResume.cjs +1 -1
- package/dist/cjs/tools/runStepResume.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +8 -12
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +2 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/tokens.cjs +8 -1
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +26 -5
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +11 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +56 -11
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +8 -1
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +2 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +43 -3
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +75 -0
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +1 -1
- package/dist/esm/llm/fake.mjs +9 -5
- package/dist/esm/llm/fake.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +3 -7
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +2 -2
- package/dist/esm/llm/providers.mjs +1 -1
- package/dist/esm/llm/request.mjs +6 -1
- package/dist/esm/llm/request.mjs.map +1 -1
- package/dist/esm/llm/truncation.mjs +1 -1
- package/dist/esm/main.mjs +15 -14
- package/dist/esm/messages/format.mjs +375 -3
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +1 -1
- package/dist/esm/run.mjs +217 -52
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/summarization/index.mjs +1 -0
- package/dist/esm/summarization/index.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +57 -84
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/summarization/semanticIndex.mjs +363 -0
- package/dist/esm/summarization/semanticIndex.mjs.map +1 -0
- package/dist/esm/summarization/shared.mjs +79 -0
- package/dist/esm/summarization/shared.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +5 -5
- package/dist/esm/tools/local/CompileCheckTool.mjs +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +1 -1
- package/dist/esm/tools/runStepResume.mjs +1 -1
- package/dist/esm/tools/runStepResume.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +8 -12
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +2 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/tokens.mjs +8 -2
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +27 -1
- package/dist/types/common/constants.d.ts +17 -0
- package/dist/types/graphs/Graph.d.ts +24 -0
- package/dist/types/hooks/executeHooks.d.ts +5 -2
- package/dist/types/hooks/index.d.ts +7 -2
- package/dist/types/hooks/types.d.ts +45 -7
- package/dist/types/langfuse.d.ts +3 -1
- package/dist/types/llm/fake.d.ts +12 -2
- package/dist/types/llm/request.d.ts +10 -0
- package/dist/types/messages/format.d.ts +17 -2
- package/dist/types/run.d.ts +19 -6
- package/dist/types/summarization/index.d.ts +6 -0
- package/dist/types/summarization/node.d.ts +0 -4
- package/dist/types/summarization/semanticIndex.d.ts +21 -0
- package/dist/types/summarization/shared.d.ts +25 -0
- package/dist/types/types/graph.d.ts +9 -1
- package/dist/types/types/run.d.ts +8 -0
- package/dist/types/types/stream.d.ts +6 -0
- package/dist/types/types/summarize.d.ts +44 -0
- package/dist/types/utils/tokens.d.ts +9 -0
- package/package.json +2 -1
- package/src/agents/AgentContext.ts +63 -6
- package/src/common/constants.ts +19 -0
- package/src/graphs/Graph.ts +86 -8
- package/src/hooks/HookRegistry.ts +3 -1
- package/src/hooks/executeHooks.ts +20 -2
- package/src/hooks/index.ts +12 -1
- package/src/hooks/types.ts +47 -4
- package/src/langfuse.ts +70 -0
- package/src/langfuseTraceShaping.ts +94 -0
- package/src/llm/fake.ts +35 -4
- package/src/llm/invoke.ts +13 -29
- package/src/llm/request.ts +24 -0
- package/src/messages/format.ts +839 -5
- package/src/run.ts +393 -187
- package/src/summarization/index.ts +11 -0
- package/src/summarization/node.ts +202 -158
- package/src/summarization/semanticIndex.ts +662 -0
- package/src/summarization/shared.ts +130 -0
- package/src/tools/runStepResume.ts +9 -0
- package/src/tools/subagent/SubagentExecutor.ts +4 -27
- package/src/tools/subagent/childGraphConfig.ts +3 -0
- package/src/types/graph.ts +9 -0
- package/src/types/run.ts +8 -0
- package/src/types/stream.ts +6 -0
- package/src/types/summarize.ts +57 -0
- package/src/utils/tokens.ts +31 -8
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { SummarizationTrigger } from '@/types';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The summary-boundary seam a caller compacting outside a run needs. The two
|
|
5
|
+
* default prompts stay out of it: LibreChat's manual flow deliberately words
|
|
6
|
+
* its own, so exporting these would publish an API with no consumer.
|
|
7
|
+
*/
|
|
8
|
+
export {
|
|
9
|
+
buildSummarizationInstruction,
|
|
10
|
+
buildSummaryCarrierText,
|
|
11
|
+
separateSummarizationParameters,
|
|
12
|
+
} from './shared';
|
|
13
|
+
|
|
3
14
|
const VALID_TRIGGER_TYPES = [
|
|
4
15
|
'token_ratio',
|
|
5
16
|
'remaining_tokens',
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AIMessage,
|
|
3
|
-
ToolMessage,
|
|
4
|
-
HumanMessage,
|
|
5
|
-
SystemMessage,
|
|
6
|
-
} from '@langchain/core/messages';
|
|
1
|
+
import { AIMessage, ToolMessage, HumanMessage } from '@langchain/core/messages';
|
|
7
2
|
import type { UsageMetadata, BaseMessage } from '@langchain/core/messages';
|
|
8
3
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
4
|
+
import type { RenderedCompactionSemanticIndex } from '@/summarization/semanticIndex';
|
|
9
5
|
import type { StreamLimitState } from '@/llm/streamLimits';
|
|
10
6
|
import type { AgentContext } from '@/agents/AgentContext';
|
|
7
|
+
import type { EncodingName } from '@/utils/tokens';
|
|
11
8
|
import type { HookRegistry } from '@/hooks';
|
|
12
9
|
import type { OnChunk } from '@/llm/invoke';
|
|
13
10
|
import type * as t from '@/types';
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_SUMMARIZATION_PROMPT,
|
|
13
|
+
DEFAULT_UPDATE_SUMMARIZATION_PROMPT,
|
|
14
|
+
buildSummaryCarrierText,
|
|
15
|
+
separateSummarizationParameters,
|
|
16
|
+
buildSummarizationInstruction,
|
|
17
|
+
} from './shared';
|
|
18
|
+
import {
|
|
19
|
+
addTailCacheControl,
|
|
20
|
+
addBedrockTailCacheControl,
|
|
21
|
+
resolvePromptCacheTtl,
|
|
22
|
+
resolveBedrockPromptCacheTtl,
|
|
23
|
+
type PromptCacheTtl,
|
|
24
|
+
} from '@/messages/cache';
|
|
14
25
|
import {
|
|
15
26
|
cloneToolMessageWithContent,
|
|
16
27
|
compactToolContent,
|
|
@@ -22,18 +33,16 @@ import {
|
|
|
22
33
|
StreamLimitExceededError,
|
|
23
34
|
STREAM_LIMIT_EPOCH_KEY,
|
|
24
35
|
} from '@/llm/streamLimits';
|
|
25
|
-
import {
|
|
26
|
-
addTailCacheControl,
|
|
27
|
-
addBedrockTailCacheControl,
|
|
28
|
-
resolvePromptCacheTtl,
|
|
29
|
-
resolveBedrockPromptCacheTtl,
|
|
30
|
-
type PromptCacheTtl,
|
|
31
|
-
} from '@/messages/cache';
|
|
32
36
|
import {
|
|
33
37
|
DEFAULT_RETAIN_RECENT_TURNS,
|
|
34
38
|
resolveIntraTurnRetainTokens,
|
|
35
39
|
splitAtRecencyBoundary,
|
|
36
40
|
} from '@/messages/recency';
|
|
41
|
+
import {
|
|
42
|
+
createTokenCounter,
|
|
43
|
+
encodingForModel,
|
|
44
|
+
encodingOfTokenCounter,
|
|
45
|
+
} from '@/utils/tokens';
|
|
37
46
|
import {
|
|
38
47
|
Constants,
|
|
39
48
|
ContentTypes,
|
|
@@ -41,19 +50,22 @@ import {
|
|
|
41
50
|
StepTypes,
|
|
42
51
|
Providers,
|
|
43
52
|
} from '@/common';
|
|
53
|
+
import {
|
|
54
|
+
getMaxOutputTokensKey,
|
|
55
|
+
resolveClientOptionsModel,
|
|
56
|
+
} from '@/llm/request';
|
|
57
|
+
import { renderCompactionSemanticIndex } from '@/summarization/semanticIndex';
|
|
44
58
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
45
|
-
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
46
59
|
import { prepareToolsForPromptCache } from '@/llm/promptCacheTools';
|
|
60
|
+
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
47
61
|
import { calculateMaxToolResultChars } from '@/utils/truncation';
|
|
48
62
|
import { makeIsDeferred } from '@/messages/anthropicToolCache';
|
|
49
63
|
import { createRemoveAllMessage } from '@/messages/reducer';
|
|
50
|
-
import {
|
|
64
|
+
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
51
65
|
import { initializeModel } from '@/llm/init';
|
|
52
66
|
import { getChunkContent } from '@/stream';
|
|
53
67
|
import { executeHooks } from '@/hooks';
|
|
54
68
|
|
|
55
|
-
const SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);
|
|
56
|
-
|
|
57
69
|
/**
|
|
58
70
|
* Default number of recent user-led turns preserved verbatim during
|
|
59
71
|
* compaction. A turn begins at a HumanMessage and includes every
|
|
@@ -64,89 +76,6 @@ const SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);
|
|
|
64
76
|
* `retainRecent.turns` to `0` reverts to the legacy "summarize every
|
|
65
77
|
* message" behavior.
|
|
66
78
|
*/
|
|
67
|
-
/**
|
|
68
|
-
* Token overhead of the XML wrapper + instruction text added around the
|
|
69
|
-
* summary at injection time in AgentContext.buildSystemRunnable:
|
|
70
|
-
* `<summary>\n${text}\n</summary>\n\nYour context window was compacted...`
|
|
71
|
-
* ~33 tokens on Anthropic, ~24-27 on OpenAI. Using 33 as a safe ceiling.
|
|
72
|
-
*/
|
|
73
|
-
const SUMMARY_WRAPPER_OVERHEAD_TOKENS = 33;
|
|
74
|
-
|
|
75
|
-
/** Structured checkpoint prompt for fresh summarization (no prior summary). */
|
|
76
|
-
export const DEFAULT_SUMMARIZATION_PROMPT = `Hold on, before you continue I need you to write me a checkpoint of everything so far. Your context window is filling up and this checkpoint replaces the messages above, so capture everything you need to pick right back up.
|
|
77
|
-
|
|
78
|
-
Don't second-guess or fact-check anything you did, your tool results reflect exactly what happened. If a tool result appears truncated, that's just a display artifact from context management: the tool executed fully. Just record what you did and what you observed. Only the checkpoint, don't respond to me or continue the conversation.
|
|
79
|
-
|
|
80
|
-
## Checkpoint
|
|
81
|
-
|
|
82
|
-
## Goal
|
|
83
|
-
What I asked you to do and any sub-goals you identified.
|
|
84
|
-
|
|
85
|
-
## Constraints & Preferences
|
|
86
|
-
Any rules, preferences, or configuration I established.
|
|
87
|
-
|
|
88
|
-
## Progress
|
|
89
|
-
### Done
|
|
90
|
-
- What you completed and the outcomes
|
|
91
|
-
|
|
92
|
-
### In Progress
|
|
93
|
-
- What you're currently working on
|
|
94
|
-
|
|
95
|
-
## Key Decisions
|
|
96
|
-
Decisions you made and why.
|
|
97
|
-
|
|
98
|
-
## Next Steps
|
|
99
|
-
Concrete task actions remaining, in priority order.
|
|
100
|
-
|
|
101
|
-
## Critical Context
|
|
102
|
-
Exact identifiers, names, error messages, URLs, and details you need to preserve verbatim.
|
|
103
|
-
|
|
104
|
-
Rules:
|
|
105
|
-
- Record what you did and observed, don't judge or re-evaluate it
|
|
106
|
-
- For each tool call: the tool name, key inputs, and the outcome
|
|
107
|
-
- Preserve exact identifiers, names, errors, and references verbatim
|
|
108
|
-
- Short declarative sentences
|
|
109
|
-
- Skip empty sections`;
|
|
110
|
-
|
|
111
|
-
/** Prompt for re-compaction when a prior summary exists. */
|
|
112
|
-
export const DEFAULT_UPDATE_SUMMARIZATION_PROMPT = `Hold on again, update your checkpoint. Merge the new messages into your existing checkpoint and give me a single consolidated replacement.
|
|
113
|
-
|
|
114
|
-
Keep it roughly the same length as your last checkpoint. Compress older details to make room for what's new, don't just append. Give recent actions more detail, compress older items to one-liners.
|
|
115
|
-
|
|
116
|
-
Don't fact-check or second-guess anything, your tool results are ground truth. If a tool result appears truncated, that's just a display artifact: the tool executed fully. Only the checkpoint, don't respond to me or continue the conversation.
|
|
117
|
-
|
|
118
|
-
Rules:
|
|
119
|
-
- Merge new progress into existing sections, don't duplicate headers
|
|
120
|
-
- Compress older completed items into one-line entries
|
|
121
|
-
- Move items from "In Progress" to "Done" when you completed them
|
|
122
|
-
- Update "Next Steps" to reflect current task priorities.
|
|
123
|
-
- For each new tool call: the tool name, key inputs, and the outcome
|
|
124
|
-
- Preserve exact identifiers, names, errors, and references verbatim
|
|
125
|
-
- Skip empty sections`;
|
|
126
|
-
|
|
127
|
-
function separateParameters(parameters: Record<string, unknown>): {
|
|
128
|
-
llmParams: Record<string, unknown>;
|
|
129
|
-
maxSummaryTokens?: number;
|
|
130
|
-
} {
|
|
131
|
-
const llmParams: Record<string, unknown> = {};
|
|
132
|
-
let maxSummaryTokens: number | undefined;
|
|
133
|
-
|
|
134
|
-
for (const [key, value] of Object.entries(parameters)) {
|
|
135
|
-
if (SUMMARIZATION_PARAM_KEYS.has(key)) {
|
|
136
|
-
if (
|
|
137
|
-
key === 'maxSummaryTokens' &&
|
|
138
|
-
typeof value === 'number' &&
|
|
139
|
-
value > 0
|
|
140
|
-
) {
|
|
141
|
-
maxSummaryTokens = value;
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
llmParams[key] = value;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { llmParams, maxSummaryTokens };
|
|
149
|
-
}
|
|
150
79
|
|
|
151
80
|
/**
|
|
152
81
|
* Generates a structural metadata summary without making an LLM call.
|
|
@@ -338,7 +267,7 @@ function buildSummarizationClientConfig(
|
|
|
338
267
|
summarizationConfig?.updatePrompt ?? DEFAULT_UPDATE_SUMMARIZATION_PROMPT;
|
|
339
268
|
|
|
340
269
|
const { llmParams, maxSummaryTokens: paramMaxSummaryTokens } =
|
|
341
|
-
|
|
270
|
+
separateSummarizationParameters(parameters);
|
|
342
271
|
|
|
343
272
|
const isSelfSummarize = provider === (agentContext.provider as string);
|
|
344
273
|
const baseOptions =
|
|
@@ -373,23 +302,104 @@ function buildSummarizationClientConfig(
|
|
|
373
302
|
};
|
|
374
303
|
}
|
|
375
304
|
|
|
376
|
-
/**
|
|
377
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Sizes a summary from the text that will actually be re-injected, carrier
|
|
307
|
+
* included, so the stored count is a measurement rather than a body count plus
|
|
308
|
+
* a remembered constant.
|
|
309
|
+
*
|
|
310
|
+
* The provider's `output_tokens` is deliberately not consulted. On a reasoning
|
|
311
|
+
* summarizer the two diverge by the hidden thinking, which is billed but never
|
|
312
|
+
* written into the checkpoint, so using it here would make every later context
|
|
313
|
+
* calculation reserve room for tokens that are never sent. Provider usage stays
|
|
314
|
+
* exclusively a billing input.
|
|
315
|
+
*
|
|
316
|
+
* A missing host counter is not hypothetical: `shouldSummarizeOverflow` fires
|
|
317
|
+
* precisely when there is nothing to count with, so that branch is the
|
|
318
|
+
* overflow-recovery summary, and its count is persisted and then reserved by
|
|
319
|
+
* `AgentContext.instructionTokens` on the retry. Undercounting it is what makes
|
|
320
|
+
* the retry overflow again, which rules out a character heuristic: measured
|
|
321
|
+
* against `o200k_base` and Anthropic's tokenizer, four-characters-per-token
|
|
322
|
+
* understates base64 by 1.5x and Korean by 4.6x, and coefficients large enough
|
|
323
|
+
* to cover those overestimate English prose by roughly 4x. So this falls back
|
|
324
|
+
* to the tokenizer this package already bundles rather than to an estimate.
|
|
325
|
+
*
|
|
326
|
+
* A host counter that is present is not automatically the right one either. In
|
|
327
|
+
* a heterogeneous multi-agent run `Run.create` derives a single counter from
|
|
328
|
+
* `agents[0]` and `StandardGraph` hands that same counter to every
|
|
329
|
+
* `AgentContext`, so a Claude agent behind a GPT first agent would otherwise
|
|
330
|
+
* measure its carrier in `o200k_base` and under-reserve by up to 1.9x on CJK.
|
|
331
|
+
* When the counter came from `createTokenCounter` its encoding is known, so a
|
|
332
|
+
* disagreement with the receiving agent's encoding takes the bundled path
|
|
333
|
+
* instead. A counter the host built itself is unstamped and stays authoritative:
|
|
334
|
+
* its units are the ones the rest of that host's accounting is denominated in.
|
|
335
|
+
*/
|
|
336
|
+
async function computeSummaryTokenCount(
|
|
378
337
|
summaryText: string,
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
338
|
+
agentContext: AgentContext
|
|
339
|
+
): Promise<number> {
|
|
340
|
+
const carrier = new HumanMessage(buildSummaryCarrierText(summaryText));
|
|
341
|
+
const encoding = encodingForReceivingAgent(agentContext);
|
|
342
|
+
const hostCounter = agentContext.tokenCounter;
|
|
343
|
+
const hostEncoding =
|
|
344
|
+
hostCounter != null ? encodingOfTokenCounter(hostCounter) : undefined;
|
|
345
|
+
if (
|
|
346
|
+
hostCounter != null &&
|
|
347
|
+
(hostEncoding == null || hostEncoding === encoding)
|
|
348
|
+
) {
|
|
349
|
+
return hostCounter(carrier);
|
|
385
350
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
351
|
+
const bundledCounter = await createTokenCounter(encoding);
|
|
352
|
+
return bundledCounter(carrier);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Encoding of the model that will *receive* the carrier.
|
|
357
|
+
*
|
|
358
|
+
* That is the agent's own model, never the summarizer's. The two are the same
|
|
359
|
+
* only by default: `summarizationConfig.model` is undefined for ordinary
|
|
360
|
+
* self-summarization and can name a different provider entirely when a cheap
|
|
361
|
+
* dedicated summarizer is configured. The count produced here is spent by
|
|
362
|
+
* `AgentContext.instructionTokens` against the agent's context window, so it
|
|
363
|
+
* has to be denominated in the agent's tokenizer, and Anthropic's counts run
|
|
364
|
+
* well above `o200k_base` on the same text: measuring CJK with the wrong one
|
|
365
|
+
* understates it by up to 1.9x.
|
|
366
|
+
*
|
|
367
|
+
* Both signals are read, because only one of them can ever be positive.
|
|
368
|
+
* `encodingForModel` matches the substring `claude`, so a name it accepts is
|
|
369
|
+
* proof, which is what keeps a Claude model reached through Bedrock or
|
|
370
|
+
* OpenRouter resolving to `claude`. A name it rejects is only the absence of
|
|
371
|
+
* proof: `production` is an opaque deployment alias, not a statement that the
|
|
372
|
+
* model behind it is something other than Claude. Both option keys are
|
|
373
|
+
* consulted for that name, since `modelName` is LangChain's alias for `model`
|
|
374
|
+
* and hosts configure agents through either, so reading one key alone would
|
|
375
|
+
* report an unconfigured model and quietly hand a Claude agent the
|
|
376
|
+
* `o200k_base` tokenizer.
|
|
377
|
+
*
|
|
378
|
+
* The provider therefore decides every case the name leaves open, not just the
|
|
379
|
+
* case where no model was recorded at all, and it is read as a family rather
|
|
380
|
+
* than as one enum value: a host can register its own provider with
|
|
381
|
+
* `family: 'anthropic'` (the trait `isThinkingEnabled` already reads the same
|
|
382
|
+
* way), and such a provider serves Claude behind whatever name and deployment
|
|
383
|
+
* alias the host chose. `BEDROCK` is left out by the same rule, since its
|
|
384
|
+
* family is `bedrock` and it also serves Llama, Titan and Mistral. The enum
|
|
385
|
+
* check stays ahead of the family lookup because the registry is populated by
|
|
386
|
+
* importing `@/llm/providers`, which a root-barrel consumer defers.
|
|
387
|
+
*
|
|
388
|
+
* On an opaque alias this deliberately parts ways with `Run.create`, which
|
|
389
|
+
* infers from the model name alone and stamps its counter `o200k_base`. That
|
|
390
|
+
* stamp records which tokenizer the counter is, not which one the agent needs,
|
|
391
|
+
* so the disagreement routes the carrier to the bundled Claude tokenizer by
|
|
392
|
+
* the same rule that rejects another agent's counter.
|
|
393
|
+
*/
|
|
394
|
+
function encodingForReceivingAgent(agentContext: AgentContext): EncodingName {
|
|
395
|
+
const model = resolveClientOptionsModel(agentContext.clientOptions);
|
|
396
|
+
if (model != null && encodingForModel(model) === 'claude') {
|
|
397
|
+
return 'claude';
|
|
391
398
|
}
|
|
392
|
-
return
|
|
399
|
+
return agentContext.provider === Providers.ANTHROPIC ||
|
|
400
|
+
getProviderFamily(agentContext.provider) === 'anthropic'
|
|
401
|
+
? 'claude'
|
|
402
|
+
: 'o200k_base';
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
/**
|
|
@@ -625,6 +635,7 @@ async function executeSummarizationWithFallback(params: {
|
|
|
625
635
|
summarizeConfig?: RunnableConfig;
|
|
626
636
|
stepId: string;
|
|
627
637
|
usePromptCache: boolean;
|
|
638
|
+
semanticIndex: RenderedCompactionSemanticIndex;
|
|
628
639
|
log: LogFn;
|
|
629
640
|
/** Carries the run's stream limits so the event cap covers summary streams. */
|
|
630
641
|
graph?: StreamLimitState & {
|
|
@@ -651,6 +662,7 @@ async function executeSummarizationWithFallback(params: {
|
|
|
651
662
|
summarizeConfig,
|
|
652
663
|
stepId,
|
|
653
664
|
usePromptCache,
|
|
665
|
+
semanticIndex,
|
|
654
666
|
log,
|
|
655
667
|
graph,
|
|
656
668
|
} = params;
|
|
@@ -693,6 +705,7 @@ async function executeSummarizationWithFallback(params: {
|
|
|
693
705
|
promptText: clientConfig.promptText,
|
|
694
706
|
updatePromptText: clientConfig.updatePromptText,
|
|
695
707
|
priorSummaryText,
|
|
708
|
+
semanticIndexAppendix: semanticIndex.appendix,
|
|
696
709
|
config: summarizeConfig,
|
|
697
710
|
stepId,
|
|
698
711
|
provider: clientConfig.provider,
|
|
@@ -704,20 +717,20 @@ async function executeSummarizationWithFallback(params: {
|
|
|
704
717
|
clientConfig.provider === Providers.OPENROUTER ||
|
|
705
718
|
clientConfig.provider === Providers.BEDROCK
|
|
706
719
|
? (
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
720
|
+
clientConfig.clientOptions as {
|
|
721
|
+
promptCacheTtl?: PromptCacheTtl;
|
|
722
|
+
}
|
|
710
723
|
).promptCacheTtl
|
|
711
724
|
: undefined,
|
|
712
725
|
bedrockModelId:
|
|
713
726
|
clientConfig.provider === Providers.BEDROCK
|
|
714
727
|
? resolveBedrockCompactionCacheModel(
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
728
|
+
clientConfig.clientOptions as
|
|
729
|
+
| {
|
|
730
|
+
applicationInferenceProfile?: string;
|
|
731
|
+
model?: string;
|
|
732
|
+
}
|
|
733
|
+
| undefined
|
|
721
734
|
)
|
|
722
735
|
: undefined,
|
|
723
736
|
log,
|
|
@@ -785,7 +798,8 @@ async function executeSummarizationWithFallback(params: {
|
|
|
785
798
|
buildSummarizationInstruction(
|
|
786
799
|
clientConfig.promptText,
|
|
787
800
|
clientConfig.updatePromptText,
|
|
788
|
-
priorSummaryText
|
|
801
|
+
priorSummaryText,
|
|
802
|
+
semanticIndex.appendix
|
|
789
803
|
)
|
|
790
804
|
),
|
|
791
805
|
],
|
|
@@ -1092,6 +1106,29 @@ export function createSummarizeNode({
|
|
|
1092
1106
|
return { summarizationRequest: undefined };
|
|
1093
1107
|
}
|
|
1094
1108
|
|
|
1109
|
+
/**
|
|
1110
|
+
* A summarizer that has already returned nothing several times in a row
|
|
1111
|
+
* keeps returning nothing, and every empty result leaves the message set
|
|
1112
|
+
* exactly as it was — so the next prune cycle re-triggers on identical
|
|
1113
|
+
* state. Stopping here bounds that loop instead of letting the run spend
|
|
1114
|
+
* its recursion budget dispatching empty summary steps.
|
|
1115
|
+
*/
|
|
1116
|
+
if (agentContext.summarizationExhausted) {
|
|
1117
|
+
emitAgentLog(
|
|
1118
|
+
config,
|
|
1119
|
+
'warn',
|
|
1120
|
+
'summarize',
|
|
1121
|
+
'Summarization skipped — consecutive attempts produced no usable summary',
|
|
1122
|
+
{
|
|
1123
|
+
failures: agentContext.summarizationFailures,
|
|
1124
|
+
reason: request.reason ?? 'trigger',
|
|
1125
|
+
},
|
|
1126
|
+
{ runId: graph.runId, agentId: request.agentId }
|
|
1127
|
+
);
|
|
1128
|
+
agentContext.markSummarizationTriggered(state.messages.length);
|
|
1129
|
+
return { summarizationRequest: undefined };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1095
1132
|
const maxCtx = agentContext.maxContextTokens ?? 0;
|
|
1096
1133
|
if (maxCtx > 0 && agentContext.instructionTokens >= maxCtx) {
|
|
1097
1134
|
emitAgentLog(
|
|
@@ -1188,6 +1225,10 @@ export function createSummarizeNode({
|
|
|
1188
1225
|
agentContext,
|
|
1189
1226
|
agentContext.summarizationConfig
|
|
1190
1227
|
);
|
|
1228
|
+
const semanticIndex = renderCompactionSemanticIndex(
|
|
1229
|
+
agentContext.compactionSemanticIndex,
|
|
1230
|
+
messagesToRefine
|
|
1231
|
+
);
|
|
1191
1232
|
|
|
1192
1233
|
const stepKey = `summarize-${request.agentId}`;
|
|
1193
1234
|
const [stepId, stepIndex] = generateStepId(stepKey);
|
|
@@ -1229,6 +1270,8 @@ export function createSummarizeNode({
|
|
|
1229
1270
|
model: clientConfig.modelName,
|
|
1230
1271
|
messagesToRefineCount: messagesToRefine.length,
|
|
1231
1272
|
summaryVersion: agentContext.summaryVersion + 1,
|
|
1273
|
+
semanticIndexEntryCount: semanticIndex.entryCount,
|
|
1274
|
+
semanticIndexCharCount: semanticIndex.charCount,
|
|
1232
1275
|
} satisfies t.SummarizeStartEvent,
|
|
1233
1276
|
runnableConfig
|
|
1234
1277
|
);
|
|
@@ -1276,6 +1319,9 @@ export function createSummarizeNode({
|
|
|
1276
1319
|
isSelfSummarize: isSelfSummarizeModel,
|
|
1277
1320
|
hasPromptCache,
|
|
1278
1321
|
provider: clientConfig.provider,
|
|
1322
|
+
semanticIndexEntryCount: semanticIndex.entryCount,
|
|
1323
|
+
semanticIndexCharCount: semanticIndex.charCount,
|
|
1324
|
+
semanticIndexOmittedEntryCount: semanticIndex.omittedEntryCount,
|
|
1279
1325
|
});
|
|
1280
1326
|
|
|
1281
1327
|
const summarizeConfig: RunnableConfig | undefined = config
|
|
@@ -1291,6 +1337,10 @@ export function createSummarizeNode({
|
|
|
1291
1337
|
agentId: request.agentId,
|
|
1292
1338
|
summarization_provider: clientConfig.provider,
|
|
1293
1339
|
summarization_model: clientConfig.modelName,
|
|
1340
|
+
compaction_semantic_index_entries: semanticIndex.entryCount,
|
|
1341
|
+
compaction_semantic_index_chars: semanticIndex.charCount,
|
|
1342
|
+
compaction_semantic_index_omitted_entries:
|
|
1343
|
+
semanticIndex.omittedEntryCount,
|
|
1294
1344
|
/**
|
|
1295
1345
|
* Per-call model attribution for usage consumers (the subagent
|
|
1296
1346
|
* usage-capture handler): the summarizer's model can differ from
|
|
@@ -1340,6 +1390,7 @@ export function createSummarizeNode({
|
|
|
1340
1390
|
summarizeConfig,
|
|
1341
1391
|
stepId,
|
|
1342
1392
|
usePromptCache: isSelfSummarizeModel && hasPromptCache,
|
|
1393
|
+
semanticIndex,
|
|
1343
1394
|
log,
|
|
1344
1395
|
graph,
|
|
1345
1396
|
});
|
|
@@ -1365,6 +1416,7 @@ export function createSummarizeNode({
|
|
|
1365
1416
|
`Summarization failed during ${preservationReason}; keeping history rather than replacing it with a metadata stub`
|
|
1366
1417
|
);
|
|
1367
1418
|
agentContext.markSummarizationTriggered(state.messages.length);
|
|
1419
|
+
agentContext.recordSummarizationFailure();
|
|
1368
1420
|
/**
|
|
1369
1421
|
* The run step was already dispatched, so it has to be resolved here or
|
|
1370
1422
|
* consumers tracking step lifecycle keep an unfinished placeholder for
|
|
@@ -1393,7 +1445,22 @@ export function createSummarizeNode({
|
|
|
1393
1445
|
}
|
|
1394
1446
|
|
|
1395
1447
|
if (!rawText) {
|
|
1396
|
-
|
|
1448
|
+
/**
|
|
1449
|
+
* An empty summary compacts nothing, so the state the pruner sees next
|
|
1450
|
+
* is byte-identical to the one that just triggered. Resetting the guard
|
|
1451
|
+
* to `0` here made `shouldSkipSummarization` answer `false` forever,
|
|
1452
|
+
* and the agent node re-triggered on that unchanged state until the
|
|
1453
|
+
* graph hit its recursion cap — a loop of empty summary steps, each one
|
|
1454
|
+
* a billed model call. Recording the current count keeps the guard
|
|
1455
|
+
* honest; the failure tally bounds the retries once new messages do
|
|
1456
|
+
* arrive and legitimately lift it.
|
|
1457
|
+
*/
|
|
1458
|
+
agentContext.markSummarizationTriggered(state.messages.length);
|
|
1459
|
+
agentContext.recordSummarizationFailure();
|
|
1460
|
+
log('warn', 'Summarization produced empty output', {
|
|
1461
|
+
failures: agentContext.summarizationFailures,
|
|
1462
|
+
messagesToRefineCount: messagesToRefine.length,
|
|
1463
|
+
});
|
|
1397
1464
|
if (runnableConfig) {
|
|
1398
1465
|
await safeDispatchCustomEvent(
|
|
1399
1466
|
GraphEvents.ON_SUMMARIZE_COMPLETE,
|
|
@@ -1411,10 +1478,9 @@ export function createSummarizeNode({
|
|
|
1411
1478
|
|
|
1412
1479
|
const summaryText = enrichSummary(rawText, messagesToRefine);
|
|
1413
1480
|
|
|
1414
|
-
const tokenCount = computeSummaryTokenCount(
|
|
1481
|
+
const tokenCount = await computeSummaryTokenCount(
|
|
1415
1482
|
summaryText,
|
|
1416
|
-
|
|
1417
|
-
agentContext.tokenCounter
|
|
1483
|
+
agentContext
|
|
1418
1484
|
);
|
|
1419
1485
|
|
|
1420
1486
|
if (usedIntraTurnFallback) {
|
|
@@ -1541,23 +1607,6 @@ function extractResponseText(response: { content: string | object }): string {
|
|
|
1541
1607
|
return parts.join('').trim();
|
|
1542
1608
|
}
|
|
1543
1609
|
|
|
1544
|
-
function buildSummarizationInstruction(
|
|
1545
|
-
promptText: string,
|
|
1546
|
-
updatePromptText: string | undefined,
|
|
1547
|
-
priorSummaryText: string
|
|
1548
|
-
): string {
|
|
1549
|
-
const effectivePrompt = priorSummaryText
|
|
1550
|
-
? (updatePromptText ?? promptText)
|
|
1551
|
-
: promptText;
|
|
1552
|
-
const parts = [effectivePrompt];
|
|
1553
|
-
if (priorSummaryText) {
|
|
1554
|
-
parts.push(
|
|
1555
|
-
`\n\n<previous-summary>\n${priorSummaryText}\n</previous-summary>`
|
|
1556
|
-
);
|
|
1557
|
-
}
|
|
1558
|
-
return parts.join('');
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
1610
|
/** Creates an `onChunk` callback that dispatches `ON_SUMMARIZE_DELTA` events for streaming. */
|
|
1562
1611
|
export function createSummarizationChunkHandler({
|
|
1563
1612
|
stepId,
|
|
@@ -1666,10 +1715,7 @@ export function applySummarizationHistoryCache(params: {
|
|
|
1666
1715
|
if (params.provider === Providers.BEDROCK) {
|
|
1667
1716
|
return addBedrockTailCacheControl(
|
|
1668
1717
|
[...params.messages],
|
|
1669
|
-
resolveBedrockPromptCacheTtl(
|
|
1670
|
-
params.promptCacheTtl,
|
|
1671
|
-
params.bedrockModelId
|
|
1672
|
-
)
|
|
1718
|
+
resolveBedrockPromptCacheTtl(params.promptCacheTtl, params.bedrockModelId)
|
|
1673
1719
|
);
|
|
1674
1720
|
}
|
|
1675
1721
|
if (
|
|
@@ -1685,9 +1731,7 @@ export function applySummarizationHistoryCache(params: {
|
|
|
1685
1731
|
}
|
|
1686
1732
|
|
|
1687
1733
|
export function resolveBedrockCompactionCacheModel(
|
|
1688
|
-
options:
|
|
1689
|
-
| { applicationInferenceProfile?: string; model?: string }
|
|
1690
|
-
| undefined
|
|
1734
|
+
options: { applicationInferenceProfile?: string; model?: string } | undefined
|
|
1691
1735
|
): string | undefined {
|
|
1692
1736
|
return options?.model;
|
|
1693
1737
|
}
|
|
@@ -1705,6 +1749,7 @@ async function summarizeWithCacheHit({
|
|
|
1705
1749
|
promptText,
|
|
1706
1750
|
updatePromptText,
|
|
1707
1751
|
priorSummaryText,
|
|
1752
|
+
semanticIndexAppendix,
|
|
1708
1753
|
config,
|
|
1709
1754
|
stepId,
|
|
1710
1755
|
provider,
|
|
@@ -1720,6 +1765,7 @@ async function summarizeWithCacheHit({
|
|
|
1720
1765
|
promptText: string;
|
|
1721
1766
|
updatePromptText?: string;
|
|
1722
1767
|
priorSummaryText: string;
|
|
1768
|
+
semanticIndexAppendix?: string;
|
|
1723
1769
|
config?: RunnableConfig;
|
|
1724
1770
|
stepId?: string;
|
|
1725
1771
|
provider: t.ProviderName;
|
|
@@ -1733,7 +1779,8 @@ async function summarizeWithCacheHit({
|
|
|
1733
1779
|
const instruction = buildSummarizationInstruction(
|
|
1734
1780
|
promptText,
|
|
1735
1781
|
updatePromptText,
|
|
1736
|
-
priorSummaryText
|
|
1782
|
+
priorSummaryText,
|
|
1783
|
+
semanticIndexAppendix
|
|
1737
1784
|
);
|
|
1738
1785
|
|
|
1739
1786
|
const cachedHistory = applySummarizationHistoryCache({
|
|
@@ -1743,10 +1790,7 @@ async function summarizeWithCacheHit({
|
|
|
1743
1790
|
promptCacheTtl,
|
|
1744
1791
|
bedrockModelId,
|
|
1745
1792
|
});
|
|
1746
|
-
const invokeMessages = [
|
|
1747
|
-
...cachedHistory,
|
|
1748
|
-
new HumanMessage(instruction),
|
|
1749
|
-
];
|
|
1793
|
+
const invokeMessages = [...cachedHistory, new HumanMessage(instruction)];
|
|
1750
1794
|
|
|
1751
1795
|
const result = await attemptInvoke(
|
|
1752
1796
|
{
|