Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
42 changes: 38 additions & 4 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,27 @@

_MODEL_PROFILES = cast(ModelProfileRegistry, _PROFILES)

_LANGSMITH_GATEWAY_DEFAULT_URL = "https://gateway.smith.langchain.com/anthropic"
_LANGSMITH_GATEWAY_API_KEY_ENV_VARS = (
"LANGSMITH_GATEWAY_API_KEY",
"LANGSMITH_API_KEY",
"LANGCHAIN_API_KEY",
)


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 _resolve_gateway_api_key() -> SecretStr | None:
return secret_from_env(_LANGSMITH_GATEWAY_API_KEY_ENV_VARS, default=None)()


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


Expand Down Expand Up @@ -945,22 +967,34 @@ 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(
alias="api_key",
default_factory=secret_from_env("ANTHROPIC_API_KEY", default=""),
default_factory=lambda: (
_resolve_gateway_api_key()
if _resolve_gateway_base_url() is not None
else None
)
or secret_from_env("ANTHROPIC_API_KEY", default="")(),
)
"""Automatically read from env var `ANTHROPIC_API_KEY` if not provided."""
"""Automatically read from env var `ANTHROPIC_API_KEY` if not provided.

If `LANGSMITH_GATEWAY` is enabled, `LANGSMITH_GATEWAY_API_KEY`,
`LANGSMITH_API_KEY`, and `LANGCHAIN_API_KEY` take precedence.
"""

anthropic_proxy: str | None = Field(
default_factory=from_env("ANTHROPIC_PROXY", default=None)
Expand Down
83 changes: 83 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,86 @@ 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"


def test_langsmith_gateway_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
llm = ChatAnthropic(model=MODEL_NAME)
assert llm.anthropic_api_key.get_secret_value() == "gateway-key"


@pytest.mark.parametrize(
("env_var", "expected"),
[
("LANGSMITH_API_KEY", "langsmith-key"),
("LANGCHAIN_API_KEY", "langchain-key"),
],
)
def test_langsmith_gateway_api_key_langsmith_fallbacks(
monkeypatch: pytest.MonkeyPatch, env_var: str, expected: str
) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.delenv("LANGSMITH_GATEWAY_API_KEY", raising=False)
monkeypatch.delenv("LANGSMITH_API_KEY", raising=False)
monkeypatch.delenv("LANGCHAIN_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.setenv(env_var, expected)
llm = ChatAnthropic(model=MODEL_NAME)
assert llm.anthropic_api_key.get_secret_value() == expected


def test_langsmith_gateway_api_key_precedence(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
monkeypatch.setenv("LANGSMITH_API_KEY", "langsmith-key")
monkeypatch.setenv("LANGCHAIN_API_KEY", "langchain-key")
monkeypatch.setenv("ANTHROPIC_API_KEY", "provider-key")
llm = ChatAnthropic(model=MODEL_NAME)
assert llm.anthropic_api_key.get_secret_value() == "gateway-key"


def test_langsmith_gateway_explicit_api_key_takes_precedence(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
llm = ChatAnthropic(model=MODEL_NAME, api_key="explicit-key")
assert llm.anthropic_api_key.get_secret_value() == "explicit-key"


def test_langsmith_gateway_api_key_not_used_without_gateway(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.delenv("LANGSMITH_GATEWAY", raising=False)
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
monkeypatch.setenv("ANTHROPIC_API_KEY", "provider-key")
llm = ChatAnthropic(model=MODEL_NAME)
assert llm.anthropic_api_key.get_secret_value() == "provider-key"
39 changes: 36 additions & 3 deletions 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,26 @@

_MODEL_PROFILES = cast("ModelProfileRegistry", _PROFILES)

_LANGSMITH_GATEWAY_DEFAULT_URL = "https://gateway.smith.langchain.com/fireworks"
_LANGSMITH_GATEWAY_API_KEY_ENV_VARS = (
"LANGSMITH_GATEWAY_API_KEY",
"LANGSMITH_API_KEY",
"LANGCHAIN_API_KEY",
)


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 _resolve_gateway_api_key() -> SecretStr | None:
return secret_from_env(_LANGSMITH_GATEWAY_API_KEY_ENV_VARS, default=None)()


def _get_default_model_profile(model_name: str) -> ModelProfile:
default = _MODEL_PROFILES.get(model_name) or {}
Expand Down Expand Up @@ -756,25 +777,37 @@ def model(self) -> str:

fireworks_api_key: SecretStr = Field(
alias="api_key",
default_factory=secret_from_env(
default_factory=lambda: (
_resolve_gateway_api_key()
if _resolve_gateway_base_url() is not None
else None
)
or secret_from_env(
"FIREWORKS_API_KEY",
error_message=(
"You must specify an api key. "
"You can pass it an argument as `api_key=...` or "
"set the environment variable `FIREWORKS_API_KEY`."
),
),
)(),
)
"""Fireworks API key.

Automatically read from env variable `FIREWORKS_API_KEY` if not provided.

If `LANGSMITH_GATEWAY` is enabled, `LANGSMITH_GATEWAY_API_KEY`,
`LANGSMITH_API_KEY`, and `LANGCHAIN_API_KEY` take precedence.
"""

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
79 changes: 79 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,82 @@ 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


def test_langsmith_gateway_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
monkeypatch.delenv("FIREWORKS_API_KEY", raising=False)
llm = ChatFireworks(model=MODEL_NAME) # type: ignore[call-arg]
assert llm.fireworks_api_key.get_secret_value() == "gateway-key"


@pytest.mark.parametrize(
("env_var", "expected"),
[
("LANGSMITH_API_KEY", "langsmith-key"),
("LANGCHAIN_API_KEY", "langchain-key"),
],
)
def test_langsmith_gateway_api_key_langsmith_fallbacks(
monkeypatch: pytest.MonkeyPatch, env_var: str, expected: str
) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.delenv("LANGSMITH_GATEWAY_API_KEY", raising=False)
monkeypatch.delenv("LANGSMITH_API_KEY", raising=False)
monkeypatch.delenv("LANGCHAIN_API_KEY", raising=False)
monkeypatch.delenv("FIREWORKS_API_KEY", raising=False)
monkeypatch.setenv(env_var, expected)
llm = ChatFireworks(model=MODEL_NAME) # type: ignore[call-arg]
assert llm.fireworks_api_key.get_secret_value() == expected


def test_langsmith_gateway_api_key_precedence(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
monkeypatch.setenv("LANGSMITH_API_KEY", "langsmith-key")
monkeypatch.setenv("LANGCHAIN_API_KEY", "langchain-key")
monkeypatch.setenv("FIREWORKS_API_KEY", "provider-key")
llm = ChatFireworks(model=MODEL_NAME) # type: ignore[call-arg]
assert llm.fireworks_api_key.get_secret_value() == "gateway-key"


def test_langsmith_gateway_explicit_api_key_takes_precedence(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("LANGSMITH_GATEWAY", "true")
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
llm = ChatFireworks(model=MODEL_NAME, api_key="explicit-key") # type: ignore[call-arg]
assert llm.fireworks_api_key.get_secret_value() == "explicit-key"


def test_langsmith_gateway_api_key_not_used_without_gateway(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.delenv("LANGSMITH_GATEWAY", raising=False)
monkeypatch.setenv("LANGSMITH_GATEWAY_API_KEY", "gateway-key")
monkeypatch.setenv("FIREWORKS_API_KEY", "provider-key")
llm = ChatFireworks(model=MODEL_NAME) # type: ignore[call-arg]
assert llm.fireworks_api_key.get_secret_value() == "provider-key"
Loading
Loading