@librechat/agents 3.0.79 → 3.0.80

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.
@@ -38,13 +38,13 @@ export function bedrockReasoningDeltaToLangchainPartialReasoningBlock(
38
38
  reasoningText: { text },
39
39
  };
40
40
  }
41
- if (signature) {
41
+ if (signature != null) {
42
42
  return {
43
43
  type: 'reasoning_content',
44
44
  reasoningText: { signature },
45
45
  };
46
46
  }
47
- if (redactedContent) {
47
+ if (redactedContent != null) {
48
48
  return {
49
49
  type: 'reasoning_content',
50
50
  redactedContent: Buffer.from(redactedContent).toString('base64'),
@@ -65,13 +65,13 @@ export function bedrockReasoningBlockToLangchainReasoningBlock(
65
65
  redactedContent?: Uint8Array;
66
66
  };
67
67
 
68
- if (reasoningText) {
68
+ if (reasoningText != null) {
69
69
  return {
70
70
  type: 'reasoning_content',
71
71
  reasoningText: reasoningText,
72
72
  };
73
73
  }
74
- if (redactedContent) {
74
+ if (redactedContent != null) {
75
75
  return {
76
76
  type: 'reasoning_content',
77
77
  redactedContent: Buffer.from(redactedContent).toString('base64'),
@@ -87,7 +87,7 @@ export function convertConverseMessageToLangChainMessage(
87
87
  message: BedrockMessage,
88
88
  responseMetadata: Omit<ConverseResponse, 'output'>
89
89
  ): AIMessage {
90
- if (!message.content) {
90
+ if (message.content == null) {
91
91
  throw new Error('No message content found in response.');
92
92
  }
93
93
  if (message.role !== 'assistant') {
@@ -99,7 +99,7 @@ export function convertConverseMessageToLangChainMessage(
99
99
  let requestId: string | undefined;
100
100
  if (
101
101
  '$metadata' in responseMetadata &&
102
- responseMetadata.$metadata &&
102
+ responseMetadata.$metadata != null &&
103
103
  typeof responseMetadata.$metadata === 'object' &&
104
104
  'requestId' in responseMetadata.$metadata
105
105
  ) {
@@ -109,7 +109,7 @@ export function convertConverseMessageToLangChainMessage(
109
109
  let tokenUsage:
110
110
  | { input_tokens: number; output_tokens: number; total_tokens: number }
111
111
  | undefined;
112
- if (responseMetadata.usage) {
112
+ if (responseMetadata.usage != null) {
113
113
  const input_tokens = responseMetadata.usage.inputTokens ?? 0;
114
114
  const output_tokens = responseMetadata.usage.outputTokens ?? 0;
115
115
  tokenUsage = {
@@ -144,9 +144,10 @@ export function convertConverseMessageToLangChainMessage(
144
144
  message.content.forEach((c) => {
145
145
  if (
146
146
  'toolUse' in c &&
147
- c.toolUse &&
148
- c.toolUse.name &&
149
- c.toolUse.input &&
147
+ c.toolUse != null &&
148
+ c.toolUse.name != null &&
149
+ c.toolUse.name !== '' &&
150
+ c.toolUse.input != null &&
150
151
  typeof c.toolUse.input === 'object'
151
152
  ) {
152
153
  toolCalls.push({
@@ -157,7 +158,7 @@ export function convertConverseMessageToLangChainMessage(
157
158
  });
158
159
  } else if ('text' in c && typeof c.text === 'string') {
159
160
  content.push({ type: 'text', text: c.text });
160
- } else if ('reasoningContent' in c && c.reasoningContent) {
161
+ } else if ('reasoningContent' in c && c.reasoningContent != null) {
161
162
  content.push(
162
163
  bedrockReasoningBlockToLangchainReasoningBlock(c.reasoningContent)
163
164
  );
@@ -182,7 +183,7 @@ export function convertConverseMessageToLangChainMessage(
182
183
  export function handleConverseStreamContentBlockDelta(
183
184
  contentBlockDelta: ContentBlockDeltaEvent
184
185
  ): ChatGenerationChunk {
185
- if (!contentBlockDelta.delta) {
186
+ if (contentBlockDelta.delta == null) {
186
187
  throw new Error('No delta found in content block.');
187
188
  }
188
189
 
@@ -196,7 +197,7 @@ export function handleConverseStreamContentBlockDelta(
196
197
  },
197
198
  }),
198
199
  });
199
- } else if (contentBlockDelta.delta.toolUse) {
200
+ } else if (contentBlockDelta.delta.toolUse != null) {
200
201
  const index = contentBlockDelta.contentBlockIndex;
201
202
  return new ChatGenerationChunk({
202
203
  text: '',
@@ -214,15 +215,28 @@ export function handleConverseStreamContentBlockDelta(
214
215
  },
215
216
  }),
216
217
  });
217
- } else if (contentBlockDelta.delta.reasoningContent) {
218
+ } else if (contentBlockDelta.delta.reasoningContent != null) {
219
+ const reasoningBlock =
220
+ bedrockReasoningDeltaToLangchainPartialReasoningBlock(
221
+ contentBlockDelta.delta.reasoningContent
222
+ );
223
+ // Extract the text for additional_kwargs.reasoning_content (for stream handler compatibility)
224
+ const reasoningText =
225
+ 'reasoningText' in reasoningBlock
226
+ ? (reasoningBlock.reasoningText.text ??
227
+ reasoningBlock.reasoningText.signature ??
228
+ ('redactedContent' in reasoningBlock
229
+ ? reasoningBlock.redactedContent
230
+ : ''))
231
+ : '';
218
232
  return new ChatGenerationChunk({
219
233
  text: '',
220
234
  message: new AIMessageChunk({
221
- content: [
222
- bedrockReasoningDeltaToLangchainPartialReasoningBlock(
223
- contentBlockDelta.delta.reasoningContent
224
- ),
225
- ],
235
+ content: [reasoningBlock],
236
+ additional_kwargs: {
237
+ // Set reasoning_content for stream handler to detect reasoning mode
238
+ reasoning_content: reasoningText,
239
+ },
226
240
  response_metadata: {
227
241
  contentBlockIndex: contentBlockDelta.contentBlockIndex,
228
242
  },
@@ -243,7 +257,7 @@ export function handleConverseStreamContentBlockStart(
243
257
  ): ChatGenerationChunk | null {
244
258
  const index = contentBlockStart.contentBlockIndex;
245
259
 
246
- if (contentBlockStart.start?.toolUse) {
260
+ if (contentBlockStart.start?.toolUse != null) {
247
261
  return new ChatGenerationChunk({
248
262
  text: '',
249
263
  message: new AIMessageChunk({
@@ -0,0 +1,159 @@
1
+ // src/scripts/thinking-bedrock.ts
2
+ import { config } from 'dotenv';
3
+ config();
4
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
5
+ import type { UsageMetadata } from '@langchain/core/messages';
6
+ import * as t from '@/types';
7
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
8
+ import { createCodeExecutionTool } from '@/tools/CodeExecutor';
9
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
10
+ import { GraphEvents, Providers } from '@/common';
11
+ import { getLLMConfig } from '@/utils/llmConfig';
12
+ import { getArgs } from '@/scripts/args';
13
+ import { Run } from '@/run';
14
+
15
+ const conversationHistory: BaseMessage[] = [];
16
+ let _contentParts: t.MessageContentComplex[] = [];
17
+ const collectedUsage: UsageMetadata[] = [];
18
+
19
+ async function testBedrockThinking(): Promise<void> {
20
+ const { userName } = await getArgs();
21
+ const instructions = `You are a helpful AI assistant for ${userName}. When answering questions, be thorough in your reasoning.`;
22
+ const { contentParts, aggregateContent } = createContentAggregator();
23
+ _contentParts = contentParts as t.MessageContentComplex[];
24
+
25
+ // Set up event handlers
26
+ const customHandlers = {
27
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
28
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
29
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
30
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
31
+ handle: (
32
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
33
+ data: t.StreamEventData
34
+ ): void => {
35
+ console.log('====== ON_RUN_STEP_COMPLETED ======');
36
+ aggregateContent({
37
+ event,
38
+ data: data as unknown as { result: t.ToolEndEvent },
39
+ });
40
+ },
41
+ },
42
+ [GraphEvents.ON_RUN_STEP]: {
43
+ handle: (event: GraphEvents.ON_RUN_STEP, data: t.RunStep) => {
44
+ aggregateContent({ event, data });
45
+ },
46
+ },
47
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
48
+ handle: (
49
+ event: GraphEvents.ON_RUN_STEP_DELTA,
50
+ data: t.RunStepDeltaEvent
51
+ ) => {
52
+ aggregateContent({ event, data });
53
+ },
54
+ },
55
+ [GraphEvents.ON_MESSAGE_DELTA]: {
56
+ handle: (
57
+ event: GraphEvents.ON_MESSAGE_DELTA,
58
+ data: t.MessageDeltaEvent
59
+ ) => {
60
+ aggregateContent({ event, data });
61
+ },
62
+ },
63
+ [GraphEvents.ON_REASONING_DELTA]: {
64
+ handle: (
65
+ event: GraphEvents.ON_REASONING_DELTA,
66
+ data: t.ReasoningDeltaEvent
67
+ ) => {
68
+ aggregateContent({ event, data });
69
+ },
70
+ },
71
+ };
72
+
73
+ const baseLlmConfig = getLLMConfig(Providers.BEDROCK);
74
+
75
+ // Enable thinking with token budget for Bedrock
76
+ const llmConfig = {
77
+ ...baseLlmConfig,
78
+ model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
79
+ maxTokens: 5000,
80
+ additionalModelRequestFields: {
81
+ thinking: { type: 'enabled', budget_tokens: 2000 },
82
+ },
83
+ };
84
+
85
+ const run = await Run.create<t.IState>({
86
+ runId: 'test-bedrock-thinking-id',
87
+ graphConfig: {
88
+ instructions,
89
+ type: 'standard',
90
+ tools: [createCodeExecutionTool()],
91
+ llmConfig,
92
+ },
93
+ returnContent: true,
94
+ customHandlers: customHandlers as t.RunConfig['customHandlers'],
95
+ });
96
+
97
+ const config = {
98
+ configurable: {
99
+ thread_id: 'bedrock-thinking-test-thread',
100
+ },
101
+ streamMode: 'values',
102
+ version: 'v2' as const,
103
+ };
104
+
105
+ // Test 1: Regular thinking mode
106
+ console.log('\n\nTest 1: Bedrock Regular thinking mode');
107
+ const userMessage1 = `Please print 'hello world' in python`;
108
+ conversationHistory.push(new HumanMessage(userMessage1));
109
+
110
+ console.log('Running first query with Bedrock thinking enabled...');
111
+ const firstInputs = { messages: [...conversationHistory] };
112
+ await run.processStream(firstInputs, config);
113
+
114
+ // Extract and display thinking blocks
115
+ const finalMessages = run.getRunMessages();
116
+ console.log('\n\nFinal messages after Test 1:');
117
+ console.dir(finalMessages, { depth: null });
118
+
119
+ // Test 2: Try multi-turn conversation
120
+ console.log(
121
+ '\n\nTest 2: Multi-turn conversation with Bedrock thinking enabled'
122
+ );
123
+ const userMessage2 = `Given your previous analysis, what would be the most significant technical challenges in making this transition?`;
124
+ conversationHistory.push(new HumanMessage(userMessage2));
125
+
126
+ console.log('Running second query with Bedrock thinking enabled...');
127
+ const secondInputs = { messages: [...conversationHistory] };
128
+ await run.processStream(secondInputs, config);
129
+
130
+ // Display thinking blocks for second response
131
+ const finalMessages2 = run.getRunMessages();
132
+ console.log('\n\nBedrock thinking feature test completed!');
133
+ console.dir(finalMessages2, { depth: null });
134
+
135
+ console.log('\n\nContent parts:');
136
+ console.dir(_contentParts, { depth: null });
137
+ }
138
+
139
+ process.on('unhandledRejection', (reason, promise) => {
140
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
141
+ console.log('Conversation history:');
142
+ console.dir(conversationHistory, { depth: null });
143
+ console.log('Content parts:');
144
+ console.dir(_contentParts, { depth: null });
145
+ process.exit(1);
146
+ });
147
+
148
+ process.on('uncaughtException', (err) => {
149
+ console.error('Uncaught Exception:', err);
150
+ });
151
+
152
+ testBedrockThinking().catch((err) => {
153
+ console.error(err);
154
+ console.log('Conversation history:');
155
+ console.dir(conversationHistory, { depth: null });
156
+ console.log('Content parts:');
157
+ console.dir(_contentParts, { depth: null });
158
+ process.exit(1);
159
+ });