@librechat/agents 3.1.34 → 3.1.36
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/llm/bedrock/index.cjs +115 -55
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +465 -0
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -0
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +154 -0
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -0
- package/dist/cjs/messages/core.cjs +3 -0
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +114 -54
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +460 -0
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -0
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +149 -0
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -0
- package/dist/esm/messages/core.mjs +3 -0
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/types/llm/bedrock/index.d.ts +29 -21
- package/package.json +1 -1
- package/src/llm/bedrock/index.ts +147 -65
- package/src/llm/bedrock/llm.spec.ts +86 -52
- package/src/llm/bedrock/utils/message_outputs.ts +6 -9
- package/src/messages/core.ts +5 -0
- package/src/scripts/bedrock-merge-test.ts +107 -0
|
@@ -212,47 +212,6 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
212
212
|
return model as any;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
test('should detect contentBlockIndex at top level', () => {
|
|
216
|
-
const model = getModelWithCleanMethods();
|
|
217
|
-
const objWithIndex = { contentBlockIndex: 0, text: 'hello' };
|
|
218
|
-
const objWithoutIndex = { text: 'hello' };
|
|
219
|
-
|
|
220
|
-
expect(model.hasContentBlockIndex(objWithIndex)).toBe(true);
|
|
221
|
-
expect(model.hasContentBlockIndex(objWithoutIndex)).toBe(false);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test('should detect contentBlockIndex in nested objects', () => {
|
|
225
|
-
const model = getModelWithCleanMethods();
|
|
226
|
-
const nestedWithIndex = {
|
|
227
|
-
outer: {
|
|
228
|
-
inner: {
|
|
229
|
-
contentBlockIndex: 1,
|
|
230
|
-
data: 'test',
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
const nestedWithoutIndex = {
|
|
235
|
-
outer: {
|
|
236
|
-
inner: {
|
|
237
|
-
data: 'test',
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
expect(model.hasContentBlockIndex(nestedWithIndex)).toBe(true);
|
|
243
|
-
expect(model.hasContentBlockIndex(nestedWithoutIndex)).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test('should return false for null, undefined, and primitives', () => {
|
|
247
|
-
const model = getModelWithCleanMethods();
|
|
248
|
-
|
|
249
|
-
expect(model.hasContentBlockIndex(null)).toBe(false);
|
|
250
|
-
expect(model.hasContentBlockIndex(undefined)).toBe(false);
|
|
251
|
-
expect(model.hasContentBlockIndex('string')).toBe(false);
|
|
252
|
-
expect(model.hasContentBlockIndex(123)).toBe(false);
|
|
253
|
-
expect(model.hasContentBlockIndex(true)).toBe(false);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
215
|
test('should remove contentBlockIndex from top level', () => {
|
|
257
216
|
const model = getModelWithCleanMethods();
|
|
258
217
|
const obj = {
|
|
@@ -315,7 +274,7 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
315
274
|
expect(model.removeContentBlockIndex(undefined)).toBeUndefined();
|
|
316
275
|
});
|
|
317
276
|
|
|
318
|
-
test('
|
|
277
|
+
test('enrichChunk should strip contentBlockIndex from response_metadata', () => {
|
|
319
278
|
const model = getModelWithCleanMethods();
|
|
320
279
|
|
|
321
280
|
const chunkWithIndex = new ChatGenerationChunk({
|
|
@@ -329,18 +288,18 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
329
288
|
}),
|
|
330
289
|
});
|
|
331
290
|
|
|
332
|
-
const
|
|
291
|
+
const enriched = model.enrichChunk(chunkWithIndex, new Set([0]));
|
|
333
292
|
|
|
334
|
-
expect(
|
|
293
|
+
expect(enriched.message.response_metadata).toEqual({
|
|
335
294
|
stopReason: null,
|
|
336
295
|
});
|
|
337
296
|
expect(
|
|
338
|
-
(
|
|
297
|
+
(enriched.message.response_metadata as any).contentBlockIndex
|
|
339
298
|
).toBeUndefined();
|
|
340
|
-
expect(
|
|
299
|
+
expect(enriched.text).toBe('Hello');
|
|
341
300
|
});
|
|
342
301
|
|
|
343
|
-
test('
|
|
302
|
+
test('enrichChunk should pass through chunks without contentBlockIndex unchanged', () => {
|
|
344
303
|
const model = getModelWithCleanMethods();
|
|
345
304
|
|
|
346
305
|
const chunkWithoutIndex = new ChatGenerationChunk({
|
|
@@ -354,15 +313,89 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
354
313
|
}),
|
|
355
314
|
});
|
|
356
315
|
|
|
357
|
-
const
|
|
316
|
+
const enriched = model.enrichChunk(chunkWithoutIndex, new Set());
|
|
358
317
|
|
|
359
|
-
expect(
|
|
318
|
+
expect(enriched.message.response_metadata).toEqual({
|
|
360
319
|
stopReason: 'end_turn',
|
|
361
320
|
usage: { inputTokens: 10, outputTokens: 5 },
|
|
362
321
|
});
|
|
363
322
|
});
|
|
364
323
|
|
|
365
|
-
test('
|
|
324
|
+
test('enrichChunk should inject index on array content blocks', () => {
|
|
325
|
+
const model = getModelWithCleanMethods();
|
|
326
|
+
|
|
327
|
+
const chunkWithArrayContent = new ChatGenerationChunk({
|
|
328
|
+
text: '',
|
|
329
|
+
message: new AIMessageChunk({
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: 'reasoning_content',
|
|
333
|
+
reasoningText: { text: 'thinking...' },
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
response_metadata: {
|
|
337
|
+
contentBlockIndex: 0,
|
|
338
|
+
},
|
|
339
|
+
}),
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const enriched = model.enrichChunk(chunkWithArrayContent, new Set([0]));
|
|
343
|
+
|
|
344
|
+
expect(Array.isArray(enriched.message.content)).toBe(true);
|
|
345
|
+
const blocks = enriched.message.content as any[];
|
|
346
|
+
expect(blocks[0].index).toBe(0);
|
|
347
|
+
expect(blocks[0].type).toBe('reasoning_content');
|
|
348
|
+
expect(
|
|
349
|
+
(enriched.message.response_metadata as any).contentBlockIndex
|
|
350
|
+
).toBeUndefined();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('enrichChunk should promote text to array when multiple block indices seen', () => {
|
|
354
|
+
const model = getModelWithCleanMethods();
|
|
355
|
+
|
|
356
|
+
const textChunk = new ChatGenerationChunk({
|
|
357
|
+
text: 'Hello world',
|
|
358
|
+
message: new AIMessageChunk({
|
|
359
|
+
content: 'Hello world',
|
|
360
|
+
response_metadata: {
|
|
361
|
+
contentBlockIndex: 1,
|
|
362
|
+
},
|
|
363
|
+
}),
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const enriched = model.enrichChunk(textChunk, new Set([0, 1]));
|
|
367
|
+
|
|
368
|
+
expect(Array.isArray(enriched.message.content)).toBe(true);
|
|
369
|
+
const blocks = enriched.message.content as any[];
|
|
370
|
+
expect(blocks).toHaveLength(1);
|
|
371
|
+
expect(blocks[0]).toEqual({
|
|
372
|
+
type: 'text',
|
|
373
|
+
text: 'Hello world',
|
|
374
|
+
index: 1,
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test('enrichChunk should keep text as string when only one block index seen', () => {
|
|
379
|
+
const model = getModelWithCleanMethods();
|
|
380
|
+
|
|
381
|
+
const textChunk = new ChatGenerationChunk({
|
|
382
|
+
text: 'Hello',
|
|
383
|
+
message: new AIMessageChunk({
|
|
384
|
+
content: 'Hello',
|
|
385
|
+
response_metadata: {
|
|
386
|
+
contentBlockIndex: 0,
|
|
387
|
+
stopReason: null,
|
|
388
|
+
},
|
|
389
|
+
}),
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const enriched = model.enrichChunk(textChunk, new Set([0]));
|
|
393
|
+
|
|
394
|
+
expect(typeof enriched.message.content).toBe('string');
|
|
395
|
+
expect(enriched.message.content).toBe('Hello');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('enrichChunk should strip deeply nested contentBlockIndex from response_metadata', () => {
|
|
366
399
|
const model = getModelWithCleanMethods();
|
|
367
400
|
|
|
368
401
|
const chunkWithNestedIndex = new ChatGenerationChunk({
|
|
@@ -370,6 +403,7 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
370
403
|
message: new AIMessageChunk({
|
|
371
404
|
content: 'Test',
|
|
372
405
|
response_metadata: {
|
|
406
|
+
contentBlockIndex: 0,
|
|
373
407
|
amazon: {
|
|
374
408
|
bedrock: {
|
|
375
409
|
contentBlockIndex: 0,
|
|
@@ -381,9 +415,9 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
381
415
|
}),
|
|
382
416
|
});
|
|
383
417
|
|
|
384
|
-
const
|
|
418
|
+
const enriched = model.enrichChunk(chunkWithNestedIndex, new Set([0]));
|
|
385
419
|
|
|
386
|
-
expect(
|
|
420
|
+
expect(enriched.message.response_metadata).toEqual({
|
|
387
421
|
amazon: {
|
|
388
422
|
bedrock: {
|
|
389
423
|
trace: { something: 'value' },
|
|
@@ -220,15 +220,12 @@ export function handleConverseStreamContentBlockDelta(
|
|
|
220
220
|
bedrockReasoningDeltaToLangchainPartialReasoningBlock(
|
|
221
221
|
contentBlockDelta.delta.reasoningContent
|
|
222
222
|
);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
? reasoningBlock.redactedContent
|
|
230
|
-
: ''))
|
|
231
|
-
: '';
|
|
223
|
+
let reasoningText = '';
|
|
224
|
+
if ('reasoningText' in reasoningBlock) {
|
|
225
|
+
reasoningText = reasoningBlock.reasoningText.text ?? '';
|
|
226
|
+
} else if ('redactedContent' in reasoningBlock) {
|
|
227
|
+
reasoningText = reasoningBlock.redactedContent;
|
|
228
|
+
}
|
|
232
229
|
return new ChatGenerationChunk({
|
|
233
230
|
text: '',
|
|
234
231
|
message: new AIMessageChunk({
|
package/src/messages/core.ts
CHANGED
|
@@ -159,6 +159,11 @@ export function modifyDeltaProperties(
|
|
|
159
159
|
(obj as Partial<AIMessageChunk>).lc_kwargs &&
|
|
160
160
|
Array.isArray(obj.lc_kwargs.content)
|
|
161
161
|
) {
|
|
162
|
+
if (provider === Providers.BEDROCK) {
|
|
163
|
+
obj.lc_kwargs.content = reduceBlocks(
|
|
164
|
+
obj.lc_kwargs.content as ContentBlock[]
|
|
165
|
+
);
|
|
166
|
+
}
|
|
162
167
|
obj.lc_kwargs.content = modifyContent({
|
|
163
168
|
provider,
|
|
164
169
|
messageType,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
config();
|
|
3
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
4
|
+
import type { AIMessageChunk } from '@langchain/core/messages';
|
|
5
|
+
import { concat } from '@langchain/core/utils/stream';
|
|
6
|
+
import { CustomChatBedrockConverse } from '@/llm/bedrock';
|
|
7
|
+
import { modifyDeltaProperties } from '@/messages/core';
|
|
8
|
+
import { Providers } from '@/common';
|
|
9
|
+
|
|
10
|
+
async function testBedrockMerge(): Promise<void> {
|
|
11
|
+
const model = new CustomChatBedrockConverse({
|
|
12
|
+
model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
|
|
13
|
+
region: process.env.BEDROCK_AWS_REGION,
|
|
14
|
+
credentials: {
|
|
15
|
+
accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID!,
|
|
16
|
+
secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY!,
|
|
17
|
+
},
|
|
18
|
+
maxTokens: 4000,
|
|
19
|
+
streaming: true,
|
|
20
|
+
streamUsage: true,
|
|
21
|
+
additionalModelRequestFields: {
|
|
22
|
+
thinking: { type: 'enabled', budget_tokens: 2000 },
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const messages = [new HumanMessage('What is 25 * 37? Think step by step.')];
|
|
27
|
+
|
|
28
|
+
console.log('Streaming from Bedrock with thinking enabled...\n');
|
|
29
|
+
|
|
30
|
+
const stream = await model.stream(messages);
|
|
31
|
+
let finalChunk: AIMessageChunk | undefined;
|
|
32
|
+
let chunkCount = 0;
|
|
33
|
+
let firstTextLogged = false;
|
|
34
|
+
|
|
35
|
+
for await (const chunk of stream) {
|
|
36
|
+
chunkCount++;
|
|
37
|
+
const isArr = Array.isArray(chunk.content);
|
|
38
|
+
const isStr = typeof chunk.content === 'string';
|
|
39
|
+
const isTextStr = isStr && (chunk.content as string).length > 0;
|
|
40
|
+
|
|
41
|
+
if (!firstTextLogged && isTextStr) {
|
|
42
|
+
console.log(
|
|
43
|
+
`chunk ${chunkCount} (first text): contentType=string, value="${chunk.content}"`
|
|
44
|
+
);
|
|
45
|
+
console.log(
|
|
46
|
+
' response_metadata:',
|
|
47
|
+
JSON.stringify(chunk.response_metadata)
|
|
48
|
+
);
|
|
49
|
+
firstTextLogged = true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (isArr) {
|
|
53
|
+
const blocks = chunk.content as Array<Record<string, unknown>>;
|
|
54
|
+
const info = blocks.map((b) => ({
|
|
55
|
+
type: b.type,
|
|
56
|
+
hasIndex: 'index' in b,
|
|
57
|
+
index: b.index,
|
|
58
|
+
}));
|
|
59
|
+
console.log(`chunk ${chunkCount}: array content, blocks:`, info);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
finalChunk = finalChunk ? concat(finalChunk, chunk) : chunk;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`Total chunks received: ${chunkCount}\n`);
|
|
66
|
+
|
|
67
|
+
console.log('=== RAW concat result (before modifyDeltaProperties) ===');
|
|
68
|
+
console.log('content type:', typeof finalChunk!.content);
|
|
69
|
+
if (Array.isArray(finalChunk!.content)) {
|
|
70
|
+
console.log('content array length:', finalChunk!.content.length);
|
|
71
|
+
const types = finalChunk!.content.map((b) =>
|
|
72
|
+
typeof b === 'object' && 'type' in b ? b.type : typeof b
|
|
73
|
+
);
|
|
74
|
+
const typeCounts = types.reduce(
|
|
75
|
+
(acc, t) => {
|
|
76
|
+
acc[t ?? ''] = (acc[t ?? ''] || 0) + 1;
|
|
77
|
+
return acc;
|
|
78
|
+
},
|
|
79
|
+
{} as Record<string, number>
|
|
80
|
+
);
|
|
81
|
+
console.log('content block type counts:', typeCounts);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log('\ncontent:');
|
|
85
|
+
console.dir(finalChunk!.content, { depth: null });
|
|
86
|
+
|
|
87
|
+
console.log('\n=== lc_kwargs.content ===');
|
|
88
|
+
if (Array.isArray(finalChunk!.lc_kwargs.content)) {
|
|
89
|
+
console.log(
|
|
90
|
+
'lc_kwargs.content length:',
|
|
91
|
+
finalChunk!.lc_kwargs.content.length
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
console.dir(finalChunk!.lc_kwargs.content, { depth: null });
|
|
95
|
+
|
|
96
|
+
const modified = modifyDeltaProperties(Providers.BEDROCK, finalChunk);
|
|
97
|
+
console.log('\n=== After modifyDeltaProperties ===');
|
|
98
|
+
console.log('content:');
|
|
99
|
+
console.dir(modified!.content, { depth: null });
|
|
100
|
+
console.log('\nlc_kwargs.content:');
|
|
101
|
+
console.dir(modified!.lc_kwargs.content, { depth: null });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
testBedrockMerge().catch((err) => {
|
|
105
|
+
console.error(err);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|