@librechat/agents 2.2.1 → 2.2.3

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 (51) hide show
  1. package/dist/cjs/graphs/Graph.cjs +56 -19
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/main.cjs +18 -8
  4. package/dist/cjs/main.cjs.map +1 -1
  5. package/dist/cjs/{messages.cjs → messages/core.cjs} +2 -2
  6. package/dist/cjs/messages/core.cjs.map +1 -0
  7. package/dist/cjs/messages/format.cjs +334 -0
  8. package/dist/cjs/messages/format.cjs.map +1 -0
  9. package/dist/cjs/messages/prune.cjs +124 -0
  10. package/dist/cjs/messages/prune.cjs.map +1 -0
  11. package/dist/cjs/run.cjs +24 -0
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/cjs/utils/tokens.cjs +64 -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 -1
  18. package/dist/esm/main.mjs.map +1 -1
  19. package/dist/esm/{messages.mjs → messages/core.mjs} +2 -2
  20. package/dist/esm/messages/core.mjs.map +1 -0
  21. package/dist/esm/messages/format.mjs +326 -0
  22. package/dist/esm/messages/format.mjs.map +1 -0
  23. package/dist/esm/messages/prune.mjs +122 -0
  24. package/dist/esm/messages/prune.mjs.map +1 -0
  25. package/dist/esm/run.mjs +24 -0
  26. package/dist/esm/run.mjs.map +1 -1
  27. package/dist/esm/utils/tokens.mjs +62 -0
  28. package/dist/esm/utils/tokens.mjs.map +1 -0
  29. package/dist/types/graphs/Graph.d.ts +8 -1
  30. package/dist/types/messages/format.d.ts +120 -0
  31. package/dist/types/messages/index.d.ts +3 -0
  32. package/dist/types/messages/prune.d.ts +16 -0
  33. package/dist/types/types/run.d.ts +4 -0
  34. package/dist/types/utils/tokens.d.ts +2 -0
  35. package/package.json +1 -1
  36. package/src/graphs/Graph.ts +54 -16
  37. package/src/messages/format.ts +460 -0
  38. package/src/messages/formatAgentMessages.test.ts +628 -0
  39. package/src/messages/formatMessage.test.ts +277 -0
  40. package/src/messages/index.ts +3 -0
  41. package/src/messages/prune.ts +167 -0
  42. package/src/messages/shiftIndexTokenCountMap.test.ts +81 -0
  43. package/src/run.ts +26 -0
  44. package/src/scripts/code_exec_simple.ts +21 -8
  45. package/src/specs/prune.test.ts +444 -0
  46. package/src/types/run.ts +5 -0
  47. package/src/utils/tokens.ts +70 -0
  48. package/dist/cjs/messages.cjs.map +0 -1
  49. package/dist/esm/messages.mjs.map +0 -1
  50. /package/dist/types/{messages.d.ts → messages/core.d.ts} +0 -0
  51. /package/src/{messages.ts → messages/core.ts} +0 -0
