diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index cd39e366797..5d15fec5db7 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -141,6 +141,7 @@ from langchain_openai.chat_models._compat import ( from langchain_openai.data._profiles import _PROFILES if TYPE_CHECKING: + import httpx from langchain_core.language_models import ModelProfile from openai.types.responses import Response @@ -150,6 +151,20 @@ logger = logging.getLogger(__name__) # https://www.python-httpx.org/advanced/ssl/#configuring-client-instances global_ssl_context = ssl.create_default_context(cafile=certifi.where()) +_ssrf_client: httpx.Client | None = None + + +def _get_ssrf_safe_client() -> httpx.Client: + global _ssrf_client + if _ssrf_client is None: + from langchain_core._security._transport import ssrf_safe_client + + _ssrf_client = ssrf_safe_client( + verify=global_ssl_context, follow_redirects=False + ) + return _ssrf_client + + _MODEL_PROFILES = cast(ModelProfileRegistry, _PROFILES) @@ -3638,28 +3653,7 @@ def _url_to_size(image_source: str) -> tuple[int, int] | None: ) return None if _is_url(image_source): - try: - import httpx - except ImportError: - logger.info( - "Unable to count image tokens. To count image tokens please install " - "`pip install -U httpx`." - ) - return None - - # Validate URL for SSRF protection - try: - from langchain_core._security._ssrf_protection import validate_safe_url - - validate_safe_url(image_source, allow_private=False, allow_http=True) - except ImportError: - logger.warning( - "SSRF protection not available. " - "Update langchain-core to get SSRF protection." - ) - except ValueError as e: - logger.warning("Image URL failed SSRF validation: %s", e) - return None + import httpx # Set reasonable limits to prevent resource exhaustion # Timeout prevents indefinite hangs on slow/malicious servers @@ -3668,10 +3662,7 @@ def _url_to_size(image_source: str) -> tuple[int, int] | None: max_size = 50 * 1024 * 1024 # 50 MB try: - response = httpx.get( - image_source, - timeout=timeout, - ) + response = _get_ssrf_safe_client().get(image_source, timeout=timeout) response.raise_for_status() # Check response size before loading into memory diff --git a/libs/partners/openai/pyproject.toml b/libs/partners/openai/pyproject.toml index 6542aefd4b6..7de1c8c3d90 100644 --- a/libs/partners/openai/pyproject.toml +++ b/libs/partners/openai/pyproject.toml @@ -23,7 +23,7 @@ classifiers = [ version = "1.1.13" requires-python = ">=3.10.0,<4.0.0" dependencies = [ - "langchain-core>=1.2.29,<2.0.0", + "langchain-core>=1.2.31,<2.0.0", "openai>=2.26.0,<3.0.0", "tiktoken>=0.7.0,<1.0.0", ]