mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
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:
parent
2c881b2050
commit
c62c92d2ad
2 changed files with 59 additions and 13 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue