fix(providers): disable proxy for local endpoints, respect env proxy for cloud
When the host has HTTP_PROXY / HTTPS_PROXY / ALL_PROXY set, httpx routes all traffic through the proxy — including requests to localhost or LAN addresses that the proxy typically cannot reach. This breaks local model servers (Ollama, llama.cpp, vLLM) silently. - Local endpoints: pass transport=httpx.AsyncHTTPTransport(proxy=None) so proxy env vars are ignored for local traffic. - Cloud endpoints: pass trust_env=True so corporate/VPN proxies work without explicit configuration. Fixes #4366
This commit is contained in:
@@ -406,9 +406,24 @@ class OpenAICompatProvider(LLMProvider):
|
||||
# opening a fresh connection for each request, which is cheap on a
|
||||
# LAN. Cloud providers benefit from keepalive, so we leave the
|
||||
# default pool settings for them.
|
||||
#
|
||||
# Also disable proxy for local endpoints: when the host has
|
||||
# HTTP_PROXY / HTTPS_PROXY / ALL_PROXY set, httpx would try to
|
||||
# route local traffic through the proxy, which typically cannot
|
||||
# reach localhost or LAN addresses.
|
||||
_local_limits = httpx.Limits(keepalive_expiry=0)
|
||||
http_client = httpx.AsyncClient(
|
||||
limits=httpx.Limits(keepalive_expiry=0),
|
||||
limits=_local_limits,
|
||||
timeout=timeout_s,
|
||||
transport=httpx.AsyncHTTPTransport(proxy=None, limits=_local_limits),
|
||||
)
|
||||
else:
|
||||
# Cloud endpoints: respect proxy environment variables
|
||||
# (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY) so corporate
|
||||
# or VPN proxies work without explicit configuration.
|
||||
http_client = httpx.AsyncClient(
|
||||
timeout=timeout_s,
|
||||
trust_env=True,
|
||||
)
|
||||
self._client = AsyncOpenAI(
|
||||
api_key=self._api_key_for_client,
|
||||
|
||||
@@ -16,7 +16,10 @@ async def test_openai_compat_provider_defers_sdk_client_until_first_use() -> Non
|
||||
|
||||
kwargs = mock_async_openai.call_args.kwargs
|
||||
_assert_openai_compat_timeout(kwargs["timeout"])
|
||||
assert kwargs["http_client"] is None
|
||||
# Cloud endpoints get an httpx client with trust_env=True to respect
|
||||
# proxy environment variables (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY).
|
||||
assert kwargs["http_client"] is not None
|
||||
assert kwargs["http_client"]._trust_env is True
|
||||
|
||||
|
||||
async def test_openai_compat_provider_sets_timeout_on_local_http_client() -> None:
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Tests for proxy environment variable handling in OpenAICompatProvider."""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
|
||||
from nanobot.providers.openai_compat_provider import OpenAICompatProvider
|
||||
|
||||
|
||||
def _make_spec(is_local: bool = False) -> MagicMock:
|
||||
spec = MagicMock()
|
||||
spec.is_local = is_local
|
||||
return spec
|
||||
|
||||
|
||||
class TestLocalEndpointProxyDisabled:
|
||||
"""Local endpoints must bypass proxy to avoid routing LAN traffic through it."""
|
||||
|
||||
async def test_local_disables_proxy(self):
|
||||
spec = _make_spec(is_local=True)
|
||||
spec.env_key = ""
|
||||
spec.default_api_base = "http://localhost:11434/v1"
|
||||
provider = OpenAICompatProvider(
|
||||
api_key="test", api_base="http://localhost:11434/v1", spec=spec,
|
||||
)
|
||||
await provider._ensure_client()
|
||||
transport = provider._client._client._transport
|
||||
# The transport should be an AsyncHTTPTransport with proxy=None
|
||||
assert isinstance(transport, httpx.AsyncHTTPTransport)
|
||||
|
||||
async def test_lan_ip_disables_proxy(self):
|
||||
spec = _make_spec(is_local=False)
|
||||
spec.env_key = ""
|
||||
spec.default_api_base = None
|
||||
provider = OpenAICompatProvider(
|
||||
api_key="test", api_base="http://192.168.8.188:1234/v1", spec=spec,
|
||||
)
|
||||
await provider._ensure_client()
|
||||
transport = provider._client._client._transport
|
||||
assert isinstance(transport, httpx.AsyncHTTPTransport)
|
||||
|
||||
|
||||
class TestCloudEndpointProxyEnabled:
|
||||
"""Cloud endpoints must respect proxy env vars for corporate/VPN proxies."""
|
||||
|
||||
async def test_cloud_respects_trust_env(self):
|
||||
spec = _make_spec(is_local=False)
|
||||
spec.env_key = ""
|
||||
spec.default_api_base = "https://api.openai.com/v1"
|
||||
provider = OpenAICompatProvider(
|
||||
api_key="test", api_base=None, spec=spec,
|
||||
)
|
||||
await provider._ensure_client()
|
||||
client = provider._client._client
|
||||
# trust_env should be True so httpx reads HTTP_PROXY etc.
|
||||
assert client._trust_env is True
|
||||
Reference in New Issue
Block a user