@librechat/agents 2.3.94 → 2.3.96
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/common/enum.cjs +1 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +6 -1
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +36 -16
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs +6 -1
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +36 -16
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +1 -0
- package/dist/types/messages/format.d.ts +3 -3
- package/dist/types/types/llm.d.ts +4 -2
- package/package.json +12 -25
- package/src/common/enum.ts +4 -3
- package/src/llm/anthropic/utils/message_inputs.ts +1 -1
- package/src/llm/providers.ts +12 -3
- package/src/messages/format.ts +97 -30
- package/src/messages/formatAgentMessages.test.ts +337 -49
- package/src/messages/formatAgentMessages.tools.test.ts +61 -10
- package/src/scripts/simple.ts +16 -0
- package/src/types/llm.ts +40 -24
- package/src/utils/llmConfig.ts +18 -6
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
HumanMessage,
|
|
3
|
+
AIMessage,
|
|
4
|
+
SystemMessage,
|
|
5
|
+
ToolMessage,
|
|
6
|
+
} from '@langchain/core/messages';
|
|
2
7
|
import type { TPayload } from '@/types';
|
|
3
8
|
import { formatAgentMessages } from './format';
|
|
4
9
|
import { ContentTypes } from '@/common';
|
|
@@ -16,7 +21,9 @@ describe('formatAgentMessages', () => {
|
|
|
16
21
|
});
|
|
17
22
|
|
|
18
23
|
it('should handle system messages', () => {
|
|
19
|
-
const payload = [
|
|
24
|
+
const payload = [
|
|
25
|
+
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
26
|
+
];
|
|
20
27
|
const result = formatAgentMessages(payload);
|
|
21
28
|
expect(result.messages).toHaveLength(1);
|
|
22
29
|
expect(result.messages[0]).toBeInstanceOf(SystemMessage);
|
|
@@ -80,7 +87,7 @@ describe('formatAgentMessages', () => {
|
|
|
80
87
|
expect(result.messages[0].content).toHaveLength(2);
|
|
81
88
|
});
|
|
82
89
|
|
|
83
|
-
it('should
|
|
90
|
+
it('should heal invalid tool call structure by creating a preceding AIMessage', () => {
|
|
84
91
|
const payload = [
|
|
85
92
|
{
|
|
86
93
|
role: 'assistant',
|
|
@@ -97,7 +104,26 @@ describe('formatAgentMessages', () => {
|
|
|
97
104
|
],
|
|
98
105
|
},
|
|
99
106
|
];
|
|
100
|
-
|
|
107
|
+
const result = formatAgentMessages(payload);
|
|
108
|
+
|
|
109
|
+
// Should have 2 messages: an AIMessage and a ToolMessage
|
|
110
|
+
expect(result.messages).toHaveLength(2);
|
|
111
|
+
expect(result.messages[0]).toBeInstanceOf(AIMessage);
|
|
112
|
+
expect(result.messages[1]).toBeInstanceOf(ToolMessage);
|
|
113
|
+
|
|
114
|
+
// The AIMessage should have an empty content and the tool_call
|
|
115
|
+
expect(result.messages[0].content).toBe('');
|
|
116
|
+
expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(1);
|
|
117
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[0]).toEqual({
|
|
118
|
+
id: '123',
|
|
119
|
+
name: 'search',
|
|
120
|
+
args: { query: 'weather' },
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// The ToolMessage should have the correct properties
|
|
124
|
+
expect((result.messages[1] as ToolMessage).tool_call_id).toBe('123');
|
|
125
|
+
expect(result.messages[1].name).toBe('search');
|
|
126
|
+
expect(result.messages[1].content).toBe('The weather is sunny.');
|
|
101
127
|
});
|
|
102
128
|
|
|
103
129
|
it('should handle tool calls with non-JSON args', () => {
|
|
@@ -105,7 +131,11 @@ describe('formatAgentMessages', () => {
|
|
|
105
131
|
{
|
|
106
132
|
role: 'assistant',
|
|
107
133
|
content: [
|
|
108
|
-
{
|
|
134
|
+
{
|
|
135
|
+
type: ContentTypes.TEXT,
|
|
136
|
+
[ContentTypes.TEXT]: 'Checking...',
|
|
137
|
+
tool_call_ids: ['123'],
|
|
138
|
+
},
|
|
109
139
|
{
|
|
110
140
|
type: ContentTypes.TOOL_CALL,
|
|
111
141
|
tool_call: {
|
|
@@ -120,7 +150,9 @@ describe('formatAgentMessages', () => {
|
|
|
120
150
|
];
|
|
121
151
|
const result = formatAgentMessages(payload);
|
|
122
152
|
expect(result.messages).toHaveLength(2);
|
|
123
|
-
expect(
|
|
153
|
+
expect(
|
|
154
|
+
(result.messages[0] as AIMessage).tool_calls?.[0].args
|
|
155
|
+
).toStrictEqual({ input: 'non-json-string' });
|
|
124
156
|
});
|
|
125
157
|
|
|
126
158
|
it('should handle complex tool calls with multiple steps', () => {
|
|
@@ -139,7 +171,8 @@ describe('formatAgentMessages', () => {
|
|
|
139
171
|
id: 'search_1',
|
|
140
172
|
name: 'search',
|
|
141
173
|
args: '{"query":"weather in New York"}',
|
|
142
|
-
output:
|
|
174
|
+
output:
|
|
175
|
+
'The weather in New York is currently sunny with a temperature of 75°F.',
|
|
143
176
|
},
|
|
144
177
|
},
|
|
145
178
|
{
|
|
@@ -156,7 +189,10 @@ describe('formatAgentMessages', () => {
|
|
|
156
189
|
output: '23.89°C',
|
|
157
190
|
},
|
|
158
191
|
},
|
|
159
|
-
{
|
|
192
|
+
{
|
|
193
|
+
type: ContentTypes.TEXT,
|
|
194
|
+
[ContentTypes.TEXT]: 'Here\'s your answer.',
|
|
195
|
+
},
|
|
160
196
|
],
|
|
161
197
|
},
|
|
162
198
|
];
|
|
@@ -171,7 +207,9 @@ describe('formatAgentMessages', () => {
|
|
|
171
207
|
expect(result.messages[4]).toBeInstanceOf(AIMessage);
|
|
172
208
|
|
|
173
209
|
// Check first AIMessage
|
|
174
|
-
expect(result.messages[0].content).toBe(
|
|
210
|
+
expect(result.messages[0].content).toBe(
|
|
211
|
+
'I\'ll search for that information.'
|
|
212
|
+
);
|
|
175
213
|
expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(1);
|
|
176
214
|
expect((result.messages[0] as AIMessage).tool_calls?.[0]).toEqual({
|
|
177
215
|
id: 'search_1',
|
|
@@ -183,11 +221,13 @@ describe('formatAgentMessages', () => {
|
|
|
183
221
|
expect((result.messages[1] as ToolMessage).tool_call_id).toBe('search_1');
|
|
184
222
|
expect(result.messages[1].name).toBe('search');
|
|
185
223
|
expect(result.messages[1].content).toBe(
|
|
186
|
-
'The weather in New York is currently sunny with a temperature of 75°F.'
|
|
224
|
+
'The weather in New York is currently sunny with a temperature of 75°F.'
|
|
187
225
|
);
|
|
188
226
|
|
|
189
227
|
// Check second AIMessage
|
|
190
|
-
expect(result.messages[2].content).toBe(
|
|
228
|
+
expect(result.messages[2].content).toBe(
|
|
229
|
+
'Now, I\'ll convert the temperature.'
|
|
230
|
+
);
|
|
191
231
|
expect((result.messages[2] as AIMessage).tool_calls).toHaveLength(1);
|
|
192
232
|
expect((result.messages[2] as AIMessage).tool_calls?.[0]).toEqual({
|
|
193
233
|
id: 'convert_1',
|
|
@@ -211,11 +251,18 @@ describe('formatAgentMessages', () => {
|
|
|
211
251
|
{ role: 'user', content: 'Hello' },
|
|
212
252
|
{
|
|
213
253
|
role: 'assistant',
|
|
214
|
-
content: [
|
|
254
|
+
content: [
|
|
255
|
+
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Hi there!' },
|
|
256
|
+
],
|
|
215
257
|
},
|
|
216
258
|
{
|
|
217
259
|
role: 'assistant',
|
|
218
|
-
content: [
|
|
260
|
+
content: [
|
|
261
|
+
{
|
|
262
|
+
type: ContentTypes.TEXT,
|
|
263
|
+
[ContentTypes.TEXT]: 'How can I help you?',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
219
266
|
},
|
|
220
267
|
{ role: 'user', content: 'What\'s the weather?' },
|
|
221
268
|
{
|
|
@@ -240,7 +287,10 @@ describe('formatAgentMessages', () => {
|
|
|
240
287
|
{
|
|
241
288
|
role: 'assistant',
|
|
242
289
|
content: [
|
|
243
|
-
{
|
|
290
|
+
{
|
|
291
|
+
type: ContentTypes.TEXT,
|
|
292
|
+
[ContentTypes.TEXT]: 'Here\'s the weather information.',
|
|
293
|
+
},
|
|
244
294
|
],
|
|
245
295
|
},
|
|
246
296
|
];
|
|
@@ -270,13 +320,18 @@ describe('formatAgentMessages', () => {
|
|
|
270
320
|
expect(result.messages[3].content).toBe('Let me check that for you.');
|
|
271
321
|
expect(result.messages[4].content).toBe('Sunny, 75°F');
|
|
272
322
|
expect(result.messages[5].content).toStrictEqual([
|
|
273
|
-
{
|
|
323
|
+
{
|
|
324
|
+
[ContentTypes.TEXT]: 'Here\'s the weather information.',
|
|
325
|
+
type: ContentTypes.TEXT,
|
|
326
|
+
},
|
|
274
327
|
]);
|
|
275
328
|
|
|
276
329
|
// Check that there are no consecutive AIMessages
|
|
277
330
|
const messageTypes = result.messages.map((message) => message.constructor);
|
|
278
331
|
for (let i = 0; i < messageTypes.length - 1; i++) {
|
|
279
|
-
expect(
|
|
332
|
+
expect(
|
|
333
|
+
messageTypes[i] === AIMessage && messageTypes[i + 1] === AIMessage
|
|
334
|
+
).toBe(false);
|
|
280
335
|
}
|
|
281
336
|
|
|
282
337
|
// Additional check to ensure the consecutive assistant messages were combined
|
|
@@ -289,7 +344,10 @@ describe('formatAgentMessages', () => {
|
|
|
289
344
|
role: 'assistant',
|
|
290
345
|
content: [
|
|
291
346
|
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Initial response' },
|
|
292
|
-
{
|
|
347
|
+
{
|
|
348
|
+
type: ContentTypes.THINK,
|
|
349
|
+
[ContentTypes.THINK]: 'Reasoning about the problem...',
|
|
350
|
+
},
|
|
293
351
|
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final answer' },
|
|
294
352
|
],
|
|
295
353
|
},
|
|
@@ -299,7 +357,9 @@ describe('formatAgentMessages', () => {
|
|
|
299
357
|
|
|
300
358
|
expect(result.messages).toHaveLength(1);
|
|
301
359
|
expect(result.messages[0]).toBeInstanceOf(AIMessage);
|
|
302
|
-
expect(result.messages[0].content).toEqual(
|
|
360
|
+
expect(result.messages[0].content).toEqual(
|
|
361
|
+
'Initial response\nFinal answer'
|
|
362
|
+
);
|
|
303
363
|
});
|
|
304
364
|
|
|
305
365
|
it('should join TEXT content as string when THINK content type is present', () => {
|
|
@@ -307,10 +367,22 @@ describe('formatAgentMessages', () => {
|
|
|
307
367
|
{
|
|
308
368
|
role: 'assistant',
|
|
309
369
|
content: [
|
|
310
|
-
{
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
370
|
+
{
|
|
371
|
+
type: ContentTypes.THINK,
|
|
372
|
+
[ContentTypes.THINK]: 'Analyzing the problem...',
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
type: ContentTypes.TEXT,
|
|
376
|
+
[ContentTypes.TEXT]: 'First part of response',
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
type: ContentTypes.TEXT,
|
|
380
|
+
[ContentTypes.TEXT]: 'Second part of response',
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
type: ContentTypes.TEXT,
|
|
384
|
+
[ContentTypes.TEXT]: 'Final part of response',
|
|
385
|
+
},
|
|
314
386
|
],
|
|
315
387
|
},
|
|
316
388
|
];
|
|
@@ -321,9 +393,11 @@ describe('formatAgentMessages', () => {
|
|
|
321
393
|
expect(result.messages[0]).toBeInstanceOf(AIMessage);
|
|
322
394
|
expect(typeof result.messages[0].content).toBe('string');
|
|
323
395
|
expect(result.messages[0].content).toBe(
|
|
324
|
-
'First part of response\nSecond part of response\nFinal part of response'
|
|
396
|
+
'First part of response\nSecond part of response\nFinal part of response'
|
|
397
|
+
);
|
|
398
|
+
expect(result.messages[0].content).not.toContain(
|
|
399
|
+
'Analyzing the problem...'
|
|
325
400
|
);
|
|
326
|
-
expect(result.messages[0].content).not.toContain('Analyzing the problem...');
|
|
327
401
|
});
|
|
328
402
|
|
|
329
403
|
it('should exclude ERROR type content parts', () => {
|
|
@@ -351,10 +425,13 @@ describe('formatAgentMessages', () => {
|
|
|
351
425
|
{ type: ContentTypes.TEXT, [ContentTypes.TEXT]: 'Final answer' },
|
|
352
426
|
]);
|
|
353
427
|
|
|
354
|
-
const hasErrorContent =
|
|
355
|
-
(
|
|
356
|
-
|
|
357
|
-
|
|
428
|
+
const hasErrorContent =
|
|
429
|
+
Array.isArray(result.messages[0].content) &&
|
|
430
|
+
result.messages[0].content.some(
|
|
431
|
+
(item) =>
|
|
432
|
+
item.type === ContentTypes.ERROR ||
|
|
433
|
+
JSON.stringify(item).includes('An error occurred')
|
|
434
|
+
);
|
|
358
435
|
expect(hasErrorContent).toBe(false);
|
|
359
436
|
});
|
|
360
437
|
it('should handle indexTokenCountMap and return updated map', () => {
|
|
@@ -364,7 +441,7 @@ describe('formatAgentMessages', () => {
|
|
|
364
441
|
];
|
|
365
442
|
|
|
366
443
|
const indexTokenCountMap = {
|
|
367
|
-
0: 5,
|
|
444
|
+
0: 5, // 5 tokens for "Hello"
|
|
368
445
|
1: 10, // 10 tokens for "Hi there!"
|
|
369
446
|
};
|
|
370
447
|
|
|
@@ -401,8 +478,8 @@ describe('formatAgentMessages', () => {
|
|
|
401
478
|
];
|
|
402
479
|
|
|
403
480
|
const indexTokenCountMap = {
|
|
404
|
-
0: 10,
|
|
405
|
-
1: 50,
|
|
481
|
+
0: 10, // 10 tokens for "What's the weather?"
|
|
482
|
+
1: 50, // 50 tokens for the assistant message with tool call
|
|
406
483
|
};
|
|
407
484
|
|
|
408
485
|
const result = formatAgentMessages(payload, indexTokenCountMap);
|
|
@@ -410,11 +487,14 @@ describe('formatAgentMessages', () => {
|
|
|
410
487
|
// The original message at index 1 should be split into two messages
|
|
411
488
|
expect(result.messages).toHaveLength(3);
|
|
412
489
|
expect(result.indexTokenCountMap).toBeDefined();
|
|
413
|
-
expect(result.indexTokenCountMap?.[0]).toBe(10);
|
|
490
|
+
expect(result.indexTokenCountMap?.[0]).toBe(10); // User message stays the same
|
|
414
491
|
|
|
415
492
|
// The assistant message tokens should be distributed across the resulting messages
|
|
416
|
-
const totalAssistantTokens =
|
|
417
|
-
.
|
|
493
|
+
const totalAssistantTokens =
|
|
494
|
+
Object.values(result.indexTokenCountMap || {}).reduce(
|
|
495
|
+
(sum, count) => sum + count,
|
|
496
|
+
0
|
|
497
|
+
) - 10; // Subtract user message tokens
|
|
418
498
|
|
|
419
499
|
expect(totalAssistantTokens).toBe(50); // Should match the original token count
|
|
420
500
|
});
|
|
@@ -462,7 +542,7 @@ describe('formatAgentMessages', () => {
|
|
|
462
542
|
];
|
|
463
543
|
|
|
464
544
|
const indexTokenCountMap = {
|
|
465
|
-
0: 100,
|
|
545
|
+
0: 100, // 100 tokens for the complex assistant message
|
|
466
546
|
};
|
|
467
547
|
|
|
468
548
|
const result = formatAgentMessages(payload, indexTokenCountMap);
|
|
@@ -472,8 +552,10 @@ describe('formatAgentMessages', () => {
|
|
|
472
552
|
expect(result.indexTokenCountMap).toBeDefined();
|
|
473
553
|
|
|
474
554
|
// The sum of all token counts should equal the original
|
|
475
|
-
const totalTokens = Object.values(result.indexTokenCountMap || {})
|
|
476
|
-
|
|
555
|
+
const totalTokens = Object.values(result.indexTokenCountMap || {}).reduce(
|
|
556
|
+
(sum, count) => sum + count,
|
|
557
|
+
0
|
|
558
|
+
);
|
|
477
559
|
|
|
478
560
|
expect(totalTokens).toBe(100);
|
|
479
561
|
|
|
@@ -497,7 +579,7 @@ describe('formatAgentMessages', () => {
|
|
|
497
579
|
];
|
|
498
580
|
|
|
499
581
|
const indexTokenCountMap = {
|
|
500
|
-
0: 60,
|
|
582
|
+
0: 60, // 60 tokens for the message with filtered content
|
|
501
583
|
};
|
|
502
584
|
|
|
503
585
|
const result = formatAgentMessages(payload, indexTokenCountMap);
|
|
@@ -524,7 +606,7 @@ describe('formatAgentMessages', () => {
|
|
|
524
606
|
];
|
|
525
607
|
|
|
526
608
|
const indexTokenCountMap = {
|
|
527
|
-
0: 40,
|
|
609
|
+
0: 40, // 40 tokens for the message with filtered content
|
|
528
610
|
};
|
|
529
611
|
|
|
530
612
|
const result = formatAgentMessages(payload, indexTokenCountMap);
|
|
@@ -563,8 +645,8 @@ describe('formatAgentMessages', () => {
|
|
|
563
645
|
];
|
|
564
646
|
|
|
565
647
|
const indexTokenCountMap = {
|
|
566
|
-
0: 15,
|
|
567
|
-
1: 45,
|
|
648
|
+
0: 15, // 15 tokens for the user message
|
|
649
|
+
1: 45, // 45 tokens for the assistant message with tool call
|
|
568
650
|
};
|
|
569
651
|
|
|
570
652
|
const result = formatAgentMessages(payload, indexTokenCountMap);
|
|
@@ -581,12 +663,210 @@ describe('formatAgentMessages', () => {
|
|
|
581
663
|
expect(result.messages[2]).toBeInstanceOf(ToolMessage);
|
|
582
664
|
|
|
583
665
|
// The sum of all token counts should equal the original total
|
|
584
|
-
const totalTokens = Object.values(result.indexTokenCountMap || {})
|
|
585
|
-
|
|
666
|
+
const totalTokens = Object.values(result.indexTokenCountMap || {}).reduce(
|
|
667
|
+
(sum, count) => sum + count,
|
|
668
|
+
0
|
|
669
|
+
);
|
|
586
670
|
|
|
587
671
|
expect(totalTokens).toBe(60); // 15 + 45
|
|
588
672
|
});
|
|
589
673
|
|
|
674
|
+
it('should handle an AI message with 5 tool calls in a single message', () => {
|
|
675
|
+
const payload = [
|
|
676
|
+
{
|
|
677
|
+
role: 'assistant',
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: ContentTypes.TEXT,
|
|
681
|
+
[ContentTypes.TEXT]: 'I\'ll perform multiple operations for you.',
|
|
682
|
+
tool_call_ids: ['tool_1', 'tool_2', 'tool_3', 'tool_4', 'tool_5'],
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
type: ContentTypes.TOOL_CALL,
|
|
686
|
+
tool_call: {
|
|
687
|
+
id: 'tool_1',
|
|
688
|
+
name: 'search',
|
|
689
|
+
args: '{"query":"latest news"}',
|
|
690
|
+
output: 'Found several news articles.',
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
type: ContentTypes.TOOL_CALL,
|
|
695
|
+
tool_call: {
|
|
696
|
+
id: 'tool_2',
|
|
697
|
+
name: 'check_weather',
|
|
698
|
+
args: '{"location":"New York"}',
|
|
699
|
+
output: 'Sunny, 75°F',
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
type: ContentTypes.TOOL_CALL,
|
|
704
|
+
tool_call: {
|
|
705
|
+
id: 'tool_3',
|
|
706
|
+
name: 'calculate',
|
|
707
|
+
args: '{"expression":"356 * 24"}',
|
|
708
|
+
output: '8544',
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
type: ContentTypes.TOOL_CALL,
|
|
713
|
+
tool_call: {
|
|
714
|
+
id: 'tool_4',
|
|
715
|
+
name: 'translate',
|
|
716
|
+
args: '{"text":"Hello world","source":"en","target":"fr"}',
|
|
717
|
+
output: 'Bonjour le monde',
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
type: ContentTypes.TOOL_CALL,
|
|
722
|
+
tool_call: {
|
|
723
|
+
id: 'tool_5',
|
|
724
|
+
name: 'fetch_data',
|
|
725
|
+
args: '{"endpoint":"/api/users","params":{"limit":5}}',
|
|
726
|
+
output:
|
|
727
|
+
'{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"},{"id":3,"name":"Charlie"},{"id":4,"name":"David"},{"id":5,"name":"Eve"}]}',
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
],
|
|
731
|
+
},
|
|
732
|
+
];
|
|
733
|
+
|
|
734
|
+
const result = formatAgentMessages(payload);
|
|
735
|
+
|
|
736
|
+
// Should have 6 messages: 1 AIMessage and 5 ToolMessages
|
|
737
|
+
expect(result.messages).toHaveLength(6);
|
|
738
|
+
|
|
739
|
+
// Check message types in the correct sequence
|
|
740
|
+
expect(result.messages[0]).toBeInstanceOf(AIMessage); // Initial message with all tool calls
|
|
741
|
+
expect(result.messages[1]).toBeInstanceOf(ToolMessage); // Tool 1 response
|
|
742
|
+
expect(result.messages[2]).toBeInstanceOf(ToolMessage); // Tool 2 response
|
|
743
|
+
expect(result.messages[3]).toBeInstanceOf(ToolMessage); // Tool 3 response
|
|
744
|
+
expect(result.messages[4]).toBeInstanceOf(ToolMessage); // Tool 4 response
|
|
745
|
+
expect(result.messages[5]).toBeInstanceOf(ToolMessage); // Tool 5 response
|
|
746
|
+
|
|
747
|
+
// Check AIMessage has all 5 tool calls
|
|
748
|
+
expect(result.messages[0].content).toBe(
|
|
749
|
+
'I\'ll perform multiple operations for you.'
|
|
750
|
+
);
|
|
751
|
+
expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(5);
|
|
752
|
+
|
|
753
|
+
// Verify each tool call in the AIMessage
|
|
754
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[0]).toEqual({
|
|
755
|
+
id: 'tool_1',
|
|
756
|
+
name: 'search',
|
|
757
|
+
args: { query: 'latest news' },
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[1]).toEqual({
|
|
761
|
+
id: 'tool_2',
|
|
762
|
+
name: 'check_weather',
|
|
763
|
+
args: { location: 'New York' },
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[2]).toEqual({
|
|
767
|
+
id: 'tool_3',
|
|
768
|
+
name: 'calculate',
|
|
769
|
+
args: { expression: '356 * 24' },
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[3]).toEqual({
|
|
773
|
+
id: 'tool_4',
|
|
774
|
+
name: 'translate',
|
|
775
|
+
args: { text: 'Hello world', source: 'en', target: 'fr' },
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[4]).toEqual({
|
|
779
|
+
id: 'tool_5',
|
|
780
|
+
name: 'fetch_data',
|
|
781
|
+
args: { endpoint: '/api/users', params: { limit: 5 } },
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// Check each ToolMessage
|
|
785
|
+
expect((result.messages[1] as ToolMessage).tool_call_id).toBe('tool_1');
|
|
786
|
+
expect(result.messages[1].name).toBe('search');
|
|
787
|
+
expect(result.messages[1].content).toBe('Found several news articles.');
|
|
788
|
+
|
|
789
|
+
expect((result.messages[2] as ToolMessage).tool_call_id).toBe('tool_2');
|
|
790
|
+
expect(result.messages[2].name).toBe('check_weather');
|
|
791
|
+
expect(result.messages[2].content).toBe('Sunny, 75°F');
|
|
792
|
+
|
|
793
|
+
expect((result.messages[3] as ToolMessage).tool_call_id).toBe('tool_3');
|
|
794
|
+
expect(result.messages[3].name).toBe('calculate');
|
|
795
|
+
expect(result.messages[3].content).toBe('8544');
|
|
796
|
+
|
|
797
|
+
expect((result.messages[4] as ToolMessage).tool_call_id).toBe('tool_4');
|
|
798
|
+
expect(result.messages[4].name).toBe('translate');
|
|
799
|
+
expect(result.messages[4].content).toBe('Bonjour le monde');
|
|
800
|
+
|
|
801
|
+
expect((result.messages[5] as ToolMessage).tool_call_id).toBe('tool_5');
|
|
802
|
+
expect(result.messages[5].name).toBe('fetch_data');
|
|
803
|
+
expect(result.messages[5].content).toBe(
|
|
804
|
+
'{"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"},{"id":3,"name":"Charlie"},{"id":4,"name":"David"},{"id":5,"name":"Eve"}]}'
|
|
805
|
+
);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it('should heal tool call structure with thinking content', () => {
|
|
809
|
+
const payload = [
|
|
810
|
+
{
|
|
811
|
+
role: 'assistant',
|
|
812
|
+
content: [
|
|
813
|
+
{
|
|
814
|
+
type: ContentTypes.THINK,
|
|
815
|
+
[ContentTypes.THINK]:
|
|
816
|
+
'I\'ll add this agreement as an observation to our existing troubleshooting task in the project memory system.',
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
type: ContentTypes.TOOL_CALL,
|
|
820
|
+
tool_call: {
|
|
821
|
+
id: 'tooluse_Zz-mw_wHTrWTvDHaCbfaZg',
|
|
822
|
+
name: 'add_observations_mcp_project-memory',
|
|
823
|
+
args: '{"observations":[{"entityName":"MCP_Tool_Error_Troubleshooting","contents":["Agreement established: Document all future tests in the project memory system to maintain a comprehensive troubleshooting log","This will provide a structured record of the entire troubleshooting process and help identify patterns in the error behavior"]}]}',
|
|
824
|
+
type: 'tool_call',
|
|
825
|
+
progress: 1,
|
|
826
|
+
output:
|
|
827
|
+
'[\n {\n "entityName": "MCP_Tool_Error_Troubleshooting",\n "addedObservations": [\n {\n "content": "Agreement established: Document all future tests in the project memory system to maintain a comprehensive troubleshooting log",\n "timestamp": "2025-03-26T00:46:42.154Z"\n },\n {\n "content": "This will provide a structured record of the entire troubleshooting process and help identify patterns in the error behavior",\n "timestamp": "2025-03-26T00:46:42.154Z"\n }\n ]\n }\n]',
|
|
828
|
+
},
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
type: ContentTypes.TEXT,
|
|
832
|
+
[ContentTypes.TEXT]:
|
|
833
|
+
'\n\nI\'ve successfully added our agreement to the project memory system. The observation has been recorded in the "MCP_Tool_Error_Troubleshooting" entity with the current timestamp.\n\nGoing forward, I will:\n\n1. Document each test we perform\n2. Record the methodology and results\n3. Update the project memory with our findings\n4. Establish appropriate relationships between tests and related components\n5. Provide a summary of what we\'ve learned from each test\n\nThis structured approach will help us build a comprehensive knowledge base of the error behavior and our troubleshooting process, which may prove valuable for resolving similar issues in the future or for other developers facing similar challenges.\n\nWhat test would you like to perform next in our troubleshooting process?',
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
},
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
const result = formatAgentMessages(payload);
|
|
840
|
+
|
|
841
|
+
// Should have 3 messages: an AIMessage with empty content, a ToolMessage, and a final AIMessage with the text
|
|
842
|
+
expect(result.messages).toHaveLength(3);
|
|
843
|
+
expect(result.messages[0]).toBeInstanceOf(AIMessage);
|
|
844
|
+
expect(result.messages[1]).toBeInstanceOf(ToolMessage);
|
|
845
|
+
expect(result.messages[2]).toBeInstanceOf(AIMessage);
|
|
846
|
+
|
|
847
|
+
// The first AIMessage should have an empty content and the tool_call
|
|
848
|
+
expect(result.messages[0].content).toBe('');
|
|
849
|
+
expect((result.messages[0] as AIMessage).tool_calls).toHaveLength(1);
|
|
850
|
+
expect((result.messages[0] as AIMessage).tool_calls?.[0].name).toBe(
|
|
851
|
+
'add_observations_mcp_project-memory'
|
|
852
|
+
);
|
|
853
|
+
|
|
854
|
+
// The ToolMessage should have the correct properties
|
|
855
|
+
expect((result.messages[1] as ToolMessage).tool_call_id).toBe(
|
|
856
|
+
'tooluse_Zz-mw_wHTrWTvDHaCbfaZg'
|
|
857
|
+
);
|
|
858
|
+
expect(result.messages[1].name).toBe('add_observations_mcp_project-memory');
|
|
859
|
+
expect(result.messages[1].content).toContain(
|
|
860
|
+
'MCP_Tool_Error_Troubleshooting'
|
|
861
|
+
);
|
|
862
|
+
|
|
863
|
+
// The final AIMessage should contain the text response
|
|
864
|
+
expect(typeof result.messages[2].content).toBe('string');
|
|
865
|
+
expect((result.messages[2].content as string).trim()).toContain(
|
|
866
|
+
'I\'ve successfully added our agreement to the project memory system'
|
|
867
|
+
);
|
|
868
|
+
});
|
|
869
|
+
|
|
590
870
|
it('should demonstrate how messages can be filtered out, reducing count', () => {
|
|
591
871
|
// Two input messages where one gets completely filtered out
|
|
592
872
|
const payload = [
|
|
@@ -594,16 +874,22 @@ describe('formatAgentMessages', () => {
|
|
|
594
874
|
{
|
|
595
875
|
role: 'assistant',
|
|
596
876
|
content: [
|
|
597
|
-
{
|
|
598
|
-
|
|
877
|
+
{
|
|
878
|
+
type: ContentTypes.THINK,
|
|
879
|
+
[ContentTypes.THINK]: 'Thinking about response...',
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
type: ContentTypes.ERROR,
|
|
883
|
+
[ContentTypes.ERROR]: 'Error in processing',
|
|
884
|
+
},
|
|
599
885
|
{ type: ContentTypes.AGENT_UPDATE, update: 'Working on it...' },
|
|
600
886
|
],
|
|
601
887
|
},
|
|
602
888
|
];
|
|
603
889
|
|
|
604
890
|
const indexTokenCountMap = {
|
|
605
|
-
0: 10,
|
|
606
|
-
1: 30,
|
|
891
|
+
0: 10, // 10 tokens for the user message
|
|
892
|
+
1: 30, // 30 tokens for the assistant message that will be filtered out
|
|
607
893
|
};
|
|
608
894
|
|
|
609
895
|
const result = formatAgentMessages(payload, indexTokenCountMap);
|
|
@@ -621,8 +907,10 @@ describe('formatAgentMessages', () => {
|
|
|
621
907
|
expect(result.indexTokenCountMap?.[0]).toBe(10);
|
|
622
908
|
|
|
623
909
|
// The total tokens should be just the user message tokens
|
|
624
|
-
const totalTokens = Object.values(result.indexTokenCountMap || {})
|
|
625
|
-
|
|
910
|
+
const totalTokens = Object.values(result.indexTokenCountMap || {}).reduce(
|
|
911
|
+
(sum, count) => sum + count,
|
|
912
|
+
0
|
|
913
|
+
);
|
|
626
914
|
|
|
627
915
|
expect(totalTokens).toBe(10);
|
|
628
916
|
});
|