mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-19 04:54:37 +00:00
fix(anthropic): scope max_retries to providers with retry wiring
This commit is contained in:
parent
56f84c8856
commit
342a634666
3 changed files with 156 additions and 12 deletions
|
|
@ -53,6 +53,16 @@ def get_supported_openai_params(
|
|||
|
||||
if provider_config and request_type == "chat_completion":
|
||||
supported_params = provider_config.get_supported_openai_params(model=model)
|
||||
allowed_openai_params = getattr(provider_config, "allowed_openai_params", None)
|
||||
supported_params = supported_params or []
|
||||
allowed_openai_params = allowed_openai_params or []
|
||||
supported_params.extend(allowed_openai_params)
|
||||
# ``max_retries`` is supported for providers with actual retry wiring
|
||||
# (OpenAI/Azure retry at the SDK layer; anthropic retries at LiteLLM's
|
||||
# httpx transport layer when ``max_retries`` is set).
|
||||
providers_with_retry_support = {"openai", "azure", "anthropic"}
|
||||
if custom_llm_provider in providers_with_retry_support and "max_retries" not in supported_params:
|
||||
supported_params.append("max_retries")
|
||||
if base_model and base_model != model:
|
||||
base_model_params = provider_config.get_supported_openai_params(model=base_model)
|
||||
supported_params = list(dict.fromkeys([*supported_params, *base_model_params]))
|
||||
|
|
|
|||
|
|
@ -3841,11 +3841,8 @@ def get_optional_params(
|
|||
supported_params = supported_params or []
|
||||
allowed_openai_params = allowed_openai_params or []
|
||||
supported_params.extend(allowed_openai_params)
|
||||
# ``max_retries`` is supported across all providers (OpenAI/Azure retry at
|
||||
# the SDK layer; non-OpenAI providers retry at LiteLLM's httpx transport
|
||||
# layer when ``max_retries`` is set). Declare it supported everywhere so it
|
||||
# is no longer silently dropped for non-OpenAI providers.
|
||||
if "max_retries" not in supported_params:
|
||||
providers_with_retry_support = {"openai", "azure", "anthropic"}
|
||||
if custom_llm_provider in providers_with_retry_support and "max_retries" not in supported_params:
|
||||
supported_params.append("max_retries")
|
||||
|
||||
_check_valid_arg(
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
Regression tests for `max_retries` support on non-OpenAI providers.
|
||||
|
||||
`max_retries` was previously silently dropped for every provider except
|
||||
OpenAI/Azure (see the TODO in `litellm/utils.py`). These tests lock in the
|
||||
fix that:
|
||||
OpenAI/Azure. These tests lock in the fix that:
|
||||
|
||||
1. Declares `max_retries` as a supported param for all providers.
|
||||
1. Declares `max_retries` as a supported param for providers with retry wiring (openai, azure, anthropic).
|
||||
2. Maps it through `AnthropicConfig` without leaking it into the request body.
|
||||
3. Wires it to LiteLLM's httpx transport so transient connection errors are
|
||||
retried.
|
||||
|
|
@ -15,7 +14,12 @@ import httpx
|
|||
import pytest
|
||||
|
||||
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
|
||||
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
AsyncHTTPHandler,
|
||||
HTTPHandler,
|
||||
get_async_httpx_client,
|
||||
_get_httpx_client,
|
||||
)
|
||||
|
||||
|
||||
class TestAnthropicMaxRetriesSupported:
|
||||
|
|
@ -67,10 +71,92 @@ class TestMaxRetriesHttpTransport:
|
|||
assert isinstance(transport, httpx.HTTPTransport)
|
||||
assert getattr(transport._pool, "_retries", 0) == 2
|
||||
|
||||
def test_default_handler_does_not_change_transport(self):
|
||||
# Without max_retries the default (aiohttp) transport path is untouched.
|
||||
def test_async_handler_retry_transport_with_force_ipv4(self):
|
||||
import litellm
|
||||
|
||||
original_force_ipv4 = litellm.force_ipv4
|
||||
litellm.force_ipv4 = True
|
||||
litellm.disable_aiohttp_transport = True
|
||||
try:
|
||||
transport = AsyncHTTPHandler._create_async_transport(max_retries=3)
|
||||
assert isinstance(transport, httpx.AsyncHTTPTransport)
|
||||
assert getattr(transport._pool, "_retries", 0) == 3
|
||||
assert transport._pool._local_address == "0.0.0.0"
|
||||
finally:
|
||||
litellm.force_ipv4 = original_force_ipv4
|
||||
litellm.disable_aiohttp_transport = False
|
||||
|
||||
def test_sync_handler_retry_transport_with_force_ipv4(self):
|
||||
import litellm
|
||||
|
||||
original_force_ipv4 = litellm.force_ipv4
|
||||
litellm.force_ipv4 = True
|
||||
litellm.disable_aiohttp_transport = True
|
||||
try:
|
||||
handler = HTTPHandler(max_retries=3, timeout=5)
|
||||
transport = handler.client._transport
|
||||
assert isinstance(transport, httpx.HTTPTransport)
|
||||
assert getattr(transport._pool, "_retries", 0) == 3
|
||||
assert transport._pool._local_address == "0.0.0.0"
|
||||
finally:
|
||||
litellm.force_ipv4 = original_force_ipv4
|
||||
litellm.disable_aiohttp_transport = False
|
||||
handler.close()
|
||||
|
||||
def test_async_handler_force_ipv4_without_max_retries(self):
|
||||
import litellm
|
||||
|
||||
original_force_ipv4 = litellm.force_ipv4
|
||||
litellm.force_ipv4 = True
|
||||
litellm.disable_aiohttp_transport = True
|
||||
try:
|
||||
transport = AsyncHTTPHandler._create_async_transport()
|
||||
assert isinstance(transport, httpx.AsyncHTTPTransport)
|
||||
assert transport._pool._local_address == "0.0.0.0"
|
||||
finally:
|
||||
litellm.force_ipv4 = original_force_ipv4
|
||||
litellm.disable_aiohttp_transport = False
|
||||
|
||||
def test_default_handler_does_not_use_retry_transport(self):
|
||||
# Without max_retries we do not use the retry transport.
|
||||
handler = AsyncHTTPHandler()
|
||||
assert not isinstance(handler.client._transport, httpx.AsyncHTTPTransport)
|
||||
transport = handler.client._transport
|
||||
|
||||
# Check if aiohttp transport is available and enabled
|
||||
aiohttp_enabled = AsyncHTTPHandler._should_use_aiohttp_transport()
|
||||
|
||||
# Import the aiohttp transport type for checking
|
||||
try:
|
||||
from litellm.llms.custom_httpx.aiohttp_transport import LiteLLMAiohttpTransport
|
||||
|
||||
HAS_AIOHTTP_TRANSPORT = True
|
||||
except ImportError:
|
||||
HAS_AIOHTTP_TRANSPORT = False
|
||||
LiteLLMAiohttpTransport = None
|
||||
|
||||
if aiohttp_enabled and HAS_AIOHTTP_TRANSPORT:
|
||||
# When aiohttp is available and enabled, we should get the aiohttp transport
|
||||
# which has no built-in retry mechanism in LiteLLM
|
||||
assert isinstance(transport, LiteLLMAiohttpTransport)
|
||||
else:
|
||||
# When aiohttp is not available/disabled, we should get an httpx transport
|
||||
# with explicit retries=0 (no retry)
|
||||
assert isinstance(transport, httpx.AsyncHTTPTransport)
|
||||
assert getattr(transport._pool, "_retries", 0) == 0
|
||||
|
||||
def test_async_handler_transport_none_when_aiohttp_disabled_and_ipv4_off(self):
|
||||
import litellm
|
||||
|
||||
original_force_ipv4 = litellm.force_ipv4
|
||||
original_disable = litellm.disable_aiohttp_transport
|
||||
litellm.force_ipv4 = False
|
||||
litellm.disable_aiohttp_transport = True
|
||||
try:
|
||||
transport = AsyncHTTPHandler._create_async_transport()
|
||||
assert transport is None
|
||||
finally:
|
||||
litellm.force_ipv4 = original_force_ipv4
|
||||
litellm.disable_aiohttp_transport = original_disable
|
||||
|
||||
|
||||
class TestMaxRetriesValidation:
|
||||
|
|
@ -85,3 +171,54 @@ class TestMaxRetriesValidation:
|
|||
|
||||
supported = get_supported_openai_params(model=model, custom_llm_provider=provider)
|
||||
assert "max_retries" in supported
|
||||
|
||||
def test_non_wired_provider_still_raises_for_max_retries(self):
|
||||
"""
|
||||
Regression test: providers without retry wiring should still raise
|
||||
UnsupportedParamsError when max_retries is passed via completion().
|
||||
"""
|
||||
from litellm.utils import get_supported_openai_params
|
||||
|
||||
# Cohere doesn't have actual retry wiring yet
|
||||
# get_supported_openai_params should NOT include max_retries for cohere
|
||||
supported_params = get_supported_openai_params(model="command", custom_llm_provider="cohere")
|
||||
assert "max_retries" not in supported_params, "max_retries should not be in supported params for cohere"
|
||||
|
||||
|
||||
class TestMaxRetriesHandlerPaths:
|
||||
def test_get_async_httpx_client_with_max_retries(self):
|
||||
"""Test that get_async_httpx_client creates client with max_retries when provided."""
|
||||
from litellm.types.utils import LlmProviders
|
||||
import asyncio
|
||||
|
||||
client = get_async_httpx_client(llm_provider=LlmProviders.ANTHROPIC, params={"max_retries": 5})
|
||||
try:
|
||||
transport = client.client._transport
|
||||
assert isinstance(transport, httpx.AsyncHTTPTransport)
|
||||
assert getattr(transport._pool, "_retries", 0) == 5
|
||||
finally:
|
||||
asyncio.run(client.close())
|
||||
|
||||
def test_get_httpx_client_with_max_retries(self):
|
||||
"""Test that _get_httpx_client creates client with max_retries when provided."""
|
||||
handler = _get_httpx_client(params={"max_retries": 4, "timeout": 30})
|
||||
try:
|
||||
transport = handler.client._transport
|
||||
assert isinstance(transport, httpx.HTTPTransport)
|
||||
assert getattr(transport._pool, "_retries", 0) == 4
|
||||
finally:
|
||||
handler.close()
|
||||
|
||||
def test_get_async_httpx_client_without_max_retries(self):
|
||||
"""Test that get_async_httpx_client creates client without max_retries when not provided."""
|
||||
from litellm.types.utils import LlmProviders
|
||||
from litellm.llms.custom_httpx.aiohttp_transport import LiteLLMAiohttpTransport
|
||||
import asyncio
|
||||
|
||||
client = get_async_httpx_client(llm_provider=LlmProviders.ANTHROPIC, params={})
|
||||
try:
|
||||
transport = client.client._transport
|
||||
# Should be LiteLLMAiohttpTransport when available (default)
|
||||
assert isinstance(transport, LiteLLMAiohttpTransport)
|
||||
finally:
|
||||
asyncio.run(client.close())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue