@librechat/agents 3.6.11 → 3.6.12

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.
Files changed (33) hide show
  1. package/dist/cjs/graphs/Graph.cjs +44 -116
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/contextPressureMeter.cjs +132 -0
  4. package/dist/cjs/llm/contextPressureMeter.cjs.map +1 -0
  5. package/dist/cjs/llm/invoke.cjs +46 -75
  6. package/dist/cjs/llm/invoke.cjs.map +1 -1
  7. package/dist/cjs/llm/prepareProviderRequest.cjs +108 -0
  8. package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -0
  9. package/dist/cjs/main.cjs +2 -0
  10. package/dist/cjs/messages/content.cjs +5 -4
  11. package/dist/cjs/messages/content.cjs.map +1 -1
  12. package/dist/esm/graphs/Graph.mjs +47 -119
  13. package/dist/esm/graphs/Graph.mjs.map +1 -1
  14. package/dist/esm/llm/contextPressureMeter.mjs +132 -0
  15. package/dist/esm/llm/contextPressureMeter.mjs.map +1 -0
  16. package/dist/esm/llm/invoke.mjs +48 -75
  17. package/dist/esm/llm/invoke.mjs.map +1 -1
  18. package/dist/esm/llm/prepareProviderRequest.mjs +105 -0
  19. package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -0
  20. package/dist/esm/main.mjs +2 -1
  21. package/dist/esm/messages/content.mjs +5 -4
  22. package/dist/esm/messages/content.mjs.map +1 -1
  23. package/dist/types/index.d.ts +2 -0
  24. package/dist/types/llm/contextPressureMeter.d.ts +30 -0
  25. package/dist/types/llm/invoke.d.ts +31 -33
  26. package/dist/types/llm/prepareProviderRequest.d.ts +55 -0
  27. package/package.json +1 -1
  28. package/src/graphs/Graph.ts +69 -276
  29. package/src/index.ts +8 -0
  30. package/src/llm/contextPressureMeter.ts +284 -0
  31. package/src/llm/invoke.ts +136 -296
  32. package/src/llm/prepareProviderRequest.ts +357 -0
  33. package/src/messages/content.ts +3 -3
