34
34
ResponseFunction ,
35
35
TextContent ,
36
36
ToolCall ,
37
+ ToolContent ,
37
38
ToolMessage ,
38
39
Usage ,
39
40
UserMessage ,
48
49
49
50
logger = logging .getLogger (__name__ )
50
51
51
- config = Config (connect_timeout = 60 , read_timeout = 120 , retries = {"max_attempts" : 1 })
52
+ config = Config (
53
+ connect_timeout = 60 , # Connection timeout: 60 seconds
54
+ read_timeout = 900 , # Read timeout: 15 minutes (suitable for long streaming responses)
55
+ retries = {
56
+ 'max_attempts' : 8 , # Maximum retry attempts
57
+ 'mode' : 'adaptive' # Adaptive retry mode
58
+ },
59
+ max_pool_connections = 50 # Maximum connection pool size
60
+ )
52
61
53
62
bedrock_runtime = boto3 .client (
54
63
service_name = "bedrock-runtime" ,
@@ -177,6 +186,7 @@ def validate(self, chat_request: ChatRequest):
177
186
# check if model is supported
178
187
if chat_request .model not in bedrock_model_list .keys ():
179
188
error = f"Unsupported model { chat_request .model } , please use models API to get a list of supported models"
189
+ logger .error ("Unsupported model: %s" , chat_request .model )
180
190
181
191
if error :
182
192
raise HTTPException (
@@ -204,13 +214,13 @@ async def _invoke_bedrock(self, chat_request: ChatRequest, stream=False):
204
214
# Run the blocking boto3 call in a thread pool
205
215
response = await run_in_threadpool (bedrock_runtime .converse , ** args )
206
216
except bedrock_runtime .exceptions .ValidationException as e :
207
- logger .error ("Validation Error: " + str (e ))
217
+ logger .error ("Bedrock validation error for model %s: %s" , chat_request . model , str (e ))
208
218
raise HTTPException (status_code = 400 , detail = str (e ))
209
219
except bedrock_runtime .exceptions .ThrottlingException as e :
210
- logger .error ( "Throttling Error: " + str (e ))
220
+ logger .warning ( "Bedrock throttling for model %s: %s" , chat_request . model , str (e ))
211
221
raise HTTPException (status_code = 429 , detail = str (e ))
212
222
except Exception as e :
213
- logger .error (e )
223
+ logger .error ("Bedrock invocation failed for model %s: %s" , chat_request . model , str ( e ) )
214
224
raise HTTPException (status_code = 500 , detail = str (e ))
215
225
return response
216
226
@@ -270,6 +280,7 @@ async def chat_stream(self, chat_request: ChatRequest) -> AsyncIterable[bytes]:
270
280
# return an [DONE] message at the end.
271
281
yield self .stream_response_to_bytes ()
272
282
except Exception as e :
283
+ logger .error ("Stream error for model %s: %s" , chat_request .model , str (e ))
273
284
error_event = Error (error = ErrorMessage (message = str (e )))
274
285
yield self .stream_response_to_bytes (error_event )
275
286
@@ -317,7 +328,16 @@ def _parse_messages(self, chat_request: ChatRequest) -> list[dict]:
317
328
}
318
329
)
319
330
elif isinstance (message , AssistantMessage ):
320
- if message .content .strip ():
331
+ # Check if message has content that's not empty
332
+ has_content = False
333
+ if isinstance (message .content , str ):
334
+ has_content = message .content .strip () != ""
335
+ elif isinstance (message .content , list ):
336
+ has_content = len (message .content ) > 0
337
+ elif message .content is not None :
338
+ has_content = True
339
+
340
+ if has_content :
321
341
# Text message
322
342
messages .append (
323
343
{
@@ -349,14 +369,18 @@ def _parse_messages(self, chat_request: ChatRequest) -> list[dict]:
349
369
# Bedrock does not support tool role,
350
370
# Add toolResult to content
351
371
# https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ToolResultBlock.html
372
+
373
+ # Handle different content formats from OpenAI SDK
374
+ tool_content = self ._extract_tool_content (message .content )
375
+
352
376
messages .append (
353
377
{
354
378
"role" : "user" ,
355
379
"content" : [
356
380
{
357
381
"toolResult" : {
358
382
"toolUseId" : message .tool_call_id ,
359
- "content" : [{"text" : message . content }],
383
+ "content" : [{"text" : tool_content }],
360
384
}
361
385
}
362
386
],
@@ -368,6 +392,57 @@ def _parse_messages(self, chat_request: ChatRequest) -> list[dict]:
368
392
continue
369
393
return self ._reframe_multi_payloard (messages )
370
394
395
+ def _extract_tool_content (self , content ) -> str :
396
+ """Extract text content from various OpenAI SDK tool message formats.
397
+
398
+ Handles:
399
+ - String content (legacy format)
400
+ - List of content objects (OpenAI SDK 1.91.0+)
401
+ - Nested JSON structures within text content
402
+ """
403
+ try :
404
+ if isinstance (content , str ):
405
+ return content
406
+
407
+ if isinstance (content , list ):
408
+ text_parts = []
409
+ for i , item in enumerate (content ):
410
+ if isinstance (item , dict ):
411
+ # Handle dict with 'text' field
412
+ if "text" in item :
413
+ item_text = item ["text" ]
414
+ if isinstance (item_text , str ):
415
+ # Try to parse as JSON if it looks like JSON
416
+ if item_text .strip ().startswith ('{' ) and item_text .strip ().endswith ('}' ):
417
+ try :
418
+ parsed_json = json .loads (item_text )
419
+ # Convert JSON object to readable text
420
+ text_parts .append (json .dumps (parsed_json , indent = 2 ))
421
+ except json .JSONDecodeError :
422
+ # Silently fallback to original text
423
+ text_parts .append (item_text )
424
+ else :
425
+ text_parts .append (item_text )
426
+ else :
427
+ text_parts .append (str (item_text ))
428
+ else :
429
+ # Handle other dict formats - convert to JSON string
430
+ text_parts .append (json .dumps (item , indent = 2 ))
431
+ elif hasattr (item , 'text' ):
432
+ # Handle ToolContent objects
433
+ text_parts .append (item .text )
434
+ else :
435
+ # Convert any other type to string
436
+ text_parts .append (str (item ))
437
+ return "\n " .join (text_parts )
438
+
439
+ # Fallback for any other type
440
+ return str (content )
441
+ except Exception as e :
442
+ logger .warning ("Tool content extraction failed: %s" , str (e ))
443
+ # Return a safe fallback
444
+ return str (content ) if content is not None else ""
445
+
371
446
def _reframe_multi_payloard (self , messages : list ) -> list :
372
447
"""Receive messages and reformat them to comply with the Claude format
373
448
0 commit comments