mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
fix(moa): make reference_timeout default inherit auxiliary config; filter recursion-guard skips
Follow-ups for salvaged #53784: - reference_timeout now defaults to None = no per-preset override, so the reference fan-out inherits auxiliary.moa_reference.timeout (900s default) via call_llm's own per-task timeout resolution. The PR's 30.0s default would have cut off long-thinking advisors mid-response, and its 300s max cap capped legitimate explicit values — both removed. Explicit per-preset values are still honored as-is. - _is_failed_reference also treats '[skipped: …]' recursion-guard notes as internal sentinels, keeping them out of both aggregator prompts. - Dashboard/desktop TS types updated to number | null; web_server validator accepts null/empty as 'inherit'.
This commit is contained in:
parent
223881e492
commit
d3fc27bbf8
7 changed files with 73 additions and 21 deletions
|
|
@ -428,7 +428,7 @@ def _run_reference(
|
|||
*,
|
||||
temperature: float | None = None,
|
||||
max_tokens: int | None = None,
|
||||
reference_timeout: float = 30.0,
|
||||
reference_timeout: float | None = None,
|
||||
) -> tuple[str, str, Any]:
|
||||
"""Call one reference model and return ``(label, text, accounting)``.
|
||||
|
||||
|
|
@ -573,7 +573,7 @@ def _run_references_parallel(
|
|||
temperature: float | None = None,
|
||||
max_tokens: int | None = None,
|
||||
progress_callback: Any = None,
|
||||
reference_timeout: float = 30.0,
|
||||
reference_timeout: float | None = None,
|
||||
) -> list[tuple[str, str, Any]]:
|
||||
"""Fan out all reference models in parallel, returning outputs in order.
|
||||
|
||||
|
|
@ -867,8 +867,15 @@ def _preset_temperature(preset: dict[str, Any], key: str) -> float | None:
|
|||
|
||||
|
||||
def _is_failed_reference(text: str) -> bool:
|
||||
"""Return whether a reference output is the internal failure sentinel."""
|
||||
return text.lstrip().lower().startswith("[failed:")
|
||||
"""Return whether a reference output is an internal failure/skip sentinel.
|
||||
|
||||
Covers both the ``[failed: …]`` notes produced when a reference call
|
||||
raises (which may embed raw provider error text) and the
|
||||
``[skipped: …]`` recursion-guard notes — neither is real advice, so
|
||||
neither belongs in the aggregator prompt.
|
||||
"""
|
||||
sentinel = text.lstrip().lower()
|
||||
return sentinel.startswith("[failed:") or sentinel.startswith("[skipped:")
|
||||
|
||||
|
||||
def _successful_references(
|
||||
|
|
@ -899,7 +906,7 @@ def aggregate_moa_context(
|
|||
temperature: float | None = None,
|
||||
aggregator_temperature: float | None = None,
|
||||
reference_max_tokens: int | None = None,
|
||||
reference_timeout: float = 30.0,
|
||||
reference_timeout: float | None = None,
|
||||
degraded_reference_policy: str = "loud",
|
||||
) -> str:
|
||||
"""Run configured reference models and synthesize their advice.
|
||||
|
|
@ -1346,7 +1353,13 @@ class MoAChatCompletions:
|
|||
# explicit values. See _preset_temperature.
|
||||
temperature = _preset_temperature(preset, "reference_temperature")
|
||||
aggregator_temperature = _preset_temperature(preset, "aggregator_temperature")
|
||||
reference_timeout = float(preset.get("reference_timeout") or 30.0)
|
||||
# None (the default) = no per-preset override; the fan-out inherits
|
||||
# auxiliary.moa_reference.timeout (900s default) via call_llm's own
|
||||
# per-task timeout resolution. Explicit per-preset values are honored.
|
||||
raw_reference_timeout = preset.get("reference_timeout")
|
||||
reference_timeout = (
|
||||
float(raw_reference_timeout) if raw_reference_timeout else None
|
||||
)
|
||||
degraded_reference_policy = str(
|
||||
preset.get("degraded_reference_policy") or "loud"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1019,7 +1019,7 @@ export interface MoaConfigResponse {
|
|||
reference_max_tokens?: number | null
|
||||
/** Fan-out cadence (per_iteration | user_turn) — round-tripped. */
|
||||
fanout?: string
|
||||
reference_timeout: number
|
||||
reference_timeout: number | null
|
||||
}
|
||||
>
|
||||
aggregator: MoaModelSlot
|
||||
|
|
@ -1029,7 +1029,7 @@ export interface MoaConfigResponse {
|
|||
max_tokens: number
|
||||
reference_models: MoaModelSlot[]
|
||||
reference_temperature: number
|
||||
reference_timeout: number
|
||||
reference_timeout: number | null
|
||||
}
|
||||
|
||||
export interface ModelAssignmentRequest {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ DEFAULT_MOA_AGGREGATOR: dict[str, str] = {
|
|||
"model": "anthropic/claude-opus-4.8",
|
||||
}
|
||||
|
||||
DEFAULT_MOA_REFERENCE_TIMEOUT = 30.0
|
||||
MAX_MOA_REFERENCE_TIMEOUT = 300.0
|
||||
DEFAULT_MOA_REFERENCE_TIMEOUT: float | None = None
|
||||
|
||||
|
||||
def _default_reference_models() -> list[dict[str, Any]]:
|
||||
|
|
@ -45,9 +44,17 @@ def _coerce_float_or_none(value: Any) -> float | None:
|
|||
return None
|
||||
|
||||
|
||||
def _coerce_reference_timeout(value: Any) -> float:
|
||||
"""Return a finite positive advisor timeout capped at five minutes."""
|
||||
if isinstance(value, bool):
|
||||
def _coerce_reference_timeout(value: Any) -> float | None:
|
||||
"""Return a finite positive advisor timeout, or None to inherit.
|
||||
|
||||
``None`` (the default) means "no per-preset override": the reference
|
||||
fan-out inherits the ``auxiliary.moa_reference.timeout`` config value
|
||||
(900s by default) via ``call_llm``'s own resolution, exactly like every
|
||||
other auxiliary task. An explicit finite positive per-preset value is
|
||||
honored as-is — no artificial cap, since long-thinking advisor models
|
||||
legitimately run far beyond five minutes.
|
||||
"""
|
||||
if value is None or value == "" or isinstance(value, bool):
|
||||
return DEFAULT_MOA_REFERENCE_TIMEOUT
|
||||
try:
|
||||
timeout = float(value)
|
||||
|
|
@ -55,7 +62,7 @@ def _coerce_reference_timeout(value: Any) -> float:
|
|||
return DEFAULT_MOA_REFERENCE_TIMEOUT
|
||||
if not math.isfinite(timeout) or timeout <= 0:
|
||||
return DEFAULT_MOA_REFERENCE_TIMEOUT
|
||||
return min(timeout, MAX_MOA_REFERENCE_TIMEOUT)
|
||||
return timeout
|
||||
|
||||
|
||||
def _coerce_degraded_reference_policy(value: Any) -> str:
|
||||
|
|
|
|||
|
|
@ -1365,13 +1365,17 @@ class MoaModelSlot(BaseModel):
|
|||
|
||||
|
||||
class _MoaReferenceControls(BaseModel):
|
||||
reference_timeout: float = 30.0
|
||||
# None = no per-preset override; the fan-out inherits
|
||||
# auxiliary.moa_reference.timeout (900s default).
|
||||
reference_timeout: Optional[float] = None
|
||||
degraded_reference_policy: Literal["loud", "silent"] = "loud"
|
||||
|
||||
@field_validator("reference_timeout", mode="before")
|
||||
@classmethod
|
||||
def _validate_reference_timeout(cls, value: Any) -> float:
|
||||
def _validate_reference_timeout(cls, value: Any) -> Optional[float]:
|
||||
"""Reject JSON booleans/non-finite values before float coercion."""
|
||||
if value is None or value == "":
|
||||
return None
|
||||
if isinstance(value, bool):
|
||||
raise ValueError("reference_timeout must be a finite positive number")
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -754,13 +754,16 @@ def test_reference_failure_controls_are_normalized_per_preset_and_flattened():
|
|||
|
||||
@pytest.mark.parametrize("value", [None, "", 0, -1, "bad"])
|
||||
def test_reference_timeout_invalid_values_fall_back_to_default(value):
|
||||
assert resolve_moa_preset(_preset(reference_timeout=value), "p")["reference_timeout"] == 30.0
|
||||
# None = inherit auxiliary.moa_reference.timeout (no per-preset override).
|
||||
assert resolve_moa_preset(_preset(reference_timeout=value), "p")["reference_timeout"] is None
|
||||
|
||||
|
||||
def test_reference_timeout_is_capped_and_unknown_policy_is_loud():
|
||||
def test_reference_timeout_is_uncapped_and_unknown_policy_is_loud():
|
||||
preset = resolve_moa_preset(
|
||||
_preset(reference_timeout=9999, degraded_reference_policy="wat"), "p"
|
||||
)
|
||||
|
||||
assert preset["reference_timeout"] == 300.0
|
||||
# Explicit per-preset values are honored as-is — long-thinking advisor
|
||||
# models legitimately run beyond any fixed cap.
|
||||
assert preset["reference_timeout"] == 9999.0
|
||||
assert preset["degraded_reference_policy"] == "loud"
|
||||
|
|
|
|||
|
|
@ -1889,6 +1889,31 @@ def test_reference_filtering_preserves_accounting_triples():
|
|||
assert _failed_reference_labels(outputs) == ["bad-model"]
|
||||
|
||||
|
||||
def test_reference_filtering_excludes_recursion_guard_skips():
|
||||
"""[skipped: …] recursion-guard notes are internal sentinels, not advice —
|
||||
they must be filtered out of the aggregator prompt like [failed: …]."""
|
||||
from agent.moa_loop import (
|
||||
_RefAccounting,
|
||||
_failed_reference_labels,
|
||||
_is_failed_reference,
|
||||
_successful_references,
|
||||
)
|
||||
from agent.usage_pricing import CanonicalUsage
|
||||
|
||||
outputs = [
|
||||
("good-model", "useful advice", _RefAccounting(CanonicalUsage())),
|
||||
(
|
||||
"moa:nested",
|
||||
"[skipped: MoA presets cannot recursively reference MoA]",
|
||||
_RefAccounting(CanonicalUsage()),
|
||||
),
|
||||
]
|
||||
|
||||
assert _is_failed_reference(outputs[1][1])
|
||||
assert _successful_references(outputs) == [outputs[0]]
|
||||
assert _failed_reference_labels(outputs) == ["moa:nested"]
|
||||
|
||||
|
||||
def test_aggregate_moa_context_sanitizes_failed_reference_and_forwards_timeout(monkeypatch):
|
||||
from agent import moa_loop
|
||||
from agent.usage_pricing import CanonicalUsage
|
||||
|
|
|
|||
|
|
@ -2344,7 +2344,7 @@ export interface MoaConfigResponse {
|
|||
aggregator: MoaModelSlot;
|
||||
reference_temperature: number;
|
||||
aggregator_temperature: number;
|
||||
reference_timeout: number;
|
||||
reference_timeout: number | null;
|
||||
degraded_reference_policy: "loud" | "silent";
|
||||
max_tokens: number;
|
||||
/** Optional advisor output cap — round-tripped, not edited here. */
|
||||
|
|
@ -2357,7 +2357,7 @@ export interface MoaConfigResponse {
|
|||
aggregator: MoaModelSlot;
|
||||
reference_temperature: number;
|
||||
aggregator_temperature: number;
|
||||
reference_timeout: number;
|
||||
reference_timeout: number | null;
|
||||
degraded_reference_policy: "loud" | "silent";
|
||||
max_tokens: number;
|
||||
enabled: boolean;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue