From 2ee8c983c0fc187e667440d660f2c5afbb6a7b55 Mon Sep 17 00:00:00 2001 From: Kailigithub <12250313+Kailigithub@users.noreply.github.com> Date: Fri, 29 May 2026 11:12:38 +0800 Subject: [PATCH 1/2] fix(web): honor Hermes config-aware SEARXNG_URL lookup --- plugins/web/searxng/provider.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins/web/searxng/provider.py b/plugins/web/searxng/provider.py index 043f6711c1b..6f747fc3f9d 100644 --- a/plugins/web/searxng/provider.py +++ b/plugins/web/searxng/provider.py @@ -31,6 +31,19 @@ from agent.web_search_provider import WebSearchProvider logger = logging.getLogger(__name__) +def _searxng_url() -> str: + """Return SEARXNG_URL from Hermes config-aware env, falling back to process env.""" + try: + from hermes_cli.config import get_env_value + + val = get_env_value("SEARXNG_URL") + except Exception: + val = None + if val is None: + val = os.getenv("SEARXNG_URL", "") + return (val or "").strip() + + class SearXNGWebSearchProvider(WebSearchProvider): """Search via a user-hosted SearXNG instance.""" @@ -44,7 +57,7 @@ class SearXNGWebSearchProvider(WebSearchProvider): def is_available(self) -> bool: """Return True when ``SEARXNG_URL`` is set.""" - return bool(os.getenv("SEARXNG_URL", "").strip()) + return bool(_searxng_url()) def supports_search(self) -> bool: return True @@ -56,7 +69,7 @@ class SearXNGWebSearchProvider(WebSearchProvider): """Execute a search against the configured SearXNG instance.""" import httpx - base_url = os.getenv("SEARXNG_URL", "").strip().rstrip("/") + base_url = _searxng_url().rstrip("/") if not base_url: return {"success": False, "error": "SEARXNG_URL is not set"} From 7df81d0557ee48aa9fff90d2c4213d6420875e2c Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Mon, 8 Jun 2026 01:12:32 +0530 Subject: [PATCH 2/2] fix(web): make _has_env config-aware so SEARXNG_URL auto-detect honors Hermes config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #34306. The provider fix made SearXNG *usable* with a config-only SEARXNG_URL, but tools/web_tools._has_env still read raw os.getenv, so the backend auto-detect cascade and check_web_api_key remained blind to it — SearXNG worked when explicitly selected but was never auto-selected. Route _has_env (and the SearXNG diagnostic print) through a config-aware _env_value helper mirroring the provider's _searxng_url(). Fixing the shared helper covers every provider key in one place. Adds regression tests for config-only auto-detect and check_web_api_key. See #34290. --- tests/tools/test_web_providers_searxng.py | 33 +++++++++++++++++++++++ tools/web_tools.py | 25 ++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) 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":