Merge pull request #41482 from kshitijk4poor/salvage/searxng-config-env-34306

fix(web): honor Hermes config-aware SEARXNG_URL lookup (salvage #34306 + auto-detect follow-up)
This commit is contained in:
kshitij 2026-06-07 12:54:32 -07:00 committed by GitHub
commit c986377236
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 70 additions and 5 deletions

View file

@ -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"}

View file

@ -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: {})

View file

@ -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":