From 92fcb65d7e92f28c31c286d9b0c08d63bf181b6b Mon Sep 17 00:00:00 2001 From: Ratish1 Date: Fri, 3 Oct 2025 21:32:04 +0400 Subject: [PATCH] fix(bedrock): accept string or dict for guardContent.text and preserve qualifiers --- src/strands/models/bedrock.py | 18 ++++++++++-- tests/strands/models/test_bedrock.py | 42 ++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index c6a500597..a9656e624 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -394,9 +394,21 @@ def _format_request_message_content(self, content: ContentBlock) -> dict[str, An # https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_GuardrailConverseContentBlock.html if "guardContent" in content: - guard = content["guardContent"] - guard_text = guard["text"] - result = {"text": {"text": guard_text["text"], "qualifiers": guard_text["qualifiers"]}} + # content["guardContent"] can be a dict-like structure. Annotate as Any for mypy. + guard: Any = content["guardContent"] + guard_text: Any = guard.get("text") + + # Guard text may be provided either as a plain string or as a dict with 'text' and optional 'qualifiers'. + if isinstance(guard_text, str): + result = {"text": {"text": guard_text}} + else: + # treat as mapping-like when not a string, be defensive using .get() + text_val = guard_text.get("text") if isinstance(guard_text, dict) else None + result = {"text": {"text": text_val}} + qualifiers = guard_text.get("qualifiers") if isinstance(guard_text, dict) else None + if qualifiers is not None: + result["text"]["qualifiers"] = qualifiers + return {"guardContent": result} # https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ImageBlock.html diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 96fee67fa..fa7e36b82 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1600,6 +1600,48 @@ def test_format_request_filters_document_content_blocks(model, model_id): assert "metadata" not in document_block +def test_format_request_guard_content_with_qualifiers(model, model_id): + """Test guardContent formatting when qualifiers are present.""" + messages = [ + { + "role": "user", + "content": [{"guardContent": {"text": {"text": "guarded text", "qualifiers": ["q1", "q2"]}}}], + } + ] + + formatted = model.format_request(messages) + guard_block = formatted["messages"][0]["content"][0]["guardContent"] + assert guard_block == {"text": {"text": "guarded text", "qualifiers": ["q1", "q2"]}} + + +def test_format_request_guard_content_without_qualifiers(model, model_id): + """Test guardContent formatting when qualifiers are not present.""" + messages = [ + { + "role": "user", + "content": [{"guardContent": {"text": {"text": "guarded text no qualifiers"}}}], + } + ] + + formatted = model.format_request(messages) + guard_block = formatted["messages"][0]["content"][0]["guardContent"] + assert guard_block == {"text": {"text": "guarded text no qualifiers"}} + + +def test_format_request_guard_content_text_as_string(model, model_id): + """Test guardContent formatting when the text field is a plain string.""" + messages = [ + { + "role": "user", + "content": [{"guardContent": {"text": "plain guard text"}}], + } + ] + + formatted = model.format_request(messages) + guard_block = formatted["messages"][0]["content"][0]["guardContent"] + assert guard_block == {"text": {"text": "plain guard text"}} + + def test_format_request_filters_nested_reasoning_content(model, model_id): """Test deep filtering of nested reasoningText fields.""" messages = [