diff --git a/tests/tools/test_web_providers_searxng.py b/tests/tools/test_web_providers_searxng.py index 3a4f6d8d6e0..e093532bf37 100644 --- a/tests/tools/test_web_providers_searxng.py +++ b/tests/tools/test_web_providers_searxng.py @@ -267,6 +267,26 @@ class TestGetBackendSearXNG: monkeypatch.setattr(web_tools, "_is_tool_gateway_ready", lambda: False) assert web_tools._get_backend() == "tavily" + def test_auto_detect_picks_searxng_when_url_only_in_hermes_config(self, monkeypatch): + """#34290 follow-up: a config-only SEARXNG_URL (absent from process env) + must still drive auto-detect via the now config-aware ``_has_env``.""" + from hermes_cli import config as hermes_config + from tools import web_tools + monkeypatch.setattr(web_tools, "_load_web_config", lambda: {}) + monkeypatch.delenv("FIRECRAWL_API_KEY", raising=False) + monkeypatch.delenv("FIRECRAWL_API_URL", raising=False) + monkeypatch.delenv("PARALLEL_API_KEY", raising=False) + monkeypatch.delenv("TAVILY_API_KEY", raising=False) + monkeypatch.delenv("EXA_API_KEY", raising=False) + monkeypatch.delenv("SEARXNG_URL", raising=False) + monkeypatch.setattr( + hermes_config, + "get_env_value", + lambda key: "http://config-only:8080" if key == "SEARXNG_URL" else None, + ) + monkeypatch.setattr(web_tools, "_is_tool_gateway_ready", lambda: False) + assert web_tools._get_backend() == "searxng" + # --------------------------------------------------------------------------- # Integration: check_web_api_key includes searxng @@ -280,6 +300,19 @@ class TestCheckWebApiKey: monkeypatch.setenv("SEARXNG_URL", "http://localhost:8080") assert web_tools.check_web_api_key() is True + def test_searxng_config_only_satisfies_check_web_api_key(self, monkeypatch): + """#34290 follow-up: config-only SEARXNG_URL satisfies the credential check.""" + from hermes_cli import config as hermes_config + from tools import web_tools + monkeypatch.setattr(web_tools, "_load_web_config", lambda: {"backend": "searxng"}) + monkeypatch.delenv("SEARXNG_URL", raising=False) + monkeypatch.setattr( + hermes_config, + "get_env_value", + lambda key: "http://config-only:8080" if key == "SEARXNG_URL" else None, + ) + assert web_tools.check_web_api_key() is True + def test_no_credentials_fails(self, monkeypatch): from tools import web_tools monkeypatch.setattr(web_tools, "_load_web_config", lambda: {}) diff --git a/tools/web_tools.py b/tools/web_tools.py index 9bdd7c32d6e..d8d922dc0ac 100644 --- a/tools/web_tools.py +++ b/tools/web_tools.py @@ -110,9 +110,28 @@ logger = logging.getLogger(__name__) # ─── Backend Selection ──────────────────────────────────────────────────────── +def _env_value(name: str) -> str: + """Resolve ``name`` via Hermes config-aware env, falling back to process env. + + Mirrors the SearXNG provider's ``_searxng_url()`` so that values set + through Hermes' config/.env layer (``hermes config set``, ``hermes tools``) + are honored here too — not just raw process-env exports. Without this, + a config-only ``SEARXNG_URL`` (or any provider key) leaves the backend + auto-detect cascade and ``check_web_api_key()`` blind to it. See #34290. + """ + try: + from hermes_cli.config import get_env_value + + val = get_env_value(name) + except Exception: + val = None + if val is None: + val = os.getenv(name, "") + return (val or "").strip() + + def _has_env(name: str) -> bool: - val = os.getenv(name) - return bool(val and val.strip()) + return bool(_env_value(name)) def _load_web_config() -> dict: """Load the ``web:`` section from ~/.hermes/config.yaml.""" @@ -1204,7 +1223,7 @@ if __name__ == "__main__": elif backend == "tavily": print(" Using Tavily API (https://tavily.com)") elif backend == "searxng": - print(f" Using SearXNG (search only): {os.getenv('SEARXNG_URL', '').strip()}") + print(f" Using SearXNG (search only): {_env_value('SEARXNG_URL')}") elif backend == "brave-free": print(" Using Brave Search free tier (search only)") elif backend == "ddgs":