diff --git a/CHANGELOG.md b/CHANGELOG.md index f23f7167..27c82e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --> +## [Unreleased] + +### Bug fixes + +* `ChatBedrock()` (with the default `api="converse"`) no longer sends assistant turns with an empty `content` array, which Converse rejects. This happens when a response carries no content blocks, for example when a guardrail intervenes before the model produces any. A `"[empty string]"` placeholder is sent instead, matching how empty text content is already normalized. (#426) + + ## [0.23.0] - 2026-09-04 ### New features diff --git a/chatlas/_provider_bedrock_converse.py b/chatlas/_provider_bedrock_converse.py index 38698e8a..00e61f41 100644 --- a/chatlas/_provider_bedrock_converse.py +++ b/chatlas/_provider_bedrock_converse.py @@ -324,6 +324,12 @@ def as_converse_messages(turns: list[Turn]) -> list[MessageUnionTypeDef]: for c in turn.contents: content.append(as_converse_content(c, document_index=index)) index += 1 + + # Converse requires non-empty content, and dropping the turn instead + # would leave two consecutive user messages. + if role == "assistant" and not content: + content = [{"text": "[empty string]"}] + messages.append({"role": role, "content": content}) return messages diff --git a/tests/test_provider_bedrock_converse.py b/tests/test_provider_bedrock_converse.py index 4d5e0f71..6164d764 100644 --- a/tests/test_provider_bedrock_converse.py +++ b/tests/test_provider_bedrock_converse.py @@ -452,6 +452,28 @@ def test_unknown_turn_role_raises(self): with pytest.raises(ValueError, match="Unknown role"): as_converse_messages([turn]) + def test_empty_assistant_turn_gets_a_placeholder(self): + from chatlas._provider_bedrock_converse import as_converse_messages + from chatlas._turn import AssistantTurn, UserTurn + + turns = [ + UserTurn("Don't say anything"), + AssistantTurn([]), + UserTurn("What did I just say?"), + ] + messages = as_converse_messages(turns) + + assert [m["role"] for m in messages] == ["user", "assistant", "user"] + assert messages[1]["content"] == [{"text": "[empty string]"}] + + def test_empty_user_turn_is_left_alone(self): + from chatlas._provider_bedrock_converse import as_converse_messages + from chatlas._turn import UserTurn + + messages = as_converse_messages([UserTurn([])]) + + assert messages[0]["content"] == [] + class TestRequestTransport: def binary_request(self) -> "ConverseRequestTypeDef":