Skip to content

Python: Fix Bedrock parallel tool-call results rejected by Converse#14074

Open
kimnamu wants to merge 2 commits into
microsoft:mainfrom
kimnamu:fix/bedrock-parallel-tool-results-merge
Open

Python: Fix Bedrock parallel tool-call results rejected by Converse#14074
kimnamu wants to merge 2 commits into
microsoft:mainfrom
kimnamu:fix/bedrock-parallel-tool-results-merge

Conversation

@kimnamu

@kimnamu kimnamu commented Jun 13, 2026

Copy link
Copy Markdown

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 ChatMessageContent per tool call/result. _prepare_chat_history_for_request mapped each 1:1 to a Converse message, so parallel toolResults 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 toolUse blocks land in one assistant message and all toolResult blocks in one user message. Mirrors the existing Anthropic connector (formatted_messages[-1][content_key] += ...).

Item Before After
2 parallel tool calls → assistant msgs ❌ 2 separate toolUse messages ✅ 1 assistant msg, 2 toolUse blocks
2 parallel tool results → user msgs ❌ 2 separate toolResult messages (Converse rejects) ✅ 1 user msg, 2 toolResult blocks
Total Converse messages (this case) ❌ 5 ✅ 3
Single tool call / no-tool histories ✅ unchanged ✅ unchanged
Streaming path, _format_* converters, public API/args ✅ unchanged ✅ unchanged

Contribution Checklist

  • Added test_prepare_chat_history_for_request_merges_parallel_tool_results. Reverting only the source makes it fail with assert 5 == 3 (proving it catches the bug); with the fix the full bedrock unit suite passes (114 passed). ruff format/ruff check/mypy clean.

This contribution was prepared with the help of an AI agent (Claude Code); a human reviewed the change, reasoning, and test results before submission.

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>
@kimnamu kimnamu requested a review from a team as a code owner June 13, 2026 14:32
Copilot AI review requested due to automatic review settings June 13, 2026 14:32
@moonbox3 moonbox3 added the python Pull requests for the Python Semantic Kernel label Jun 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
"""

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 5 | Confidence: 93% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage, Failure Modes, Design Approach


Automated review by kimnamu's agents

- 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.
@kimnamu

kimnamu commented Jun 13, 2026

Copy link
Copy Markdown
Author

Thanks for the review! Addressed both points in the latest commit:

  1. Explicit list merge — replaced the in-place content += ... with building a new combined list ([*prev, *new]). Clearer type intent and avoids mutating the previously-appended message.
  2. Non-tool merge coverage — added test_prepare_chat_history_for_request_merges_consecutive_same_role_messages, which verifies two consecutive plain user text messages collapse into a single user message with both text blocks preserved in order.

Full bedrock connector suite passes (40 tests); ruff check/format clean.

@kimnamu

kimnamu commented Jun 13, 2026

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.Net: Python: Bedrock connector rejects parallel tool calls (toolResult blocks not merged into one Converse message)

3 participants