From 7a9f03af3f7a292bcf937c8d9e64ba1a5e9fa46b Mon Sep 17 00:00:00 2001 From: Yash Raj Pandey <55940078+devYRPauli@users.noreply.github.com> Date: Fri, 19 Jun 2026 04:15:21 -0400 Subject: [PATCH] fix(llms,embeddings): repair HTTP proxy support (httpx>=0.28) and preserve proxies in LlmFactory (#5447) Co-authored-by: kartik-mem0 --- mem0/configs/embeddings/base.py | 7 +++--- mem0/configs/llms/base.py | 5 ++-- mem0/llms/anthropic.py | 2 +- mem0/llms/azure_openai.py | 2 +- mem0/llms/deepseek.py | 2 +- mem0/llms/lmstudio.py | 2 +- mem0/llms/minimax.py | 2 +- mem0/llms/ollama.py | 2 +- mem0/llms/openai.py | 2 +- mem0/llms/vllm.py | 2 +- mem0/utils/factory.py | 2 +- mem0/utils/http.py | 13 +++++++++++ pyproject.toml | 1 + tests/llms/test_azure_openai.py | 2 +- tests/llms/test_openai.py | 13 +++++++++++ tests/test_http_client_proxies.py | 39 +++++++++++++++++++++++++++++++ 16 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 mem0/utils/http.py create mode 100644 tests/test_http_client_proxies.py diff --git a/mem0/configs/embeddings/base.py b/mem0/configs/embeddings/base.py index 3882b4b2e..de2f4324f 100644 --- a/mem0/configs/embeddings/base.py +++ b/mem0/configs/embeddings/base.py @@ -2,9 +2,8 @@ import os from abc import ABC from typing import Dict, Optional, Union -import httpx - from mem0.configs.base import AzureConfig +from mem0.utils.http import build_http_client class BaseEmbedderConfig(ABC): @@ -81,7 +80,8 @@ class BaseEmbedderConfig(ABC): self.embedding_dims = embedding_dims # AzureOpenAI specific - self.http_client = httpx.Client(proxies=http_client_proxies) if http_client_proxies else None + self.http_client_proxies = http_client_proxies + self.http_client = build_http_client(http_client_proxies) # Ollama specific self.ollama_base_url = ollama_base_url @@ -109,4 +109,3 @@ class BaseEmbedderConfig(ABC): self.aws_secret_access_key = aws_secret_access_key self.aws_session_token = aws_session_token self.aws_region = aws_region or os.environ.get("AWS_REGION") or "us-west-2" - diff --git a/mem0/configs/llms/base.py b/mem0/configs/llms/base.py index 7d16543c3..666acef5a 100644 --- a/mem0/configs/llms/base.py +++ b/mem0/configs/llms/base.py @@ -1,7 +1,7 @@ from abc import ABC from typing import Dict, Optional, Union -import httpx +from mem0.utils.http import build_http_client class BaseLlmConfig(ABC): @@ -74,4 +74,5 @@ class BaseLlmConfig(ABC): self.vision_details = vision_details self.reasoning_effort = reasoning_effort self.is_reasoning_model = is_reasoning_model - self.http_client = httpx.Client(proxies=http_client_proxies) if http_client_proxies else None + self.http_client_proxies = http_client_proxies + self.http_client = build_http_client(http_client_proxies) diff --git a/mem0/llms/anthropic.py b/mem0/llms/anthropic.py index e7b2b6810..f5e8dbce4 100644 --- a/mem0/llms/anthropic.py +++ b/mem0/llms/anthropic.py @@ -29,7 +29,7 @@ class AnthropicLLM(LLMBase): top_k=config.top_k, enable_vision=config.enable_vision, vision_details=config.vision_details, - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, ) super().__init__(config) diff --git a/mem0/llms/azure_openai.py b/mem0/llms/azure_openai.py index eb72683e7..fbb69e48a 100644 --- a/mem0/llms/azure_openai.py +++ b/mem0/llms/azure_openai.py @@ -32,7 +32,7 @@ class AzureOpenAILLM(LLMBase): enable_vision=config.enable_vision, vision_details=config.vision_details, reasoning_effort=getattr(config, 'reasoning_effort', None), - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, is_reasoning_model=getattr(config, 'is_reasoning_model', None), ) diff --git a/mem0/llms/deepseek.py b/mem0/llms/deepseek.py index b6b5e4a86..369df3ac3 100644 --- a/mem0/llms/deepseek.py +++ b/mem0/llms/deepseek.py @@ -28,7 +28,7 @@ class DeepSeekLLM(LLMBase): top_k=config.top_k, enable_vision=config.enable_vision, vision_details=config.vision_details, - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, ) super().__init__(config) diff --git a/mem0/llms/lmstudio.py b/mem0/llms/lmstudio.py index aab5d0777..bc2d9cce4 100644 --- a/mem0/llms/lmstudio.py +++ b/mem0/llms/lmstudio.py @@ -27,7 +27,7 @@ class LMStudioLLM(LLMBase): top_k=config.top_k, enable_vision=config.enable_vision, vision_details=config.vision_details, - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, ) super().__init__(config) diff --git a/mem0/llms/minimax.py b/mem0/llms/minimax.py index 82f6f2622..7faa51c61 100644 --- a/mem0/llms/minimax.py +++ b/mem0/llms/minimax.py @@ -28,7 +28,7 @@ class MiniMaxLLM(LLMBase): top_k=config.top_k, enable_vision=config.enable_vision, vision_details=config.vision_details, - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, ) super().__init__(config) diff --git a/mem0/llms/ollama.py b/mem0/llms/ollama.py index af976dc24..722f3fac4 100644 --- a/mem0/llms/ollama.py +++ b/mem0/llms/ollama.py @@ -30,7 +30,7 @@ class OllamaLLM(LLMBase): top_k=config.top_k, enable_vision=config.enable_vision, vision_details=config.vision_details, - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, ) super().__init__(config) diff --git a/mem0/llms/openai.py b/mem0/llms/openai.py index 314bbf4d1..5fb2864cb 100644 --- a/mem0/llms/openai.py +++ b/mem0/llms/openai.py @@ -30,7 +30,7 @@ class OpenAILLM(LLMBase): enable_vision=config.enable_vision, vision_details=config.vision_details, reasoning_effort=getattr(config, 'reasoning_effort', None), - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, is_reasoning_model=getattr(config, 'is_reasoning_model', None), ) diff --git a/mem0/llms/vllm.py b/mem0/llms/vllm.py index ce2b61956..525006bff 100644 --- a/mem0/llms/vllm.py +++ b/mem0/llms/vllm.py @@ -28,7 +28,7 @@ class VllmLLM(LLMBase): top_k=config.top_k, enable_vision=config.enable_vision, vision_details=config.vision_details, - http_client_proxies=config.http_client, + http_client_proxies=config.http_client_proxies, ) super().__init__(config) diff --git a/mem0/utils/factory.py b/mem0/utils/factory.py index f6397ea90..451726bcf 100644 --- a/mem0/utils/factory.py +++ b/mem0/utils/factory.py @@ -100,7 +100,7 @@ class LlmFactory: "top_k": config.top_k, "enable_vision": config.enable_vision, "vision_details": config.vision_details, - "http_client_proxies": config.http_client, + "http_client_proxies": config.http_client_proxies, } config_dict.update(kwargs) config = config_class(**config_dict) diff --git a/mem0/utils/http.py b/mem0/utils/http.py new file mode 100644 index 000000000..70c772108 --- /dev/null +++ b/mem0/utils/http.py @@ -0,0 +1,13 @@ +from typing import Dict, Optional, Union + +import httpx + + +def build_http_client(http_client_proxies: Optional[Union[Dict, str]]) -> Optional[httpx.Client]: + if not http_client_proxies: + return None + if isinstance(http_client_proxies, dict): + return httpx.Client( + mounts={scheme: httpx.HTTPTransport(proxy=url) for scheme, url in http_client_proxies.items()} + ) + return httpx.Client(proxy=http_client_proxies) diff --git a/pyproject.toml b/pyproject.toml index 481da3c41..185a2e567 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dependencies = [ "qdrant-client>=1.12.0", "pydantic>=2.7.3", "openai>=1.90.0", + "httpx>=0.28.0", "posthog>=7.14.0", "pytz>=2024.1", "sqlalchemy>=2.0.31", diff --git a/tests/llms/test_azure_openai.py b/tests/llms/test_azure_openai.py index 164495199..37df2cd60 100644 --- a/tests/llms/test_azure_openai.py +++ b/tests/llms/test_azure_openai.py @@ -289,7 +289,7 @@ def test_generate_with_http_proxies(default_headers): api_version=None, default_headers=default_headers, ) - mock_http_client.assert_called_once_with(proxies="http://testproxy.mem0.net:8000") + mock_http_client.assert_called_once_with(proxy="http://testproxy.mem0.net:8000") def test_init_with_api_key(monkeypatch): diff --git a/tests/llms/test_openai.py b/tests/llms/test_openai.py index ab99114d7..42fe845c0 100644 --- a/tests/llms/test_openai.py +++ b/tests/llms/test_openai.py @@ -1,8 +1,10 @@ import os from unittest.mock import Mock, patch +import httpx import pytest +from mem0.configs.llms.base import BaseLlmConfig from mem0.configs.llms.openai import OpenAIConfig from mem0.llms.openai import OpenAILLM @@ -451,3 +453,14 @@ def test_callback_with_tools(mock_openai_client): mock_callback.assert_called_once() # Check that tool_calls exists in the message assert hasattr(mock_callback.call_args[0][1].choices[0].message, 'tool_calls') + + +def test_openai_llm_preserves_proxies_from_base_config(mock_openai_client): + config = BaseLlmConfig( + model="gpt-4.1-nano-2025-04-14", + api_key="api_key", + http_client_proxies="http://proxy.local:8080", + ) + llm = OpenAILLM(config) + assert llm.config.http_client_proxies == "http://proxy.local:8080" + assert isinstance(llm.config.http_client, httpx.Client) diff --git a/tests/test_http_client_proxies.py b/tests/test_http_client_proxies.py new file mode 100644 index 000000000..ab32df435 --- /dev/null +++ b/tests/test_http_client_proxies.py @@ -0,0 +1,39 @@ +import httpx +import pytest + +from mem0.configs.embeddings.base import BaseEmbedderConfig +from mem0.configs.llms.base import BaseLlmConfig +from mem0.utils.factory import LlmFactory + + +@pytest.mark.parametrize("config_cls", [BaseLlmConfig, BaseEmbedderConfig]) +def test_config_with_string_proxy_builds_client(config_cls): + config = config_cls(http_client_proxies="http://proxy.local:8080") + assert isinstance(config.http_client, httpx.Client) + assert config.http_client_proxies == "http://proxy.local:8080" + + +@pytest.mark.parametrize("config_cls", [BaseLlmConfig, BaseEmbedderConfig]) +def test_config_with_dict_proxy_builds_client(config_cls): + proxies = {"http://": "http://p:8080", "https://": "http://p:8080"} + config = config_cls(http_client_proxies=proxies) + assert isinstance(config.http_client, httpx.Client) + assert config.http_client_proxies == proxies + + +@pytest.mark.parametrize("config_cls", [BaseLlmConfig, BaseEmbedderConfig]) +def test_config_without_proxy_has_no_client(config_cls): + config = config_cls() + assert config.http_client is None + assert config.http_client_proxies is None + + +def test_llm_factory_preserves_http_client_proxies(): + base = BaseLlmConfig( + model="gpt-4o-mini", + api_key="sk-test", + http_client_proxies="http://proxy.local:8080", + ) + llm = LlmFactory.create("openai", base) + assert llm.config.http_client_proxies == "http://proxy.local:8080" + assert isinstance(llm.config.http_client, httpx.Client)