diff --git a/litellm/anthropic_beta_gated_fields.py b/litellm/anthropic_beta_gated_fields.py index 730395f2d66..fc36699bc93 100644 --- a/litellm/anthropic_beta_gated_fields.py +++ b/litellm/anthropic_beta_gated_fields.py @@ -89,7 +89,8 @@ def _as_sequence(value: object) -> tuple[object, ...] | None: def _is_tool_change_block(block: object) -> bool: mapping: Final = _as_mapping(block) - return mapping is not None and mapping.get("type") in _TOOL_CHANGE_BLOCK_TYPES + block_type: Final = mapping.get("type") if mapping is not None else None + return isinstance(block_type, str) and block_type in _TOOL_CHANGE_BLOCK_TYPES def _header_beta_values(headers: Mapping[str, str]) -> frozenset[str]: diff --git a/tests/unit/test_anthropic_beta_gated_fields.py b/tests/unit/test_anthropic_beta_gated_fields.py index f1edde64396..a2b384b4ac4 100644 --- a/tests/unit/test_anthropic_beta_gated_fields.py +++ b/tests/unit/test_anthropic_beta_gated_fields.py @@ -159,6 +159,13 @@ def test_inline_tools_beta_also_unlocks_system_tool_changes() -> None: ] +def test_system_block_with_unhashable_type_is_kept_for_the_provider_to_reject() -> None: + messages: Final = [{"role": "system", "content": [{"type": [], "text": "x"}]}] + result: Final = strip_beta_gated_system_messages(messages, frozenset(), drop_params=True) + assert result.messages is messages + assert result.stripped == () + + def test_system_message_emptied_by_the_strip_is_dropped() -> None: messages: Final = [ {"role": "user", "content": "hi"},