@illuma-ai/agents 1.0.98 → 1.1.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 +6 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/constants.cjs +53 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +195 -31
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +14 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/dedup.cjs +95 -0
- package/dist/cjs/messages/dedup.cjs.map +1 -0
- package/dist/cjs/tools/CodeExecutor.cjs +22 -3
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/types/graph.cjs.map +1 -1
- package/dist/cjs/utils/pruneCalibration.cjs +78 -0
- package/dist/cjs/utils/pruneCalibration.cjs.map +1 -0
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolDiscoveryCache.cjs +127 -0
- package/dist/cjs/utils/toolDiscoveryCache.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +6 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/constants.mjs +48 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +196 -32
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/dedup.mjs +93 -0
- package/dist/esm/messages/dedup.mjs.map +1 -0
- package/dist/esm/tools/CodeExecutor.mjs +22 -3
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/types/graph.mjs.map +1 -1
- package/dist/esm/utils/pruneCalibration.mjs +74 -0
- package/dist/esm/utils/pruneCalibration.mjs.map +1 -0
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolDiscoveryCache.mjs +125 -0
- package/dist/esm/utils/toolDiscoveryCache.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +4 -1
- package/dist/types/common/constants.d.ts +35 -0
- package/dist/types/graphs/Graph.d.ts +34 -0
- package/dist/types/messages/dedup.d.ts +25 -0
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/types/graph.d.ts +63 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/pruneCalibration.d.ts +43 -0
- package/dist/types/utils/toolDiscoveryCache.d.ts +77 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +7 -0
- package/src/common/constants.ts +56 -0
- package/src/graphs/Graph.ts +250 -50
- package/src/graphs/gapFeatures.test.ts +520 -0
- package/src/graphs/nonBlockingSummarization.test.ts +307 -0
- package/src/messages/__tests__/dedup.test.ts +166 -0
- package/src/messages/dedup.ts +104 -0
- package/src/messages/index.ts +1 -0
- package/src/tools/CodeExecutor.ts +22 -3
- package/src/types/graph.ts +73 -0
- package/src/utils/__tests__/pruneCalibration.test.ts +148 -0
- package/src/utils/__tests__/toolDiscoveryCache.test.ts +214 -0
- package/src/utils/contextPressure.test.ts +24 -9
- package/src/utils/index.ts +2 -0
- package/src/utils/pruneCalibration.ts +92 -0
- package/src/utils/run.ts +108 -108
- package/src/utils/tokens.ts +118 -118
- package/src/utils/toolDiscoveryCache.ts +150 -0
|
@@ -10,9 +10,9 @@ import { createPruneMessages } from '../messages/prune.mjs';
|
|
|
10
10
|
import { ensureThinkingBlockInMessages } from '../messages/format.mjs';
|
|
11
11
|
import { addCacheControl, addBedrockCacheControl } from '../messages/cache.mjs';
|
|
12
12
|
import { formatContentStrings } from '../messages/content.mjs';
|
|
13
|
-
import { extractToolDiscoveries } from '../messages/tools.mjs';
|
|
14
13
|
import { GraphNodeKeys, Providers, ContentTypes, GraphEvents, MessageTypes, StepTypes, Constants } from '../common/enum.mjs';
|
|
15
|
-
import { TOOL_TURN_THINKING_BUDGET } from '../common/constants.mjs';
|
|
14
|
+
import { TOOL_TURN_THINKING_BUDGET, SUMMARIZATION_CONTEXT_THRESHOLD } from '../common/constants.mjs';
|
|
15
|
+
import { deduplicateSystemMessages } from '../messages/dedup.mjs';
|
|
16
16
|
import { resetIfNotEmpty, joinKeys } from '../utils/graph.mjs';
|
|
17
17
|
import { isOpenAILike, isGoogleLike } from '../utils/llm.mjs';
|
|
18
18
|
import { ChatModelStreamHandler } from '../stream.mjs';
|
|
@@ -23,6 +23,8 @@ import '../utils/toonFormat.mjs';
|
|
|
23
23
|
import { buildContextAnalytics } from '../utils/contextAnalytics.mjs';
|
|
24
24
|
import 'zod-to-json-schema';
|
|
25
25
|
import { hasTaskTool, buildPostPruneNote, detectDocuments, shouldInjectMultiDocHint, buildMultiDocHintContent } from '../utils/contextPressure.mjs';
|
|
26
|
+
import { ToolDiscoveryCache } from '../utils/toolDiscoveryCache.mjs';
|
|
27
|
+
import { createPruneCalibration, applyCalibration, updatePruneCalibration } from '../utils/pruneCalibration.mjs';
|
|
26
28
|
import { getChatModelClass, manualToolStreamProviders } from '../llm/providers.mjs';
|
|
27
29
|
import { ToolNode, toolsCondition } from '../tools/ToolNode.mjs';
|
|
28
30
|
import { ChatOpenAI, AzureChatOpenAI } from '../llm/openai/index.mjs';
|
|
@@ -91,6 +93,22 @@ class StandardGraph extends Graph {
|
|
|
91
93
|
runId;
|
|
92
94
|
startIndex = 0;
|
|
93
95
|
signal;
|
|
96
|
+
/** Cached summary from the first prune in this run.
|
|
97
|
+
* Reused for subsequent prunes to avoid blocking LLM calls on every tool iteration. */
|
|
98
|
+
_cachedRunSummary;
|
|
99
|
+
/** EMA-based pruning calibration state — smooths token budget adjustments across iterations */
|
|
100
|
+
_pruneCalibration;
|
|
101
|
+
/** Run-scoped tool discovery cache — avoids re-parsing conversation history on every iteration */
|
|
102
|
+
_toolDiscoveryCache;
|
|
103
|
+
/**
|
|
104
|
+
* SCALE: Tracks whether a summary call is already in-flight for this Graph instance.
|
|
105
|
+
* Prevents multiple concurrent summary LLM calls when rapid tool iterations each
|
|
106
|
+
* trigger pruning. At 2000 users with 3+ tool calls per turn, this prevents
|
|
107
|
+
* 6000+ summary calls/turn from becoming 2000.
|
|
108
|
+
*/
|
|
109
|
+
_summaryInFlight = false;
|
|
110
|
+
/** Messages accumulated across tool iterations while a summary call is in-flight */
|
|
111
|
+
_pendingMessagesToRefine = [];
|
|
94
112
|
/** Map of agent contexts by agent ID */
|
|
95
113
|
agentContexts = new Map();
|
|
96
114
|
/** Default agent ID to use */
|
|
@@ -111,6 +129,19 @@ class StandardGraph extends Graph {
|
|
|
111
129
|
this.agentContexts.set(agentConfig.agentId, agentContext);
|
|
112
130
|
}
|
|
113
131
|
this.defaultAgentId = agents[0].agentId;
|
|
132
|
+
// Seed cached summary from persisted storage so the first prune in a
|
|
133
|
+
// resumed conversation can also skip the synchronous LLM summarization call
|
|
134
|
+
const primaryContext = this.agentContexts.get(this.defaultAgentId);
|
|
135
|
+
if (primaryContext?.persistedSummary) {
|
|
136
|
+
this._cachedRunSummary = primaryContext.persistedSummary;
|
|
137
|
+
}
|
|
138
|
+
// Initialize EMA pruning calibration
|
|
139
|
+
this._pruneCalibration = createPruneCalibration();
|
|
140
|
+
// Initialize tool discovery cache, seeded with any pre-existing discoveries
|
|
141
|
+
this._toolDiscoveryCache = new ToolDiscoveryCache();
|
|
142
|
+
if (primaryContext?.discoveredToolNames.size) {
|
|
143
|
+
this._toolDiscoveryCache.seed([...primaryContext.discoveredToolNames]);
|
|
144
|
+
}
|
|
114
145
|
}
|
|
115
146
|
/* Init */
|
|
116
147
|
resetValues(keepContent) {
|
|
@@ -133,6 +164,11 @@ class StandardGraph extends Graph {
|
|
|
133
164
|
this.messageStepHasToolCalls = resetIfNotEmpty(this.messageStepHasToolCalls, new Map());
|
|
134
165
|
this.prelimMessageIdsByStepKey = resetIfNotEmpty(this.prelimMessageIdsByStepKey, new Map());
|
|
135
166
|
this.invokedToolIds = resetIfNotEmpty(this.invokedToolIds, undefined);
|
|
167
|
+
// Reset EMA calibration, tool discovery cache, and summary debounce for fresh run
|
|
168
|
+
this._pruneCalibration = createPruneCalibration();
|
|
169
|
+
this._toolDiscoveryCache.reset();
|
|
170
|
+
this._summaryInFlight = false;
|
|
171
|
+
this._pendingMessagesToRefine = [];
|
|
136
172
|
for (const context of this.agentContexts.values()) {
|
|
137
173
|
context.reset();
|
|
138
174
|
}
|
|
@@ -221,6 +257,62 @@ class StandardGraph extends Graph {
|
|
|
221
257
|
}
|
|
222
258
|
return clientOptions;
|
|
223
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Determines whether summarization should trigger based on SummarizationConfig.
|
|
262
|
+
*
|
|
263
|
+
* Supports three trigger strategies:
|
|
264
|
+
* - contextPercentage (default): Trigger when context utilization >= threshold%
|
|
265
|
+
* - messageCount: Trigger when pruned message count >= threshold
|
|
266
|
+
* - tokenThreshold: Trigger when total estimated tokens >= threshold
|
|
267
|
+
*
|
|
268
|
+
* When no config is provided, always triggers (preserves backward compatibility).
|
|
269
|
+
*
|
|
270
|
+
* @param prunedMessageCount - Number of messages that were pruned
|
|
271
|
+
* @param maxContextTokens - Maximum context token budget
|
|
272
|
+
* @param indexTokenCountMap - Token count map by message index
|
|
273
|
+
* @param instructionTokens - Token count for instructions/system message
|
|
274
|
+
* @param config - Optional SummarizationConfig
|
|
275
|
+
* @returns Whether summarization should be triggered
|
|
276
|
+
*/
|
|
277
|
+
shouldTriggerSummarization(prunedMessageCount, maxContextTokens, indexTokenCountMap, instructionTokens, config) {
|
|
278
|
+
// No pruned messages means nothing to summarize
|
|
279
|
+
if (prunedMessageCount === 0) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
// No config = backward compatible (always summarize when messages are pruned)
|
|
283
|
+
if (!config || !config.triggerType) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
const threshold = config.triggerThreshold;
|
|
287
|
+
switch (config.triggerType) {
|
|
288
|
+
case 'contextPercentage': {
|
|
289
|
+
if (maxContextTokens <= 0)
|
|
290
|
+
return true;
|
|
291
|
+
const effectiveThreshold = threshold ?? SUMMARIZATION_CONTEXT_THRESHOLD;
|
|
292
|
+
let totalTokens = instructionTokens;
|
|
293
|
+
for (const key in indexTokenCountMap) {
|
|
294
|
+
totalTokens += indexTokenCountMap[key] ?? 0;
|
|
295
|
+
}
|
|
296
|
+
const utilization = (totalTokens / maxContextTokens) * 100;
|
|
297
|
+
return utilization >= effectiveThreshold;
|
|
298
|
+
}
|
|
299
|
+
case 'messageCount': {
|
|
300
|
+
const effectiveThreshold = threshold ?? 5;
|
|
301
|
+
return prunedMessageCount >= effectiveThreshold;
|
|
302
|
+
}
|
|
303
|
+
case 'tokenThreshold': {
|
|
304
|
+
if (threshold == null)
|
|
305
|
+
return true;
|
|
306
|
+
let totalTokens = instructionTokens;
|
|
307
|
+
for (const key in indexTokenCountMap) {
|
|
308
|
+
totalTokens += indexTokenCountMap[key] ?? 0;
|
|
309
|
+
}
|
|
310
|
+
return totalTokens >= threshold;
|
|
311
|
+
}
|
|
312
|
+
default:
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
224
316
|
/**
|
|
225
317
|
* Returns the normalized finish/stop reason from the last LLM invocation.
|
|
226
318
|
* Used by callers to detect when the response was truncated due to max_tokens.
|
|
@@ -359,7 +451,6 @@ class StandardGraph extends Graph {
|
|
|
359
451
|
/* Misc.*/
|
|
360
452
|
getRunMessages() {
|
|
361
453
|
const result = this.messages.slice(this.startIndex);
|
|
362
|
-
console.debug(`[Graph] getRunMessages() | totalMessages=${this.messages.length} | startIndex=${this.startIndex} | runMessages=${result.length}`);
|
|
363
454
|
return result;
|
|
364
455
|
}
|
|
365
456
|
getContentParts() {
|
|
@@ -915,10 +1006,12 @@ class StandardGraph extends Graph {
|
|
|
915
1006
|
});
|
|
916
1007
|
messages = [dynamicContextMessage, ackMessage, ...messages];
|
|
917
1008
|
}
|
|
918
|
-
//
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1009
|
+
// Tool discovery caching: only scan new messages since last iteration
|
|
1010
|
+
// instead of re-parsing the full history via extractToolDiscoveries()
|
|
1011
|
+
const cachedDiscoveries = this._toolDiscoveryCache.getNewDiscoveries(messages);
|
|
1012
|
+
if (cachedDiscoveries.length > 0) {
|
|
1013
|
+
agentContext.markToolsAsDiscovered(cachedDiscoveries);
|
|
1014
|
+
console.debug(`[Graph:ToolDiscovery] Cached ${cachedDiscoveries.length} new tools (total: ${this._toolDiscoveryCache.size})`);
|
|
922
1015
|
}
|
|
923
1016
|
const toolsForBinding = agentContext.getToolsForBinding();
|
|
924
1017
|
// PERF: Detect subsequent ReAct iterations (tool results present in messages)
|
|
@@ -968,56 +1061,136 @@ class StandardGraph extends Graph {
|
|
|
968
1061
|
(agentContext.provider === Providers.OPENAI &&
|
|
969
1062
|
agentContext.clientOptions.modelKwargs
|
|
970
1063
|
?.thinking?.type === 'enabled');
|
|
1064
|
+
// Apply EMA calibration to max token budget — smooths pruning across iterations
|
|
1065
|
+
const calibratedMaxTokens = applyCalibration(agentContext.maxContextTokens, this._pruneCalibration);
|
|
971
1066
|
agentContext.pruneMessages = createPruneMessages({
|
|
972
1067
|
startIndex: this.startIndex,
|
|
973
1068
|
provider: agentContext.provider,
|
|
974
1069
|
tokenCounter: agentContext.tokenCounter,
|
|
975
|
-
maxTokens:
|
|
1070
|
+
maxTokens: calibratedMaxTokens,
|
|
976
1071
|
thinkingEnabled: isAnthropicWithThinking,
|
|
977
1072
|
indexTokenCountMap: agentContext.indexTokenCountMap,
|
|
978
1073
|
});
|
|
979
1074
|
}
|
|
1075
|
+
// Update EMA calibration with actual token usage from API response
|
|
1076
|
+
if (agentContext.currentUsage?.input_tokens &&
|
|
1077
|
+
agentContext.maxContextTokens) {
|
|
1078
|
+
const estimatedTokens = Object.values(agentContext.indexTokenCountMap).reduce((sum, v) => (sum ?? 0) + (v ?? 0), 0);
|
|
1079
|
+
if (estimatedTokens > 0) {
|
|
1080
|
+
this._pruneCalibration = updatePruneCalibration(this._pruneCalibration, agentContext.currentUsage.input_tokens, estimatedTokens);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
980
1083
|
if (agentContext.pruneMessages) {
|
|
981
|
-
console.debug(`[Graph:ContextMgmt] Pruning messages | inputCount=${messages.length} | maxTokens=${agentContext.maxContextTokens}`);
|
|
982
1084
|
const { context, indexTokenCountMap, messagesToRefine } = agentContext.pruneMessages({
|
|
983
1085
|
messages,
|
|
984
1086
|
usageMetadata: agentContext.currentUsage,
|
|
985
|
-
// startOnMessageType: 'human',
|
|
986
1087
|
});
|
|
987
1088
|
agentContext.indexTokenCountMap = indexTokenCountMap;
|
|
988
1089
|
messagesToUse = context;
|
|
989
|
-
|
|
990
|
-
//
|
|
1090
|
+
// ── Non-blocking summarization ──────────────────────────────────
|
|
1091
|
+
// NEVER block the LLM call waiting for summarization. Instead:
|
|
1092
|
+
// 1. If _cachedRunSummary exists → use it, fire async update
|
|
1093
|
+
// 2. If persistedSummary exists → use it as fallback, fire async update
|
|
1094
|
+
// 3. If NOTHING exists (first-ever prune) → skip summary, fire async generation
|
|
1095
|
+
// The summary catches up asynchronously and is available for subsequent
|
|
1096
|
+
// iterations (tool calls) and the next conversation turn.
|
|
1097
|
+
//
|
|
1098
|
+
// SummarizationConfig integration:
|
|
1099
|
+
// - triggerType/triggerThreshold control WHEN summarization fires
|
|
1100
|
+
// - reserveRatio is enforced via calibrated maxTokens (above)
|
|
1101
|
+
// - initialSummary provides cross-run seeding as fallback before persistedSummary
|
|
991
1102
|
let hasSummary = false;
|
|
992
|
-
|
|
993
|
-
|
|
1103
|
+
const sumConfig = agentContext.summarizationConfig;
|
|
1104
|
+
const shouldSummarize = this.shouldTriggerSummarization(messagesToRefine.length, agentContext.maxContextTokens ?? 0, agentContext.indexTokenCountMap, agentContext.instructionTokens, sumConfig);
|
|
1105
|
+
if (messagesToRefine.length > 0 &&
|
|
1106
|
+
agentContext.summarizeCallback &&
|
|
1107
|
+
shouldSummarize) {
|
|
994
1108
|
try {
|
|
995
|
-
|
|
996
|
-
|
|
1109
|
+
let summary;
|
|
1110
|
+
let summarySource;
|
|
1111
|
+
if (this._cachedRunSummary != null) {
|
|
1112
|
+
summary = this._cachedRunSummary;
|
|
1113
|
+
summarySource = 'cached';
|
|
1114
|
+
}
|
|
1115
|
+
else if (agentContext.persistedSummary != null &&
|
|
1116
|
+
agentContext.persistedSummary !== '') {
|
|
1117
|
+
summary = agentContext.persistedSummary;
|
|
1118
|
+
this._cachedRunSummary = summary;
|
|
1119
|
+
summarySource = 'persisted';
|
|
1120
|
+
}
|
|
1121
|
+
else if (sumConfig?.initialSummary != null &&
|
|
1122
|
+
sumConfig.initialSummary !== '') {
|
|
1123
|
+
// Cross-run seed: use initialSummary when no persisted summary exists
|
|
1124
|
+
summary = sumConfig.initialSummary;
|
|
1125
|
+
this._cachedRunSummary = summary;
|
|
1126
|
+
summarySource = 'initial-seed';
|
|
1127
|
+
}
|
|
1128
|
+
else {
|
|
1129
|
+
summarySource = 'none';
|
|
1130
|
+
}
|
|
1131
|
+
// Single consolidated log for the entire prune+summarize decision
|
|
1132
|
+
console.debug(`[Graph:ContextMgmt] Pruned ${messages.length}→${context.length} msgs (${messagesToRefine.length} discarded) | summary=${summarySource}${summary ? ` (len=${summary.length})` : ''} | calibration=${this._pruneCalibration.ratio.toFixed(3)}(${this._pruneCalibration.iterations})`);
|
|
1133
|
+
// SCALE: Debounce background summarization — if a summary call is already
|
|
1134
|
+
// in-flight (from a prior tool iteration), accumulate messages instead of
|
|
1135
|
+
// firing another concurrent LLM call. At 2000 users with 3+ tool calls
|
|
1136
|
+
// per turn, this prevents 3x summary call volume.
|
|
1137
|
+
if (this._summaryInFlight) {
|
|
1138
|
+
this._pendingMessagesToRefine.push(...messagesToRefine);
|
|
1139
|
+
console.debug(`[Graph:ContextMgmt] Summary in-flight, queued ${messagesToRefine.length} msgs (pending=${this._pendingMessagesToRefine.length})`);
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
this._summaryInFlight = true;
|
|
1143
|
+
const allMessages = this._pendingMessagesToRefine.length > 0
|
|
1144
|
+
? [...this._pendingMessagesToRefine, ...messagesToRefine]
|
|
1145
|
+
: messagesToRefine;
|
|
1146
|
+
this._pendingMessagesToRefine = [];
|
|
1147
|
+
agentContext
|
|
1148
|
+
.summarizeCallback(allMessages)
|
|
1149
|
+
.then((updated) => {
|
|
1150
|
+
if (updated != null && updated !== '') {
|
|
1151
|
+
this._cachedRunSummary = updated;
|
|
1152
|
+
}
|
|
1153
|
+
})
|
|
1154
|
+
.catch((err) => {
|
|
1155
|
+
console.error('[Graph] Background summary failed (non-fatal):', err);
|
|
1156
|
+
})
|
|
1157
|
+
.finally(() => {
|
|
1158
|
+
this._summaryInFlight = false;
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
997
1161
|
if (summary != null && summary !== '') {
|
|
998
1162
|
hasSummary = true;
|
|
999
1163
|
const summaryMsg = new SystemMessage(`[Conversation Summary]\n${summary}`);
|
|
1000
|
-
// Insert after system message (if present), before conversation messages
|
|
1001
1164
|
const systemIdx = messagesToUse[0]?.getType() === 'system' ? 1 : 0;
|
|
1002
1165
|
messagesToUse = [
|
|
1003
1166
|
...messagesToUse.slice(0, systemIdx),
|
|
1004
1167
|
summaryMsg,
|
|
1005
1168
|
...messagesToUse.slice(systemIdx),
|
|
1006
1169
|
];
|
|
1007
|
-
console.debug(`[Graph:ContextMgmt] Summary injected at index ${systemIdx} | finalMsgCount=${messagesToUse.length}`);
|
|
1008
1170
|
}
|
|
1009
1171
|
}
|
|
1010
1172
|
catch (err) {
|
|
1011
|
-
console.error('[Graph] Summarization
|
|
1173
|
+
console.error('[Graph] Summarization failed:', err);
|
|
1012
1174
|
}
|
|
1013
1175
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1176
|
+
else if (messagesToRefine.length > 0) {
|
|
1177
|
+
// Log pruning even when no summarize callback (discard mode)
|
|
1178
|
+
console.debug(`[Graph:ContextMgmt] Pruned ${messages.length}→${context.length} msgs (${messagesToRefine.length} discarded, no summary callback) | calibration=${this._pruneCalibration.ratio.toFixed(3)}`);
|
|
1179
|
+
}
|
|
1180
|
+
// Deduplicate system messages that accumulate from repeated tool iterations
|
|
1181
|
+
const { messages: dedupedMessages, removedCount } = deduplicateSystemMessages(messagesToUse);
|
|
1182
|
+
if (removedCount > 0) {
|
|
1183
|
+
messagesToUse = dedupedMessages;
|
|
1184
|
+
console.debug(`[Graph:Dedup] Removed ${removedCount} duplicate system message(s)`);
|
|
1185
|
+
}
|
|
1186
|
+
// Post-prune context note for task-tool-enabled agents
|
|
1016
1187
|
if (messagesToRefine.length > 0 && hasTaskTool(agentContext.tools)) {
|
|
1017
1188
|
const postPruneNote = buildPostPruneNote(messagesToRefine.length, hasSummary);
|
|
1018
1189
|
if (postPruneNote) {
|
|
1019
|
-
messagesToUse = [
|
|
1020
|
-
|
|
1190
|
+
messagesToUse = [
|
|
1191
|
+
...messagesToUse,
|
|
1192
|
+
new SystemMessage(postPruneNote),
|
|
1193
|
+
];
|
|
1021
1194
|
}
|
|
1022
1195
|
}
|
|
1023
1196
|
}
|
|
@@ -1141,11 +1314,6 @@ class StandardGraph extends Graph {
|
|
|
1141
1314
|
// ====================================================================
|
|
1142
1315
|
if (hasTaskTool(agentContext.tools)) {
|
|
1143
1316
|
const { count: documentCount, names: documentNames } = detectDocuments(finalMessages);
|
|
1144
|
-
// Observability log (no token numbers exposed to LLM)
|
|
1145
|
-
if (contextAnalytics.utilizationPercent != null) {
|
|
1146
|
-
console.debug(`[Graph] Context utilization: ${contextAnalytics.utilizationPercent.toFixed(1)}% | ` +
|
|
1147
|
-
`messages: ${finalMessages.length} | docs: ${documentCount}`);
|
|
1148
|
-
}
|
|
1149
1317
|
// Multi-document delegation: first iteration only (before AI has responded)
|
|
1150
1318
|
const hasAiResponse = finalMessages.some((m) => m._getType() === 'ai' || m._getType() === 'tool');
|
|
1151
1319
|
if (shouldInjectMultiDocHint(documentCount, hasAiResponse)) {
|
|
@@ -1549,10 +1717,6 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
|
|
|
1549
1717
|
reducer: (a, b) => {
|
|
1550
1718
|
if (!a.length) {
|
|
1551
1719
|
this.startIndex = a.length + b.length;
|
|
1552
|
-
console.debug(`[Graph:Reducer] Initial messages | startIndex=${this.startIndex} | inputMsgCount=${b.length}`);
|
|
1553
|
-
}
|
|
1554
|
-
else {
|
|
1555
|
-
console.debug(`[Graph:Reducer] Appending messages | existing=${a.length} | new=${b.length} | startIndex=${this.startIndex}`);
|
|
1556
1720
|
}
|
|
1557
1721
|
const result = messagesStateReducer(a, b);
|
|
1558
1722
|
this.messages = result;
|