diff --git a/instructor/providers/bedrock/utils.py b/instructor/providers/bedrock/utils.py index f6a8cd5c7..b5814d5ca 100644 --- a/instructor/providers/bedrock/utils.py +++ b/instructor/providers/bedrock/utils.py @@ -225,10 +225,14 @@ def _to_bedrock_content_items(content: Any) -> list[dict[str, Any]]: {"text":"..."} {"image":{"format":"jpeg|png|gif|webp","source":{"bytes": }}} {"document":{"format":"pdf|csv|doc|docx|xls|xlsx|html|txt|md","name":"...","source":{"bytes": }}} + {"cachePoint":{"type":"default"}} + Any other valid Bedrock ContentBlock dict (guardContent, toolUse, + toolResult, audio, video, reasoningContent, etc.) Note: - - We do not validate or normalize Bedrock-native image/document blocks here. - Caller is responsible for providing valid 'format' and raw 'bytes'. + - We do not validate or normalize Bedrock-native blocks here. + Caller is responsible for providing valid structure. + The Bedrock API will reject malformed content blocks. """ # Plain string if isinstance(content, str): @@ -269,7 +273,10 @@ def _to_bedrock_content_items(content: Any) -> list[dict[str, Any]]: items.append(p) continue - raise ValueError(f"Unsupported dict content for Bedrock: {p}") + # Pass-through any other Bedrock-native content block (cachePoint, + # guardContent, toolUse, toolResult, audio, video, etc.) + items.append(p) + continue # Plain string elements inside list if isinstance(p, str): diff --git a/tests/llm/test_bedrock/test_bedrock_native_passthrough.py b/tests/llm/test_bedrock/test_bedrock_native_passthrough.py index c79d35d47..f1b167938 100644 --- a/tests/llm/test_bedrock/test_bedrock_native_passthrough.py +++ b/tests/llm/test_bedrock/test_bedrock_native_passthrough.py @@ -1,4 +1,7 @@ from __future__ import annotations + +import pytest + from instructor.providers.bedrock.utils import _to_bedrock_content_items @@ -18,3 +21,49 @@ def test_bedrock_native_document_passthrough(tiny_pdf_bytes: bytes): native = {"document": {"format": "pdf", "source": {"bytes": tiny_pdf_bytes}}} items = _to_bedrock_content_items([native]) assert items[0] == native + + +def test_bedrock_native_cachepoint_passthrough(): + """Regression test for #1954: cachePoint dicts must pass through.""" + cache_point = {"cachePoint": {"type": "default"}} + items = _to_bedrock_content_items([cache_point]) + assert items == [cache_point] + + +def test_bedrock_native_cachepoint_with_ttl(): + cache_point = {"cachePoint": {"type": "default", "ttl": "5m"}} + items = _to_bedrock_content_items([cache_point]) + assert items == [cache_point] + + +def test_bedrock_native_guard_content_passthrough(): + guard = {"guardContent": {"text": {"text": "test content"}}} + items = _to_bedrock_content_items([guard]) + assert items == [guard] + + +@pytest.mark.parametrize( + "block", + [ + {"cachePoint": {"type": "default"}}, + {"guardContent": {"text": {"text": "check this"}}}, + {"video": {"format": "mp4", "source": {"bytes": b"fake"}}}, + {"audio": {"format": "mp3", "source": {"bytes": b"fake"}}}, + ], + ids=["cachePoint", "guardContent", "video", "audio"], +) +def test_bedrock_native_content_block_passthrough(block: dict): + """All Bedrock-native content blocks should pass through unchanged.""" + items = _to_bedrock_content_items([block]) + assert items == [block] + + +def test_mixed_content_with_cachepoint(): + """Regression test for #1954: cachePoint mixed with text in real usage.""" + content = [ + {"text": "Say hello world."}, + {"cachePoint": {"type": "default"}}, + {"text": "This is a test message."}, + ] + items = _to_bedrock_content_items(content) + assert items == content