@@ -0,0 +1,277 @@
1
+ import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
2
+ import { formatMessage, formatLangChainMessages, formatFromLangChain } from './format';
3
+
4
+ const NO_PARENT = '00000000-0000-0000-0000-000000000000';
5
+
6
+ describe('formatMessage', () => {
7
+ it('formats user message', () => {
8
+ const input = {
9
+ message: {
10
+ sender: 'user',
11
+ text: 'Hello',
12
+ },
13
+ userName: 'John',
14
+ };
15
+ const result = formatMessage(input);
16
+ expect(result).toEqual({
17
+ role: 'user',
18
+ content: 'Hello',
19
+ name: 'John',
20
+ });
21
+ });
22
+
23
+ it('sanitizes the name by replacing invalid characters (per OpenAI)', () => {
24
+ const input = {
25
+ message: {
26
+ sender: 'user',
27
+ text: 'Hello',
28
+ },
29
+ userName: ' John$Doe@Example! ',
30
+ };
31
+ const result = formatMessage(input);
32
+ expect(result).toEqual({
33
+ role: 'user',
34
+ content: 'Hello',
35
+ name: '_John_Doe_Example__',
36
+ });
37
+ });
38
+
39
+ it('trims the name to a maximum length of 64 characters', () => {
40
+ const longName = 'a'.repeat(100);
41
+ const input = {
42
+ message: {
43
+ sender: 'user',
44
+ text: 'Hello',
45
+ },
46
+ userName: longName,
47
+ };
48
+ const result = formatMessage(input);
49
+ expect(result.name?.length).toBe(64);
50
+ expect(result.name).toBe('a'.repeat(64));
51
+ });
52
+
53
+ it('formats a realistic user message', () => {
54
+ const input = {
55
+ message: {
56
+ _id: '6512cdfb92cbf69fea615331',
57
+ messageId: 'b620bf73-c5c3-4a38-b724-76886aac24c4',
58
+ __v: 0,
59
+ conversationId: '5c23d24f-941f-4aab-85df-127b596c8aa5',
60
+ createdAt: Date.now(),
61
+ error: false,
62
+ finish_reason: null,
63
+ isCreatedByUser: true,
64
+ model: null,
65
+ parentMessageId: NO_PARENT,
66
+ sender: 'User',
67
+ text: 'hi',
68
+ tokenCount: 5,
69
+ unfinished: false,
70
+ updatedAt: Date.now(),
71
+ user: '6512cdf475f05c86d44c31d2',
72
+ },
73
+ userName: 'John',
74
+ };
75
+ const result = formatMessage(input);
76
+ expect(result).toEqual({
77
+ role: 'user',
78
+ content: 'hi',
79
+ name: 'John',
80
+ });
81
+ });
82
+
83
+ it('formats assistant message', () => {
84
+ const input = {
85
+ message: {
86
+ sender: 'assistant',
87
+ text: 'Hi there',
88
+ },
89
+ assistantName: 'Assistant',
90
+ };
91
+ const result = formatMessage(input);
92
+ expect(result).toEqual({
93
+ role: 'assistant',
94
+ content: 'Hi there',
95
+ name: 'Assistant',
96
+ });
97
+ });
98
+
99
+ it('formats system message', () => {
100
+ const input = {
101
+ message: {
102
+ role: 'system',
103
+ text: 'Hi there',
104
+ },
105
+ };
106
+ const result = formatMessage(input);
107
+ expect(result).toEqual({
108
+ role: 'system',
109
+ content: 'Hi there',
110
+ });
111
+ });
112
+
113
+ it('formats user message with langChain', () => {
114
+ const input = {
115
+ message: {
116
+ sender: 'user',
117
+ text: 'Hello',
118
+ },
119
+ userName: 'John',
120
+ langChain: true,
121
+ };
122
+ const result = formatMessage(input);
123
+ expect(result).toBeInstanceOf(HumanMessage);
124
+ expect(result.lc_kwargs.content).toEqual(input.message.text);
125
+ expect(result.lc_kwargs.name).toEqual(input.userName);
126
+ });
127
+
128
+ it('formats assistant message with langChain', () => {
129
+ const input = {
130
+ message: {
131
+ sender: 'assistant',
132
+ text: 'Hi there',
133
+ },
134
+ assistantName: 'Assistant',
135
+ langChain: true,
136
+ };
137
+ const result = formatMessage(input);
138
+ expect(result).toBeInstanceOf(AIMessage);
139
+ expect(result.lc_kwargs.content).toEqual(input.message.text);
140
+ expect(result.lc_kwargs.name).toEqual(input.assistantName);
141
+ });
142
+
143
+ it('formats system message with langChain', () => {
144
+ const input = {
145
+ message: {
146
+ role: 'system',
147
+ text: 'This is a system message.',
148
+ },
149
+ langChain: true,
150
+ };
151
+ const result = formatMessage(input);
152
+ expect(result).toBeInstanceOf(SystemMessage);
153
+ expect(result.lc_kwargs.content).toEqual(input.message.text);
154
+ });
155
+
156
+ it('formats langChain messages into OpenAI payload format', () => {
157
+ const human = {
158
+ message: new HumanMessage({
159
+ content: 'Hello',
160
+ }),
161
+ };
162
+ const system = {
163
+ message: new SystemMessage({
164
+ content: 'Hello',
165
+ }),
166
+ };
167
+ const ai = {
168
+ message: new AIMessage({
169
+ content: 'Hello',
170
+ }),
171
+ };
172
+ const humanResult = formatMessage(human);
173
+ const systemResult = formatMessage(system);
174
+ const aiResult = formatMessage(ai);
175
+ expect(humanResult).toEqual({
176
+ role: 'user',
177
+ content: 'Hello',
178
+ });
179
+ expect(systemResult).toEqual({
180
+ role: 'system',
181
+ content: 'Hello',
182
+ });
183
+ expect(aiResult).toEqual({
184
+ role: 'assistant',
185
+ content: 'Hello',
186
+ });
187
+ });
188
+ });
189
+
190
+ describe('formatLangChainMessages', () => {
191
+ it('formats an array of messages for LangChain', () => {
192
+ const messages = [
193
+ {
194
+ role: 'system',
195
+ content: 'This is a system message',
196
+ },
197
+ {
198
+ sender: 'user',
199
+ text: 'Hello',
200
+ },
201
+ {
202
+ sender: 'assistant',
203
+ text: 'Hi there',
204
+ },
205
+ ];
206
+ const formatOptions = {
207
+ userName: 'John',
208
+ assistantName: 'Assistant',
209
+ };
210
+ const result = formatLangChainMessages(messages, formatOptions);
211
+ expect(result).toHaveLength(3);
212
+ expect(result[0]).toBeInstanceOf(SystemMessage);
213
+ expect(result[1]).toBeInstanceOf(HumanMessage);
214
+ expect(result[2]).toBeInstanceOf(AIMessage);
215
+
216
+ expect(result[0].lc_kwargs.content).toEqual(messages[0].content);
217
+ expect(result[1].lc_kwargs.content).toEqual(messages[1].text);
218
+ expect(result[2].lc_kwargs.content).toEqual(messages[2].text);
219
+
220
+ expect(result[1].lc_kwargs.name).toEqual(formatOptions.userName);
221
+ expect(result[2].lc_kwargs.name).toEqual(formatOptions.assistantName);
222
+ });
223
+
224
+ describe('formatFromLangChain', () => {
225
+ it('should merge kwargs and additional_kwargs', () => {
226
+ const message = {
227
+ kwargs: {
228
+ content: 'some content',
229
+ name: 'dan',
230
+ additional_kwargs: {
231
+ function_call: {
232
+ name: 'dall-e',
233
+ arguments: '{\n "input": "Subject: hedgehog, Style: cute"\n}',
234
+ },
235
+ },
236
+ },
237
+ };
238
+
239
+ const expected = {
240
+ content: 'some content',
241
+ name: 'dan',
242
+ function_call: {
243
+ name: 'dall-e',
244
+ arguments: '{\n "input": "Subject: hedgehog, Style: cute"\n}',
245
+ },
246
+ };
247
+
248
+ expect(formatFromLangChain(message)).toEqual(expected);
249
+ });
250
+
251
+ it('should handle messages without additional_kwargs', () => {
252
+ const message = {
253
+ kwargs: {
254
+ content: 'some content',
255
+ name: 'dan',
256
+ },
257
+ };
258
+
259
+ const expected = {
260
+ content: 'some content',
261
+ name: 'dan',
262
+ };
263
+
264
+ expect(formatFromLangChain(message)).toEqual(expected);
265
+ });
266
+
267
+ it('should handle empty messages', () => {
268
+ const message = {
269
+ kwargs: {},
270
+ };
271
+
272
+ const expected = {};
273
+
274
+ expect(formatFromLangChain(message)).toEqual(expected);
275
+ });
276
+ });
277
+ });
@@ -0,0 +1,3 @@
1
+ export * from './core';
2
+ export * from './prune';
3
+ export * from './format';
@@ -0,0 +1,167 @@
1
+ import type { BaseMessage, UsageMetadata } from '@langchain/core/messages';
2
+ import type { TokenCounter } from '@/types/run';
3
+ export type PruneMessagesFactoryParams = {
4
+ maxTokens: number;
5
+ startIndex: number;
6
+ tokenCounter: TokenCounter;
7
+ indexTokenCountMap: Record<string, number>;
8
+ };
9
+ export type PruneMessagesParams = {
10
+ messages: BaseMessage[];
11
+ usageMetadata?: Partial<UsageMetadata>;
12
+ }
13
+
14
+ /**
15
+ * Calculates the total tokens from a single usage object
16
+ *
17
+ * @param usage The usage metadata object containing token information
18
+ * @returns An object containing the total input and output tokens
19
+ */
20
+ function calculateTotalTokens(usage: Partial<UsageMetadata>): UsageMetadata {
21
+ const baseInputTokens = Number(usage.input_tokens) || 0;
22
+ const cacheCreation = Number(usage.input_token_details?.cache_creation) || 0;
23
+ const cacheRead = Number(usage.input_token_details?.cache_read) || 0;
24
+
25
+ const totalInputTokens = baseInputTokens + cacheCreation + cacheRead;
26
+ const totalOutputTokens = Number(usage.output_tokens) || 0;
27
+
28
+ return {
29
+ input_tokens: totalInputTokens,
30
+ output_tokens: totalOutputTokens,
31
+ total_tokens: totalInputTokens + totalOutputTokens
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Processes an array of messages and returns a context of messages that fit within a specified token limit.
37
+ * It iterates over the messages from newest to oldest, adding them to the context until the token limit is reached.
38
+ *
39
+ * @param options Configuration options for processing messages
40
+ * @returns Object containing the message context, remaining tokens, messages not included, and summary index
41
+ */
42
+ function getMessagesWithinTokenLimit({
43
+ messages: _messages,
44
+ maxContextTokens,
45
+ indexTokenCountMap,
46
+ }: {
47
+ messages: BaseMessage[];
48
+ maxContextTokens: number;
49
+ indexTokenCountMap: Record<string, number>;
50
+ }): {
51
+ context: BaseMessage[];
52
+ remainingContextTokens: number;
53
+ messagesToRefine: BaseMessage[];
54
+ summaryIndex: number;
55
+ } {
56
+ // Every reply is primed with <|start|>assistant<|message|>, so we
57
+ // start with 3 tokens for the label after all messages have been counted.
58
+ let summaryIndex = -1;
59
+ let currentTokenCount = 3;
60
+ const instructions = _messages?.[0]?.getType() === 'system' ? _messages[0] : undefined;
61
+ const instructionsTokenCount = instructions != null ? indexTokenCountMap[0] : 0;
62
+ let remainingContextTokens = maxContextTokens - instructionsTokenCount;
63
+ const messages = [..._messages];
64
+ const context: BaseMessage[] = [];
65
+
66
+ if (currentTokenCount < remainingContextTokens) {
67
+ let currentIndex = messages.length;
68
+ while (messages.length > 0 && currentTokenCount < remainingContextTokens && currentIndex > 1) {
69
+ currentIndex--;
70
+ if (messages.length === 1 && instructions) {
71
+ break;
72
+ }
73
+ const poppedMessage = messages.pop();
74
+ if (!poppedMessage) continue;
75
+
76
+ const tokenCount = indexTokenCountMap[currentIndex] || 0;
77
+
78
+ if ((currentTokenCount + tokenCount) <= remainingContextTokens) {
79
+ context.push(poppedMessage);
80
+ currentTokenCount += tokenCount;
81
+ } else {
82
+ messages.push(poppedMessage);
83
+ break;
84
+ }
85
+ }
86
+ }
87
+
88
+ if (instructions && _messages.length > 0) {
89
+ context.push(_messages[0] as BaseMessage);
90
+ messages.shift();
91
+ }
92
+
93
+ const prunedMemory = messages;
94
+ summaryIndex = prunedMemory.length - 1;
95
+ remainingContextTokens -= currentTokenCount;
96
+
97
+ return {
98
+ summaryIndex,
99
+ remainingContextTokens,
100
+ context: context.reverse(),
101
+ messagesToRefine: prunedMemory,
102
+ };
103
+ }
104
+
105
+ function checkValidNumber(value: unknown): value is number {
106
+ return typeof value === 'number' && !isNaN(value) && value > 0;
107
+ }
108
+
109
+ export function createPruneMessages(factoryParams: PruneMessagesFactoryParams) {
110
+ const indexTokenCountMap = { ...factoryParams.indexTokenCountMap };
111
+ let lastTurnStartIndex = factoryParams.startIndex;
112
+ let totalTokens = (Object.values(indexTokenCountMap)).reduce((a, b) => a + b, 0);
113
+ return function pruneMessages(params: PruneMessagesParams): {
114
+ context: BaseMessage[];
115
+ indexTokenCountMap: Record<string, number>;
116
+ } {
117
+ let currentUsage: UsageMetadata | undefined;
118
+ if (params.usageMetadata && (
119
+ checkValidNumber(params.usageMetadata.input_tokens)
120
+ || (
121
+ checkValidNumber(params.usageMetadata.input_token_details)
122
+ && (
123
+ checkValidNumber(params.usageMetadata.input_token_details.cache_creation)
124
+ || checkValidNumber(params.usageMetadata.input_token_details.cache_read)
125
+ )
126
+ )
127
+ ) && checkValidNumber(params.usageMetadata.output_tokens)) {
128
+ currentUsage = calculateTotalTokens(params.usageMetadata);
129
+ totalTokens = currentUsage.total_tokens;
130
+ }
131
+
132
+ for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
133
+ const message = params.messages[i];
134
+ if (i === lastTurnStartIndex && indexTokenCountMap[i] === undefined && currentUsage) {
135
+ indexTokenCountMap[i] = currentUsage.output_tokens;
136
+ } else if (indexTokenCountMap[i] === undefined) {
137
+ indexTokenCountMap[i] = factoryParams.tokenCounter(message);
138
+ totalTokens += indexTokenCountMap[i];
139
+ }
140
+ }
141
+
142
+ // If `currentUsage` is defined, we need to distribute the current total tokensto our `indexTokenCountMap`,
143
+ // for all message index keys before `lastTurnStartIndex`, as it has the most accurate count for those messages.
144
+ // We must distribute it in a weighted manner, so that the total token count is equal to `currentUsage.total_tokens`,
145
+ // relative the manually counted tokens in `indexTokenCountMap`.
146
+ if (currentUsage) {
147
+ const totalIndexTokens = Object.values(indexTokenCountMap).reduce((a, b) => a + b, 0);
148
+ const ratio = currentUsage.total_tokens / totalIndexTokens;
149
+ for (const key in indexTokenCountMap) {
150
+ indexTokenCountMap[key] = Math.round(indexTokenCountMap[key] * ratio);
151
+ }
152
+ }
153
+
154
+ lastTurnStartIndex = params.messages.length;
155
+ if (totalTokens <= factoryParams.maxTokens) {
156
+ return { context: params.messages, indexTokenCountMap };
157
+ }
158
+
159
+ const { context } = getMessagesWithinTokenLimit({
160
+ maxContextTokens: factoryParams.maxTokens,
161
+ messages: params.messages,
162
+ indexTokenCountMap,
163
+ });
164
+
165
+ return { context, indexTokenCountMap };
166
+ }
167
+ }
@@ -0,0 +1,81 @@
1
+ import { shiftIndexTokenCountMap } from './format';
2
+
3
+ describe('shiftIndexTokenCountMap', () => {
4
+ it('should add a system message token count at index 0 and shift all other indices', () => {
5
+ const originalMap: Record<number, number> = {
6
+ 0: 10,
7
+ 1: 20,
8
+ 2: 30
9
+ };
10
+
11
+ const systemMessageTokenCount = 15;
12
+
13
+ const result = shiftIndexTokenCountMap(originalMap, systemMessageTokenCount);
14
+
15
+ // Check that the system message token count is at index 0
16
+ expect(result[0]).toBe(15);
17
+
18
+ // Check that all other indices are shifted by 1
19
+ expect(result[1]).toBe(10);
20
+ expect(result[2]).toBe(20);
21
+ expect(result[3]).toBe(30);
22
+
23
+ // Check that the original map is not modified
24
+ expect(originalMap[0]).toBe(10);
25
+ expect(originalMap[1]).toBe(20);
26
+ expect(originalMap[2]).toBe(30);
27
+ });
28
+
29
+ it('should handle an empty map', () => {
30
+ const emptyMap: Record<number, number> = {};
31
+ const systemMessageTokenCount = 15;
32
+
33
+ const result = shiftIndexTokenCountMap(emptyMap, systemMessageTokenCount);
34
+
35
+ // Check that only the system message token count is in the result
36
+ expect(Object.keys(result).length).toBe(1);
37
+ expect(result[0]).toBe(15);
38
+ });
39
+
40
+ it('should handle non-sequential indices', () => {
41
+ const nonSequentialMap: Record<number, number> = {
42
+ 0: 10,
43
+ 2: 20,
44
+ 5: 30
45
+ };
46
+
47
+ const systemMessageTokenCount = 15;
48
+
49
+ const result = shiftIndexTokenCountMap(nonSequentialMap, systemMessageTokenCount);
50
+
51
+ // Check that the system message token count is at index 0
52
+ expect(result[0]).toBe(15);
53
+
54
+ // Check that all other indices are shifted by 1
55
+ expect(result[1]).toBe(10);
56
+ expect(result[3]).toBe(20);
57
+ expect(result[6]).toBe(30);
58
+ });
59
+
60
+ it('should handle string keys', () => {
61
+ // TypeScript will convert string keys to numbers when accessing the object
62
+ const mapWithStringKeys: Record<string, number> = {
63
+ '0': 10,
64
+ '1': 20,
65
+ '2': 30
66
+ };
67
+
68
+ const systemMessageTokenCount = 15;
69
+
70
+ // Cast to Record<number, number> to match the function signature
71
+ const result = shiftIndexTokenCountMap(mapWithStringKeys as unknown as Record<number, number>, systemMessageTokenCount);
72
+
73
+ // Check that the system message token count is at index 0
74
+ expect(result[0]).toBe(15);
75
+
76
+ // Check that all other indices are shifted by 1
77
+ expect(result[1]).toBe(10);
78
+ expect(result[2]).toBe(20);
79
+ expect(result[3]).toBe(30);
80
+ });
81
+ });
package/src/run.ts CHANGED
@@ -1,13 +1,17 @@
1
1
  // src/run.ts
2
+ import { zodToJsonSchema } from "zod-to-json-schema";
2
3
  import { PromptTemplate } from '@langchain/core/prompts';
3
4
  import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
5
+ import { SystemMessage } from '@langchain/core/messages';
4
6
  import type { BaseMessage, MessageContentComplex } from '@langchain/core/messages';
5
7
  import type { ClientCallbacks, SystemCallbacks } from '@/graphs/Graph';
6
8
  import type { RunnableConfig } from '@langchain/core/runnables';
7
9
  import type * as t from '@/types';
8
10
  import { GraphEvents, Providers, Callback } from '@/common';
9
11
  import { manualToolStreamProviders } from '@/llm/providers';
12
+ import { shiftIndexTokenCountMap } from '@/messages/format';
10
13
  import { createTitleRunnable } from '@/utils/title';
14
+ import { createTokenCounter } from '@/utils/tokens';
11
15
  import { StandardGraph } from '@/graphs/Graph';
12
16
  import { HandlerRegistry } from '@/events';
13
17
  import { isOpenAILike } from '@/utils/llm';
@@ -106,6 +110,28 @@ export class Run<T extends t.BaseGraphState> {
106
110
  throw new Error('Run ID not provided');
107
111
  }
108
112
 
113
+ const tokenCounter = streamOptions?.tokenCounter ?? (streamOptions?.indexTokenCountMap ? await createTokenCounter() : undefined);
114
+ const toolTokens = tokenCounter ? (this.Graph.tools?.reduce((acc, tool) => {
115
+ if (!tool.schema) {
116
+ return acc;
117
+ }
118
+
119
+ const jsonSchema = zodToJsonSchema(tool.schema.describe(tool.description ?? ''), tool.name);
120
+ return acc + tokenCounter(new SystemMessage(JSON.stringify(jsonSchema)));
121
+ }, 0) ?? 0) : 0;
122
+ let instructionTokens = toolTokens;
123
+ if (this.Graph.systemMessage && tokenCounter) {
124
+ instructionTokens += tokenCounter(this.Graph.systemMessage);
125
+ }
126
+ if (instructionTokens > 0) {
127
+ this.Graph.indexTokenCountMap = shiftIndexTokenCountMap(streamOptions?.indexTokenCountMap ?? {}, instructionTokens);
128
+ } else {
129
+ this.Graph.indexTokenCountMap = streamOptions?.indexTokenCountMap ?? {};
130
+ }
131
+
132
+ this.Graph.maxContextTokens = streamOptions?.maxContextTokens;
133
+ this.Graph.tokenCounter = tokenCounter;
134
+
109
135
  config.run_id = this.id;
110
136
  config.configurable = Object.assign(config.configurable ?? {}, { run_id: this.id, provider: this.provider });
111
137
 
@@ -1,12 +1,13 @@
1
1
  // src/scripts/cli.ts
2
2
  import { config } from 'dotenv';
3
3
  config();
4
- import { HumanMessage, AIMessage, BaseMessage } from '@langchain/core/messages';
4
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
5
5
  import { TavilySearchResults } from '@langchain/community/tools/tavily_search';
6
6
  import type * as t from '@/types';
7
7
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
8
- import { ToolEndHandler, ModelEndHandler, createMetadataAggregator } from '@/events';
9
8
  import { createCodeExecutionTool } from '@/tools/CodeExecutor';
9
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
10
+ import { createTokenCounter } from '@/utils/tokens';
10
11
  import { getLLMConfig } from '@/utils/llmConfig';
11
12
  import { getArgs } from '@/scripts/args';
12
13
  import { GraphEvents } from '@/common';
@@ -58,19 +59,22 @@ async function testCodeExecution(): Promise<void> {
58
59
  };
59
60
 
60
61
  const llmConfig = getLLMConfig(provider);
62
+ const instructions = 'You are a friendly AI assistant with coding capabilities. Always address the user by their name.';
63
+ const additional_instructions = `The user's name is ${userName} and they are located in ${location}.`;
61
64
 
62
- const run = await Run.create<t.IState>({
65
+ const runConfig: t.RunConfig = {
63
66
  runId: 'message-num-1',
64
67
  graphConfig: {
65
68
  type: 'standard',
66
69
  llmConfig,
67
70
  tools: [new TavilySearchResults(), createCodeExecutionTool()],
68
- instructions: 'You are a friendly AI assistant with coding capabilities. Always address the user by their name.',
69
- additional_instructions: `The user's name is ${userName} and they are located in ${location}.`,
71
+ instructions,
72
+ additional_instructions,
70
73
  },
71
74
  returnContent: true,
72
75
  customHandlers,
73
- });
76
+ };
77
+ const run = await Run.create<t.IState>(runConfig);
74
78
 
75
79
  const config = {
76
80
  configurable: {
@@ -86,13 +90,22 @@ async function testCodeExecution(): Promise<void> {
86
90
  // const userMessage1 = `how much memory is this (its in bytes) in MB? 31192000`;
87
91
  // const userMessage1 = `can you show me a good use case for rscript by running some code`;
88
92
  const userMessage1 = `Run hello world in french and in english, using python. please run 2 parallel code executions.`;
93
+ const humanMessage = new HumanMessage(userMessage1);
94
+ const tokenCounter = await createTokenCounter();
95
+ const indexTokenCountMap = {
96
+ 0: tokenCounter(humanMessage),
97
+ };
89
98
 
90
- conversationHistory.push(new HumanMessage(userMessage1));
99
+ conversationHistory.push(humanMessage);
91
100
 
92
101
  let inputs = {
93
102
  messages: conversationHistory,
94
103
  };
95
- const finalContentParts1 = await run.processStream(inputs, config);
104
+ const finalContentParts1 = await run.processStream(inputs, config, {
105
+ maxContextTokens: 8000,
106
+ indexTokenCountMap,
107
+ tokenCounter,
108
+ });
96
109
  const finalMessages1 = run.getRunMessages();
97
110
  if (finalMessages1) {
98
111
  conversationHistory.push(...finalMessages1);