"""Environment + provider configuration. Single source of truth for which LLM provider/model to use and for constructing the underlying SDK client. Fails fast with a clear message when the required API key is missing. """ from __future__ import annotations import os from dotenv import load_dotenv load_dotenv() PROVIDER = os.getenv("LLM_PROVIDER", "anthropic").strip().lower() _SUPPORTED = {"anthropic", "openai", "gemini"} _DEFAULT_MODELS = { "anthropic": "claude-sonnet-4-6", "openai": "gpt-4o", "gemini": "gemini-2.5-flash", } # Gemini's OpenAI-compatible surface — lets us reuse the OpenAI client/code path. _GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/" # Use the explicit MODEL if set, else a sensible per-provider default. MODEL = (os.getenv("MODEL") or _DEFAULT_MODELS.get(PROVIDER, "claude-sonnet-4-6")).strip() class ConfigError(RuntimeError): """Raised when configuration is incomplete or invalid.""" def _require_key(name: str) -> str: key = os.getenv(name, "").strip() if not key: raise ConfigError( f"{name} is not set. Copy .env.example to .env and add your key " f"(LLM_PROVIDER={PROVIDER})." ) return key def get_llm_client(): """Return an SDK client for the configured provider. The returned object is the raw provider SDK client. Callers should use ``src.utils.complete`` rather than calling the client directly so that provider differences stay in one place. """ if PROVIDER == "anthropic": import anthropic return anthropic.Anthropic(api_key=_require_key("ANTHROPIC_API_KEY")) if PROVIDER == "openai": from openai import OpenAI return OpenAI(api_key=_require_key("OPENAI_API_KEY")) if PROVIDER == "gemini": from openai import OpenAI # GEMINI_API_KEY preferred; GOOGLE_API_KEY accepted as a fallback. key = os.getenv("GEMINI_API_KEY", "").strip() or os.getenv( "GOOGLE_API_KEY", "" ).strip() if not key: raise ConfigError( "GEMINI_API_KEY is not set. Add it to .env (LLM_PROVIDER=gemini)." ) return OpenAI(api_key=key, base_url=_GEMINI_BASE_URL) raise ConfigError( f"Unsupported LLM_PROVIDER={PROVIDER!r}. Supported: {sorted(_SUPPORTED)}." )