mirror of
https://github.com/hwchase17/langchain.git
synced 2025-08-19 09:30:15 +00:00
fix(anthropic): Add proxy (#32409)
Thank you for contributing to LangChain! Follow these steps to mark your pull request as ready for review. **If any of these steps are not completed, your PR will not be considered for review.** - [x] **PR title**: Follows the format: {TYPE}({SCOPE}): {DESCRIPTION} - [x] **PR message**: ***Delete this entire checklist*** and replace with fix #30146 - [x] **Add tests and docs**: If you're adding a new integration, you must include: - [x] **Lint and test**: Run `make format`, `make lint` and `make test` from the root of the package(s) you've modified. **We will not consider a PR unless these three are passing in CI.** See [contribution guidelines](https://python.langchain.com/docs/contributing/) for more. Additional guidelines: - Make sure optional dependencies are imported within a function. - Please do not add dependencies to `pyproject.toml` files (even optional ones) unless they are **required** for unit tests. - Most PRs should not touch more than one package. - Changes should be backwards compatible. --------- Co-authored-by: Mason Daugherty <mason@langchain.dev> Co-authored-by: Mason Daugherty <github@mdrxy.com>
This commit is contained in:
parent
be83ce74a7
commit
b9dcce95be
@ -50,6 +50,7 @@ def _get_default_httpx_client(
|
||||
*,
|
||||
base_url: Optional[str],
|
||||
timeout: Any = _NOT_GIVEN,
|
||||
anthropic_proxy: Optional[str] = None,
|
||||
) -> _SyncHttpxClientWrapper:
|
||||
kwargs: dict[str, Any] = {
|
||||
"base_url": base_url
|
||||
@ -58,6 +59,8 @@ def _get_default_httpx_client(
|
||||
}
|
||||
if timeout is not _NOT_GIVEN:
|
||||
kwargs["timeout"] = timeout
|
||||
if anthropic_proxy is not None:
|
||||
kwargs["proxy"] = anthropic_proxy
|
||||
return _SyncHttpxClientWrapper(**kwargs)
|
||||
|
||||
|
||||
@ -66,6 +69,7 @@ def _get_default_async_httpx_client(
|
||||
*,
|
||||
base_url: Optional[str],
|
||||
timeout: Any = _NOT_GIVEN,
|
||||
anthropic_proxy: Optional[str] = None,
|
||||
) -> _AsyncHttpxClientWrapper:
|
||||
kwargs: dict[str, Any] = {
|
||||
"base_url": base_url
|
||||
@ -74,4 +78,6 @@ def _get_default_async_httpx_client(
|
||||
}
|
||||
if timeout is not _NOT_GIVEN:
|
||||
kwargs["timeout"] = timeout
|
||||
if anthropic_proxy is not None:
|
||||
kwargs["proxy"] = anthropic_proxy
|
||||
return _AsyncHttpxClientWrapper(**kwargs)
|
||||
|
@ -502,6 +502,9 @@ class ChatAnthropic(BaseChatModel):
|
||||
Key init args — client params:
|
||||
timeout: Optional[float]
|
||||
Timeout for requests.
|
||||
anthropic_proxy: Optional[str]
|
||||
Proxy to use for the Anthropic clients, will be used for every API call.
|
||||
If not passed in will be read from env var ``ANTHROPIC_PROXY``.
|
||||
max_retries: int
|
||||
Max number of retries if a request fails.
|
||||
api_key: Optional[str]
|
||||
@ -1245,6 +1248,14 @@ class ChatAnthropic(BaseChatModel):
|
||||
)
|
||||
"""Automatically read from env var ``ANTHROPIC_API_KEY`` if not provided."""
|
||||
|
||||
anthropic_proxy: Optional[str] = Field(
|
||||
default_factory=from_env("ANTHROPIC_PROXY", default=None)
|
||||
)
|
||||
"""Proxy to use for the Anthropic clients, will be used for every API call.
|
||||
|
||||
If not provided, will attempt to read from the ``ANTHROPIC_PROXY`` environment
|
||||
variable."""
|
||||
|
||||
default_headers: Optional[Mapping[str, str]] = None
|
||||
"""Headers to pass to the Anthropic clients, will be used for every API call."""
|
||||
|
||||
@ -1360,6 +1371,8 @@ class ChatAnthropic(BaseChatModel):
|
||||
http_client_params = {"base_url": client_params["base_url"]}
|
||||
if "timeout" in client_params:
|
||||
http_client_params["timeout"] = client_params["timeout"]
|
||||
if self.anthropic_proxy:
|
||||
http_client_params["anthropic_proxy"] = self.anthropic_proxy
|
||||
http_client = _get_default_httpx_client(**http_client_params)
|
||||
params = {
|
||||
**client_params,
|
||||
@ -1373,6 +1386,8 @@ class ChatAnthropic(BaseChatModel):
|
||||
http_client_params = {"base_url": client_params["base_url"]}
|
||||
if "timeout" in client_params:
|
||||
http_client_params["timeout"] = client_params["timeout"]
|
||||
if self.anthropic_proxy:
|
||||
http_client_params["anthropic_proxy"] = self.anthropic_proxy
|
||||
http_client = _get_default_async_httpx_client(**http_client_params)
|
||||
params = {
|
||||
**client_params,
|
||||
|
@ -62,6 +62,55 @@ def test_anthropic_client_caching() -> None:
|
||||
assert llm1._client._client is not llm5._client._client
|
||||
|
||||
|
||||
def test_anthropic_proxy_support() -> None:
|
||||
"""Test that both sync and async clients support proxy configuration."""
|
||||
proxy_url = "http://proxy.example.com:8080"
|
||||
|
||||
# Test sync client with proxy
|
||||
llm_sync = ChatAnthropic(
|
||||
model="claude-3-5-sonnet-latest", anthropic_proxy=proxy_url
|
||||
)
|
||||
sync_client = llm_sync._client
|
||||
assert sync_client is not None
|
||||
|
||||
# Test async client with proxy - this should not raise TypeError
|
||||
async_client = llm_sync._async_client
|
||||
assert async_client is not None
|
||||
|
||||
# Test that clients with different proxy settings are not cached together
|
||||
llm_no_proxy = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
||||
llm_with_proxy = ChatAnthropic(
|
||||
model="claude-3-5-sonnet-latest", anthropic_proxy=proxy_url
|
||||
)
|
||||
|
||||
# Different proxy settings should result in different cached clients
|
||||
assert llm_no_proxy._client._client is not llm_with_proxy._client._client
|
||||
|
||||
|
||||
def test_anthropic_proxy_from_environment() -> None:
|
||||
"""Test that proxy can be set from ANTHROPIC_PROXY environment variable."""
|
||||
proxy_url = "http://env-proxy.example.com:8080"
|
||||
|
||||
# Test with environment variable set
|
||||
with patch.dict(os.environ, {"ANTHROPIC_PROXY": proxy_url}):
|
||||
llm = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
||||
assert llm.anthropic_proxy == proxy_url
|
||||
|
||||
# Should be able to create clients successfully
|
||||
sync_client = llm._client
|
||||
async_client = llm._async_client
|
||||
assert sync_client is not None
|
||||
assert async_client is not None
|
||||
|
||||
# Test that explicit parameter overrides environment variable
|
||||
with patch.dict(os.environ, {"ANTHROPIC_PROXY": "http://env-proxy.com"}):
|
||||
explicit_proxy = "http://explicit-proxy.com"
|
||||
llm = ChatAnthropic(
|
||||
model="claude-3-5-sonnet-latest", anthropic_proxy=explicit_proxy
|
||||
)
|
||||
assert llm.anthropic_proxy == explicit_proxy
|
||||
|
||||
|
||||
@pytest.mark.requires("anthropic")
|
||||
def test_anthropic_model_name_param() -> None:
|
||||
llm = ChatAnthropic(model_name="foo") # type: ignore[call-arg, call-arg]
|
||||
|
@ -0,0 +1,62 @@
|
||||
"""Test client utility functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from langchain_anthropic._client_utils import (
|
||||
_get_default_async_httpx_client,
|
||||
_get_default_httpx_client,
|
||||
)
|
||||
|
||||
|
||||
def test_sync_client_without_proxy() -> None:
|
||||
"""Test sync client creation without proxy."""
|
||||
client = _get_default_httpx_client(base_url="https://api.anthropic.com")
|
||||
|
||||
# Should not have proxy configured
|
||||
assert not hasattr(client, "proxies") or client.proxies is None
|
||||
|
||||
|
||||
def test_sync_client_with_proxy() -> None:
|
||||
"""Test sync client creation with proxy."""
|
||||
proxy_url = "http://proxy.example.com:8080"
|
||||
client = _get_default_httpx_client(
|
||||
base_url="https://api.anthropic.com", anthropic_proxy=proxy_url
|
||||
)
|
||||
|
||||
# Check internal _transport since httpx stores proxy configuration in the transport
|
||||
# layer
|
||||
transport = getattr(client, "_transport", None)
|
||||
assert transport is not None
|
||||
|
||||
|
||||
def test_async_client_without_proxy() -> None:
|
||||
"""Test async client creation without proxy."""
|
||||
client = _get_default_async_httpx_client(base_url="https://api.anthropic.com")
|
||||
|
||||
assert not hasattr(client, "proxies") or client.proxies is None
|
||||
|
||||
|
||||
def test_async_client_with_proxy() -> None:
|
||||
"""Test async client creation with proxy."""
|
||||
proxy_url = "http://proxy.example.com:8080"
|
||||
client = _get_default_async_httpx_client(
|
||||
base_url="https://api.anthropic.com", anthropic_proxy=proxy_url
|
||||
)
|
||||
|
||||
transport = getattr(client, "_transport", None)
|
||||
assert transport is not None
|
||||
|
||||
|
||||
def test_client_proxy_none_value() -> None:
|
||||
"""Test that explicitly passing None for proxy works correctly."""
|
||||
sync_client = _get_default_httpx_client(
|
||||
base_url="https://api.anthropic.com", anthropic_proxy=None
|
||||
)
|
||||
|
||||
async_client = _get_default_async_httpx_client(
|
||||
base_url="https://api.anthropic.com", anthropic_proxy=None
|
||||
)
|
||||
|
||||
# Both should be created successfully with None proxy
|
||||
assert sync_client is not None
|
||||
assert async_client is not None
|
Loading…
Reference in New Issue
Block a user