@librechat/agents 2.2.2 → 2.2.4

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 (59) hide show
  1. package/dist/cjs/graphs/Graph.cjs +51 -14
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/main.cjs +6 -4
  4. package/dist/cjs/main.cjs.map +1 -1
  5. package/dist/cjs/messages/format.cjs +21 -0
  6. package/dist/cjs/messages/format.cjs.map +1 -1
  7. package/dist/cjs/messages/prune.cjs +124 -0
  8. package/dist/cjs/messages/prune.cjs.map +1 -0
  9. package/dist/cjs/run.cjs +24 -0
  10. package/dist/cjs/run.cjs.map +1 -1
  11. package/dist/cjs/tools/ToolNode.cjs +1 -0
  12. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  13. package/dist/cjs/utils/tokens.cjs +65 -0
  14. package/dist/cjs/utils/tokens.cjs.map +1 -0
  15. package/dist/esm/graphs/Graph.mjs +51 -14
  16. package/dist/esm/graphs/Graph.mjs.map +1 -1
  17. package/dist/esm/main.mjs +3 -3
  18. package/dist/esm/messages/format.mjs +21 -1
  19. package/dist/esm/messages/format.mjs.map +1 -1
  20. package/dist/esm/messages/prune.mjs +122 -0
  21. package/dist/esm/messages/prune.mjs.map +1 -0
  22. package/dist/esm/run.mjs +24 -0
  23. package/dist/esm/run.mjs.map +1 -1
  24. package/dist/esm/tools/ToolNode.mjs +1 -0
  25. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  26. package/dist/esm/utils/tokens.mjs +62 -0
  27. package/dist/esm/utils/tokens.mjs.map +1 -0
  28. package/dist/types/graphs/Graph.d.ts +8 -1
  29. package/dist/types/messages/format.d.ts +9 -0
  30. package/dist/types/messages/index.d.ts +1 -2
  31. package/dist/types/messages/prune.d.ts +16 -0
  32. package/dist/types/types/run.d.ts +4 -0
  33. package/dist/types/utils/index.d.ts +1 -0
  34. package/dist/types/utils/tokens.d.ts +3 -0
  35. package/package.json +1 -1
  36. package/src/graphs/Graph.ts +54 -16
  37. package/src/messages/format.ts +27 -0
  38. package/src/messages/index.ts +1 -2
  39. package/src/messages/prune.ts +167 -0
  40. package/src/messages/shiftIndexTokenCountMap.test.ts +81 -0
  41. package/src/run.ts +26 -0
  42. package/src/scripts/code_exec_simple.ts +21 -8
  43. package/src/specs/prune.test.ts +444 -0
  44. package/src/types/run.ts +5 -0
  45. package/src/utils/index.ts +2 -1
  46. package/src/utils/tokens.ts +70 -0
  47. package/dist/cjs/messages/transformers.cjs +0 -318
  48. package/dist/cjs/messages/transformers.cjs.map +0 -1
  49. package/dist/cjs/messages/trimMessagesFactory.cjs +0 -129
  50. package/dist/cjs/messages/trimMessagesFactory.cjs.map +0 -1
  51. package/dist/esm/messages/transformers.mjs +0 -316
  52. package/dist/esm/messages/transformers.mjs.map +0 -1
  53. package/dist/esm/messages/trimMessagesFactory.mjs +0 -127
  54. package/dist/esm/messages/trimMessagesFactory.mjs.map +0 -1
  55. package/dist/types/messages/transformers.d.ts +0 -320
  56. package/dist/types/messages/trimMessagesFactory.d.ts +0 -37
  57. package/src/messages/transformers.ts +0 -786
  58. package/src/messages/trimMessagesFactory.test.ts +0 -331
  59. package/src/messages/trimMessagesFactory.ts +0 -140
