style(anthropic): trim excess thinking-block predicate/test commentary

This commit is contained in:
Jeremy Schoemaker 2026-08-27 15:40:32 -05:00
parent 01683f2bd9
commit dfba491fa4
No known key found for this signature in database
2 changed files with 13 additions and 54 deletions

View file

@ -2302,20 +2302,14 @@ def sanitize_messages_for_tool_calling(
def _is_unsignable_thinking_block(block: object) -> bool:
"""A thinking block that Anthropic cannot accept on input.
Anthropic verifies the thinking signature cryptographically, so a block whose
signature is null, empty, or missing (e.g. from an open-source reasoning model)
is rejected with a 400 and must be dropped rather than blanked or repaired, and
so is a block whose signature or data carries another provider's encrypted
reasoning. A `redacted_thinking` block Anthropic minted is always kept.
Anthropic also rejects a `thinking` block whose `thinking` text is empty or
whitespace-only ("each thinking block must contain thinking"), regardless of
signature. This shape reaches us when a caller replays a `thinking_blocks`
history item that originated from a non-Anthropic reasoning provider (e.g. an
OpenAI Responses-API turn with no summary text) through this Anthropic-shaped
request path (`/v1/chat/completions` -> anthropic/vertex_ai's claude models),
which is the same failure the Anthropic Responses-bridge adapter guards
against (see PR #36033) for its own separate content-block path.
Anthropic verifies the signature cryptographically, so a block with a null,
empty, or missing signature (e.g. from an open-source reasoning model) is
rejected with a 400, and so is a block whose signature or data carries
another provider's encrypted reasoning. It also rejects a `thinking` block
whose text is empty or whitespace-only ("each thinking block must contain
thinking"), regardless of signature, e.g. when a `thinking_blocks` history
item from a non-Anthropic reasoning provider is replayed through this path.
`redacted_thinking` blocks carry no signature and are always kept.
"""
if is_encrypted_reasoning_block(block):
return True

View file

@ -3722,14 +3722,7 @@ def test_convert_to_anthropic_tool_invoke_keeps_paired_server_tool_use():
server_result,
]
def test_anthropic_messages_pt_drops_empty_but_signed_thinking_block():
"""
Anthropic rejects a `thinking` block whose `thinking` text is empty, even
when it carries a valid-looking signature, with:
400 messages.N.content.M.thinking: each thinking block must contain thinking
This shape is reachable via cross-provider replay of a `thinking_blocks`
history item (see PR #36033), so `_is_unsignable_thinking_block()` must
also check the thinking text, not just the signature.
"""
"""An empty-but-signed thinking block must still be dropped."""
from litellm.litellm_core_utils.prompt_templates.factory import (
anthropic_messages_pt,
)
@ -3762,10 +3755,7 @@ def test_anthropic_messages_pt_drops_empty_but_signed_thinking_block():
def test_anthropic_messages_pt_keeps_non_empty_signed_thinking_block():
"""
Regression: a real, non-empty, signed thinking block must still pass
through unchanged.
"""
"""Regression: a non-empty, signed thinking block passes through unchanged."""
from litellm.litellm_core_utils.prompt_templates.factory import (
anthropic_messages_pt,
)
@ -3800,11 +3790,7 @@ def test_anthropic_messages_pt_keeps_non_empty_signed_thinking_block():
def test_anthropic_messages_pt_keeps_redacted_thinking_block():
"""
Regression: `redacted_thinking` blocks carry no signature and no plaintext
`thinking` field by design, and must always be kept regardless of the new
emptiness check (which only applies to `type == "thinking"` blocks).
"""
"""Regression: redacted_thinking blocks are unaffected by the emptiness check."""
from litellm.litellm_core_utils.prompt_templates.factory import (
anthropic_messages_pt,
)
@ -3836,11 +3822,7 @@ def test_anthropic_messages_pt_keeps_redacted_thinking_block():
def test_anthropic_messages_pt_drops_unsigned_thinking_block():
"""
Regression (pre-existing behaviour): a thinking block with no signature
(or an empty/null one) must still be dropped, independent of whether the
thinking text is populated.
"""
"""Regression: an unsigned thinking block is still dropped, regardless of text."""
from litellm.litellm_core_utils.prompt_templates.factory import (
anthropic_messages_pt,
)
@ -3873,24 +3855,7 @@ def test_anthropic_messages_pt_drops_unsigned_thinking_block():
def test_is_unsignable_thinking_block_treats_whitespace_only_as_empty():
"""
Edge case: a `thinking` field that is present but whitespace-only (e.g.
a single trailing newline forwarded from another provider's empty
reasoning summary) is functionally empty and Anthropic's API will still
reject it with "each thinking block must contain thinking". We treat it
the same as a fully empty string and drop the block.
Note the sibling check ~30 lines below this function's definition
(the "don't pass empty text blocks" comment) uses a bare
`len(thinking_block) > 0`, which by itself would treat whitespace-only
text as non-empty. That sibling check is always combined with
`not _is_unsignable_thinking_block(m)` in an `and`, so this function's
stricter whitespace-aware check is still the one that decides whether a
whitespace-only block survives the two checks don't disagree in
practice, but this function is intentionally the stricter of the two
since it is also called standalone (via `_drop_unsignable_thinking_blocks`)
without that extra `len(...) > 0` guard.
"""
"""Whitespace-only `thinking` text is treated as empty and dropped."""
from litellm.litellm_core_utils.prompt_templates.factory import (
_is_unsignable_thinking_block,
)