mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-28 18:19:28 +00:00
fix(moa): surface stale presets without retries
Keep invalid persisted preset names fail-closed, list the valid configured choices, and classify the local lookup failure as deterministic so it reaches Desktop immediately.
This commit is contained in:
parent
61bbc39330
commit
4c0546c9cc
4 changed files with 62 additions and 1 deletions
|
|
@ -775,6 +775,14 @@ def classify_api_error(
|
|||
if classified is not None:
|
||||
return classified
|
||||
|
||||
# Local MoA config drift is deterministic: a persisted session can retain
|
||||
# a preset name that was later renamed/deleted. Retrying the same lookup
|
||||
# cannot recover and makes a clear config error look like an API outage.
|
||||
from agent.errors import MoAPresetNotFoundError
|
||||
|
||||
if isinstance(error, MoAPresetNotFoundError):
|
||||
return _result(FailoverReason.model_not_found, retryable=False)
|
||||
|
||||
# ── 3. Error code classification ────────────────────────────────
|
||||
|
||||
if error_code:
|
||||
|
|
|
|||
|
|
@ -7,3 +7,7 @@ class EmptyStreamError(RuntimeError):
|
|||
"""Raised when a provider closes a stream without yielding a response."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MoAPresetNotFoundError(ValueError):
|
||||
"""Raised when a persisted MoA preset no longer exists in config."""
|
||||
|
|
|
|||
|
|
@ -295,7 +295,13 @@ def resolve_moa_preset(config: Any, name: str | None = None) -> dict[str, Any]:
|
|||
preset_name = str(name or cfg.get("default_preset") or DEFAULT_MOA_PRESET_NAME).strip()
|
||||
preset = cfg["presets"].get(preset_name)
|
||||
if preset is None:
|
||||
raise KeyError(preset_name)
|
||||
from agent.errors import MoAPresetNotFoundError
|
||||
|
||||
available = ", ".join(cfg["presets"]) or "(none)"
|
||||
raise MoAPresetNotFoundError(
|
||||
f"MoA preset '{preset_name}' was not found. Available presets: "
|
||||
f"{available}. Run `hermes moa list`."
|
||||
)
|
||||
return deepcopy(preset)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from agent.errors import MoAPresetNotFoundError
|
||||
from hermes_cli.moa_config import (
|
||||
DEFAULT_MOA_AGGREGATOR,
|
||||
DEFAULT_MOA_PRESET_NAME,
|
||||
|
|
@ -205,6 +208,46 @@ def test_resolve_moa_preset_returns_requested_model_set():
|
|||
]
|
||||
|
||||
|
||||
def test_resolve_missing_moa_preset_has_actionable_error():
|
||||
cfg = {
|
||||
"default_preset": "日常对话-高峰",
|
||||
"presets": {"日常对话-高峰": {}, "日常对话-非高峰": {}},
|
||||
}
|
||||
|
||||
with pytest.raises(MoAPresetNotFoundError) as exc_info:
|
||||
resolve_moa_preset(cfg, "日常对话-高峰期")
|
||||
|
||||
message = str(exc_info.value)
|
||||
assert "日常对话-高峰期" in message
|
||||
assert "日常对话-高峰" in message
|
||||
assert "日常对话-非高峰" in message
|
||||
assert "hermes moa list" in message
|
||||
|
||||
|
||||
def test_resolve_missing_moa_preset_does_not_silently_fallback():
|
||||
cfg = {
|
||||
"default_preset": "日常对话-高峰",
|
||||
"presets": {"日常对话-高峰": {}},
|
||||
}
|
||||
|
||||
with pytest.raises(MoAPresetNotFoundError):
|
||||
resolve_moa_preset(cfg, "renamed-preset")
|
||||
|
||||
|
||||
def test_missing_moa_preset_is_non_retryable():
|
||||
from agent.error_classifier import FailoverReason, classify_api_error
|
||||
|
||||
result = classify_api_error(
|
||||
MoAPresetNotFoundError("MoA preset 'old' was not found"),
|
||||
provider="moa",
|
||||
model="old",
|
||||
)
|
||||
|
||||
assert result.reason == FailoverReason.model_not_found
|
||||
assert result.retryable is False
|
||||
assert result.should_fallback is False
|
||||
|
||||
|
||||
def test_build_moa_turn_prompt_encodes_one_shot_default_preset():
|
||||
prompt = build_moa_turn_prompt("write a file then inspect it")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue