Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions libs/partners/anthropic/langchain_anthropic/chat_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import datetime
import hashlib
import json
import os
import re
import warnings
from collections.abc import AsyncIterator, Callable, Iterator, Mapping, Sequence
Expand Down Expand Up @@ -76,6 +77,18 @@

_MODEL_PROFILES = cast(ModelProfileRegistry, _PROFILES)

_LANGSMITH_GATEWAY_DEFAULT_URL = "https://gateway.smith.langchain.com/anthropic"


def _resolve_gateway_base_url() -> str | None:
raw = os.getenv("LANGSMITH_GATEWAY")
if raw is None or raw.lower() in ("false", "0", "no"):
return None
if raw.lower() in ("true", "1", "yes"):
return _LANGSMITH_GATEWAY_DEFAULT_URL
return raw


_USER_AGENT: Final[str] = f"langchain-anthropic/{__version__}"


Expand Down Expand Up @@ -945,15 +958,18 @@ class ChatAnthropic(BaseChatModel):

anthropic_api_url: str | None = Field(
alias="base_url",
default_factory=from_env(
default_factory=lambda: _resolve_gateway_base_url()
or from_env(
["ANTHROPIC_API_URL", "ANTHROPIC_BASE_URL"],
default="https://api.anthropic.com",
),
)(),
)
"""Base URL for API requests. Only specify if using a proxy or service emulator.

If a value isn't passed in, will attempt to read the value first from
`ANTHROPIC_API_URL` and if that is not set, `ANTHROPIC_BASE_URL`.

If `LANGSMITH_GATEWAY` is set, it takes precedence over both env vars.
"""