@@ -1,331 +0,0 @@
1
- import { AIMessage, BaseMessage, HumanMessage, SystemMessage } from "@langchain/core/messages";
2
- import { createTrimMessagesFunction } from "./trimMessagesFactory";
3
-
4
- describe("createTrimMessagesFunction", () => {
5
- // Mock token counter that simply counts characters as tokens for testing
6
- const mockTokenCounter = (messages: any[]): number => {
7
- return messages.reduce((sum, msg) => {
8
- const content = typeof msg.content === "string"
9
- ? msg.content
10
- : JSON.stringify(msg.content);
11
- return sum + content.length;
12
- }, 0);
13
- };
14
-
15
- it("should create a function that trims messages", async () => {
16
- const trimmer = createTrimMessagesFunction({
17
- trimOptions: {
18
- maxTokens: 50,
19
- tokenCounter: mockTokenCounter,
20
- strategy: "last"
21
- }
22
- });
23
-
24
- const messages = [
25
- new SystemMessage("System prompt with some instructions"),
26
- new HumanMessage("First user message"),
27
- new AIMessage("First AI response"),
28
- new HumanMessage("Second user message"),
29
- new AIMessage("Second AI response that is quite long and should be trimmed")
30
- ];
31
-
32
- const result = await trimmer(messages);
33
-
34
- expect(result.messages.length).toBeLessThan(messages.length);
35
- expect(result.indexTokenCountMap).toBeDefined();
36
- expect(Object.keys(result.indexTokenCountMap).length).toBeGreaterThan(0);
37
- });
38
-
39
- it("should maintain token counts across multiple calls", async () => {
40
- const trimmer = createTrimMessagesFunction({
41
- trimOptions: {
42
- maxTokens: 100,
43
- tokenCounter: mockTokenCounter,
44
- strategy: "last",
45
- includeSystem: true
46
- }
47
- });
48
-
49
- // Initial set of messages
50
- const initialMessages = [
51
- new SystemMessage("System prompt"),
52
- new HumanMessage("First user message"),
53
- new AIMessage("First AI response")
54
- ];
55
-
56
- // First call should count all messages
57
- const firstResult = await trimmer(initialMessages);
58
- expect(firstResult.messages.length).toBe(3);
59
- expect(Object.keys(firstResult.indexTokenCountMap).length).toBe(3);
60
-
61
- // Add a new message
62
- const updatedMessages = [
63
- ...initialMessages,
64
- new HumanMessage("Second user message")
65
- ];
66
-
67
- // Second call should only count the new message
68
- const secondResult = await trimmer(updatedMessages);
69
- expect(secondResult.messages.length).toBe(4);
70
- expect(Object.keys(secondResult.indexTokenCountMap).length).toBe(4);
71
-
72
- // The token counts for the first three messages should be the same
73
- for (let i = 0; i < 3; i++) {
74
- expect(secondResult.indexTokenCountMap[i]).toBe(firstResult.indexTokenCountMap[i]);
75
- }
76
-
77
- // The new message should have a token count
78
- expect(secondResult.indexTokenCountMap[3]).toBeDefined();
79
- });
80
-
81
- it("should handle different trimming strategies", async () => {
82
- // Test with "first" strategy
83
- const firstTrimmer = createTrimMessagesFunction({
84
- trimOptions: {
85
- maxTokens: 50,
86
- tokenCounter: mockTokenCounter,
87
- strategy: "first"
88
- }
89
- });
90
-
91
- const messages = [
92
- new SystemMessage("System prompt with some instructions"),
93
- new HumanMessage("First user message"),
94
- new AIMessage("First AI response"),
95
- new HumanMessage("Second user message"),
96
- new AIMessage("Second AI response that is quite long and should be trimmed")
97
- ];
98
-
99
- const firstResult = await firstTrimmer(messages);
100
- expect(firstResult.messages.length).toBeLessThan(messages.length);
101
-
102
- // Test with "last" strategy
103
- const lastTrimmer = createTrimMessagesFunction({
104
- trimOptions: {
105
- maxTokens: 50,
106
- tokenCounter: mockTokenCounter,
107
- strategy: "last",
108
- includeSystem: true
109
- }
110
- });
111
-
112
- const lastResult = await lastTrimmer(messages);
113
- expect(lastResult.messages.length).toBeLessThan(messages.length);
114
-
115
- // The system message should be included in the "last" strategy result
116
- const hasSystemMessage = lastResult.messages.some(
117
- msg => msg instanceof SystemMessage
118
- );
119
- expect(hasSystemMessage).toBe(true);
120
- });
121
-
122
- it("should handle accumulating messages efficiently", async () => {
123
- // Create a spy token counter to track how many times it's called
124
- const tokenCounterCalls: number[] = [];
125
- const spyTokenCounter = (messages: any[]): number => {
126
- tokenCounterCalls.push(messages.length);
127
- return messages.reduce((sum, msg) => {
128
- const content = typeof msg.content === "string"
129
- ? msg.content
130
- : JSON.stringify(msg.content);
131
- return sum + content.length;
132
- }, 0);
133
- };
134
-
135
- const trimmer = createTrimMessagesFunction({
136
- trimOptions: {
137
- maxTokens: 200,
138
- tokenCounter: spyTokenCounter,
139
- strategy: "last",
140
- includeSystem: true
141
- }
142
- });
143
-
144
- // Start with a few messages
145
- let currentMessages = [
146
- new SystemMessage("System prompt"),
147
- new HumanMessage("First user message"),
148
- new AIMessage("First AI response")
149
- ];
150
-
151
- // First call
152
- const firstResult = await trimmer(currentMessages);
153
-
154
- // Add more messages one by one and verify token counting efficiency
155
- for (let i = 0; i < 5; i++) {
156
- // Add a new message pair
157
- currentMessages = [
158
- ...currentMessages,
159
- new HumanMessage(`User message ${i + 2}`),
160
- new AIMessage(`AI response ${i + 2}`)
161
- ];
162
-
163
- // Get trimmed messages
164
- const result = await trimmer(currentMessages);
165
-
166
- // Verify that all messages have token counts
167
- expect(Object.keys(result.indexTokenCountMap).length).toBe(currentMessages.length);
168
-
169
- // The token counts for existing messages should remain the same
170
- for (let j = 0; j < currentMessages.length - 2; j++) {
171
- if (j < Object.keys(firstResult.indexTokenCountMap).length) {
172
- expect(result.indexTokenCountMap[j]).toBe(firstResult.indexTokenCountMap[j]);
173
- }
174
- }
175
- }
176
-
177
- // Verify that the token counter was called with single messages for new messages
178
- // The first call should count 3 messages (initial batch)
179
- // Then each subsequent call should count only 1 message at a time (the new message)
180
- expect(tokenCounterCalls[0]).toBe(1); // First message
181
- expect(tokenCounterCalls[1]).toBe(1); // Second message
182
- expect(tokenCounterCalls[2]).toBe(1); // Third message
183
- // After initial batch, each new message should be counted individually
184
- for (let i = 3; i < tokenCounterCalls.length; i++) {
185
- expect(tokenCounterCalls[i]).toBe(1);
186
- }
187
- });
188
-
189
- it("should handle function-based token counter that returns a promise", async () => {
190
- // Track calls to the token counter
191
- const tokenCounterCalls: Array<BaseMessage[]> = [];
192
-
193
- // Mock token counter that returns a promise
194
- const asyncTokenCounter = async (messages: BaseMessage[]): Promise<number> => {
195
- tokenCounterCalls.push([...messages]);
196
- return Promise.resolve(messages.reduce((sum, msg) => {
197
- const content = typeof msg.content === "string"
198
- ? msg.content
199
- : JSON.stringify(msg.content);
200
- return sum + content.length;
201
- }, 0));
202
- };
203
-
204
- const trimmer = createTrimMessagesFunction({
205
- trimOptions: {
206
- maxTokens: 40, // Smaller token limit to ensure trimming occurs
207
- tokenCounter: asyncTokenCounter,
208
- strategy: "last"
209
- }
210
- });
211
-
212
- const messages = [
213
- new SystemMessage("System prompt with instructions"),
214
- new HumanMessage("First user message that is longer"),
215
- new AIMessage("First AI response with details"),
216
- new HumanMessage("Second user message with question"),
217
- new AIMessage("Second AI response with answer")
218
- ];
219
-
220
- const result = await trimmer(messages);
221
- expect(result.messages.length).toBeLessThan(messages.length);
222
- expect(Object.keys(result.indexTokenCountMap).length).toBeGreaterThan(0);
223
-
224
- // Verify that each message was counted individually
225
- expect(tokenCounterCalls.length).toBe(messages.length);
226
- tokenCounterCalls.forEach(call => {
227
- expect(call.length).toBe(1); // Each call should have exactly one message
228
- });
229
- });
230
-
231
- it("should correctly handle growing message arrays", async () => {
232
- // Create a map to track which messages are processed
233
- const processedMessages = new Map<number, string>();
234
-
235
- const tokenCounter = (messages: BaseMessage[]): number => {
236
- // We should only get one message at a time
237
- expect(messages.length).toBe(1);
238
-
239
- const msg = messages[0];
240
- const content = typeof msg.content === "string"
241
- ? msg.content
242
- : JSON.stringify(msg.content);
243
-
244
- // Find the index of this message in the current array
245
- const msgContent = msg.content.toString();
246
-
247
- // Log for debugging
248
- console.log(`Processing message: ${msgContent}`);
249
-
250
- // Store the message content with its index
251
- processedMessages.set(processedMessages.size, msgContent);
252
-
253
- return content.length;
254
- };
255
-
256
- const trimmer = createTrimMessagesFunction({
257
- trimOptions: {
258
- maxTokens: 500, // Large enough to include all messages
259
- tokenCounter,
260
- strategy: "last"
261
- }
262
- });
263
-
264
- // Initial messages
265
- const initialMessages = [
266
- new SystemMessage("System prompt"),
267
- new HumanMessage("First user message")
268
- ];
269
-
270
- // Clear the map before first call
271
- processedMessages.clear();
272
-
273
- console.log("First call with 2 messages");
274
- await trimmer(initialMessages);
275
- expect(processedMessages.size).toBe(2);
276
-
277
- // Add more messages
278
- const moreMessages = [
279
- ...initialMessages,
280
- new AIMessage("First AI response"),
281
- new HumanMessage("Second user message")
282
- ];
283
-
284
- // Clear the map before second call
285
- processedMessages.clear();
286
-
287
- console.log("Second call with 4 messages");
288
- await trimmer(moreMessages);
289
- // Only the new messages should be processed
290
- expect(processedMessages.size).toBe(2);
291
-
292
- // Check if the processed messages are the new ones
293
- const secondCallMessages = Array.from(processedMessages.values());
294
- expect(secondCallMessages).toContain("First AI response");
295
- expect(secondCallMessages).toContain("Second user message");
296
-
297
- // Add even more messages
298
- const evenMoreMessages = [
299
- ...moreMessages,
300
- new AIMessage("Second AI response"),
301
- new HumanMessage("Third user message"),
302
- new AIMessage("Third AI response")
303
- ];
304
-
305
- // Clear the map before third call
306
- processedMessages.clear();
307
-
308
- console.log("Third call with 7 messages");
309
- const finalResult = await trimmer(evenMoreMessages);
310
- // Only the new messages should be processed
311
- expect(processedMessages.size).toBe(3);
312
-
313
- // Check if the processed messages are the new ones
314
- const thirdCallMessages = Array.from(processedMessages.values());
315
- expect(thirdCallMessages).toContain("Second AI response");
316
- expect(thirdCallMessages).toContain("Third user message");
317
- expect(thirdCallMessages).toContain("Third AI response");
318
-
319
- // Verify all messages are in the token count map
320
- expect(Object.keys(finalResult.indexTokenCountMap).length).toBe(7);
321
-
322
- // Verify the token counts are accurate
323
- for (let i = 0; i < evenMoreMessages.length; i++) {
324
- const msg = evenMoreMessages[i];
325
- const content = typeof msg.content === "string"
326
- ? msg.content
327
- : JSON.stringify(msg.content);
328
- expect(finalResult.indexTokenCountMap[i]).toBe(content.length);
329
- }
330
- });
331
- });
@@ -1,140 +0,0 @@
1
- import { BaseMessage } from "@langchain/core/messages";
2
- import { trimMessages, TrimMessagesFields } from "./transformers";
3
-
4
- /**
5
- * Factory function that creates a trimMessages function that maintains an indexTokenCountMap
6
- * during a run. This allows for efficient token counting by avoiding recounting tokens
7
- * for messages that have already been processed.
8
- *
9
- * @param {Object} options - Configuration options for the trimMessages function
10
- * @param {TrimMessagesFields} options.trimOptions - Options for the trimMessages function
11
- * @returns {Function} A function that trims messages while maintaining a token count map
12
- *
13
- * @example
14
- * ```typescript
15
- * // Create a trimmer function with specific options
16
- * const trimmer = createTrimMessagesFunction({
17
- * trimOptions: {
18
- * maxTokens: 4000,
19
- * tokenCounter: myTokenCounter,
20
- * strategy: "last",
21
- * includeSystem: true
22
- * }
23
- * });
24
- *
25
- * // Use the trimmer with an array of messages
26
- * // First call with initial messages
27
- * const { messages: trimmedMessages1 } = await trimmer(initialMessages);
28
- *
29
- * // Later call with additional messages
30
- * const { messages: trimmedMessages2 } = await trimmer([...initialMessages, ...newMessages]);
31
- * ```
32
- */
33
- export function createTrimMessagesFunction({ trimOptions }: {
34
- trimOptions: TrimMessagesFields
35
- }) {
36
- // Initialize the token count map and keep track of the last processed message array
37
- const indexTokenCountMap: Record<number, number> = {};
38
- let lastProcessedMessages: BaseMessage[] = [];
39
-
40
- /**
41
- * Trims messages while maintaining a token count map for efficiency
42
- *
43
- * @param {BaseMessage[]} messages - Array of messages to trim
44
- * @returns {Promise<Object>} Object containing trimmed messages and the current token count map
45
- */
46
- return async function trimMessagesWithMap(messages: BaseMessage[]): Promise<{
47
- messages: BaseMessage[];
48
- indexTokenCountMap: Record<number, number>;
49
- }> {
50
- // Determine which messages are new by comparing with the last processed messages
51
- const newMessages: BaseMessage[] = [];
52
- const newIndices: number[] = [];
53
-
54
- // If this is the first call or we have more messages than before
55
- if (lastProcessedMessages.length === 0) {
56
- // First call, all messages are new
57
- for (let i = 0; i < messages.length; i++) {
58
- newMessages.push(messages[i]);
59
- newIndices.push(i);
60
- }
61
- } else if (messages.length > lastProcessedMessages.length) {
62
- // We have new messages, only count those
63
- for (let i = lastProcessedMessages.length; i < messages.length; i++) {
64
- newMessages.push(messages[i]);
65
- newIndices.push(i);
66
- }
67
- }
68
-
69
- // Update the last processed messages
70
- lastProcessedMessages = [...messages];
71
-
72
- // Create a copy of the token counter to use for new messages
73
- const originalTokenCounter = trimOptions.tokenCounter;
74
-
75
- // Count tokens for new messages
76
- for (let i = 0; i < newMessages.length; i++) {
77
- const messageIndex = newIndices[i];
78
- let messageTokenCount: number;
79
-
80
- if (typeof originalTokenCounter === 'function') {
81
- // Count this single message by passing it as a one-element array
82
- messageTokenCount = await originalTokenCounter([newMessages[i]]);
83
- } else if ('getNumTokens' in originalTokenCounter) {
84
- // Use the language model's getNumTokens method
85
- messageTokenCount = await originalTokenCounter.getNumTokens(newMessages[i].content);
86
- } else {
87
- throw new Error("Unsupported token counter type");
88
- }
89
-
90
- // Store the token count for this message
91
- indexTokenCountMap[messageIndex] = messageTokenCount;
92
- }
93
-
94
- // Create a token counter that uses the map
95
- const enhancedTokenCounter = async (msgsToCount: BaseMessage[]): Promise<number> => {
96
- let totalTokens = 0;
97
-
98
- // Use the cached token counts
99
- for (let i = 0; i < msgsToCount.length; i++) {
100
- if (indexTokenCountMap[i] !== undefined) {
101
- totalTokens += indexTokenCountMap[i];
102
- } else {
103
- // This shouldn't happen if we've counted all messages
104
- console.warn(`Missing token count for message at index ${i}`);
105
-
106
- // Count it now as a fallback
107
- let messageTokenCount: number;
108
-
109
- if (typeof originalTokenCounter === 'function') {
110
- messageTokenCount = await originalTokenCounter([msgsToCount[i]]);
111
- } else if ('getNumTokens' in originalTokenCounter) {
112
- messageTokenCount = await originalTokenCounter.getNumTokens(msgsToCount[i].content);
113
- } else {
114
- throw new Error("Unsupported token counter type");
115
- }
116
-
117
- indexTokenCountMap[i] = messageTokenCount;
118
- totalTokens += messageTokenCount;
119
- }
120
- }
121
-
122
- return totalTokens;
123
- };
124
-
125
- // Create modified trim options with our enhanced token counter
126
- const modifiedTrimOptions: TrimMessagesFields = {
127
- ...trimOptions,
128
- tokenCounter: enhancedTokenCounter
129
- };
130
-
131
- // Trim the messages using the original function
132
- const trimmedMessages = await trimMessages(messages, modifiedTrimOptions);
133
-
134
- // Return both the trimmed messages and the updated token count map
135
- return {
136
- messages: trimmedMessages,
137
- indexTokenCountMap
138
- };
139
- };
140
- }