feat: validate LLM API keys on startup (#1438)

* feat: validate LLM API keys on startup

* fix: correct relative import for options module

* refactor: move imports to top of file

* style: fix linting and formatting issues

* refactor: scope LLM validation to local dev and rename function

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: open-swe[bot] <johannes@langchain.dev>
This commit is contained in:
Suraj Bayas 2026-06-18 21:54:46 +05:30 • committed by GitHub
parent c07434a221
commit 4030001ebf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 0 deletions

View file

@ -1,7 +1,10 @@
import os
from typing import Literal, TypedDict, Unpack
from langchain.chat_models import init_chat_model
from ..dashboard.options import DEFAULT_MODEL_ID
OPENAI_RESPONSES_WS_BASE_URL = "wss://api.openai.com/v1"
# Anthropic SDK default is 2; a 529 burst can outlive that. Bump to give the
@ -185,3 +188,29 @@ def provider_model_kwargs(
if effort is not None:
kwargs["model_kwargs"] = {"reasoning_effort": effort}
return kwargs
def validate_local_dev_llm_config() -> None:
"""Validate API keys for the locally configured default model.
This check only runs in localhost development environments and is
intended to catch missing credentials for the default model specified
via LLM_MODEL_ID/DEFAULT_MODEL_ID. Runtime model selection may come
from team, profile, or thread configuration and is not validated here.
"""
dashboard_url = os.environ.get("DASHBOARD_BASE_URL", "")
if not dashboard_url.startswith("http://localhost"):
return
model_id = os.environ.get("LLM_MODEL_ID", DEFAULT_MODEL_ID)
if model_id.startswith("openai:") and not os.environ.get("OPENAI_API_KEY"):
raise ValueError(f"OPENAI_API_KEY is required for configured model {model_id}")
elif model_id.startswith("anthropic:") and not os.environ.get("ANTHROPIC_API_KEY"):
raise ValueError(f"ANTHROPIC_API_KEY is required for configured model {model_id}")
elif model_id.startswith("google_genai:") and not os.environ.get("GOOGLE_API_KEY"):
raise ValueError(f"GOOGLE_API_KEY is required for configured model {model_id}")
elif model_id.startswith("groq:") and not os.environ.get("GROQ_API_KEY"):
raise ValueError(f"GROQ_API_KEY is required for configured model {model_id}")
elif model_id.startswith("fireworks:") and not os.environ.get("FIREWORKS_API_KEY"):
raise ValueError(f"FIREWORKS_API_KEY is required for configured model {model_id}")

View file

@ -131,9 +131,11 @@ logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
from .utils.model import validate_local_dev_llm_config
from .utils.sandbox import validate_sandbox_startup_config
validate_sandbox_startup_config()
validate_local_dev_llm_config()
yield