@@ -367,13 +367,16 @@ def _map_inference_config(
367
367
async def _map_messages (
368
368
self , messages : list [ModelMessage ]
369
369
) -> tuple [list [SystemContentBlockTypeDef ], list [MessageUnionTypeDef ]]:
370
- """Just maps a `pydantic_ai.Message` to the Bedrock `MessageUnionTypeDef`."""
370
+ """Maps a `pydantic_ai.Message` to the Bedrock `MessageUnionTypeDef`.
371
+
372
+ Groups consecutive ToolReturnPart objects into a single user message as required by Bedrock Claude/Nova models.
373
+ """
371
374
system_prompt : list [SystemContentBlockTypeDef ] = []
372
375
bedrock_messages : list [MessageUnionTypeDef ] = []
373
376
document_count : Iterator [int ] = count (1 )
374
- for m in messages :
375
- if isinstance (m , ModelRequest ):
376
- for part in m .parts :
377
+ for message in messages :
378
+ if isinstance (message , ModelRequest ):
379
+ for part in message .parts :
377
380
if isinstance (part , SystemPromptPart ):
378
381
system_prompt .append ({'text' : part .content })
379
382
elif isinstance (part , UserPromptPart ):
@@ -414,22 +417,41 @@ async def _map_messages(
414
417
],
415
418
}
416
419
)
417
- elif isinstance (m , ModelResponse ):
420
+ elif isinstance (message , ModelResponse ):
418
421
content : list [ContentBlockOutputTypeDef ] = []
419
- for item in m .parts :
422
+ for item in message .parts :
420
423
if isinstance (item , TextPart ):
421
424
content .append ({'text' : item .content })
422
425
else :
423
426
assert isinstance (item , ToolCallPart )
424
427
content .append (self ._map_tool_call (item ))
425
428
bedrock_messages .append ({'role' : 'assistant' , 'content' : content })
426
429
else :
427
- assert_never (m )
430
+ assert_never (message )
431
+
432
+ # Merge together sequential user messages.
433
+ processed_messages : list [MessageUnionTypeDef ] = []
434
+ last_message : dict [str , Any ] | None = None
435
+ for current_message in bedrock_messages :
436
+ if (
437
+ last_message is not None
438
+ and current_message ['role' ] == last_message ['role' ]
439
+ and current_message ['role' ] == 'user'
440
+ ):
441
+ # Add the new user content onto the existing user message.
442
+ last_content = list (last_message ['content' ])
443
+ last_content .extend (current_message ['content' ])
444
+ last_message ['content' ] = last_content
445
+ continue
446
+
447
+ # Add the entire message to the list of messages.
448
+ processed_messages .append (current_message )
449
+ last_message = cast (dict [str , Any ], current_message )
428
450
429
451
if instructions := self ._get_instructions (messages ):
430
452
system_prompt .insert (0 , {'text' : instructions })
431
453
432
- return system_prompt , bedrock_messages
454
+ return system_prompt , processed_messages
433
455
434
456
@staticmethod
435
457
async def _map_user_prompt (part : UserPromptPart , document_count : Iterator [int ]) -> list [MessageUnionTypeDef ]:
0 commit comments