mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
Merge e24a7b9b94 into b4a8bc715f
This commit is contained in:
commit
dd21d931b3
3 changed files with 68 additions and 4 deletions
|
|
@ -214,6 +214,12 @@ def _default_cached_client_timeout() -> httpx.Timeout:
|
|||
return httpx.Timeout(timeout=configured, connect=HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS)
|
||||
|
||||
|
||||
def _with_bounded_connect(timeout: float | httpx.Timeout | None) -> httpx.Timeout | None:
|
||||
if timeout is None or isinstance(timeout, httpx.Timeout):
|
||||
return timeout
|
||||
return httpx.Timeout(timeout=timeout, connect=min(timeout, HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS))
|
||||
|
||||
|
||||
_CLIENT_REFCOUNT_WHEN_HANDLER_IS_SOLE_REFERRER: Final = 2
|
||||
|
||||
|
||||
|
|
@ -959,7 +965,7 @@ class AsyncHTTPHandler:
|
|||
json=json,
|
||||
params=params,
|
||||
headers=headers,
|
||||
timeout=timeout,
|
||||
timeout=_with_bounded_connect(timeout),
|
||||
files=files,
|
||||
content=request_content,
|
||||
)
|
||||
|
|
@ -970,7 +976,7 @@ class AsyncHTTPHandler:
|
|||
return response
|
||||
except httpx.ConnectError:
|
||||
# Retry the request with a new session if there is a connection error
|
||||
new_client: Final = self.create_client(timeout=timeout, event_hooks=self.event_hooks)
|
||||
new_client: Final = self.create_client(timeout=_with_bounded_connect(timeout), event_hooks=self.event_hooks)
|
||||
try:
|
||||
return await self.single_connection_request(
|
||||
url=url,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from litellm.a2a_protocol.providers.pydantic_ai_agents.config import PydanticAIP
|
|||
from litellm.a2a_protocol.providers.pydantic_ai_agents.transformation import (
|
||||
PydanticAITransformation,
|
||||
)
|
||||
from litellm.constants import HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS
|
||||
|
||||
|
||||
def _build_mock_client(response_payload):
|
||||
|
|
@ -240,7 +241,12 @@ async def test_provider_config_non_streaming_defaults_to_sixty_second_timeout(ag
|
|||
"id": "req-5",
|
||||
"result": {"kind": "message", "role": "agent", "parts": [{"kind": "text", "text": "ok"}], "messageId": ANY},
|
||||
}
|
||||
assert request.extensions["timeout"] == {"connect": 60.0, "read": 60.0, "write": 60.0, "pool": 60.0}
|
||||
assert request.extensions["timeout"] == {
|
||||
"connect": min(60.0, HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS),
|
||||
"read": 60.0,
|
||||
"write": 60.0,
|
||||
"pool": 60.0,
|
||||
}
|
||||
assert request.headers["content-type"] == "application/json"
|
||||
|
||||
|
||||
|
|
@ -259,7 +265,12 @@ async def test_provider_config_non_streaming_forwards_timeout_and_ignores_unrela
|
|||
|
||||
request: Final = agent_route.calls.last.request
|
||||
assert response["id"] == "req-5"
|
||||
assert request.extensions["timeout"] == {"connect": 5.5, "read": 5.5, "write": 5.5, "pool": 5.5}
|
||||
assert request.extensions["timeout"] == {
|
||||
"connect": min(5.5, HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS),
|
||||
"read": 5.5,
|
||||
"write": 5.5,
|
||||
"pool": 5.5,
|
||||
}
|
||||
assert request.headers["x-trace-id"] == "abc-123"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from yarl import URL
|
|||
|
||||
import litellm
|
||||
from litellm.caching.llm_caching_handler import LLMClientCache
|
||||
from litellm.constants import HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS
|
||||
from litellm.llms.custom_httpx.aiohttp_transport import LiteLLMAiohttpTransport
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
_CLIENT_REFCOUNT_WHEN_HANDLER_IS_SOLE_REFERRER,
|
||||
|
|
@ -141,6 +142,52 @@ def test_sync_post_streaming_status_error_should_not_wait_forever_for_body(
|
|||
litellm_handler.close()
|
||||
|
||||
|
||||
def _echo_timeout(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(200, json=request.extensions["timeout"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("timeout", "expected"),
|
||||
[
|
||||
(600.0, {"connect": HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS, "read": 600.0, "write": 600.0, "pool": 600.0}),
|
||||
(2.0, {"connect": 2.0, "read": 2.0, "write": 2.0, "pool": 2.0}),
|
||||
(httpx.Timeout(600.0), {"connect": 600.0, "read": 600.0, "write": 600.0, "pool": 600.0}),
|
||||
],
|
||||
)
|
||||
async def test_async_post_numeric_timeout_keeps_bounded_connect_timeout(
|
||||
timeout: float | httpx.Timeout, expected: Mapping[str, float]
|
||||
):
|
||||
handler: Final = AsyncHTTPHandler(transport=httpx.MockTransport(_echo_timeout))
|
||||
try:
|
||||
response: Final = await handler.post("http://upstream.invalid/v1/chat/completions", json={}, timeout=timeout)
|
||||
finally:
|
||||
await handler.close()
|
||||
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_post_replay_after_connect_error_keeps_bounded_connect_timeout():
|
||||
def refuse(request: httpx.Request) -> httpx.Response:
|
||||
raise httpx.ConnectError("connection refused", request=request)
|
||||
|
||||
handler: Final = AsyncHTTPHandler(transport=httpx.MockTransport(_echo_timeout))
|
||||
await handler.client.aclose()
|
||||
handler.client = httpx.AsyncClient(transport=httpx.MockTransport(refuse))
|
||||
try:
|
||||
response: Final = await handler.post("http://upstream.invalid/v1/chat/completions", json={}, timeout=600.0)
|
||||
finally:
|
||||
await handler.client.aclose()
|
||||
|
||||
assert response.json() == {
|
||||
"connect": HTTP_HANDLER_CONNECT_TIMEOUT_SECONDS,
|
||||
"read": 600.0,
|
||||
"write": 600.0,
|
||||
"pool": 600.0,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ssl_security_level(monkeypatch):
|
||||
# Ensure aiohttp transport is enabled for this test
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue