From cde20cf825072a373611a70cdff107853b087ce2 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Mon, 23 Jun 2025 09:30:40 -0700 Subject: [PATCH] fix - checking proxy settings (#11947) --- litellm/__init__.py | 1 + .../llms/custom_httpx/aiohttp_transport.py | 36 +++++++++++++++- .../custom_httpx/test_aiohttp_transport.py | 43 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/litellm/__init__.py b/litellm/__init__.py index 614d6de8e7d..5407dd85d58 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -324,6 +324,7 @@ use_aiohttp_transport: bool = ( True # Older variable, aiohttp is now the default. use disable_aiohttp_transport instead. ) disable_aiohttp_transport: bool = False # Set this to true to use httpx instead +disable_aiohttp_trust_env: bool = False # When False, aiohttp will respect HTTP(S)_PROXY env vars force_ipv4: bool = ( False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6. ) diff --git a/litellm/llms/custom_httpx/aiohttp_transport.py b/litellm/llms/custom_httpx/aiohttp_transport.py index 279cf2e9f45..3ed7d04bde6 100644 --- a/litellm/llms/custom_httpx/aiohttp_transport.py +++ b/litellm/llms/custom_httpx/aiohttp_transport.py @@ -1,6 +1,8 @@ import asyncio import contextlib +import os import typing +import urllib.request from typing import Callable, Dict, Union import aiohttp @@ -9,7 +11,9 @@ import aiohttp.http_exceptions import httpx from aiohttp.client import ClientResponse, ClientSession +import litellm from litellm._logging import verbose_logger +from litellm.secret_managers.main import str_to_bool AIOHTTP_EXC_MAP: Dict = { # Order matters here, most specific exception first @@ -182,7 +186,7 @@ class LiteLLMAiohttpTransport(AiohttpTransport): self.client = ClientSession() return self.client - + async def handle_async_request( self, request: httpx.Request, @@ -196,6 +200,9 @@ class LiteLLMAiohttpTransport(AiohttpTransport): # Use helper to ensure we have a valid session for the current event loop client_session = self._get_valid_client_session() + # Resolve proxy settings from environment variables + proxy = await self._get_proxy_settings(request) + with map_aiohttp_exceptions(): try: data = request.content @@ -215,6 +222,7 @@ class LiteLLMAiohttpTransport(AiohttpTransport): sock_read=timeout.get("read"), connect=timeout.get("pool"), ), + proxy=proxy, server_hostname=sni_hostname, ).__aenter__() @@ -224,3 +232,29 @@ class LiteLLMAiohttpTransport(AiohttpTransport): content=AiohttpResponseStream(response), request=request, ) + + + async def _get_proxy_settings(self, request: httpx.Request): + proxy = None + if not ( + litellm.disable_aiohttp_trust_env + or str_to_bool(os.getenv("DISABLE_AIOHTTP_TRUST_ENV", "False")) + ): + try: + proxy = self._proxy_from_env(request.url) + except Exception as e: # pragma: no cover - best effort + verbose_logger.debug(f"Error reading proxy env: {e}") + + return proxy + + + def _proxy_from_env(self, url: httpx.URL) -> typing.Optional[str]: + """Return proxy URL from env for the given request URL.""" + proxies = urllib.request.getproxies() + if urllib.request.proxy_bypass(url.host): + return None + + proxy = proxies.get(url.scheme) or proxies.get("all") + if proxy and "://" not in proxy: + proxy = f"http://{proxy}" + return proxy diff --git a/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py b/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py index 4d1023d873f..f296f076178 100644 --- a/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py +++ b/tests/test_litellm/llms/custom_httpx/test_aiohttp_transport.py @@ -176,3 +176,46 @@ async def test_timeout_exception_gets_mapped(): # Should have received the first chunk before the error assert received_chunks == [b"chunk1"] + + +@pytest.mark.asyncio +async def test_handle_async_request_uses_env_proxy(monkeypatch): + """Aiohttp transport should honor HTTP(S)_PROXY env vars""" + proxy_url = "http://proxy.local:3128" + monkeypatch.setenv("HTTP_PROXY", proxy_url) + monkeypatch.setenv("http_proxy", proxy_url) + monkeypatch.setenv("HTTPS_PROXY", proxy_url) + monkeypatch.setenv("https_proxy", proxy_url) + monkeypatch.delenv("DISABLE_AIOHTTP_TRUST_ENV", raising=False) + + captured = {} + + class FakeSession: + def request(self, *args, **kwargs): + captured["proxy"] = kwargs.get("proxy") + + class Resp: + status = 200 + headers = {} + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + pass + + @property + def content(self): + class C: + async def iter_chunked(self, size): + yield b"" + + return C() + + return Resp() + + transport = LiteLLMAiohttpTransport(client=lambda: FakeSession()) + request = httpx.Request("GET", "http://example.com") + await transport.handle_async_request(request) + + assert captured["proxy"] == proxy_url