anthropic_api_key: SecretStr = Field(
Expand Down
24 changes: 24 additions & 0 deletions libs/partners/anthropic/tests/unit_tests/test_chat_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3574,3 +3574,27 @@ def mock_create(_payload: Any) -> list:
message_finish = cast("dict[str, Any]", stream_events[-1])
assert message_finish["event"] == "message-finish"
assert message_finish["metadata"]["stop_reason"] == "tool_use"


def test_langsmith_gateway_true(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.delenv("ANTHROPIC_API_URL", raising=False)
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
llm = ChatAnthropic(model=MODEL_NAME, api_key="test")
assert llm.anthropic_api_url == "https://gateway.smith.langchain.com/anthropic"


def test_langsmith_gateway_false(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "false")
monkeypatch.delenv("ANTHROPIC_API_URL", raising=False)
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
llm = ChatAnthropic(model=MODEL_NAME, api_key="test")
assert llm.anthropic_api_url == "https://api.anthropic.com"


def test_langsmith_gateway_unset(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("LANGSMITH_GATEWAY", raising=False)
monkeypatch.delenv("ANTHROPIC_API_URL", raising=False)
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
llm = ChatAnthropic(model=MODEL_NAME, api_key="test")
assert llm.anthropic_api_url == "https://api.anthropic.com"
18 changes: 17 additions & 1 deletion libs/partners/fireworks/langchain_fireworks/chat_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import contextlib
import json
import logging
import os
from collections.abc import AsyncIterator, Callable, Iterator, Mapping, Sequence
from operator import itemgetter
from typing import (
Expand Down Expand Up @@ -108,6 +109,17 @@

_MODEL_PROFILES = cast("ModelProfileRegistry", _PROFILES)

_LANGSMITH_GATEWAY_DEFAULT_URL = "https://gateway.smith.langchain.com/fireworks"


def _resolve_gateway_base_url() -> str | None:
raw = os.getenv("LANGSMITH_GATEWAY")
if raw is None or raw.lower() in ("false", "0", "no"):
return None
if raw.lower() in ("true", "1", "yes"):
return _LANGSMITH_GATEWAY_DEFAULT_URL
return raw


def _get_default_model_profile(model_name: str) -> ModelProfile:
default = _MODEL_PROFILES.get(model_name) or {}
Expand Down Expand Up @@ -771,10 +783,14 @@ def model(self) -> str:
"""

fireworks_api_base: str | None = Field(
alias="base_url", default_factory=from_env("FIREWORKS_API_BASE", default=None)
alias="base_url",
default_factory=lambda: _resolve_gateway_base_url()
or from_env("FIREWORKS_API_BASE", default=None)(),
)
"""Base URL path for API requests, leave blank if not using a proxy or service
emulator.

If `LANGSMITH_GATEWAY` is set, it takes precedence over `FIREWORKS_API_BASE`.
"""

request_timeout: float | tuple[float, float] | Any | None = Field(
Expand Down
20 changes: 20 additions & 0 deletions libs/partners/fireworks/tests/unit_tests/test_chat_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1591,3 +1591,23 @@ def test_request_timeout_tuple_normalized_to_httpx_timeout(
assert forwarded.connect == 5.0
assert forwarded.read == 30.0
assert async_mock.call_args.kwargs["timeout"] == forwarded


def test_langsmith_gateway_true(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
llm = _make_model()
assert llm.fireworks_api_base == "https://gateway.smith.langchain.com/fireworks"


def test_langsmith_gateway_false(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "false")
monkeypatch.delenv("FIREWORKS_API_BASE", raising=False)
llm = _make_model()
assert llm.fireworks_api_base is None


def test_langsmith_gateway_unset(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("LANGSMITH_GATEWAY", raising=False)
monkeypatch.delenv("FIREWORKS_API_BASE", raising=False)
llm = _make_model()
assert llm.fireworks_api_base is None
56 changes: 37 additions & 19 deletions libs/partners/openai/langchain_openai/chat_models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,17 @@ def _get_ssrf_safe_client() -> httpx.Client:

_MODEL_PROFILES = cast(ModelProfileRegistry, _PROFILES)

_LANGSMITH_GATEWAY_DEFAULT_URL = "https://gateway.smith.langchain.com/openai/v1"


def _resolve_gateway_base_url() -> str | None:
raw = os.getenv("LANGSMITH_GATEWAY")
if raw is None or raw.lower() in ("false", "0", "no"):
return None
if raw.lower() in ("true", "1", "yes"):
return _LANGSMITH_GATEWAY_DEFAULT_URL
return raw


def _get_default_model_profile(model_name: str) -> ModelProfile:
default = _MODEL_PROFILES.get(model_name) or {}
Expand Down Expand Up @@ -1167,26 +1178,33 @@ def validate_environment(self) -> Self:
or os.getenv("OPENAI_ORG_ID")
or os.getenv("OPENAI_ORGANIZATION")
)
self.openai_api_base = self.openai_api_base or os.getenv("OPENAI_API_BASE")

# Enable stream_usage by default if using default base URL and client
if (
all(
getattr(self, key, None) is None
for key in (
"stream_usage",
"openai_proxy",
"openai_api_base",
"base_url",
"client",
"root_client",
"async_client",
"root_async_client",
"http_client",
"http_async_client",
)
_gateway_base_url = _resolve_gateway_base_url()
_base_url_from_gateway = False
if self.openai_api_base is None:
if _gateway_base_url is not None:
self.openai_api_base = _gateway_base_url
_base_url_from_gateway = True
else:
self.openai_api_base = os.getenv("OPENAI_API_BASE")

# Enable stream_usage by default if using default base URL and client,
# or when the base URL was set by the LangSmith gateway (which proxies
# to OpenAI and supports streaming token usage).
if all(
getattr(self, key, None) is None
for key in (
"stream_usage",
"openai_proxy",
"client",
"root_client",
"async_client",
"root_async_client",
"http_client",
"http_async_client",
)
and "OPENAI_BASE_URL" not in os.environ
) and (
_base_url_from_gateway
or (self.openai_api_base is None and "OPENAI_BASE_URL" not in os.environ)
):
self.stream_usage = True

Expand Down
19 changes: 19 additions & 0 deletions libs/partners/openai/tests/unit_tests/chat_models/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4501,3 +4501,22 @@ def test_defer_loading_in_responses_api_payload() -> None:
assert weather_tool["defer_loading"] is True
assert weather_tool["type"] == "function"
assert {"type": "tool_search"} in result["tools"]


def test_langsmith_gateway_true(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
llm = ChatOpenAI(model=OPENAI_TEST_MODEL, api_key="test")
assert llm.openai_api_base == "https://gateway.smith.langchain.com/openai/v1"
assert llm.stream_usage is True


def test_langsmith_gateway_false(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "false")
llm = ChatOpenAI(model=OPENAI_TEST_MODEL, api_key="test")
assert llm.openai_api_base is None


def test_langsmith_gateway_unset(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("LANGSMITH_GATEWAY", raising=False)
llm = ChatOpenAI(model=OPENAI_TEST_MODEL, api_key="test")
assert llm.openai_api_base is None
Loading