From 33f6ee9bc94f0f1c78d0244ce35daf345d7f8c40 Mon Sep 17 00:00:00 2001 From: kimnamu Date: Wed, 23 Sep 2026 15:13:31 +0900 Subject: [PATCH] Python: Fix Claude Opus 5.5 on Bedrock by skipping reasoningContent blocks With no thinking settings, Claude Opus 5.5 (and Opus 5) on Bedrock send a reasoningContent block ahead of the text or toolUse blocks whenever the model decides to think; how often depends on the prompt. The Converse response parser raised ServiceInvalidResponseError on that block, and the streaming delta parser read the reasoning delta as toolUse (KeyError). Skip reasoningContent blocks in responses; other unknown blocks still raise. Stream deltas that are neither text nor toolUse now give an empty chunk, like _parse_content_block_start_event. Nothing is surfaced or sent back. --- .../services/bedrock_chat_completion.py | 30 ++++++---- .../services/test_bedrock_chat_completion.py | 60 +++++++++++++++++++ 2 files changed, 79 insertions(+), 11 deletions(-) diff --git a/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py b/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py index 77a73fcc800a..45a4394eb995 100644 --- a/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/bedrock/services/bedrock_chat_completion.py @@ -253,6 +253,9 @@ def _create_chat_message_content(self, response: dict[str, Any]) -> ChatMessageC arguments=content["toolUse"]["input"], ) ) + elif "reasoningContent" in content: + # Reasoning blocks (sent by newer Claude models when they think) are not surfaced yet. + continue else: raise ServiceInvalidResponseError(f"Unsupported content type in the response: {content}") @@ -337,19 +340,24 @@ def _parse_content_block_delta_event( The content block delta event contains the completion. https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ContentBlockDeltaEvent.html """ - items: list[STREAMING_ITEM_TYPES] = [ - StreamingTextContent( - choice_index=0, - text=event["contentBlockDelta"]["delta"]["text"], - inner_content=event, + items: list[STREAMING_ITEM_TYPES] = [] + if "text" in event["contentBlockDelta"]["delta"]: + items.append( + StreamingTextContent( + choice_index=0, + text=event["contentBlockDelta"]["delta"]["text"], + inner_content=event, + ) ) - if "text" in event["contentBlockDelta"]["delta"] - else FunctionCallContent( - arguments=event["contentBlockDelta"]["delta"]["toolUse"]["input"], - inner_content=event, - index=event["contentBlockDelta"]["contentBlockIndex"], + elif "toolUse" in event["contentBlockDelta"]["delta"]: + items.append( + FunctionCallContent( + arguments=event["contentBlockDelta"]["delta"]["toolUse"]["input"], + inner_content=event, + index=event["contentBlockDelta"]["contentBlockIndex"], + ) ) - ] + # Other deltas (e.g. reasoningContent from newer Claude models) are not surfaced yet. return StreamingChatMessageContent( ai_model_id=self.ai_model_id, diff --git a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py index efa702a43813..dde878797736 100644 --- a/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py +++ b/python/tests/unit/connectors/ai/bedrock/services/test_bedrock_chat_completion.py @@ -368,4 +368,64 @@ async def test_bedrock_streaming_chat_completion_invalid_event( pass +async def test_bedrock_chat_completion_skips_reasoning_content(chat_history: ChatHistory) -> None: + """Test that reasoning content blocks in the response are skipped.""" + response = { + "output": { + "message": { + "role": "assistant", + "content": [ + {"reasoningContent": {"reasoningText": {"text": "", "signature": "sig"}}}, + {"text": "Hi!"}, + ], + } + }, + "stopReason": "end_turn", + "usage": {"inputTokens": 10, "outputTokens": 5, "totalTokens": 15}, + } + with patch.object(MockBedrockRuntimeClient, "converse", return_value=response): + bedrock_chat_completion = BedrockChatCompletion( + model_id="us.anthropic.claude-opus-5-5", + runtime_client=MockBedrockRuntimeClient(), + client=MockBedrockClient(), + ) + + result = await bedrock_chat_completion.get_chat_message_content( + chat_history=chat_history, settings=BedrockChatPromptExecutionSettings() + ) + + assert len(result.items) == 1 + assert isinstance(result.items[0], TextContent) + assert result.content == "Hi!" + + +async def test_bedrock_streaming_chat_completion_skips_reasoning_delta(chat_history: ChatHistory) -> None: + """Test that reasoning content deltas in the stream are skipped.""" + events = [ + {"messageStart": {"role": "assistant"}}, + {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"reasoningContent": {"text": ""}}}}, + {"contentBlockDelta": {"contentBlockIndex": 0, "delta": {"reasoningContent": {"signature": "sig"}}}}, + {"contentBlockStop": {"contentBlockIndex": 0}}, + {"contentBlockDelta": {"contentBlockIndex": 1, "delta": {"text": "Hi!"}}}, + {"contentBlockStop": {"contentBlockIndex": 1}}, + {"messageStop": {"stopReason": "end_turn"}}, + ] + with patch.object(MockBedrockRuntimeClient, "converse_stream", return_value={"stream": iter(events)}): + bedrock_chat_completion = BedrockChatCompletion( + model_id="us.anthropic.claude-opus-5-5", + runtime_client=MockBedrockRuntimeClient(), + client=MockBedrockClient(), + ) + + chunks: list[StreamingChatMessageContent] = [] + async for streaming_messages in bedrock_chat_completion.get_streaming_chat_message_contents( + chat_history=chat_history, settings=BedrockChatPromptExecutionSettings() + ): + chunks.extend(streaming_messages) + response = reduce(lambda p, r: p + r, chunks) + + assert response.content == "Hi!" + assert response.finish_reason == FinishReason.STOP + + # endregion