mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(web): don't crash on a null web/backend config value
`_load_web_config()` is typed `-> dict` but returned `load_config().get("web",
{})`, which is `None` when the config has a present-but-null `web:` section
(YAML `web:` with no body). Every caller then does
`_load_web_config().get(...)` and raises `AttributeError: 'NoneType' object
has no attribute 'get'` — this hits `_get_backend`, `check_web_api_key`, and
the extract-char-limit reader.
Separately, `check_web_api_key()` read the backend as
`.get("backend", "").lower()`; a null `web.backend` value yields `None` (the
`""` default only applies when the key is absent), so `None.lower()` raised.
`check_web_api_key` is the `check_fn` gate for `web_search`/`web_extract`, so
this surfaced as an exception during tool-availability checking.
- Make `_load_web_config()` honor its `-> dict` contract (`... or {}`), fixing
the null-`web:`-section crash at every call site.
- Guard the backend value in `check_web_api_key` with `or ""`, mirroring the
existing guard in `_get_backend`.
Adds regression tests for both the null-backend-value and null-web-section
cases.
This commit is contained in:
parent
1a47769715
commit
5693265775
2 changed files with 24 additions and 2 deletions
|
|
@ -576,6 +576,22 @@ class TestCheckWebApiKey:
|
|||
from tools.web_tools import check_web_api_key
|
||||
assert check_web_api_key() is True
|
||||
|
||||
def test_null_backend_value_does_not_crash(self):
|
||||
# config.yaml with ``web:\n backend:`` yields backend=None. The gate
|
||||
# must not raise AttributeError on None.lower() — mirrors _get_backend.
|
||||
with patch("tools.web_tools._load_web_config", return_value={"backend": None}):
|
||||
from tools.web_tools import check_web_api_key
|
||||
assert check_web_api_key() is False
|
||||
|
||||
def test_null_web_section_does_not_crash(self):
|
||||
# config.yaml with a present-but-null ``web:`` section makes the raw
|
||||
# ``.get("web", {})`` return None; _load_web_config must still yield a
|
||||
# dict so no caller does None.get(...).
|
||||
with patch("hermes_cli.config.load_config", return_value={"web": None}):
|
||||
from tools.web_tools import _load_web_config, check_web_api_key
|
||||
assert _load_web_config() == {}
|
||||
assert check_web_api_key() is False
|
||||
|
||||
def test_firecrawl_key_only(self):
|
||||
with patch.dict(os.environ, {"FIRECRAWL_API_KEY": "fc-test"}):
|
||||
from tools.web_tools import check_web_api_key
|
||||
|
|
|
|||
|
|
@ -132,7 +132,11 @@ def _load_web_config() -> dict:
|
|||
"""Load the ``web:`` section from ~/.hermes/config.yaml."""
|
||||
try:
|
||||
from hermes_cli.config import load_config
|
||||
return load_config().get("web", {})
|
||||
# ``or {}``: a present-but-null ``web:`` section (YAML ``web:`` with no
|
||||
# body) makes ``.get("web", {})`` return None, which would break every
|
||||
# caller that does ``_load_web_config().get(...)``. Honor the ``-> dict``
|
||||
# contract so callers never see None.
|
||||
return load_config().get("web") or {}
|
||||
except (ImportError, Exception):
|
||||
return {}
|
||||
|
||||
|
|
@ -1004,7 +1008,9 @@ def check_web_api_key() -> bool:
|
|||
:func:`_is_backend_available`, which delegates non-legacy names to the
|
||||
registry.
|
||||
"""
|
||||
configured = _load_web_config().get("backend", "").lower().strip()
|
||||
# ``or ""``: a null ``web.backend`` value yields None from ``.get``, and
|
||||
# ``None.lower()`` would raise. Mirrors ``_get_backend``.
|
||||
configured = (_load_web_config().get("backend") or "").lower().strip()
|
||||
if configured and _is_backend_available(configured):
|
||||
return True
|
||||
# Any built-in backend with credentials present. This is a boolean OR, so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue