Python: Fix Bedrock parallel tool-call results rejected by Converse#14074
Open
kimnamu wants to merge 2 commits into
Open
Python: Fix Bedrock parallel tool-call results rejected by Converse#14074kimnamu wants to merge 2 commits into
kimnamu wants to merge 2 commits into
Conversation
When a model requests multiple tools in a single turn, the function-calling
loop appends one ChatMessageContent per tool call and one per tool result.
The Bedrock connector mapped each of these 1:1 to a Converse message, so N
parallel tool results became N separate user messages each with a single
toolResult block. Converse rejects this with:
Expected toolResult blocks at messages.X.content for the following
Ids: ... but found: ...
Merge consecutive messages that map to the same Bedrock role in
_prepare_chat_history_for_request, so all toolUse blocks for an assistant
turn land in one assistant message and all toolResult blocks land in one
user message. This mirrors the existing behavior of the sibling Anthropic
connector, which already groups parallel tool messages.
Python analogue of microsoft#13647 (reported for .NET).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds Bedrock Converse compatibility by coalescing consecutive same-role messages (notably parallel tool calls/results) into single messages to avoid Bedrock request validation errors.
Changes:
- Update Bedrock chat history preparation to merge consecutive messages that map to the same Bedrock role.
- Add a unit test verifying parallel tool call + tool result messages are merged into the required Bedrock structure.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py | Merge consecutive same-role Bedrock messages by concatenating content blocks. |
| python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py | Add test coverage for merging parallel tool calls/results into single assistant/user messages. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+178
to
+186
| formatted_message = MESSAGE_CONVERTERS[message.role](message) | ||
| if messages and messages[-1][role_key] == formatted_message[role_key]: | ||
| # The Bedrock Converse API requires that consecutive messages with the same role be | ||
| # combined into a single message. In particular, SK emits one tool message per parallel | ||
| # tool result (all mapped to the "user" role), which Bedrock rejects unless every | ||
| # toolResult block for an assistant turn is grouped in a single user message. | ||
| messages[-1][content_key] += formatted_message[content_key] | ||
| else: | ||
| messages.append(formatted_message) |
Comment on lines
+155
to
+163
| @patch.object(boto3, "client", return_value=Mock()) | ||
| def test_prepare_chat_history_for_request_merges_parallel_tool_results(mock_client, bedrock_unit_test_env) -> None: | ||
| """Test that parallel tool calls and their results are merged into single Bedrock messages. | ||
|
|
||
| When a model requests multiple tools in one turn, SK emits one assistant message per tool call | ||
| and one tool message per tool result. The Bedrock Converse API requires every toolUse block for a | ||
| turn to be in a single assistant message and every toolResult block to be in a single user message, | ||
| otherwise the request is rejected with an "Expected toolResult blocks ..." error. | ||
| """ |
- Build a new combined content list instead of in-place += (clearer type intent, no mutation of the previous message), per review feedback. - Add a regression test for merging consecutive non-tool same-role messages.
Author
|
Thanks for the review! Addressed both points in the latest commit:
Full bedrock connector suite passes (40 tests); |
Author
|
@microsoft-github-policy-service agree |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thanks to the SK maintainers for the Bedrock connector and the consistent connector design — this change just fills a gap the sibling Anthropic connector already handles.
Motivation and Context
Fixes #14073. Python analogue of #13647 (.NET).
When a model calls multiple tools in one turn, SK emits one
ChatMessageContentper tool call/result._prepare_chat_history_for_requestmapped each 1:1 to a Converse message, so paralleltoolResults landed in separate user messages → Converse rejects:Expected toolResult blocks at messages.X.content ....Description
Merge consecutive messages that map to the same Bedrock role, so all
toolUseblocks land in one assistant message and alltoolResultblocks in one user message. Mirrors the existing Anthropic connector (formatted_messages[-1][content_key] += ...).toolUsemessagestoolUseblockstoolResultmessages (Converse rejects)toolResultblocks_format_*converters, public API/argsContribution Checklist
test_prepare_chat_history_for_request_merges_parallel_tool_results. Reverting only the source makes it fail withassert 5 == 3(proving it catches the bug); with the fix the full bedrock unit suite passes (114 passed).ruff format/ruff check/mypyclean.This contribution was prepared with the help of an AI agent (Claude Code); a human reviewed the change, reasoning, and test results before submission.