diff --git a/hermes_cli/moa_config.py b/hermes_cli/moa_config.py index 4c0db3f2c35..bd74ced49f8 100644 --- a/hermes_cli/moa_config.py +++ b/hermes_cli/moa_config.py @@ -21,6 +21,27 @@ DEFAULT_MOA_AGGREGATOR: dict[str, str] = { } +def _coerce_float(value: Any, default: float) -> float: + if value is None or value == "": + return default + try: + return float(value) + except (TypeError, ValueError): + return default + + +def _coerce_int(value: Any, default: int) -> int: + if value is None or value == "": + return default + try: + return int(value) + except (TypeError, ValueError): + try: + return int(float(value)) + except (TypeError, ValueError): + return default + + def _clean_slot(slot: Any) -> dict[str, str] | None: if not isinstance(slot, dict): return None @@ -57,9 +78,9 @@ def _normalize_preset(raw: Any) -> dict[str, Any]: "enabled": bool(raw.get("enabled", True)), "reference_models": refs, "aggregator": aggregator, - "reference_temperature": float(raw.get("reference_temperature", 0.6) or 0.6), - "aggregator_temperature": float(raw.get("aggregator_temperature", 0.4) or 0.4), - "max_tokens": int(raw.get("max_tokens", 4096) or 4096), + "reference_temperature": _coerce_float(raw.get("reference_temperature"), 0.6), + "aggregator_temperature": _coerce_float(raw.get("aggregator_temperature"), 0.4), + "max_tokens": _coerce_int(raw.get("max_tokens"), 4096), } diff --git a/tests/hermes_cli/test_moa_config.py b/tests/hermes_cli/test_moa_config.py index d02b2f06459..630d183927d 100644 --- a/tests/hermes_cli/test_moa_config.py +++ b/tests/hermes_cli/test_moa_config.py @@ -55,6 +55,45 @@ def test_legacy_flat_config_becomes_default_preset(): ] +def test_normalize_moa_config_tolerates_non_numeric_values(): + """Non-numeric strings in hand-edited config.yaml must degrade to defaults + instead of crashing normalize_moa_config with ValueError.""" + cfg = normalize_moa_config( + { + "presets": { + "broken": { + "max_tokens": "notanumber", + "reference_temperature": "hot", + "aggregator_temperature": "", + } + } + } + ) + + preset = cfg["presets"]["broken"] + assert preset["max_tokens"] == 4096 + assert preset["reference_temperature"] == 0.6 + assert preset["aggregator_temperature"] == 0.4 + + +def test_normalize_moa_config_coerces_numeric_strings(): + """Valid numeric strings (e.g. from YAML round-trip) must coerce correctly.""" + cfg = normalize_moa_config({"max_tokens": "8192", "reference_temperature": "0.9"}) + + preset = cfg["presets"][DEFAULT_MOA_PRESET_NAME] + assert preset["max_tokens"] == 8192 + assert preset["reference_temperature"] == 0.9 + + +def test_normalize_moa_config_coerces_float_max_tokens(): + """max_tokens: 4096.0 (float from YAML) must coerce to int.""" + cfg = normalize_moa_config({"max_tokens": 4096.0}) + assert cfg["presets"][DEFAULT_MOA_PRESET_NAME]["max_tokens"] == 4096 + + cfg2 = normalize_moa_config({"max_tokens": "4096.5"}) + assert cfg2["presets"][DEFAULT_MOA_PRESET_NAME]["max_tokens"] == 4096 + + def test_exact_preset_matching_is_not_fuzzy(): config = {"presets": {"coding": {}, "review": {}}}