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:
Mukil Loganathan committed 2026-07-12 01:12:34 +00:00
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: