Set LiteLLM API key from LLM_API_KEY env var if present. Load order (override=False, so first one wins): 1. System environment variables (already set) 2. KB-local .env (kb_dir/.env) 3. Global .env (~/.config/openkb/.env) Also propagates to provider-specific env vars (OPENAI
(kb_dir: Path | None = None)
| 134 | |
| 135 | |
| 136 | def _setup_llm_key(kb_dir: Path | None = None) -> None: |
| 137 | """Set LiteLLM API key from LLM_API_KEY env var if present. |
| 138 | |
| 139 | Load order (override=False, so first one wins): |
| 140 | 1. System environment variables (already set) |
| 141 | 2. KB-local .env (kb_dir/.env) |
| 142 | 3. Global .env (~/.config/openkb/.env) |
| 143 | |
| 144 | Also propagates to provider-specific env vars (OPENAI_API_KEY, etc.) |
| 145 | so that the Agents SDK litellm provider can pick them up. |
| 146 | Provider is auto-detected from the KB config when available; otherwise |
| 147 | a common provider set is used as a fallback. |
| 148 | """ |
| 149 | if kb_dir is not None: |
| 150 | env_file = kb_dir / ".env" |
| 151 | if env_file.exists(): |
| 152 | load_dotenv(env_file, override=False) |
| 153 | |
| 154 | from openkb.config import GLOBAL_CONFIG_DIR |
| 155 | |
| 156 | global_env = GLOBAL_CONFIG_DIR / ".env" |
| 157 | if global_env.exists(): |
| 158 | load_dotenv(global_env, override=False) |
| 159 | |
| 160 | api_key = os.environ.get("LLM_API_KEY", "") |
| 161 | |
| 162 | # Try to resolve the active provider, extra headers, and request timeout |
| 163 | # from the KB config |
| 164 | provider: str | None = None |
| 165 | extra_headers: dict[str, str] = {} |
| 166 | timeout: float | None = None |
| 167 | litellm_settings: dict = {} |
| 168 | if kb_dir is not None: |
| 169 | config_path = kb_dir / ".openkb" / "config.yaml" |
| 170 | if config_path.exists(): |
| 171 | config = load_config(config_path) |
| 172 | model = config.get("model", "") |
| 173 | provider = _extract_provider(str(model)) |
| 174 | extra_headers = resolve_extra_headers(config) |
| 175 | timeout = resolve_timeout(config) |
| 176 | litellm_settings = resolve_litellm_settings(config) |
| 177 | # `timeout` / `extra_headers` in the block route to the per-call |
| 178 | # stashes (replacing the legacy top-level keys); the rest are globals. |
| 179 | if "extra_headers" in litellm_settings: |
| 180 | extra_headers = resolve_extra_headers( |
| 181 | {"extra_headers": litellm_settings.pop("extra_headers")} |
| 182 | ) |
| 183 | if "timeout" in litellm_settings: |
| 184 | timeout = resolve_timeout({"timeout": litellm_settings.pop("timeout")}) |
| 185 | set_extra_headers(extra_headers) |
| 186 | set_timeout(timeout) |
| 187 | _apply_litellm_settings(litellm_settings) |
| 188 | |
| 189 | if not api_key: |
| 190 | # Check if any provider key is already set. OAuth-based providers |
| 191 | # (ChatGPT subscription, GitHub Copilot) don't use API keys at all, |
| 192 | # so the warning is skipped for them. |
| 193 | check_keys = (f"{provider.upper()}_API_KEY",) if provider else _KNOWN_PROVIDER_KEYS |