From 4030001ebf070948b8a7efe144c946ad684a57e7 Mon Sep 17 00:00:00 2001 From: Suraj Bayas Date: Thu, 18 Jun 2026 21:54:46 +0530 Subject: [PATCH] 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] Co-authored-by: open-swe[bot] --- agent/utils/model.py | 29 +++++++++++++++++++++++++++++ agent/webapp.py | 2 ++ 2 files changed, 31 insertions(+) diff --git a/agent/utils/model.py b/agent/utils/model.py index 77f2f651..19a77404 100644 --- a/agent/utils/model.py +++ b/agent/utils/model.py @@ -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}") diff --git a/agent/webapp.py b/agent/webapp.py index a834cd88..0ec55c50 100644 --- a/agent/webapp.py +++ b/agent/webapp.py @@ -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