@librechat/agents 2.4.13 → 2.4.14

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.
@@ -1,7 +1,13 @@
1
1
  // src/specs/prune.test.ts
2
2
  import { config } from 'dotenv';
3
3
  config();
4
- import { HumanMessage, AIMessage, SystemMessage, BaseMessage, ToolMessage } from '@langchain/core/messages';
4
+ import {
5
+ HumanMessage,
6
+ AIMessage,
7
+ SystemMessage,
8
+ BaseMessage,
9
+ ToolMessage,
10
+ } from '@langchain/core/messages';
5
11
  import type { RunnableConfig } from '@langchain/core/runnables';
6
12
  import type { UsageMetadata } from '@langchain/core/messages';
7
13
  import type * as t from '@/types';
@@ -15,7 +21,10 @@ const createTestTokenCounter = (): t.TokenCounter => {
15
21
  // This simple token counter just counts characters as tokens for predictable testing
16
22
  return (message: BaseMessage): number => {
17
23
  // Use type assertion to help TypeScript understand the type
18
- const content = message.content as string | Array<t.MessageContentComplex | string> | undefined;
24
+ const content = message.content as
25
+ | string
26
+ | Array<t.MessageContentComplex | string>
27
+ | undefined;
19
28
 
20
29
  // Handle string content
21
30
  if (typeof content === 'string') {
@@ -57,7 +66,7 @@ function calculateTotalTokens(usage: Partial<UsageMetadata>): UsageMetadata {
57
66
  return {
58
67
  input_tokens: totalInputTokens,
59
68
  output_tokens: totalOutputTokens,
60
- total_tokens: totalInputTokens + totalOutputTokens
69
+ total_tokens: totalInputTokens + totalOutputTokens,
61
70
  };
62
71
  }
63
72
 
@@ -81,15 +90,21 @@ function getMessagesWithinTokenLimit({
81
90
  // start with 3 tokens for the label after all messages have been counted.
82
91
  let summaryIndex = -1;
83
92
  let currentTokenCount = 3;
84
- const instructions = _messages[0]?.getType() === 'system' ? _messages[0] : undefined;
85
- const instructionsTokenCount = instructions != null ? indexTokenCountMap[0] : 0;
93
+ const instructions =
94
+ _messages[0]?.getType() === 'system' ? _messages[0] : undefined;
95
+ const instructionsTokenCount =
96
+ instructions != null ? indexTokenCountMap[0] : 0;
86
97
  let remainingContextTokens = maxContextTokens - instructionsTokenCount;
87
98
  const messages = [..._messages];
88
99
  const context: BaseMessage[] = [];
89
100
 
90
101
  if (currentTokenCount < remainingContextTokens) {
91
102
  let currentIndex = messages.length;
92
- while (messages.length > 0 && currentTokenCount < remainingContextTokens && currentIndex > 1) {
103
+ while (
104
+ messages.length > 0 &&
105
+ currentTokenCount < remainingContextTokens &&
106
+ currentIndex > 1
107
+ ) {
93
108
  currentIndex--;
94
109
  if (messages.length === 1 && instructions) {
95
110
  break;
@@ -99,7 +114,7 @@ function getMessagesWithinTokenLimit({
99
114
 
100
115
  const tokenCount = indexTokenCountMap[currentIndex] || 0;
101
116
 
102
- if ((currentTokenCount + tokenCount) <= remainingContextTokens) {
117
+ if (currentTokenCount + tokenCount <= remainingContextTokens) {
103
118
  context.push(poppedMessage);
104
119
  currentTokenCount += tokenCount;
105
120
  } else {
@@ -109,8 +124,10 @@ function getMessagesWithinTokenLimit({
109
124
  }
110
125
 
111
126
  // If startType is specified, discard messages until we find one of the required type
112
- if (startType && context.length > 0) {
113
- const requiredTypeIndex = context.findIndex(msg => msg.getType() === startType);
127
+ if (startType != null && startType && context.length > 0) {
128
+ const requiredTypeIndex = context.findIndex(
129
+ (msg) => msg.getType() === startType
130
+ );
114
131
 
115
132
  if (requiredTypeIndex > 0) {
116
133
  // If we found a message of the required type, discard all messages before it
@@ -152,8 +169,8 @@ describe('Prune Messages Tests', () => {
152
169
  output_tokens: 50,
153
170
  input_token_details: {
154
171
  cache_creation: 10,
155
- cache_read: 5
156
- }
172
+ cache_read: 5,
173
+ },
157
174
  };
158
175
 
159
176
  const result = calculateTotalTokens(usage);
@@ -166,7 +183,7 @@ describe('Prune Messages Tests', () => {
166
183
  it('should handle missing fields gracefully', () => {
167
184
  const usage: Partial<UsageMetadata> = {
168
185
  input_tokens: 100,
169
- output_tokens: 50
186
+ output_tokens: 50,
170
187
  };
171
188
 
172
189
  const result = calculateTotalTokens(usage);
@@ -192,19 +209,19 @@ describe('Prune Messages Tests', () => {
192
209
  const messages = [
193
210
  new SystemMessage('System instruction'),
194
211
  new HumanMessage('Hello'),
195
- new AIMessage('Hi there')
212
+ new AIMessage('Hi there'),
196
213
  ];
197
214
 
198
215
  const indexTokenCountMap = {
199
216
  0: 17, // "System instruction"
200
- 1: 5, // "Hello"
201
- 2: 8 // "Hi there"
217
+ 1: 5, // "Hello"
218
+ 2: 8, // "Hi there"
202
219
  };
203
220
 
204
221
  const result = getMessagesWithinTokenLimit({
205
222
  messages,
206
223
  maxContextTokens: 100,
207
- indexTokenCountMap
224
+ indexTokenCountMap,
208
225
  });
209
226
 
210
227
  expect(result.context.length).toBe(3);
@@ -220,22 +237,22 @@ describe('Prune Messages Tests', () => {
220
237
  new HumanMessage('Message 1'),
221
238
  new AIMessage('Response 1'),
222
239
  new HumanMessage('Message 2'),
223
- new AIMessage('Response 2')
240
+ new AIMessage('Response 2'),
224
241
  ];
225
242
 
226
243
  const indexTokenCountMap = {
227
244
  0: 17, // "System instruction"
228
- 1: 9, // "Message 1"
245
+ 1: 9, // "Message 1"
229
246
  2: 10, // "Response 1"
230
- 3: 9, // "Message 2"
231
- 4: 10 // "Response 2"
247
+ 3: 9, // "Message 2"
248
+ 4: 10, // "Response 2"
232
249
  };
233
250
 
234
251
  // Set a limit that can only fit the system message and the last two messages
235
252
  const result = getMessagesWithinTokenLimit({
236
253
  messages,
237
254
  maxContextTokens: 40,
238
- indexTokenCountMap
255
+ indexTokenCountMap,
239
256
  });
240
257
 
241
258
  // Should include system message and the last two messages
@@ -255,20 +272,20 @@ describe('Prune Messages Tests', () => {
255
272
  const messages = [
256
273
  new SystemMessage('System instruction'),
257
274
  new HumanMessage('Hello'),
258
- new AIMessage('Hi there')
275
+ new AIMessage('Hi there'),
259
276
  ];
260
277
 
261
278
  const indexTokenCountMap = {
262
279
  0: 17, // "System instruction"
263
- 1: 5, // "Hello"
264
- 2: 8 // "Hi there"
280
+ 1: 5, // "Hello"
281
+ 2: 8, // "Hi there"
265
282
  };
266
283
 
267
284
  // Set a limit that can only fit the system message
268
285
  const result = getMessagesWithinTokenLimit({
269
286
  messages,
270
287
  maxContextTokens: 20,
271
- indexTokenCountMap
288
+ indexTokenCountMap,
272
289
  });
273
290
 
274
291
  expect(result.context.length).toBe(1);
@@ -283,7 +300,7 @@ describe('Prune Messages Tests', () => {
283
300
  new AIMessage('AI message 1'),
284
301
  new HumanMessage('Human message 1'),
285
302
  new AIMessage('AI message 2'),
286
- new HumanMessage('Human message 2')
303
+ new HumanMessage('Human message 2'),
287
304
  ];
288
305
 
289
306
  const indexTokenCountMap = {
@@ -291,7 +308,7 @@ describe('Prune Messages Tests', () => {
291
308
  1: 12, // "AI message 1"
292
309
  2: 15, // "Human message 1"
293
310
  3: 12, // "AI message 2"
294
- 4: 15 // "Human message 2"
311
+ 4: 15, // "Human message 2"
295
312
  };
296
313
 
297
314
  // Set a limit that can fit all messages
@@ -299,7 +316,7 @@ describe('Prune Messages Tests', () => {
299
316
  messages,
300
317
  maxContextTokens: 100,
301
318
  indexTokenCountMap,
302
- startType: 'human'
319
+ startType: 'human',
303
320
  });
304
321
 
305
322
  // All messages should be included since we're under the token limit
@@ -318,13 +335,13 @@ describe('Prune Messages Tests', () => {
318
335
  const messages = [
319
336
  new SystemMessage('System instruction'),
320
337
  new AIMessage('AI message 1'),
321
- new AIMessage('AI message 2')
338
+ new AIMessage('AI message 2'),
322
339
  ];
323
340
 
324
341
  const indexTokenCountMap = {
325
342
  0: 17, // "System instruction"
326
343
  1: 12, // "AI message 1"
327
- 2: 12 // "AI message 2"
344
+ 2: 12, // "AI message 2"
328
345
  };
329
346
 
330
347
  // Set a limit that can fit all messages
@@ -332,7 +349,7 @@ describe('Prune Messages Tests', () => {
332
349
  messages,
333
350
  maxContextTokens: 100,
334
351
  indexTokenCountMap,
335
- startType: 'human'
352
+ startType: 'human',
336
353
  });
337
354
 
338
355
  // Should include all messages since no human messages exist to start from
@@ -373,20 +390,20 @@ describe('Prune Messages Tests', () => {
373
390
  const messages = [
374
391
  new SystemMessage('System instruction'),
375
392
  new HumanMessage('Hello'),
376
- new AIMessage('Hi there')
393
+ new AIMessage('Hi there'),
377
394
  ];
378
395
 
379
396
  const indexTokenCountMap = {
380
397
  0: tokenCounter(messages[0]),
381
398
  1: tokenCounter(messages[1]),
382
- 2: tokenCounter(messages[2])
399
+ 2: tokenCounter(messages[2]),
383
400
  };
384
401
 
385
402
  const pruneMessages = createPruneMessages({
386
403
  maxTokens: 100,
387
404
  startIndex: 0,
388
405
  tokenCounter,
389
- indexTokenCountMap
406
+ indexTokenCountMap,
390
407
  });
391
408
 
392
409
  const result = pruneMessages({ messages });
@@ -402,7 +419,7 @@ describe('Prune Messages Tests', () => {
402
419
  new HumanMessage('Message 1'),
403
420
  new AIMessage('Response 1'),
404
421
  new HumanMessage('Message 2'),
405
- new AIMessage('Response 2')
422
+ new AIMessage('Response 2'),
406
423
  ];
407
424
 
408
425
  const indexTokenCountMap = {
@@ -410,7 +427,7 @@ describe('Prune Messages Tests', () => {
410
427
  1: tokenCounter(messages[1]),
411
428
  2: tokenCounter(messages[2]),
412
429
  3: tokenCounter(messages[3]),
413
- 4: tokenCounter(messages[4])
430
+ 4: tokenCounter(messages[4]),
414
431
  };
415
432
 
416
433
  // Set a limit that can only fit the system message and the last two messages
@@ -418,7 +435,7 @@ describe('Prune Messages Tests', () => {
418
435
  maxTokens: 40,
419
436
  startIndex: 0,
420
437
  tokenCounter,
421
- indexTokenCountMap
438
+ indexTokenCountMap,
422
439
  });
423
440
 
424
441
  const result = pruneMessages({ messages });
@@ -437,7 +454,7 @@ describe('Prune Messages Tests', () => {
437
454
  new AIMessage('AI message 1'),
438
455
  new HumanMessage('Human message 1'),
439
456
  new AIMessage('AI message 2'),
440
- new HumanMessage('Human message 2')
457
+ new HumanMessage('Human message 2'),
441
458
  ];
442
459
 
443
460
  const indexTokenCountMap = {
@@ -445,7 +462,7 @@ describe('Prune Messages Tests', () => {
445
462
  1: tokenCounter(messages[1]),
446
463
  2: tokenCounter(messages[2]),
447
464
  3: tokenCounter(messages[3]),
448
- 4: tokenCounter(messages[4])
465
+ 4: tokenCounter(messages[4]),
449
466
  };
450
467
 
451
468
  // Set a limit that can fit all messages
@@ -453,12 +470,12 @@ describe('Prune Messages Tests', () => {
453
470
  maxTokens: 100,
454
471
  startIndex: 0,
455
472
  tokenCounter,
456
- indexTokenCountMap: { ...indexTokenCountMap }
473
+ indexTokenCountMap: { ...indexTokenCountMap },
457
474
  });
458
475
 
459
476
  const result = pruneMessages({
460
477
  messages,
461
- startType: 'human'
478
+ startType: 'human',
462
479
  });
463
480
 
464
481
  // All messages should be included since we're under the token limit
@@ -475,39 +492,42 @@ describe('Prune Messages Tests', () => {
475
492
  const messages = [
476
493
  new SystemMessage('System instruction'),
477
494
  new HumanMessage('Hello'),
478
- new AIMessage('Hi there')
495
+ new AIMessage('Hi there'),
479
496
  ];
480
497
 
481
498
  const indexTokenCountMap = {
482
499
  0: tokenCounter(messages[0]),
483
500
  1: tokenCounter(messages[1]),
484
- 2: tokenCounter(messages[2])
501
+ 2: tokenCounter(messages[2]),
485
502
  };
486
503
 
487
504
  const pruneMessages = createPruneMessages({
488
505
  maxTokens: 100,
489
506
  startIndex: 0,
490
507
  tokenCounter,
491
- indexTokenCountMap: { ...indexTokenCountMap }
508
+ indexTokenCountMap: { ...indexTokenCountMap },
492
509
  });
493
510
 
494
511
  // Provide usage metadata that indicates different token counts
495
512
  const usageMetadata: Partial<UsageMetadata> = {
496
513
  input_tokens: 50,
497
514
  output_tokens: 25,
498
- total_tokens: 75
515
+ total_tokens: 75,
499
516
  };
500
517
 
501
518
  const result = pruneMessages({
502
519
  messages,
503
- usageMetadata
520
+ usageMetadata,
504
521
  });
505
522
 
506
523
  // The function should have updated the indexTokenCountMap based on the usage metadata
507
524
  expect(result.indexTokenCountMap).not.toEqual(indexTokenCountMap);
508
525
 
509
526
  // The total of all values in indexTokenCountMap should equal the total_tokens from usageMetadata
510
- const totalTokens = Object.values(result.indexTokenCountMap).reduce((a, b) => a + b, 0);
527
+ const totalTokens = Object.values(result.indexTokenCountMap).reduce(
528
+ (a = 0, b = 0) => a + b,
529
+ 0
530
+ );
511
531
  expect(totalTokens).toBe(75);
512
532
  });
513
533
  });
@@ -520,7 +540,7 @@ describe('Prune Messages Tests', () => {
520
540
  new AIMessage('AI message 1'),
521
541
  new ToolMessage({ content: 'Tool result 1', tool_call_id: 'tool1' }),
522
542
  new AIMessage('AI message 2'),
523
- new ToolMessage({ content: 'Tool result 2', tool_call_id: 'tool2' })
543
+ new ToolMessage({ content: 'Tool result 2', tool_call_id: 'tool2' }),
524
544
  ];
525
545
 
526
546
  const indexTokenCountMap = {
@@ -528,7 +548,7 @@ describe('Prune Messages Tests', () => {
528
548
  1: 12, // AI message 1
529
549
  2: 13, // Tool result 1
530
550
  3: 12, // AI message 2
531
- 4: 13 // Tool result 2
551
+ 4: 13, // Tool result 2
532
552
  };
533
553
 
534
554
  // Create a pruneMessages function with a token limit that will only include the last few messages
@@ -536,7 +556,7 @@ describe('Prune Messages Tests', () => {
536
556
  maxTokens: 58, // Only enough for system + last 3 messages + 3, but should not include a parent-less tool message
537
557
  startIndex: 0,
538
558
  tokenCounter,
539
- indexTokenCountMap: { ...indexTokenCountMap }
559
+ indexTokenCountMap: { ...indexTokenCountMap },
540
560
  });
541
561
 
542
562
  const result = pruneMessages({ messages });
@@ -557,7 +577,7 @@ describe('Prune Messages Tests', () => {
557
577
  new AIMessage('AI message 1'),
558
578
  new ToolMessage({ content: 'Tool result 1', tool_call_id: 'tool1' }),
559
579
  new HumanMessage('Human message 2'),
560
- new ToolMessage({ content: 'Tool result 2', tool_call_id: 'tool2' })
580
+ new ToolMessage({ content: 'Tool result 2', tool_call_id: 'tool2' }),
561
581
  ];
562
582
 
563
583
  const indexTokenCountMap = {
@@ -566,7 +586,7 @@ describe('Prune Messages Tests', () => {
566
586
  2: 12, // AI message 1
567
587
  3: 13, // Tool result 1
568
588
  4: 15, // Human message 2
569
- 5: 13 // Tool result 2
589
+ 5: 13, // Tool result 2
570
590
  };
571
591
 
572
592
  // Create a pruneMessages function with a token limit that will only include the last few messages
@@ -574,7 +594,7 @@ describe('Prune Messages Tests', () => {
574
594
  maxTokens: 48, // Only enough for system + last 2 messages
575
595
  startIndex: 0,
576
596
  tokenCounter,
577
- indexTokenCountMap: { ...indexTokenCountMap }
597
+ indexTokenCountMap: { ...indexTokenCountMap },
578
598
  });
579
599
 
580
600
  const result = pruneMessages({ messages });
@@ -594,7 +614,7 @@ describe('Prune Messages Tests', () => {
594
614
  new HumanMessage('Human message'),
595
615
  new AIMessage('AI message with tool use'),
596
616
  new ToolMessage({ content: 'Tool result', tool_call_id: 'tool1' }),
597
- new AIMessage('AI message after tool')
617
+ new AIMessage('AI message after tool'),
598
618
  ];
599
619
 
600
620
  const indexTokenCountMap = {
@@ -602,14 +622,14 @@ describe('Prune Messages Tests', () => {
602
622
  1: 13, // Human message
603
623
  2: 22, // AI message with tool use
604
624
  3: 11, // Tool result
605
- 4: 19 // AI message after tool
625
+ 4: 19, // AI message after tool
606
626
  };
607
627
 
608
628
  const pruneMessages = createPruneMessages({
609
629
  maxTokens: 50,
610
630
  startIndex: 0,
611
631
  tokenCounter,
612
- indexTokenCountMap: { ...indexTokenCountMap }
632
+ indexTokenCountMap: { ...indexTokenCountMap },
613
633
  });
614
634
 
615
635
  const result = pruneMessages({ messages });
@@ -626,7 +646,7 @@ describe('Prune Messages Tests', () => {
626
646
  new HumanMessage('Human message 1'),
627
647
  new AIMessage('AI message with tool use'),
628
648
  new ToolMessage({ content: 'Tool result', tool_call_id: 'tool1' }),
629
- new HumanMessage('Human message 2')
649
+ new HumanMessage('Human message 2'),
630
650
  ];
631
651
 
632
652
  const indexTokenCountMap = {
@@ -634,14 +654,14 @@ describe('Prune Messages Tests', () => {
634
654
  1: 15, // Human message 1
635
655
  2: 22, // AI message with tool use
636
656
  3: 11, // Tool result
637
- 4: 15 // Human message 2
657
+ 4: 15, // Human message 2
638
658
  };
639
659
 
640
660
  const pruneMessages = createPruneMessages({
641
661
  maxTokens: 46,
642
662
  startIndex: 0,
643
663
  tokenCounter,
644
- indexTokenCountMap: { ...indexTokenCountMap }
664
+ indexTokenCountMap: { ...indexTokenCountMap },
645
665
  });
646
666
 
647
667
  const result = pruneMessages({ messages });
@@ -661,7 +681,7 @@ describe('Prune Messages Tests', () => {
661
681
  new AIMessage('AI message 2 with tool use'),
662
682
  new ToolMessage({ content: 'Tool result 2', tool_call_id: 'tool2' }),
663
683
  new AIMessage('AI message 3 with tool use'),
664
- new ToolMessage({ content: 'Tool result 3', tool_call_id: 'tool3' })
684
+ new ToolMessage({ content: 'Tool result 3', tool_call_id: 'tool3' }),
665
685
  ];
666
686
 
667
687
  const indexTokenCountMap = {
@@ -672,14 +692,14 @@ describe('Prune Messages Tests', () => {
672
692
  4: 26, // AI message 2 with tool use
673
693
  5: 13, // Tool result 2
674
694
  6: 26, // AI message 3 with tool use
675
- 7: 13 // Tool result 3
695
+ 7: 13, // Tool result 3
676
696
  };
677
697
 
678
698
  const pruneMessages = createPruneMessages({
679
699
  maxTokens: 111,
680
700
  startIndex: 0,
681
701
  tokenCounter,
682
- indexTokenCountMap: { ...indexTokenCountMap }
702
+ indexTokenCountMap: { ...indexTokenCountMap },
683
703
  });
684
704
 
685
705
  const result = pruneMessages({ messages });
@@ -712,15 +732,16 @@ describe('Prune Messages Tests', () => {
712
732
  // Override the model to use a fake LLM
713
733
  run.Graph?.overrideTestModel(['This is a test response'], 1);
714
734
 
715
- const messages = [
716
- new HumanMessage('Hello, how are you?')
717
- ];
735
+ const messages = [new HumanMessage('Hello, how are you?')];
718
736
 
719
737
  const indexTokenCountMap = {
720
- 0: tokenCounter(messages[0])
738
+ 0: tokenCounter(messages[0]),
721
739
  };
722
740
 
723
- const config: Partial<RunnableConfig> & { version: 'v1' | 'v2'; streamMode: string } = {
741
+ const config: Partial<RunnableConfig> & {
742
+ version: 'v1' | 'v2';
743
+ streamMode: string;
744
+ } = {
724
745
  configurable: {
725
746
  thread_id: 'test-thread',
726
747
  },
@@ -728,15 +749,11 @@ describe('Prune Messages Tests', () => {
728
749
  version: 'v2' as const,
729
750
  };
730
751
 
731
- await run.processStream(
732
- { messages },
733
- config,
734
- {
735
- maxContextTokens: 1000,
736
- indexTokenCountMap,
737
- tokenCounter,
738
- }
739
- );
752
+ await run.processStream({ messages }, config, {
753
+ maxContextTokens: 1000,
754
+ indexTokenCountMap,
755
+ tokenCounter,
756
+ });
740
757
 
741
758
  const finalMessages = run.getRunMessages();
742
759
  expect(finalMessages).toBeDefined();