fix(anthropic pass-through): empty thinking/signature in content_block_start for thinking blocks
Some checks failed
Unit Tests: Proxy DB Operations / proxy-db (auth-checks, tests/proxy_unit_tests/test_auth_checks.py tests/proxy_unit_tests/test_user_api_key_auth.py, 20, 8) (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-db (key-generation, tests/proxy_unit_tests/test_key_generate_prisma.py, 30, 0) (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-db (remaining, tests/proxy_unit_tests --ignore=tests/proxy_unit_tests/test_key_generate_prisma.py --ignore=tests/proxy_unit_tests/test_auth_checks.py --ignore=tests/proxy_unit_tests/test_user_api_key_auth.py, 20, 8) (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled

Per the Anthropic SSE protocol, content_block_start for thinking blocks must
have empty thinking and signature fields. Actual thinking text must come in
content_block_delta events (thinking_delta type), not in content_block_start.

This fixes test_claude_agent_sdk_streaming[bedrock-converse-claude-sonnet-4.5]
where the claude CLI binary was receiving a malformed content_block_start with
non-empty thinking text and raising 'Content block is not a text block'.
This commit is contained in:
Ishaan Jaffer 2026-04-14 10:24:35 -07:00
parent 0c20e5bcb2
commit 377fa9baef
No known key found for this signature in database

View file

@ -550,9 +550,9 @@ class LiteLLMAnthropicMessagesAdapter:
## ASSISTANT MESSAGE ##
assistant_message_str: Optional[str] = None
assistant_content_list: List[
Dict[str, Any]
] = [] # For content blocks with cache_control
assistant_content_list: List[Dict[str, Any]] = (
[]
) # For content blocks with cache_control
has_cache_control_in_text = False
tool_calls: List[ChatCompletionAssistantToolCall] = []
thinking_blocks: List[
@ -595,12 +595,12 @@ class LiteLLMAnthropicMessagesAdapter:
function_chunk.get("provider_specific_fields")
or {}
)
provider_specific_fields[
"thought_signature"
] = signature
function_chunk[
"provider_specific_fields"
] = provider_specific_fields
provider_specific_fields["thought_signature"] = (
signature
)
function_chunk["provider_specific_fields"] = (
provider_specific_fields
)
tool_call = ChatCompletionAssistantToolCall(
id=content.get("id", ""),
@ -1334,9 +1334,9 @@ class LiteLLMAnthropicMessagesAdapter:
hasattr(usage, "_cache_creation_input_tokens")
and usage._cache_creation_input_tokens > 0
):
anthropic_usage[
"cache_creation_input_tokens"
] = usage._cache_creation_input_tokens
anthropic_usage["cache_creation_input_tokens"] = (
usage._cache_creation_input_tokens
)
if cached_tokens > 0:
anthropic_usage["cache_read_input_tokens"] = cached_tokens
@ -1394,8 +1394,11 @@ class LiteLLMAnthropicMessagesAdapter:
"Both `thinking` and `signature` in a single streaming chunk isn't supported."
)
# content_block_start for thinking must have empty thinking/signature.
# Per the Anthropic SSE protocol, actual thinking text comes in
# content_block_delta events, not in content_block_start.
return "thinking", ChatCompletionThinkingBlock(
type="thinking", thinking=thinking, signature=signature
type="thinking", thinking="", signature=""
)
return "text", TextBlock(type="text", text="")
@ -1513,9 +1516,9 @@ class LiteLLMAnthropicMessagesAdapter:
hasattr(litellm_usage_chunk, "_cache_creation_input_tokens")
and litellm_usage_chunk._cache_creation_input_tokens > 0
):
usage_delta[
"cache_creation_input_tokens"
] = litellm_usage_chunk._cache_creation_input_tokens
usage_delta["cache_creation_input_tokens"] = (
litellm_usage_chunk._cache_creation_input_tokens
)
if cached_tokens > 0:
usage_delta["cache_read_input_tokens"] = cached_tokens
else: