mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix(anthropic): tighten thinking-signature retry (Greptile)
- Omit messages whose list content is empty after stripping thinking blocks - Retry only on HTTP 400 plus invalid-signature body match - Return response inline from retry loop; drop unreachable None guard - Tests: thinking-only turn dropped, non-400 no retry Made-with: Cursor
This commit is contained in:
parent
0f453cc59d
commit
5670f6c7d4
4 changed files with 41 additions and 13 deletions
|
|
@ -760,6 +760,9 @@ def strip_thinking_blocks_from_anthropic_messages(messages: List[Any]) -> List[A
|
|||
"""
|
||||
Return a new message list with thinking / redacted_thinking content blocks removed
|
||||
from each message. Used to recover from invalid thinking signatures on retry.
|
||||
|
||||
Messages whose content is a list and becomes empty after stripping are omitted,
|
||||
since Anthropic rejects empty content arrays.
|
||||
"""
|
||||
out: List[Any] = []
|
||||
for m in messages:
|
||||
|
|
@ -769,7 +772,7 @@ def strip_thinking_blocks_from_anthropic_messages(messages: List[Any]) -> List[A
|
|||
mm = copy.deepcopy(m)
|
||||
content = mm.get("content")
|
||||
if isinstance(content, list):
|
||||
mm["content"] = [
|
||||
filtered = [
|
||||
b
|
||||
for b in content
|
||||
if not (
|
||||
|
|
@ -777,6 +780,9 @@ def strip_thinking_blocks_from_anthropic_messages(messages: List[Any]) -> List[A
|
|||
and b.get("type") in ("thinking", "redacted_thinking")
|
||||
)
|
||||
]
|
||||
if not filtered:
|
||||
continue
|
||||
mm["content"] = filtered
|
||||
out.append(mm)
|
||||
return out
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,9 @@ class BaseAnthropicMessagesConfig(ABC):
|
|||
is_anthropic_invalid_thinking_signature_error,
|
||||
)
|
||||
|
||||
return is_anthropic_invalid_thinking_signature_error(e.response.text)
|
||||
return e.response.status_code == 400 and is_anthropic_invalid_thinking_signature_error(
|
||||
e.response.text
|
||||
)
|
||||
|
||||
def transform_anthropic_messages_request_on_http_error(
|
||||
self, e: httpx.HTTPStatusError, request_data: dict
|
||||
|
|
@ -154,6 +156,8 @@ class BaseAnthropicMessagesConfig(ABC):
|
|||
strip_thinking_blocks_from_anthropic_messages_request_dict,
|
||||
)
|
||||
|
||||
if is_anthropic_invalid_thinking_signature_error(e.response.text):
|
||||
if e.response.status_code == 400 and is_anthropic_invalid_thinking_signature_error(
|
||||
e.response.text
|
||||
):
|
||||
strip_thinking_blocks_from_anthropic_messages_request_dict(request_data)
|
||||
return request_data
|
||||
|
|
|
|||
|
|
@ -1833,7 +1833,6 @@ class BaseLLMHTTPHandler:
|
|||
max_attempts = max(provider_config.max_retry_on_anthropic_messages_http_error, 1)
|
||||
litellm_params_dict = dict(litellm_params)
|
||||
optional_params_dict = dict(litellm_params)
|
||||
response: Optional[httpx.Response] = None
|
||||
for attempt_idx in range(max_attempts):
|
||||
try:
|
||||
response = await async_httpx_client.post(
|
||||
|
|
@ -1844,6 +1843,7 @@ class BaseLLMHTTPHandler:
|
|||
logging_obj=logging_obj,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except httpx.HTTPStatusError as e:
|
||||
hit_max_attempt = attempt_idx + 1 == max_attempts
|
||||
should_retry = provider_config.should_retry_anthropic_messages_on_http_error(
|
||||
|
|
@ -1874,14 +1874,10 @@ class BaseLLMHTTPHandler:
|
|||
raise self._handle_error(e=e, provider_config=provider_config)
|
||||
except Exception as e:
|
||||
raise self._handle_error(e=e, provider_config=provider_config)
|
||||
break
|
||||
|
||||
if response is None:
|
||||
raise self._handle_error(
|
||||
e=ValueError("No response from Anthropic /v1/messages"),
|
||||
provider_config=provider_config,
|
||||
)
|
||||
return response
|
||||
raise RuntimeError(
|
||||
"unreachable: anthropic messages HTTP retry loop exited without return"
|
||||
)
|
||||
|
||||
async def async_anthropic_messages_handler(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -1181,6 +1181,24 @@ class TestAnthropicThinkingSignatureSelfHeal:
|
|||
assert out[1]["content"][0]["type"] == "text"
|
||||
assert messages[1]["content"][0]["type"] == "thinking"
|
||||
|
||||
def test_strip_thinking_blocks_drops_message_when_only_thinking_blocks(self):
|
||||
from litellm.llms.anthropic.common_utils import (
|
||||
strip_thinking_blocks_from_anthropic_messages,
|
||||
)
|
||||
|
||||
messages = [
|
||||
{"role": "user", "content": "hi"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{"type": "thinking", "thinking": "plan", "signature": "sig"},
|
||||
],
|
||||
},
|
||||
]
|
||||
out = strip_thinking_blocks_from_anthropic_messages(messages)
|
||||
assert len(out) == 1
|
||||
assert out[0]["role"] == "user"
|
||||
|
||||
def test_strip_thinking_blocks_from_anthropic_messages_request_dict(self):
|
||||
from litellm.llms.anthropic.common_utils import (
|
||||
strip_thinking_blocks_from_anthropic_messages_request_dict,
|
||||
|
|
@ -1204,7 +1222,7 @@ class TestAnthropicThinkingSignatureSelfHeal:
|
|||
}
|
||||
strip_thinking_blocks_from_anthropic_messages_request_dict(data)
|
||||
assert "thinking" not in data
|
||||
assert data["messages"][0]["content"] == []
|
||||
assert data["messages"] == []
|
||||
|
||||
def test_anthropic_messages_config_http_retry_helpers(self):
|
||||
import httpx
|
||||
|
|
@ -1230,6 +1248,10 @@ class TestAnthropicThinkingSignatureSelfHeal:
|
|||
err_bad = httpx.HTTPStatusError("bad", request=req, response=resp_bad)
|
||||
assert config.should_retry_anthropic_messages_on_http_error(err_bad, {}) is False
|
||||
|
||||
resp_500 = httpx.Response(500, request=req, text=err_text)
|
||||
err_500 = httpx.HTTPStatusError("bad", request=req, response=resp_500)
|
||||
assert config.should_retry_anthropic_messages_on_http_error(err_500, {}) is False
|
||||
|
||||
data = {
|
||||
"model": "claude-sonnet-4-20250514",
|
||||
"messages": [
|
||||
|
|
@ -1248,4 +1270,4 @@ class TestAnthropicThinkingSignatureSelfHeal:
|
|||
}
|
||||
config.transform_anthropic_messages_request_on_http_error(err, data)
|
||||
assert "thinking" not in data
|
||||
assert data["messages"][0]["content"] == []
|
||||
assert data["messages"] == []
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue