@librechat/agents 3.1.57 → 3.1.60
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 +326 -62
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +13 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +7 -27
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +303 -222
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +4 -4
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +6 -2
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +60 -0
- package/dist/cjs/llm/init.cjs.map +1 -0
- package/dist/cjs/llm/invoke.cjs +90 -0
- package/dist/cjs/llm/invoke.cjs.map +1 -0
- package/dist/cjs/llm/openai/index.cjs +2 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/request.cjs +41 -0
- package/dist/cjs/llm/request.cjs.map +1 -0
- package/dist/cjs/main.cjs +40 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +76 -89
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +156 -0
- package/dist/cjs/messages/contextPruning.cjs.map +1 -0
- package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
- package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
- package/dist/cjs/messages/core.cjs +23 -37
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +156 -11
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +1161 -49
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/reducer.cjs +87 -0
- package/dist/cjs/messages/reducer.cjs.map +1 -0
- package/dist/cjs/run.cjs +81 -42
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +54 -7
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/index.cjs +75 -0
- package/dist/cjs/summarization/index.cjs.map +1 -0
- package/dist/cjs/summarization/node.cjs +663 -0
- package/dist/cjs/summarization/node.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +16 -8
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +2 -0
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +115 -0
- package/dist/cjs/utils/errors.cjs.map +1 -0
- package/dist/cjs/utils/events.cjs +17 -0
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/handlers.cjs +16 -0
- package/dist/cjs/utils/handlers.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs +10 -0
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +247 -14
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/truncation.cjs +107 -0
- package/dist/cjs/utils/truncation.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +325 -61
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +13 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +8 -28
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +307 -226
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +4 -4
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +6 -2
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +58 -0
- package/dist/esm/llm/init.mjs.map +1 -0
- package/dist/esm/llm/invoke.mjs +87 -0
- package/dist/esm/llm/invoke.mjs.map +1 -0
- package/dist/esm/llm/openai/index.mjs +2 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/request.mjs +38 -0
- package/dist/esm/llm/request.mjs.map +1 -0
- package/dist/esm/main.mjs +13 -3
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +76 -89
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +154 -0
- package/dist/esm/messages/contextPruning.mjs.map +1 -0
- package/dist/esm/messages/contextPruningSettings.mjs +50 -0
- package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
- package/dist/esm/messages/core.mjs +23 -37
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +156 -11
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +1158 -52
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/reducer.mjs +83 -0
- package/dist/esm/messages/reducer.mjs.map +1 -0
- package/dist/esm/run.mjs +82 -43
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +54 -7
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/index.mjs +73 -0
- package/dist/esm/summarization/index.mjs.map +1 -0
- package/dist/esm/summarization/node.mjs +659 -0
- package/dist/esm/summarization/node.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +16 -8
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +2 -0
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +111 -0
- package/dist/esm/utils/errors.mjs.map +1 -0
- package/dist/esm/utils/events.mjs +17 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/handlers.mjs +16 -0
- package/dist/esm/utils/handlers.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs +10 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +245 -15
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/truncation.mjs +102 -0
- package/dist/esm/utils/truncation.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +124 -6
- package/dist/types/common/enum.d.ts +14 -1
- package/dist/types/graphs/Graph.d.ts +22 -27
- package/dist/types/index.d.ts +5 -0
- package/dist/types/llm/init.d.ts +18 -0
- package/dist/types/llm/invoke.d.ts +48 -0
- package/dist/types/llm/request.d.ts +14 -0
- package/dist/types/messages/contextPruning.d.ts +42 -0
- package/dist/types/messages/contextPruningSettings.d.ts +44 -0
- package/dist/types/messages/core.d.ts +1 -1
- package/dist/types/messages/format.d.ts +17 -1
- package/dist/types/messages/index.d.ts +3 -0
- package/dist/types/messages/prune.d.ts +162 -1
- package/dist/types/messages/reducer.d.ts +18 -0
- package/dist/types/run.d.ts +12 -1
- package/dist/types/summarization/index.d.ts +20 -0
- package/dist/types/summarization/node.d.ts +29 -0
- package/dist/types/tools/ToolNode.d.ts +3 -1
- package/dist/types/types/graph.d.ts +44 -6
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +30 -0
- package/dist/types/types/stream.d.ts +31 -4
- package/dist/types/types/summarize.d.ts +47 -0
- package/dist/types/types/tools.d.ts +7 -0
- package/dist/types/utils/errors.d.ts +28 -0
- package/dist/types/utils/events.d.ts +13 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/llm.d.ts +4 -0
- package/dist/types/utils/tokens.d.ts +14 -1
- package/dist/types/utils/truncation.d.ts +49 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +388 -58
- package/src/agents/__tests__/AgentContext.test.ts +265 -5
- package/src/common/enum.ts +13 -0
- package/src/events.ts +9 -39
- package/src/graphs/Graph.ts +468 -331
- package/src/index.ts +7 -0
- package/src/llm/anthropic/llm.spec.ts +3 -3
- package/src/llm/anthropic/utils/message_inputs.ts +6 -4
- package/src/llm/bedrock/llm.spec.ts +1 -1
- package/src/llm/bedrock/utils/message_inputs.ts +6 -2
- package/src/llm/init.ts +63 -0
- package/src/llm/invoke.ts +144 -0
- package/src/llm/request.ts +55 -0
- package/src/messages/__tests__/observationMasking.test.ts +221 -0
- package/src/messages/cache.ts +77 -102
- package/src/messages/contextPruning.ts +191 -0
- package/src/messages/contextPruningSettings.ts +90 -0
- package/src/messages/core.ts +32 -53
- package/src/messages/ensureThinkingBlock.test.ts +39 -39
- package/src/messages/format.ts +227 -15
- package/src/messages/formatAgentMessages.test.ts +511 -1
- package/src/messages/index.ts +3 -0
- package/src/messages/prune.ts +1548 -62
- package/src/messages/reducer.ts +22 -0
- package/src/run.ts +104 -51
- package/src/scripts/bedrock-merge-test.ts +1 -1
- package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
- package/src/scripts/test-thinking-handoff.ts +1 -1
- package/src/scripts/thinking-bedrock.ts +1 -1
- package/src/scripts/thinking.ts +1 -1
- package/src/specs/anthropic.simple.test.ts +1 -1
- package/src/specs/multi-agent-summarization.test.ts +396 -0
- package/src/specs/prune.test.ts +1196 -23
- package/src/specs/summarization-unit.test.ts +868 -0
- package/src/specs/summarization.test.ts +3810 -0
- package/src/specs/summarize-prune.test.ts +376 -0
- package/src/specs/thinking-handoff.test.ts +10 -10
- package/src/specs/thinking-prune.test.ts +7 -4
- package/src/specs/token-accounting-e2e.test.ts +1034 -0
- package/src/specs/token-accounting-pipeline.test.ts +882 -0
- package/src/specs/token-distribution-edge-case.test.ts +25 -26
- package/src/splitStream.test.ts +42 -33
- package/src/stream.ts +64 -11
- package/src/summarization/__tests__/aggregator.test.ts +153 -0
- package/src/summarization/__tests__/node.test.ts +708 -0
- package/src/summarization/__tests__/trigger.test.ts +50 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/node.ts +982 -0
- package/src/tools/ToolNode.ts +25 -3
- package/src/types/graph.ts +62 -7
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +32 -0
- package/src/types/stream.ts +45 -5
- package/src/types/summarize.ts +58 -0
- package/src/types/tools.ts +7 -0
- package/src/utils/errors.ts +117 -0
- package/src/utils/events.ts +31 -0
- package/src/utils/handlers.ts +18 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/llm.ts +12 -0
- package/src/utils/tokens.ts +336 -18
- package/src/utils/truncation.ts +124 -0
- package/src/scripts/image.ts +0 -180
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { RunnableLambda } from '@langchain/core/runnables';
|
|
6
|
-
import { SystemMessage, AIMessageChunk, ToolMessage } from '@langchain/core/messages';
|
|
7
|
-
import { convertMessagesToContent, modifyDeltaProperties, formatAnthropicArtifactContent, formatArtifactPayload } from '../messages/core.mjs';
|
|
2
|
+
import { AIMessageChunk, ToolMessage } from '@langchain/core/messages';
|
|
3
|
+
import { Annotation, StateGraph, START, END } from '@langchain/langgraph';
|
|
4
|
+
import { convertMessagesToContent, formatAnthropicArtifactContent, formatArtifactPayload } from '../messages/core.mjs';
|
|
8
5
|
import { getMessageId } from '../messages/ids.mjs';
|
|
9
|
-
import { createPruneMessages } from '../messages/prune.mjs';
|
|
6
|
+
import { createPruneMessages, sanitizeOrphanToolBlocks } from '../messages/prune.mjs';
|
|
10
7
|
import { ensureThinkingBlockInMessages } from '../messages/format.mjs';
|
|
11
8
|
import { addCacheControl, addBedrockCacheControl } from '../messages/cache.mjs';
|
|
12
9
|
import { formatContentStrings } from '../messages/content.mjs';
|
|
13
10
|
import { extractToolDiscoveries } from '../messages/tools.mjs';
|
|
14
|
-
import {
|
|
11
|
+
import { messagesStateReducer } from '../messages/reducer.mjs';
|
|
12
|
+
import { GraphNodeKeys, ContentTypes, Providers, StepTypes, GraphEvents } from '../common/enum.mjs';
|
|
15
13
|
import { resetIfNotEmpty, joinKeys } from '../utils/graph.mjs';
|
|
16
|
-
import { isOpenAILike, isGoogleLike } from '../utils/llm.mjs';
|
|
17
|
-
import { ChatModelStreamHandler } from '../stream.mjs';
|
|
14
|
+
import { isAnthropicLike, isOpenAILike, isGoogleLike } from '../utils/llm.mjs';
|
|
18
15
|
import { handleToolCalls } from '../tools/handlers.mjs';
|
|
19
16
|
import { sleep } from '../utils/run.mjs';
|
|
20
17
|
import 'ai-tokenizer';
|
|
21
18
|
import 'zod-to-json-schema';
|
|
22
|
-
import { getChatModelClass, manualToolStreamProviders } from '../llm/providers.mjs';
|
|
23
19
|
import { ToolNode, toolsCondition } from '../tools/ToolNode.mjs';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
20
|
+
import { emitAgentLog, safeDispatchCustomEvent } from '../utils/events.mjs';
|
|
21
|
+
import { attemptInvoke, tryFallbackProviders } from '../llm/invoke.mjs';
|
|
22
|
+
import { shouldTriggerSummarization } from '../summarization/index.mjs';
|
|
23
|
+
import { createSummarizeNode } from '../summarization/node.mjs';
|
|
26
24
|
import { createSchemaOnlyTools } from '../tools/schema.mjs';
|
|
27
25
|
import { AgentContext } from '../agents/AgentContext.mjs';
|
|
28
26
|
import { createFakeStreamingLLM } from '../llm/fake.mjs';
|
|
27
|
+
import { isThinkingEnabled } from '../llm/request.mjs';
|
|
28
|
+
import { initializeModel } from '../llm/init.mjs';
|
|
29
29
|
|
|
30
30
|
/* eslint-disable no-console */
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const { AGENT, TOOLS, SUMMARIZE } = GraphNodeKeys;
|
|
32
|
+
/** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
|
|
33
|
+
const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
|
|
33
34
|
class Graph {
|
|
34
35
|
messageStepHasToolCalls = new Map();
|
|
35
36
|
messageIdsByStepKey = new Map();
|
|
@@ -39,6 +40,12 @@ class Graph {
|
|
|
39
40
|
stepKeyIds = new Map();
|
|
40
41
|
contentIndexMap = new Map();
|
|
41
42
|
toolCallStepIds = new Map();
|
|
43
|
+
/**
|
|
44
|
+
* Step IDs that have been dispatched via handler registry directly
|
|
45
|
+
* (in dispatchRunStep). Used by the custom event callback to skip
|
|
46
|
+
* duplicate dispatch through the LangGraph callback chain.
|
|
47
|
+
*/
|
|
48
|
+
handlerDispatchedStepIds = new Set();
|
|
42
49
|
signal;
|
|
43
50
|
/** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
|
|
44
51
|
invokedToolIds;
|
|
@@ -74,16 +81,23 @@ class StandardGraph extends Graph {
|
|
|
74
81
|
/** Optional compile options passed into workflow.compile() */
|
|
75
82
|
compileOptions;
|
|
76
83
|
messages = [];
|
|
84
|
+
/** Cached run messages preserved before clearHeavyState() so getRunMessages() works after cleanup. */
|
|
85
|
+
cachedRunMessages;
|
|
77
86
|
runId;
|
|
87
|
+
/**
|
|
88
|
+
* Boundary between historical messages (loaded from conversation state)
|
|
89
|
+
* and messages produced during the current run. Set once in the state
|
|
90
|
+
* reducer when messages first arrive. Used by `getRunMessages()` and
|
|
91
|
+
* multi-agent message filtering — NOT for pruner token counting (the
|
|
92
|
+
* pruner maintains its own `lastTurnStartIndex` in its closure).
|
|
93
|
+
*/
|
|
78
94
|
startIndex = 0;
|
|
79
95
|
signal;
|
|
80
96
|
/** Map of agent contexts by agent ID */
|
|
81
97
|
agentContexts = new Map();
|
|
82
98
|
/** Default agent ID to use */
|
|
83
99
|
defaultAgentId;
|
|
84
|
-
constructor({
|
|
85
|
-
// parent-level graph inputs
|
|
86
|
-
runId, signal, agents, tokenCounter, indexTokenCountMap, }) {
|
|
100
|
+
constructor({ runId, signal, agents, tokenCounter, indexTokenCountMap, calibrationRatio, }) {
|
|
87
101
|
super();
|
|
88
102
|
this.runId = runId;
|
|
89
103
|
this.signal = signal;
|
|
@@ -92,6 +106,9 @@ class StandardGraph extends Graph {
|
|
|
92
106
|
}
|
|
93
107
|
for (const agentConfig of agents) {
|
|
94
108
|
const agentContext = AgentContext.fromConfig(agentConfig, tokenCounter, indexTokenCountMap);
|
|
109
|
+
if (calibrationRatio != null && calibrationRatio > 0) {
|
|
110
|
+
agentContext.calibrationRatio = calibrationRatio;
|
|
111
|
+
}
|
|
95
112
|
this.agentContexts.set(agentConfig.agentId, agentContext);
|
|
96
113
|
}
|
|
97
114
|
this.defaultAgentId = agents[0].agentId;
|
|
@@ -99,6 +116,7 @@ class StandardGraph extends Graph {
|
|
|
99
116
|
/* Init */
|
|
100
117
|
resetValues(keepContent) {
|
|
101
118
|
this.messages = [];
|
|
119
|
+
this.cachedRunMessages = undefined;
|
|
102
120
|
this.config = resetIfNotEmpty(this.config, undefined);
|
|
103
121
|
if (keepContent !== true) {
|
|
104
122
|
this.contentData = resetIfNotEmpty(this.contentData, []);
|
|
@@ -112,6 +130,7 @@ class StandardGraph extends Graph {
|
|
|
112
130
|
* a stale reference on 2nd+ processStream calls.
|
|
113
131
|
*/
|
|
114
132
|
this.toolCallStepIds.clear();
|
|
133
|
+
this.handlerDispatchedStepIds = resetIfNotEmpty(this.handlerDispatchedStepIds, new Set());
|
|
115
134
|
this.messageIdsByStepKey = resetIfNotEmpty(this.messageIdsByStepKey, new Map());
|
|
116
135
|
this.messageStepHasToolCalls = resetIfNotEmpty(this.messageStepHasToolCalls, new Map());
|
|
117
136
|
this.prelimMessageIdsByStepKey = resetIfNotEmpty(this.prelimMessageIdsByStepKey, new Map());
|
|
@@ -121,6 +140,7 @@ class StandardGraph extends Graph {
|
|
|
121
140
|
}
|
|
122
141
|
}
|
|
123
142
|
clearHeavyState() {
|
|
143
|
+
this.cachedRunMessages = this.messages.slice(this.startIndex);
|
|
124
144
|
super.clearHeavyState();
|
|
125
145
|
this.messages = [];
|
|
126
146
|
this.overrideModel = undefined;
|
|
@@ -151,6 +171,9 @@ class StandardGraph extends Graph {
|
|
|
151
171
|
else if (currentNode.startsWith(TOOLS)) {
|
|
152
172
|
agentId = currentNode.substring(TOOLS.length);
|
|
153
173
|
}
|
|
174
|
+
else if (currentNode.startsWith(SUMMARIZE)) {
|
|
175
|
+
agentId = currentNode.substring(SUMMARIZE.length);
|
|
176
|
+
}
|
|
154
177
|
const agentContext = this.agentContexts.get(agentId ?? '');
|
|
155
178
|
if (!agentContext) {
|
|
156
179
|
throw new Error(`No agent context found for agent ID ${agentId}`);
|
|
@@ -220,11 +243,26 @@ class StandardGraph extends Graph {
|
|
|
220
243
|
}
|
|
221
244
|
/* Misc.*/
|
|
222
245
|
getRunMessages() {
|
|
246
|
+
if (this.messages.length === 0 && this.cachedRunMessages != null) {
|
|
247
|
+
return this.cachedRunMessages;
|
|
248
|
+
}
|
|
223
249
|
return this.messages.slice(this.startIndex);
|
|
224
250
|
}
|
|
225
251
|
getContentParts() {
|
|
226
252
|
return convertMessagesToContent(this.messages.slice(this.startIndex));
|
|
227
253
|
}
|
|
254
|
+
getCalibrationRatio() {
|
|
255
|
+
const context = this.agentContexts.get(this.defaultAgentId);
|
|
256
|
+
return context?.calibrationRatio ?? 1;
|
|
257
|
+
}
|
|
258
|
+
getResolvedInstructionOverhead() {
|
|
259
|
+
const context = this.agentContexts.get(this.defaultAgentId);
|
|
260
|
+
return context?.resolvedInstructionOverhead;
|
|
261
|
+
}
|
|
262
|
+
getToolCount() {
|
|
263
|
+
const context = this.agentContexts.get(this.defaultAgentId);
|
|
264
|
+
return ((context?.tools?.length ?? 0) + (context?.toolDefinitions?.length ?? 0));
|
|
265
|
+
}
|
|
228
266
|
/**
|
|
229
267
|
* Get all run steps, optionally filtered by agent ID
|
|
230
268
|
*/
|
|
@@ -276,35 +314,6 @@ class StandardGraph extends Graph {
|
|
|
276
314
|
return contentPartAgentMap;
|
|
277
315
|
}
|
|
278
316
|
/* Graph */
|
|
279
|
-
createSystemRunnable({ provider, clientOptions, instructions, additional_instructions, }) {
|
|
280
|
-
let finalInstructions = instructions;
|
|
281
|
-
if (additional_instructions != null && additional_instructions !== '') {
|
|
282
|
-
finalInstructions =
|
|
283
|
-
finalInstructions != null && finalInstructions
|
|
284
|
-
? `${finalInstructions}\n\n${additional_instructions}`
|
|
285
|
-
: additional_instructions;
|
|
286
|
-
}
|
|
287
|
-
if (finalInstructions != null &&
|
|
288
|
-
finalInstructions &&
|
|
289
|
-
provider === Providers.ANTHROPIC &&
|
|
290
|
-
clientOptions.promptCache === true) {
|
|
291
|
-
finalInstructions = {
|
|
292
|
-
content: [
|
|
293
|
-
{
|
|
294
|
-
type: 'text',
|
|
295
|
-
text: instructions,
|
|
296
|
-
cache_control: { type: 'ephemeral' },
|
|
297
|
-
},
|
|
298
|
-
],
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
if (finalInstructions != null && finalInstructions !== '') {
|
|
302
|
-
const systemMessage = new SystemMessage(finalInstructions);
|
|
303
|
-
return RunnableLambda.from((messages) => {
|
|
304
|
-
return [systemMessage, ...messages];
|
|
305
|
-
}).withConfig({ runName: 'prompt' });
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
317
|
initializeTools({ currentTools, currentToolMap, agentContext, }) {
|
|
309
318
|
const toolDefinitions = agentContext?.toolDefinitions;
|
|
310
319
|
const eventDrivenMode = toolDefinitions != null && toolDefinitions.length > 0;
|
|
@@ -334,6 +343,8 @@ class StandardGraph extends Graph {
|
|
|
334
343
|
toolCallStepIds: this.toolCallStepIds,
|
|
335
344
|
toolRegistry: agentContext?.toolRegistry,
|
|
336
345
|
directToolNames: directToolNames.size > 0 ? directToolNames : undefined,
|
|
346
|
+
maxContextTokens: agentContext?.maxContextTokens,
|
|
347
|
+
maxToolResultChars: agentContext?.maxToolResultChars,
|
|
337
348
|
errorHandler: (data, metadata) => StandardGraph.handleToolCallErrorStatic(this, data, metadata),
|
|
338
349
|
});
|
|
339
350
|
}
|
|
@@ -357,42 +368,10 @@ class StandardGraph extends Graph {
|
|
|
357
368
|
errorHandler: (data, metadata) => StandardGraph.handleToolCallErrorStatic(this, data, metadata),
|
|
358
369
|
toolRegistry: agentContext?.toolRegistry,
|
|
359
370
|
sessions: this.sessions,
|
|
371
|
+
maxContextTokens: agentContext?.maxContextTokens,
|
|
372
|
+
maxToolResultChars: agentContext?.maxToolResultChars,
|
|
360
373
|
});
|
|
361
374
|
}
|
|
362
|
-
initializeModel({ provider, tools, clientOptions, }) {
|
|
363
|
-
const ChatModelClass = getChatModelClass(provider);
|
|
364
|
-
const model = new ChatModelClass(clientOptions ?? {});
|
|
365
|
-
if (isOpenAILike(provider) &&
|
|
366
|
-
(model instanceof ChatOpenAI || model instanceof AzureChatOpenAI)) {
|
|
367
|
-
model.temperature = clientOptions
|
|
368
|
-
.temperature;
|
|
369
|
-
model.topP = clientOptions.topP;
|
|
370
|
-
model.frequencyPenalty = clientOptions
|
|
371
|
-
.frequencyPenalty;
|
|
372
|
-
model.presencePenalty = clientOptions
|
|
373
|
-
.presencePenalty;
|
|
374
|
-
model.n = clientOptions.n;
|
|
375
|
-
}
|
|
376
|
-
else if (provider === Providers.VERTEXAI &&
|
|
377
|
-
model instanceof ChatVertexAI) {
|
|
378
|
-
model.temperature = clientOptions
|
|
379
|
-
.temperature;
|
|
380
|
-
model.topP = clientOptions.topP;
|
|
381
|
-
model.topK = clientOptions.topK;
|
|
382
|
-
model.topLogprobs = clientOptions
|
|
383
|
-
.topLogprobs;
|
|
384
|
-
model.frequencyPenalty = clientOptions
|
|
385
|
-
.frequencyPenalty;
|
|
386
|
-
model.presencePenalty = clientOptions
|
|
387
|
-
.presencePenalty;
|
|
388
|
-
model.maxOutputTokens = clientOptions
|
|
389
|
-
.maxOutputTokens;
|
|
390
|
-
}
|
|
391
|
-
if (!tools || tools.length === 0) {
|
|
392
|
-
return model;
|
|
393
|
-
}
|
|
394
|
-
return model.bindTools(tools);
|
|
395
|
-
}
|
|
396
375
|
overrideTestModel(responses, sleep, toolCalls) {
|
|
397
376
|
this.overrideModel = createFakeStreamingLLM({
|
|
398
377
|
responses,
|
|
@@ -400,10 +379,6 @@ class StandardGraph extends Graph {
|
|
|
400
379
|
toolCalls,
|
|
401
380
|
});
|
|
402
381
|
}
|
|
403
|
-
getNewModel({ provider, clientOptions, }) {
|
|
404
|
-
const ChatModelClass = getChatModelClass(provider);
|
|
405
|
-
return new ChatModelClass(clientOptions ?? {});
|
|
406
|
-
}
|
|
407
382
|
getUsageMetadata(finalMessage) {
|
|
408
383
|
if (finalMessage &&
|
|
409
384
|
'usage_metadata' in finalMessage &&
|
|
@@ -411,58 +386,6 @@ class StandardGraph extends Graph {
|
|
|
411
386
|
return finalMessage.usage_metadata;
|
|
412
387
|
}
|
|
413
388
|
}
|
|
414
|
-
/** Execute model invocation with streaming support */
|
|
415
|
-
async attemptInvoke({ currentModel, finalMessages, provider, tools: _tools, }, config) {
|
|
416
|
-
const model = this.overrideModel ?? currentModel;
|
|
417
|
-
if (!model) {
|
|
418
|
-
throw new Error('No model found');
|
|
419
|
-
}
|
|
420
|
-
if (model.stream) {
|
|
421
|
-
/**
|
|
422
|
-
* Process all model output through a local ChatModelStreamHandler in the
|
|
423
|
-
* graph execution context. Each chunk is awaited before the next one is
|
|
424
|
-
* consumed, so by the time the stream is exhausted every run step
|
|
425
|
-
* (MESSAGE_CREATION, TOOL_CALLS) has been created and toolCallStepIds is
|
|
426
|
-
* fully populated — the graph will not transition to ToolNode until this
|
|
427
|
-
* is done.
|
|
428
|
-
*
|
|
429
|
-
* This replaces the previous pattern where ChatModelStreamHandler lived
|
|
430
|
-
* in the for-await stream consumer (handler registry). That consumer
|
|
431
|
-
* runs concurrently with graph execution, so the graph could advance to
|
|
432
|
-
* ToolNode before the consumer had processed all events. By handling
|
|
433
|
-
* chunks here, inside the agent node, the race is eliminated.
|
|
434
|
-
*
|
|
435
|
-
* The for-await consumer no longer needs a ChatModelStreamHandler; its
|
|
436
|
-
* on_chat_model_stream events are simply ignored (no handler registered).
|
|
437
|
-
* The dispatched custom events (ON_RUN_STEP, ON_MESSAGE_DELTA, etc.)
|
|
438
|
-
* still reach the content aggregator and SSE handlers through the custom
|
|
439
|
-
* event callback in Run.createCustomEventCallback.
|
|
440
|
-
*/
|
|
441
|
-
const metadata = config?.metadata;
|
|
442
|
-
const streamHandler = new ChatModelStreamHandler();
|
|
443
|
-
const stream = await model.stream(finalMessages, config);
|
|
444
|
-
let finalChunk;
|
|
445
|
-
for await (const chunk of stream) {
|
|
446
|
-
await streamHandler.handle(GraphEvents.CHAT_MODEL_STREAM, { chunk }, metadata, this);
|
|
447
|
-
finalChunk = finalChunk ? concat(finalChunk, chunk) : chunk;
|
|
448
|
-
}
|
|
449
|
-
if (manualToolStreamProviders.has(provider)) {
|
|
450
|
-
finalChunk = modifyDeltaProperties(provider, finalChunk);
|
|
451
|
-
}
|
|
452
|
-
if ((finalChunk?.tool_calls?.length ?? 0) > 0) {
|
|
453
|
-
finalChunk.tool_calls = finalChunk.tool_calls?.filter((tool_call) => !!tool_call.name);
|
|
454
|
-
}
|
|
455
|
-
return { messages: [finalChunk] };
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
/** Fallback for models without stream support. */
|
|
459
|
-
const finalMessage = await model.invoke(finalMessages, config);
|
|
460
|
-
if ((finalMessage.tool_calls?.length ?? 0) > 0) {
|
|
461
|
-
finalMessage.tool_calls = finalMessage.tool_calls?.filter((tool_call) => !!tool_call.name);
|
|
462
|
-
}
|
|
463
|
-
return { messages: [finalMessage] };
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
389
|
cleanupSignalListener(currentModel) {
|
|
467
390
|
if (!this.signal) {
|
|
468
391
|
return;
|
|
@@ -480,9 +403,6 @@ class StandardGraph extends Graph {
|
|
|
480
403
|
}
|
|
481
404
|
createCallModel(agentId = 'default') {
|
|
482
405
|
return async (state, config) => {
|
|
483
|
-
/**
|
|
484
|
-
* Get agent context - it must exist by this point
|
|
485
|
-
*/
|
|
486
406
|
const agentContext = this.agentContexts.get(agentId);
|
|
487
407
|
if (!agentContext) {
|
|
488
408
|
throw new Error(`Agent context not found for agentId: ${agentId}`);
|
|
@@ -491,14 +411,13 @@ class StandardGraph extends Graph {
|
|
|
491
411
|
throw new Error('No config provided');
|
|
492
412
|
}
|
|
493
413
|
const { messages } = state;
|
|
494
|
-
// Extract tool discoveries from current turn only (similar to formatArtifactPayload pattern)
|
|
495
414
|
const discoveredNames = extractToolDiscoveries(messages);
|
|
496
415
|
if (discoveredNames.length > 0) {
|
|
497
416
|
agentContext.markToolsAsDiscovered(discoveredNames);
|
|
498
417
|
}
|
|
499
418
|
const toolsForBinding = agentContext.getToolsForBinding();
|
|
500
419
|
let model = this.overrideModel ??
|
|
501
|
-
|
|
420
|
+
initializeModel({
|
|
502
421
|
tools: toolsForBinding,
|
|
503
422
|
provider: agentContext.provider,
|
|
504
423
|
clientOptions: agentContext.clientOptions,
|
|
@@ -516,34 +435,97 @@ class StandardGraph extends Graph {
|
|
|
516
435
|
let messagesToUse = messages;
|
|
517
436
|
if (!agentContext.pruneMessages &&
|
|
518
437
|
agentContext.tokenCounter &&
|
|
519
|
-
agentContext.maxContextTokens != null
|
|
520
|
-
agentContext.indexTokenCountMap[0] != null) {
|
|
521
|
-
const isAnthropicWithThinking = (agentContext.provider === Providers.ANTHROPIC &&
|
|
522
|
-
agentContext.clientOptions.thinking !=
|
|
523
|
-
null) ||
|
|
524
|
-
(agentContext.provider === Providers.BEDROCK &&
|
|
525
|
-
agentContext.clientOptions
|
|
526
|
-
.additionalModelRequestFields?.['thinking'] != null) ||
|
|
527
|
-
(agentContext.provider === Providers.OPENAI &&
|
|
528
|
-
agentContext.clientOptions.modelKwargs
|
|
529
|
-
?.thinking?.type === 'enabled');
|
|
438
|
+
agentContext.maxContextTokens != null) {
|
|
530
439
|
agentContext.pruneMessages = createPruneMessages({
|
|
531
|
-
startIndex: this.startIndex,
|
|
440
|
+
startIndex: agentContext.indexTokenCountMap[0] != null ? this.startIndex : 0,
|
|
532
441
|
provider: agentContext.provider,
|
|
533
442
|
tokenCounter: agentContext.tokenCounter,
|
|
534
443
|
maxTokens: agentContext.maxContextTokens,
|
|
535
|
-
thinkingEnabled:
|
|
444
|
+
thinkingEnabled: isThinkingEnabled(agentContext.provider, agentContext.clientOptions),
|
|
536
445
|
indexTokenCountMap: agentContext.indexTokenCountMap,
|
|
446
|
+
contextPruningConfig: agentContext.contextPruningConfig,
|
|
447
|
+
summarizationEnabled: agentContext.summarizationEnabled,
|
|
448
|
+
reserveRatio: agentContext.summarizationConfig?.reserveRatio,
|
|
449
|
+
calibrationRatio: agentContext.calibrationRatio,
|
|
450
|
+
getInstructionTokens: () => agentContext.instructionTokens,
|
|
451
|
+
log: (level, message, data) => {
|
|
452
|
+
emitAgentLog(config, level, 'prune', message, data, {
|
|
453
|
+
runId: this.runId,
|
|
454
|
+
agentId,
|
|
455
|
+
});
|
|
456
|
+
},
|
|
537
457
|
});
|
|
538
458
|
}
|
|
539
459
|
if (agentContext.pruneMessages) {
|
|
540
|
-
const { context, indexTokenCountMap } = agentContext.pruneMessages({
|
|
460
|
+
const { context, indexTokenCountMap, messagesToRefine, prePruneContextTokens, remainingContextTokens, originalToolContent, calibrationRatio, resolvedInstructionOverhead, } = agentContext.pruneMessages({
|
|
541
461
|
messages,
|
|
542
462
|
usageMetadata: agentContext.currentUsage,
|
|
543
|
-
|
|
463
|
+
lastCallUsage: agentContext.lastCallUsage,
|
|
464
|
+
totalTokensFresh: agentContext.totalTokensFresh,
|
|
544
465
|
});
|
|
545
466
|
agentContext.indexTokenCountMap = indexTokenCountMap;
|
|
467
|
+
if (calibrationRatio != null && calibrationRatio > 0) {
|
|
468
|
+
agentContext.calibrationRatio = calibrationRatio;
|
|
469
|
+
}
|
|
470
|
+
if (resolvedInstructionOverhead != null) {
|
|
471
|
+
agentContext.resolvedInstructionOverhead =
|
|
472
|
+
resolvedInstructionOverhead;
|
|
473
|
+
const nonToolOverhead = agentContext.instructionTokens - agentContext.toolSchemaTokens;
|
|
474
|
+
const calibratedToolTokens = Math.max(0, resolvedInstructionOverhead - nonToolOverhead);
|
|
475
|
+
const currentToolTokens = agentContext.toolSchemaTokens;
|
|
476
|
+
const variance = currentToolTokens > 0
|
|
477
|
+
? Math.abs(calibratedToolTokens - currentToolTokens) /
|
|
478
|
+
currentToolTokens
|
|
479
|
+
: 1;
|
|
480
|
+
if (variance > CALIBRATION_VARIANCE_THRESHOLD) {
|
|
481
|
+
agentContext.toolSchemaTokens = calibratedToolTokens;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
546
484
|
messagesToUse = context;
|
|
485
|
+
const hasPrunedMessages = agentContext.summarizationEnabled === true &&
|
|
486
|
+
Array.isArray(messagesToRefine) &&
|
|
487
|
+
messagesToRefine.length > 0;
|
|
488
|
+
if (hasPrunedMessages) {
|
|
489
|
+
const shouldSkip = agentContext.shouldSkipSummarization(messages.length);
|
|
490
|
+
const triggerResult = !shouldSkip &&
|
|
491
|
+
shouldTriggerSummarization({
|
|
492
|
+
trigger: agentContext.summarizationConfig?.trigger,
|
|
493
|
+
maxContextTokens: agentContext.maxContextTokens,
|
|
494
|
+
prePruneContextTokens: prePruneContextTokens != null
|
|
495
|
+
? prePruneContextTokens + agentContext.instructionTokens
|
|
496
|
+
: undefined,
|
|
497
|
+
remainingContextTokens,
|
|
498
|
+
messagesToRefineCount: messagesToRefine.length,
|
|
499
|
+
});
|
|
500
|
+
if (triggerResult) {
|
|
501
|
+
if (originalToolContent != null && originalToolContent.size > 0) {
|
|
502
|
+
agentContext.pendingOriginalToolContent = originalToolContent;
|
|
503
|
+
}
|
|
504
|
+
emitAgentLog(config, 'info', 'graph', 'Summarization triggered', undefined, { runId: this.runId, agentId });
|
|
505
|
+
emitAgentLog(config, 'debug', 'graph', 'Summarization trigger details', {
|
|
506
|
+
totalMessages: messages.length,
|
|
507
|
+
remainingContextTokens: remainingContextTokens ?? 0,
|
|
508
|
+
summaryVersion: agentContext.summaryVersion + 1,
|
|
509
|
+
toolSchemaTokens: agentContext.toolSchemaTokens,
|
|
510
|
+
instructionTokens: agentContext.instructionTokens,
|
|
511
|
+
systemMessageTokens: agentContext.systemMessageTokens,
|
|
512
|
+
}, { runId: this.runId, agentId });
|
|
513
|
+
agentContext.markSummarizationTriggered(messages.length);
|
|
514
|
+
return {
|
|
515
|
+
summarizationRequest: {
|
|
516
|
+
remainingContextTokens: remainingContextTokens ?? 0,
|
|
517
|
+
agentId: agentId || agentContext.agentId,
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
if (shouldSkip) {
|
|
522
|
+
emitAgentLog(config, 'debug', 'graph', 'Summarization skipped — no new messages or per-run cap reached', {
|
|
523
|
+
messageCount: messages.length,
|
|
524
|
+
messagesToRefineCount: messagesToRefine.length,
|
|
525
|
+
contextLength: context.length,
|
|
526
|
+
}, { runId: this.runId, agentId });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
547
529
|
}
|
|
548
530
|
let finalMessages = messagesToUse;
|
|
549
531
|
if (agentContext.useLegacyContent) {
|
|
@@ -555,26 +537,29 @@ class StandardGraph extends Graph {
|
|
|
555
537
|
const lastMessageY = finalMessages.length >= 1
|
|
556
538
|
? finalMessages[finalMessages.length - 1]
|
|
557
539
|
: null;
|
|
540
|
+
const anthropicLike = isAnthropicLike(agentContext.provider, agentContext.clientOptions);
|
|
558
541
|
if (agentContext.provider === Providers.BEDROCK &&
|
|
559
542
|
lastMessageX instanceof AIMessageChunk &&
|
|
560
543
|
lastMessageY instanceof ToolMessage &&
|
|
561
544
|
typeof lastMessageX.content === 'string') {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (isLatestToolMessage &&
|
|
566
|
-
agentContext.provider === Providers.ANTHROPIC) {
|
|
567
|
-
formatAnthropicArtifactContent(finalMessages);
|
|
545
|
+
const trimmed = lastMessageX.content.trim();
|
|
546
|
+
finalMessages[finalMessages.length - 2].content =
|
|
547
|
+
trimmed.length > 0 ? [{ type: 'text', text: trimmed }] : '';
|
|
568
548
|
}
|
|
569
|
-
|
|
570
|
-
(
|
|
549
|
+
if (lastMessageY instanceof ToolMessage) {
|
|
550
|
+
if (anthropicLike) {
|
|
551
|
+
formatAnthropicArtifactContent(finalMessages);
|
|
552
|
+
}
|
|
553
|
+
else if ((isOpenAILike(agentContext.provider) &&
|
|
571
554
|
agentContext.provider !== Providers.DEEPSEEK) ||
|
|
572
|
-
isGoogleLike(agentContext.provider))
|
|
573
|
-
|
|
555
|
+
isGoogleLike(agentContext.provider)) {
|
|
556
|
+
formatArtifactPayload(finalMessages);
|
|
557
|
+
}
|
|
574
558
|
}
|
|
575
559
|
if (agentContext.provider === Providers.ANTHROPIC) {
|
|
576
560
|
const anthropicOptions = agentContext.clientOptions;
|
|
577
|
-
if (anthropicOptions?.promptCache === true
|
|
561
|
+
if (anthropicOptions?.promptCache === true &&
|
|
562
|
+
!agentContext.systemRunnable) {
|
|
578
563
|
finalMessages = addCacheControl(finalMessages);
|
|
579
564
|
}
|
|
580
565
|
}
|
|
@@ -584,19 +569,25 @@ class StandardGraph extends Graph {
|
|
|
584
569
|
finalMessages = addBedrockCacheControl(finalMessages);
|
|
585
570
|
}
|
|
586
571
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
(agentContext.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
572
|
+
if (isThinkingEnabled(agentContext.provider, agentContext.clientOptions)) {
|
|
573
|
+
finalMessages = ensureThinkingBlockInMessages(finalMessages, agentContext.provider, config);
|
|
574
|
+
}
|
|
575
|
+
// Intentionally broad: runs when the pruner wasn't used OR any post-pruning
|
|
576
|
+
// transform (addCacheControl, ensureThinkingBlock, etc.) reassigned finalMessages.
|
|
577
|
+
// sanitizeOrphanToolBlocks fast-paths to a Set diff check when no orphans exist,
|
|
578
|
+
// so the cost is negligible and this acts as a safety net for Anthropic/Bedrock.
|
|
579
|
+
const needsOrphanSanitize = anthropicLike &&
|
|
580
|
+
(!agentContext.pruneMessages || finalMessages !== messagesToUse);
|
|
581
|
+
if (needsOrphanSanitize) {
|
|
582
|
+
const beforeSanitize = finalMessages.length;
|
|
583
|
+
finalMessages = sanitizeOrphanToolBlocks(finalMessages);
|
|
584
|
+
if (finalMessages.length !== beforeSanitize) {
|
|
585
|
+
emitAgentLog(config, 'warn', 'sanitize', 'Orphan tool blocks removed', {
|
|
586
|
+
before: beforeSanitize,
|
|
587
|
+
after: finalMessages.length,
|
|
588
|
+
dropped: beforeSanitize - finalMessages.length,
|
|
589
|
+
}, { runId: this.runId, agentId });
|
|
590
|
+
}
|
|
600
591
|
}
|
|
601
592
|
if (agentContext.lastStreamCall != null &&
|
|
602
593
|
agentContext.streamBuffer != null) {
|
|
@@ -608,52 +599,68 @@ class StandardGraph extends Graph {
|
|
|
608
599
|
}
|
|
609
600
|
}
|
|
610
601
|
agentContext.lastStreamCall = Date.now();
|
|
602
|
+
agentContext.markTokensStale();
|
|
611
603
|
let result;
|
|
612
604
|
const fallbacks = agentContext.clientOptions?.fallbacks ??
|
|
613
605
|
[];
|
|
614
|
-
if (finalMessages.length === 0
|
|
606
|
+
if (finalMessages.length === 0 &&
|
|
607
|
+
!agentContext.hasPendingCompactionSummary()) {
|
|
608
|
+
const budgetBreakdown = agentContext.getTokenBudgetBreakdown(messages);
|
|
609
|
+
const breakdown = agentContext.formatTokenBudgetBreakdown(messages);
|
|
610
|
+
const instructionsExceedBudget = budgetBreakdown.instructionTokens > budgetBreakdown.maxContextTokens;
|
|
611
|
+
let guidance;
|
|
612
|
+
if (instructionsExceedBudget) {
|
|
613
|
+
const toolPct = budgetBreakdown.toolSchemaTokens > 0
|
|
614
|
+
? Math.round((budgetBreakdown.toolSchemaTokens /
|
|
615
|
+
budgetBreakdown.instructionTokens) *
|
|
616
|
+
100)
|
|
617
|
+
: 0;
|
|
618
|
+
guidance =
|
|
619
|
+
toolPct > 50
|
|
620
|
+
? `Tool definitions consume ${budgetBreakdown.toolSchemaTokens} tokens (${toolPct}% of instructions) across ${budgetBreakdown.toolCount} tools, exceeding maxContextTokens (${budgetBreakdown.maxContextTokens}). Reduce the number of tools or increase maxContextTokens.`
|
|
621
|
+
: `Instructions (${budgetBreakdown.instructionTokens} tokens) exceed maxContextTokens (${budgetBreakdown.maxContextTokens}). Increase maxContextTokens or shorten the system prompt.`;
|
|
622
|
+
if (agentContext.summarizationEnabled === true) {
|
|
623
|
+
guidance +=
|
|
624
|
+
' Summarization was skipped because the summary would further increase the instruction overhead.';
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
guidance =
|
|
629
|
+
'Please increase the context window size or make your message shorter.';
|
|
630
|
+
}
|
|
631
|
+
emitAgentLog(config, 'error', 'graph', 'Empty messages after pruning', {
|
|
632
|
+
messageCount: messages.length,
|
|
633
|
+
instructionsExceedBudget,
|
|
634
|
+
breakdown,
|
|
635
|
+
}, { runId: this.runId, agentId });
|
|
615
636
|
throw new Error(JSON.stringify({
|
|
616
637
|
type: 'empty_messages',
|
|
617
|
-
info:
|
|
638
|
+
info: `Message pruning removed all messages as none fit in the context window. ${guidance}\n${breakdown}`,
|
|
618
639
|
}));
|
|
619
640
|
}
|
|
641
|
+
const invokeStart = Date.now();
|
|
642
|
+
const invokeMeta = { runId: this.runId, agentId };
|
|
643
|
+
emitAgentLog(config, 'debug', 'graph', 'Invoking LLM', {
|
|
644
|
+
messageCount: finalMessages.length,
|
|
645
|
+
provider: agentContext.provider,
|
|
646
|
+
}, invokeMeta, { force: true });
|
|
620
647
|
try {
|
|
621
|
-
result = await
|
|
622
|
-
|
|
623
|
-
finalMessages,
|
|
648
|
+
result = await attemptInvoke({
|
|
649
|
+
model: (this.overrideModel ?? model),
|
|
650
|
+
messages: finalMessages,
|
|
624
651
|
provider: agentContext.provider,
|
|
625
|
-
|
|
652
|
+
context: this,
|
|
626
653
|
}, config);
|
|
627
654
|
}
|
|
628
655
|
catch (primaryError) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
model = (!bindableTools || bindableTools.length === 0
|
|
638
|
-
? model
|
|
639
|
-
: model.bindTools(bindableTools));
|
|
640
|
-
result = await this.attemptInvoke({
|
|
641
|
-
currentModel: model,
|
|
642
|
-
finalMessages,
|
|
643
|
-
provider: fb.provider,
|
|
644
|
-
tools: agentContext.tools,
|
|
645
|
-
}, config);
|
|
646
|
-
lastError = undefined;
|
|
647
|
-
break;
|
|
648
|
-
}
|
|
649
|
-
catch (e) {
|
|
650
|
-
lastError = e;
|
|
651
|
-
continue;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
if (lastError !== undefined) {
|
|
655
|
-
throw lastError;
|
|
656
|
-
}
|
|
656
|
+
result = await tryFallbackProviders({
|
|
657
|
+
fallbacks,
|
|
658
|
+
tools: agentContext.tools,
|
|
659
|
+
messages: finalMessages,
|
|
660
|
+
config,
|
|
661
|
+
primaryError,
|
|
662
|
+
context: this,
|
|
663
|
+
});
|
|
657
664
|
}
|
|
658
665
|
if (!result) {
|
|
659
666
|
throw new Error('No result after model invocation');
|
|
@@ -753,20 +760,42 @@ class StandardGraph extends Graph {
|
|
|
753
760
|
}
|
|
754
761
|
}
|
|
755
762
|
}
|
|
763
|
+
const invokeElapsed = ((Date.now() - invokeStart) / 1000).toFixed(2);
|
|
756
764
|
agentContext.currentUsage = this.getUsageMetadata(result.messages?.[0]);
|
|
765
|
+
if (agentContext.currentUsage) {
|
|
766
|
+
agentContext.updateLastCallUsage(agentContext.currentUsage);
|
|
767
|
+
emitAgentLog(config, 'debug', 'graph', `LLM call complete (${invokeElapsed}s)`, {
|
|
768
|
+
...agentContext.currentUsage,
|
|
769
|
+
elapsedSeconds: Number(invokeElapsed),
|
|
770
|
+
instructionTokens: agentContext.instructionTokens,
|
|
771
|
+
toolSchemaTokens: agentContext.toolSchemaTokens,
|
|
772
|
+
messageCount: finalMessages.length,
|
|
773
|
+
}, invokeMeta, { force: true });
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
emitAgentLog(config, 'debug', 'graph', `LLM call complete (${invokeElapsed}s)`, {
|
|
777
|
+
elapsedSeconds: Number(invokeElapsed),
|
|
778
|
+
messageCount: finalMessages.length,
|
|
779
|
+
}, invokeMeta, { force: true });
|
|
780
|
+
}
|
|
757
781
|
this.cleanupSignalListener();
|
|
758
782
|
return result;
|
|
759
783
|
};
|
|
760
784
|
}
|
|
761
785
|
createAgentNode(agentId) {
|
|
786
|
+
const getConfig = () => this.config;
|
|
762
787
|
const agentContext = this.agentContexts.get(agentId);
|
|
763
788
|
if (!agentContext) {
|
|
764
789
|
throw new Error(`Agent context not found for agentId: ${agentId}`);
|
|
765
790
|
}
|
|
766
791
|
const agentNode = `${AGENT}${agentId}`;
|
|
767
792
|
const toolNode = `${TOOLS}${agentId}`;
|
|
793
|
+
const summarizeNode = `${SUMMARIZE}${agentId}`;
|
|
768
794
|
const routeMessage = (state, config) => {
|
|
769
795
|
this.config = config;
|
|
796
|
+
if (state.summarizationRequest != null) {
|
|
797
|
+
return summarizeNode;
|
|
798
|
+
}
|
|
770
799
|
return toolsCondition(state, toolNode, this.invokedToolIds);
|
|
771
800
|
};
|
|
772
801
|
const StateAnnotation = Annotation.Root({
|
|
@@ -774,6 +803,10 @@ class StandardGraph extends Graph {
|
|
|
774
803
|
reducer: messagesStateReducer,
|
|
775
804
|
default: () => [],
|
|
776
805
|
}),
|
|
806
|
+
summarizationRequest: Annotation({
|
|
807
|
+
reducer: (_, b) => b,
|
|
808
|
+
default: () => undefined,
|
|
809
|
+
}),
|
|
777
810
|
});
|
|
778
811
|
const workflow = new StateGraph(StateAnnotation)
|
|
779
812
|
.addNode(agentNode, this.createCallModel(agentId))
|
|
@@ -781,15 +814,54 @@ class StandardGraph extends Graph {
|
|
|
781
814
|
currentTools: agentContext.tools,
|
|
782
815
|
currentToolMap: agentContext.toolMap,
|
|
783
816
|
agentContext,
|
|
817
|
+
}))
|
|
818
|
+
.addNode(summarizeNode, createSummarizeNode({
|
|
819
|
+
agentContext,
|
|
820
|
+
graph: {
|
|
821
|
+
contentData: this.contentData,
|
|
822
|
+
contentIndexMap: this.contentIndexMap,
|
|
823
|
+
get config() {
|
|
824
|
+
return getConfig();
|
|
825
|
+
},
|
|
826
|
+
runId: this.runId,
|
|
827
|
+
isMultiAgent: this.isMultiAgentGraph(),
|
|
828
|
+
dispatchRunStep: async (runStep, nodeConfig) => {
|
|
829
|
+
this.contentData.push(runStep);
|
|
830
|
+
this.contentIndexMap.set(runStep.id, runStep.index);
|
|
831
|
+
const resolvedConfig = nodeConfig ?? this.config;
|
|
832
|
+
const handler = this.handlerRegistry?.getHandler(GraphEvents.ON_RUN_STEP);
|
|
833
|
+
if (handler) {
|
|
834
|
+
await handler.handle(GraphEvents.ON_RUN_STEP, runStep, resolvedConfig?.configurable, this);
|
|
835
|
+
this.handlerDispatchedStepIds.add(runStep.id);
|
|
836
|
+
}
|
|
837
|
+
if (resolvedConfig) {
|
|
838
|
+
await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP, runStep, resolvedConfig);
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
dispatchRunStepCompleted: async (stepId, result, nodeConfig) => {
|
|
842
|
+
const resolvedConfig = nodeConfig ?? this.config;
|
|
843
|
+
const runStep = this.contentData.find((s) => s.id === stepId);
|
|
844
|
+
const handler = this.handlerRegistry?.getHandler(GraphEvents.ON_RUN_STEP_COMPLETED);
|
|
845
|
+
if (handler) {
|
|
846
|
+
await handler.handle(GraphEvents.ON_RUN_STEP_COMPLETED, {
|
|
847
|
+
result: {
|
|
848
|
+
...result,
|
|
849
|
+
id: stepId,
|
|
850
|
+
index: runStep?.index ?? 0,
|
|
851
|
+
},
|
|
852
|
+
}, resolvedConfig?.configurable, this);
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
generateStepId: (stepKey) => this.generateStepId(stepKey),
|
|
784
857
|
}))
|
|
785
858
|
.addEdge(START, agentNode)
|
|
786
859
|
.addConditionalEdges(agentNode, routeMessage)
|
|
860
|
+
.addEdge(summarizeNode, agentNode)
|
|
787
861
|
.addEdge(toolNode, agentContext.toolEnd ? END : agentNode);
|
|
788
|
-
|
|
789
|
-
return workflow.compile(this.compileOptions);
|
|
862
|
+
return workflow.compile();
|
|
790
863
|
}
|
|
791
864
|
createWorkflow() {
|
|
792
|
-
/** Use the default (first) agent for now */
|
|
793
865
|
const agentNode = this.createAgentNode(this.defaultAgentId);
|
|
794
866
|
const StateAnnotation = Annotation.Root({
|
|
795
867
|
messages: Annotation({
|
|
@@ -807,7 +879,8 @@ class StandardGraph extends Graph {
|
|
|
807
879
|
const workflow = new StateGraph(StateAnnotation)
|
|
808
880
|
.addNode(this.defaultAgentId, agentNode, { ends: [END] })
|
|
809
881
|
.addEdge(START, this.defaultAgentId)
|
|
810
|
-
|
|
882
|
+
// LangGraph compile() types are overly strict for opt-in options
|
|
883
|
+
.compile(this.compileOptions);
|
|
811
884
|
return workflow;
|
|
812
885
|
}
|
|
813
886
|
/**
|
|
@@ -858,18 +931,11 @@ class StandardGraph extends Graph {
|
|
|
858
931
|
if (runId) {
|
|
859
932
|
runStep.runId = runId;
|
|
860
933
|
}
|
|
861
|
-
/**
|
|
862
|
-
* Extract agentId and parallelGroupId from metadata
|
|
863
|
-
* Only set agentId for MultiAgentGraph (so frontend knows when to show agent labels)
|
|
864
|
-
*/
|
|
865
934
|
if (metadata) {
|
|
866
935
|
try {
|
|
867
936
|
const agentContext = this.getAgentContext(metadata);
|
|
868
937
|
if (this.isMultiAgentGraph() && agentContext.agentId) {
|
|
869
|
-
// Only include agentId for MultiAgentGraph - enables frontend to show agent labels
|
|
870
938
|
runStep.agentId = agentContext.agentId;
|
|
871
|
-
// Set group ID if this agent is part of a parallel group
|
|
872
|
-
// Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order
|
|
873
939
|
const groupId = this.getParallelGroupIdForAgent(agentContext.agentId);
|
|
874
940
|
if (groupId != null) {
|
|
875
941
|
runStep.groupId = groupId;
|
|
@@ -882,6 +948,21 @@ class StandardGraph extends Graph {
|
|
|
882
948
|
}
|
|
883
949
|
this.contentData.push(runStep);
|
|
884
950
|
this.contentIndexMap.set(stepId, runStep.index);
|
|
951
|
+
// Primary dispatch: handler registry (reliable, always works).
|
|
952
|
+
// This mirrors how handleToolCallCompleted dispatches ON_RUN_STEP_COMPLETED
|
|
953
|
+
// via the handler registry, ensuring the event always reaches the handler
|
|
954
|
+
// even when LangGraph's callback system drops the custom event.
|
|
955
|
+
const handler = this.handlerRegistry?.getHandler(GraphEvents.ON_RUN_STEP);
|
|
956
|
+
if (handler) {
|
|
957
|
+
await handler.handle(GraphEvents.ON_RUN_STEP, runStep, metadata, this);
|
|
958
|
+
this.handlerDispatchedStepIds.add(stepId);
|
|
959
|
+
}
|
|
960
|
+
// Secondary dispatch: custom event for LangGraph callback chain
|
|
961
|
+
// (tracing, Langfuse, external consumers). May be silently dropped
|
|
962
|
+
// in some scenarios (stale run ID, subgraph callback propagation issues),
|
|
963
|
+
// but the primary dispatch above guarantees the event reaches the handler.
|
|
964
|
+
// The customEventCallback in run.ts skips events already dispatched above
|
|
965
|
+
// to prevent double handling.
|
|
885
966
|
await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP, runStep, this.config);
|
|
886
967
|
return stepId;
|
|
887
968
|
}
|