From 306e67f32d34c59b262b93c0af8287ad85e502a0 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 22 Mar 2026 03:59:29 -0700 Subject: [PATCH] fix: fail fast when explicit provider has no API key instead of silent OpenRouter fallback (#2445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a non-OpenRouter provider (e.g. minimax, anthropic) is set in config.yaml but its API key is missing, Hermes silently fell back to OpenRouter, causing confusing 404 errors. Now checks if the user explicitly configured a provider before falling back. Explicit providers raise RuntimeError with a clear message naming the missing env var. Auto/openrouter/custom providers still fall through to OpenRouter as before. Three code paths fixed: - run_agent.py AIAgent.__init__ — main client initialization - auxiliary_client.py call_llm — sync auxiliary calls - auxiliary_client.py call_llm_streaming — async auxiliary calls Based on PR #2272 by @StefanIsMe. Applied manually to fix a pconfig NameError in the original and extend to call_llm_streaming. Co-authored-by: StefanIsMe --- agent/auxiliary_client.py | 23 ++++++++++++++++++++--- run_agent.py | 10 ++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 163ad007..9589edad 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -1451,8 +1451,18 @@ def call_llm( api_key=resolved_api_key, ) if client is None: - # Fallback: try openrouter - if resolved_provider != "openrouter" and not resolved_base_url: + # When the user explicitly chose a non-OpenRouter provider but no + # credentials were found, fail fast instead of silently routing + # through OpenRouter (which causes confusing 404s). + _explicit = (resolved_provider or "").strip().lower() + if _explicit and _explicit not in ("auto", "openrouter", "custom"): + raise RuntimeError( + f"Provider '{_explicit}' is set in config.yaml but no API key " + f"was found. Set the {_explicit.upper()}_API_KEY environment " + f"variable, or switch to a different provider with `hermes model`." + ) + # For auto/custom, fall back to OpenRouter + if not resolved_base_url: logger.warning("Provider %s unavailable, falling back to openrouter", resolved_provider) client, final_model = _get_cached_client( @@ -1534,7 +1544,14 @@ async def async_call_llm( api_key=resolved_api_key, ) if client is None: - if resolved_provider != "openrouter" and not resolved_base_url: + _explicit = (resolved_provider or "").strip().lower() + if _explicit and _explicit not in ("auto", "openrouter", "custom"): + raise RuntimeError( + f"Provider '{_explicit}' is set in config.yaml but no API key " + f"was found. Set the {_explicit.upper()}_API_KEY environment " + f"variable, or switch to a different provider with `hermes model`." + ) + if not resolved_base_url: logger.warning("Provider %s unavailable, falling back to openrouter", resolved_provider) client, final_model = _get_cached_client( diff --git a/run_agent.py b/run_agent.py index 1074bba3..29df5373 100644 --- a/run_agent.py +++ b/run_agent.py @@ -736,6 +736,16 @@ class AIAgent: if hasattr(_routed_client, '_default_headers') and _routed_client._default_headers: client_kwargs["default_headers"] = dict(_routed_client._default_headers) else: + # When the user explicitly chose a non-OpenRouter provider + # but no credentials were found, fail fast with a clear + # message instead of silently routing through OpenRouter. + _explicit = (self.provider or "").strip().lower() + if _explicit and _explicit not in ("auto", "openrouter", "custom"): + raise RuntimeError( + f"Provider '{_explicit}' is set in config.yaml but no API key " + f"was found. Set the {_explicit.upper()}_API_KEY environment " + f"variable, or switch to a different provider with `hermes model`." + ) # Final fallback: try raw OpenRouter key client_kwargs = { "api_key": os.getenv("OPENROUTER_API_KEY", ""),