fix(moa): tolerate non-numeric values in hand-edited MoA preset config

_normalize_preset uses bare float() and int() to coerce
reference_temperature, aggregator_temperature, and max_tokens from
config.yaml.  When a user hand-edits a non-numeric value (e.g.
max_tokens: "8k" or reference_temperature: "hot"), the coercion raises
ValueError.  Since normalize_moa_config runs on every model-selection
and MoA turn (via resolve_moa_preset), the crash is unrecoverable and
blocks all MoA usage until the config is manually fixed.

Replace the bare casts with _coerce_float / _coerce_int helpers that
fall back to the default on TypeError/ValueError instead of raising.
This commit is contained in:
srojk34 2026-06-26 03:22:57 +03:00 committed by Teknium
parent 9b2af36d5a
commit f0678b031e
2 changed files with 63 additions and 3 deletions

View file

@ -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),
}

View file

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