fix(anthropic): stop logging rejected encrypted reasoning

Quote the retry helper return type so importing litellm no longer evaluates a string alias with |. The warning keeps provider, model, status, and error code, and leaves the ciphertext out.
This commit is contained in:
DDT 2026-11-07 00:50:52 +00:00
parent 2c881b2050
commit c62c92d2ad
2 changed files with 59 additions and 13 deletions

View file

@ -84,15 +84,46 @@ class _ChatTargetReasoning:
summary: str | None = None
def _provider_error_code(body: object) -> str | None:
mapping: Final = as_str_mapping(body)
if mapping is None:
return None
nested: Final = as_str_mapping(mapping.get("error"))
source: Final = nested if nested is not None else mapping
code: Final = source.get("code")
return code if isinstance(code, str) else None
def _logged_rejection_code(error: litellm.BadRequestError) -> str:
if error.code == "invalid_encrypted_content" or _provider_error_code(error.body) == "invalid_encrypted_content":
return "invalid_encrypted_content"
if "invalid_encrypted_content" in str(error):
return "invalid_encrypted_content"
return "could_not_be_verified"
def _completion_text(completion_kwargs: _CompletionKwargs, key: str) -> str:
value: Final = completion_kwargs.get(key)
return value if isinstance(value, str) else ""
def _messages_without_rejected_reasoning(
messages: _AnthropicMessages,
error: litellm.BadRequestError,
) -> _AnthropicMessages | None:
completion_kwargs: _CompletionKwargs,
) -> "_AnthropicMessages | None":
error_message: Final = str(error)
rejected: Final = "invalid_encrypted_content" in error_message or "could not be verified" in error_message
if not rejected or not messages_carry_encrypted_reasoning(messages):
return None
verbose_logger.warning("Backend rejected replayed encrypted reasoning, retrying once without it: %s", error)
verbose_logger.warning(
"Backend rejected replayed encrypted reasoning, retrying once without it. "
"provider=%s model=%s status=%s code=%s",
_completion_text(completion_kwargs, "custom_llm_provider"),
_completion_text(completion_kwargs, "model"),
error.status_code,
_logged_rejection_code(error),
)
return _ANTHROPIC_MESSAGES.validate_python(strip_encrypted_reasoning_blocks_from_anthropic_messages(messages))
@ -104,7 +135,7 @@ async def _acompletion_without_rejected_reasoning(
try:
return completion_kwargs, await litellm.acompletion(**completion_kwargs)
except litellm.BadRequestError as error:
replayed_messages: Final = _messages_without_rejected_reasoning(messages, error)
replayed_messages: Final = _messages_without_rejected_reasoning(messages, error, completion_kwargs)
if replayed_messages is None:
raise
replay_kwargs: Final = prepare_replay(replayed_messages)
@ -119,7 +150,7 @@ def _completion_without_rejected_reasoning(
try:
return completion_kwargs, litellm.completion(**completion_kwargs)
except litellm.BadRequestError as error:
replayed_messages: Final = _messages_without_rejected_reasoning(messages, error)
replayed_messages: Final = _messages_without_rejected_reasoning(messages, error, completion_kwargs)
if replayed_messages is None:
raise
replay_kwargs: Final = prepare_replay(replayed_messages)

View file

@ -5827,25 +5827,40 @@ def _sent_reasoning_items(request: httpx.Request) -> list[object]:
@pytest.mark.usefixtures("_chat_bridge_httpx")
async def test_async_messages_retry_once_without_reasoning_the_backend_cannot_decrypt(
respx_mock: respx.MockRouter,
caplog: pytest.LogCaptureFixture,
) -> None:
route: Final = respx_mock.post(url__regex=_RETRY_URL).mock(
side_effect=[_RETRY_REJECTION, httpx.Response(200, json=_RETRY_REPLY)]
)
response: Final = await litellm.anthropic.messages.acreate(
model="azure/gpt-5.4-nano",
api_base=_RETRY_API_BASE,
api_key="fake-key",
api_version="2025-04-01-preview",
max_tokens=64,
tools=_RETRY_TOOLS,
messages=_history_with_foreign_reasoning(),
)
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
response: Final = await litellm.anthropic.messages.acreate(
model="azure/gpt-5.4-nano",
api_base=_RETRY_API_BASE,
api_key="fake-key",
api_version="2025-04-01-preview",
max_tokens=64,
tools=_RETRY_TOOLS,
messages=_history_with_foreign_reasoning(),
)
warnings: Final = tuple(
record.getMessage()
for record in caplog.records
if record.levelno >= logging.WARNING and record.name == "LiteLLM"
)
retry_warnings: Final = tuple(message for message in warnings if "retrying once without it" in message)
assert route.call_count == 2
assert len(_sent_reasoning_items(route.calls[0].request)) == 1
assert _sent_reasoning_items(route.calls[1].request) == []
assert [block["text"] for block in response["content"] if block["type"] == "text"] == ["Oslo"]
assert retry_warnings == (
"Backend rejected replayed encrypted reasoning, retrying once without it. "
"provider=azure model=azure/gpt-5.4-nano status=400 code=invalid_encrypted_content",
)
assert all(
"gAAA_foreign" not in message and "litellm_encrypted_reasoning:" not in message for message in warnings
)
@pytest.mark.usefixtures("_chat_bridge_httpx")