@@ -0,0 +1,284 @@
1
+ import { ToolMessage } from '@langchain/core/messages';
2
+
3
+ import type { BaseMessage } from '@langchain/core/messages';
4
+ import type { ProviderPayloadMeasurement } from '@/llm/prepareProviderRequest';
5
+ import type * as t from '@/types';
6
+
7
+ import {
8
+ REPLY_PRIMER_TOKENS,
9
+ isSyntheticProviderContextMessage,
10
+ } from '@/messages';
11
+ import { apportionTokenCounts } from '@/utils';
12
+
13
+ interface ContextPressureUsage {
14
+ contextBudget?: number;
15
+ effectiveInstructionTokens?: number;
16
+ remainingContextTokens?: number;
17
+ calibrationRatio?: number;
18
+ }
19
+
20
+ interface ContextPressureMeterParams {
21
+ tokenCounter?: t.TokenCounter;
22
+ sourceMessages: BaseMessage[];
23
+ retainedMessages: BaseMessage[];
24
+ indexTokenCountMap: Record<string, number | undefined>;
25
+ contextUsage?: ContextPressureUsage | null;
26
+ instructionTokens: number;
27
+ calibrationRatio: number;
28
+ }
29
+
30
+ interface ProviderPayloadMeasureOptions {
31
+ contextBudget?: number;
32
+ forceRawRecount?: boolean;
33
+ }
34
+
35
+ interface ProviderMessageBaseline {
36
+ rawTokens: number;
37
+ accountingWeight: number;
38
+ }
39
+
40
+ interface ProviderBaselineAttribution {
41
+ attributedByOrigin: number[];
42
+ projectedBaseTokens: number;
43
+ }
44
+
45
+ export interface ContextPressureMeter {
46
+ trackProjection(before: BaseMessage[], after: BaseMessage[]): BaseMessage[];
47
+ trackClone(source: BaseMessage, clone: BaseMessage): void;
48
+ measure(
49
+ messages: BaseMessage[],
50
+ options?: ProviderPayloadMeasureOptions
51
+ ): ProviderPayloadMeasurement;
52
+ }
53
+
54
+ function getProviderMessageOriginKey(message: BaseMessage): string | undefined {
55
+ const type = message.getType();
56
+ if (
57
+ message instanceof ToolMessage &&
58
+ typeof message.tool_call_id === 'string' &&
59
+ message.tool_call_id.length > 0
60
+ ) {
61
+ return `tool:call:${message.tool_call_id}`;
62
+ }
63
+ if (typeof message.id === 'string' && message.id.length > 0) {
64
+ return `${type}:id:${message.id}`;
65
+ }
66
+ return undefined;
67
+ }
68
+
69
+ /** Measures repeated provider projections while tokenizing each message object once. */
70
+ export function createContextPressureMeter({
71
+ tokenCounter,
72
+ sourceMessages,
73
+ retainedMessages,
74
+ indexTokenCountMap,
75
+ contextUsage,
76
+ instructionTokens,
77
+ calibrationRatio,
78
+ }: ContextPressureMeterParams): ContextPressureMeter {
79
+ const tokenCounts = new WeakMap<BaseMessage, number>();
80
+ const origins = new WeakMap<BaseMessage, number>();
81
+ const baselineAttributions = new Map<number, ProviderBaselineAttribution>();
82
+ let baseline: ProviderMessageBaseline[] | undefined;
83
+ let baselineWeights: Record<string, number> | undefined;
84
+ let totalBaselineWeight = 0;
85
+
86
+ const count = (message: BaseMessage): number => {
87
+ const cached = tokenCounts.get(message);
88
+ if (cached != null) {
89
+ return cached;
90
+ }
91
+ const tokens = tokenCounter?.(message) ?? 0;
92
+ tokenCounts.set(message, tokens);
93
+ return tokens;
94
+ };
95
+
96
+ if (contextUsage != null && tokenCounter != null) {
97
+ const sourceIndices = new WeakMap<BaseMessage, number>();
98
+ for (let i = 0; i < sourceMessages.length; i++) {
99
+ sourceIndices.set(sourceMessages[i], i);
100
+ }
101
+ baseline = retainedMessages.map((message, index) => {
102
+ const rawTokens = count(message);
103
+ const sourceIndex = sourceIndices.get(message);
104
+ const indexedTokens =
105
+ sourceIndex != null ? indexTokenCountMap[sourceIndex] : undefined;
106
+ const accountingWeight =
107
+ indexedTokens != null &&
108
+ Number.isFinite(indexedTokens) &&
109
+ indexedTokens >= 0
110
+ ? indexedTokens
111
+ : rawTokens;
112
+ if (!origins.has(message)) {
113
+ origins.set(message, index);
114
+ }
115
+ return { rawTokens, accountingWeight };
116
+ });
117
+ baselineWeights = {};
118
+ for (let i = 0; i < baseline.length; i++) {
119
+ baselineWeights[i] = baseline[i].accountingWeight;
120
+ totalBaselineWeight += baseline[i].accountingWeight;
121
+ }
122
+ }
123
+
124
+ const trackClone = (source: BaseMessage, clone: BaseMessage): void => {
125
+ const origin = origins.get(source);
126
+ if (origin != null && !origins.has(clone)) {
127
+ origins.set(clone, origin);
128
+ }
129
+ };
130
+
131
+ const trackProjection = (
132
+ before: BaseMessage[],
133
+ after: BaseMessage[]
134
+ ): BaseMessage[] => {
135
+ if (baseline == null || before === after) {
136
+ return after;
137
+ }
138
+ if (before.length === after.length) {
139
+ for (let i = 0; i < after.length; i++) {
140
+ if (
141
+ before[i].getType() === after[i].getType() &&
142
+ !isSyntheticProviderContextMessage(after[i])
143
+ ) {
144
+ trackClone(before[i], after[i]);
145
+ }
146
+ }
147
+ return after;
148
+ }
149
+
150
+ const keyedOrigins = new Map<string, number | null>();
151
+ for (const message of before) {
152
+ const origin = origins.get(message);
153
+ const key = getProviderMessageOriginKey(message);
154
+ if (origin == null || key == null) {
155
+ continue;
156
+ }
157
+ keyedOrigins.set(key, keyedOrigins.has(key) ? null : origin);
158
+ }
159
+ for (const message of after) {
160
+ if (origins.has(message) || isSyntheticProviderContextMessage(message)) {
161
+ continue;
162
+ }
163
+ const key = getProviderMessageOriginKey(message);
164
+ const origin = key != null ? keyedOrigins.get(key) : undefined;
165
+ if (origin != null) {
166
+ origins.set(message, origin);
167
+ }
168
+ }
169
+ return after;
170
+ };
171
+
172
+ const measure = (
173
+ messages: BaseMessage[],
174
+ options?: ProviderPayloadMeasureOptions
175
+ ): ProviderPayloadMeasurement => {
176
+ const contextBudget = options?.contextBudget ?? contextUsage?.contextBudget;
177
+ const forceRawRecount = options?.forceRawRecount === true;
178
+ const effectiveInstructionTokens =
179
+ contextUsage?.effectiveInstructionTokens ??
180
+ (forceRawRecount ? instructionTokens : undefined);
181
+ if (
182
+ tokenCounter == null ||
183
+ contextBudget == null ||
184
+ effectiveInstructionTokens == null
185
+ ) {
186
+ return { fits: true };
187
+ }
188
+ const availableMessageTokens = Math.max(
189
+ 0,
190
+ contextBudget - effectiveInstructionTokens
191
+ );
192
+ let usageRatio = calibrationRatio > 0 ? calibrationRatio : 1;
193
+ if (
194
+ contextUsage?.calibrationRatio != null &&
195
+ contextUsage.calibrationRatio > 0
196
+ ) {
197
+ usageRatio = contextUsage.calibrationRatio;
198
+ }
199
+ if (forceRawRecount) {
200
+ usageRatio = Math.max(1, usageRatio);
201
+ }
202
+ const baselineRemaining = contextUsage?.remainingContextTokens;
203
+ const accountedMessageTokens =
204
+ !forceRawRecount &&
205
+ baseline != null &&
206
+ baselineRemaining != null &&
207
+ Number.isFinite(baselineRemaining)
208
+ ? availableMessageTokens -
209
+ Math.min(availableMessageTokens, Math.max(0, baselineRemaining))
210
+ : undefined;
211
+
212
+ let projectedMessageTokens: number;
213
+ if (
214
+ accountedMessageTokens != null &&
215
+ baseline != null &&
216
+ baselineWeights != null
217
+ ) {
218
+ let attribution = baselineAttributions.get(availableMessageTokens);
219
+ if (attribution == null) {
220
+ const replyPrimerTokens = Math.round(
221
+ REPLY_PRIMER_TOKENS * usageRatio
222
+ );
223
+ const attributableTokens =
224
+ totalBaselineWeight > 0
225
+ ? Math.min(
226
+ Math.max(0, accountedMessageTokens - replyPrimerTokens),
227
+ Math.round(totalBaselineWeight * usageRatio)
228
+ )
229
+ : 0;
230
+ const apportionedTokens =
231
+ totalBaselineWeight > 0
232
+ ? apportionTokenCounts(
233
+ baselineWeights,
234
+ attributableTokens / totalBaselineWeight,
235
+ attributableTokens
236
+ )
237
+ : {};
238
+ attribution = {
239
+ attributedByOrigin: baseline.map(
240
+ (_, origin) => apportionedTokens[origin] || 0
241
+ ),
242
+ projectedBaseTokens: Math.max(
243
+ replyPrimerTokens,
244
+ accountedMessageTokens - attributableTokens
245
+ ),
246
+ };
247
+ baselineAttributions.set(availableMessageTokens, attribution);
248
+ }
249
+ projectedMessageTokens = attribution.projectedBaseTokens;
250
+ let newRawTokens = 0;
251
+ const usedOrigins = new Set<number>();
252
+ for (const message of messages) {
253
+ const rawTokens = count(message);
254
+ const origin = origins.get(message);
255
+ if (origin == null || usedOrigins.has(origin)) {
256
+ newRawTokens += rawTokens;
257
+ continue;
258
+ }
259
+ usedOrigins.add(origin);
260
+ projectedMessageTokens += Math.max(
261
+ 0,
262
+ attribution.attributedByOrigin[origin] +
263
+ Math.round((rawTokens - baseline[origin].rawTokens) * usageRatio)
264
+ );
265
+ }
266
+ projectedMessageTokens += Math.round(newRawTokens * usageRatio);
267
+ } else {
268
+ let rawTokens = REPLY_PRIMER_TOKENS;
269
+ for (const message of messages) {
270
+ rawTokens += count(message);
271
+ }
272
+ projectedMessageTokens = Math.round(rawTokens * usageRatio);
273
+ }
274
+ return {
275
+ fits: projectedMessageTokens <= availableMessageTokens,
276
+ projectedMessageTokens,
277
+ availableMessageTokens,
278
+ contextBudget,
279
+ effectiveInstructionTokens,
280
+ };
281
+ };
282
+
283
+ return { trackProjection, trackClone, measure };
284
+ }