mirror of
https://github.com/langchain-ai/langchain.git
synced 2026-10-05 17:35:28 +03:00
feat(openai): use LangSmith keys for gateway auth
When LangSmith Gateway is enabled, provider integrations can use deployment-injected LangSmith keys without requiring duplicate provider-specific API key env vars.\n\nCo-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
1 parent
9e9f6be79d
commit
dc729dcfa3
6 files changed
+180
-28
No files matched your search
@@ -78,6 +78,11 @@ _message_type_lookups = {
|
||||
_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:
|
||||
@@ -89,6 +94,10 @@ def _resolve_gateway_base_url() -> str | None:
|
||||
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__}"
|
||||
|
||||
|
||||
@@ -974,18 +983,17 @@ class ChatAnthropic(BaseChatModel):
|
||||
|
||||
anthropic_api_key: SecretStr = Field(
|
||||
alias="api_key",
|
||||
default_factory=lambda: SecretStr(
|
||||
(
|
||||
os.getenv("LANGSMITH_GATEWAY_API_KEY")
|
||||
if _resolve_gateway_base_url() is not None
|
||||
else None
|
||||
)
|
||||
or secret_from_env("ANTHROPIC_API_KEY", default="")().get_secret_value()
|
||||
),
|
||||
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.
|
||||
|
||||
If `LANGSMITH_GATEWAY` is enabled, `LANGSMITH_GATEWAY_API_KEY` takes precedence.
|
||||
If `LANGSMITH_GATEWAY` is enabled, `LANGSMITH_GATEWAY_API_KEY`,
|
||||
`LANGSMITH_API_KEY`, and `LANGCHAIN_API_KEY` take precedence.
|
||||
"""
|
||||
|
||||
anthropic_proxy: str | None = Field(
|
||||
|
||||
@@ -3608,6 +3608,47 @@ def test_langsmith_gateway_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
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:
|
||||
|
||||
@@ -110,6 +110,11 @@ logger = logging.getLogger(__name__)
|
||||
_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:
|
||||
@@ -121,6 +126,10 @@ def _resolve_gateway_base_url() -> str | None:
|
||||
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 {}
|
||||
return default.copy()
|
||||
@@ -768,27 +777,26 @@ class ChatFireworks(BaseChatModel):
|
||||
|
||||
fireworks_api_key: SecretStr = Field(
|
||||
alias="api_key",
|
||||
default_factory=lambda: SecretStr(
|
||||
(
|
||||
os.getenv("LANGSMITH_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`."
|
||||
),
|
||||
)().get_secret_value()
|
||||
),
|
||||
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` takes precedence.
|
||||
If `LANGSMITH_GATEWAY` is enabled, `LANGSMITH_GATEWAY_API_KEY`,
|
||||
`LANGSMITH_API_KEY`, and `LANGCHAIN_API_KEY` take precedence.
|
||||
"""
|
||||
|
||||
fireworks_api_base: str | None = Field(
|
||||
|
||||
@@ -1621,6 +1621,47 @@ def test_langsmith_gateway_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
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:
|
||||
|
||||
@@ -179,6 +179,11 @@ def _get_ssrf_safe_client() -> httpx.Client:
|
||||
_MODEL_PROFILES = cast(ModelProfileRegistry, _PROFILES)
|
||||
|
||||
_LANGSMITH_GATEWAY_DEFAULT_URL = "https://gateway.smith.langchain.com/openai/v1"
|
||||
_LANGSMITH_GATEWAY_API_KEY_ENV_VARS = (
|
||||
"LANGSMITH_GATEWAY_API_KEY",
|
||||
"LANGSMITH_API_KEY",
|
||||
"LANGCHAIN_API_KEY",
|
||||
)
|
||||
|
||||
|
||||
def _resolve_gateway_base_url() -> str | None:
|
||||
@@ -190,6 +195,10 @@ def _resolve_gateway_base_url() -> str | None:
|
||||
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 {}
|
||||
return default.copy()
|
||||
@@ -1212,10 +1221,11 @@ class BaseChatOpenAI(BaseChatModel):
|
||||
sync_api_key_value: str | Callable[[], str] | None = None
|
||||
async_api_key_value: str | Callable[[], Awaitable[str]] | None = None
|
||||
|
||||
if self.openai_api_key is None and _base_url_from_gateway:
|
||||
gateway_api_key = os.getenv("LANGSMITH_GATEWAY_API_KEY")
|
||||
explicit_api_key = bool({"api_key", "openai_api_key"} & self.model_fields_set)
|
||||
if _base_url_from_gateway and not explicit_api_key:
|
||||
gateway_api_key = _resolve_gateway_api_key()
|
||||
if gateway_api_key is not None:
|
||||
self.openai_api_key = SecretStr(gateway_api_key)
|
||||
self.openai_api_key = gateway_api_key
|
||||
|
||||
if self.openai_api_key is not None:
|
||||
# Because OpenAI and AsyncOpenAI clients support either sync or async
|
||||
|
||||
@@ -4531,6 +4531,50 @@ def test_langsmith_gateway_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert llm.openai_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("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.setenv(env_var, expected)
|
||||
llm = ChatOpenAI(model=OPENAI_TEST_MODEL)
|
||||
assert isinstance(llm.openai_api_key, SecretStr)
|
||||
assert llm.openai_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("OPENAI_API_KEY", "provider-key")
|
||||
llm = ChatOpenAI(model=OPENAI_TEST_MODEL)
|
||||
assert isinstance(llm.openai_api_key, SecretStr)
|
||||
assert llm.openai_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 = ChatOpenAI(model=OPENAI_TEST_MODEL, api_key="explicit-key")
|
||||
assert isinstance(llm.openai_api_key, SecretStr)
|
||||
assert llm.openai_api_key.get_secret_value() == "explicit-key"
|
||||
|
||||
|
||||
def test_langsmith_gateway_api_key_not_used_without_gateway(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
||||
Reference in new issue
Block a user