mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-24 16:54:43 +00:00
fix(moa): skip aggregator synthesis when all references fail
When every MoA reference model returns a failure (HTTP error, timeout, etc.) or is skipped by the recursion guard, the one-shot aggregator synthesis call is now skipped entirely. Previously it would try to synthesise a wall of failure sentinels, which could block for the full provider timeout (observed ~6 min on SenseNova) before returning a non-retryable error that left the session hanging. The early return carries the sanitized unavailability notice (never raw provider error text, per the failed-reference containment) so the main agent loop can still act in single-model mode. Salvaged from #56975, reworked atop the _is_failed_reference helpers.
This commit is contained in:
parent
d3fc27bbf8
commit
f0ed77b627
2 changed files with 82 additions and 0 deletions
|
|
@ -963,6 +963,26 @@ def aggregate_moa_context(
|
|||
degraded = _degraded_notice(failed_labels, degraded_reference_policy)
|
||||
if degraded:
|
||||
joined = f"{joined}\n\n{degraded}" if joined else degraded
|
||||
|
||||
# Skip the aggregator call when every reference failed or was skipped —
|
||||
# synthesising over zero real advice wastes tokens and can block for the
|
||||
# full provider timeout (observed: ~6 min on SenseNova) before returning
|
||||
# a non-retryable error that leaves the session hanging. The early return
|
||||
# carries only the sanitized unavailability notice (never raw provider
|
||||
# error text) so the main agent loop can still act in single-model mode.
|
||||
if reference_outputs and not successful_outputs:
|
||||
logger.warning(
|
||||
"MoA: all %d reference(s) failed — skipping aggregator synthesis",
|
||||
len(reference_outputs),
|
||||
)
|
||||
notice = degraded or "[Reference models unavailable]"
|
||||
return (
|
||||
"[Mixture of Agents context — all reference models failed. "
|
||||
"Proceeding without aggregated guidance.]\n"
|
||||
f"References: {', '.join(_slot_label(slot) for slot in reference_models)}\n\n"
|
||||
f"{notice}"
|
||||
)
|
||||
|
||||
synth_prompt = (
|
||||
"You are the aggregator in a Mixture of Agents process. Synthesize the "
|
||||
"reference responses into concise, actionable guidance for the main "
|
||||
|
|
|
|||
|
|
@ -2067,3 +2067,65 @@ def test_run_reference_forwards_configured_timeout(monkeypatch):
|
|||
assert (label, text) == ("openrouter:advisor", "advice")
|
||||
assert calls[0]["timeout"] == 23.5
|
||||
assert isinstance(accounting, moa_loop._RefAccounting)
|
||||
|
||||
|
||||
def test_aggregate_skips_aggregator_when_all_references_failed(monkeypatch):
|
||||
"""When every reference returns [failed: …], the aggregator is skipped entirely."""
|
||||
from agent.moa_loop import aggregate_moa_context
|
||||
|
||||
call_count = {"n": 0}
|
||||
|
||||
def fake_call_llm(**kwargs):
|
||||
call_count["n"] += 1
|
||||
if kwargs["task"] == "moa_reference":
|
||||
raise RuntimeError("provider down key=super-secret")
|
||||
raise AssertionError("aggregator should not be called when all references fail")
|
||||
|
||||
monkeypatch.setattr("agent.moa_loop.call_llm", fake_call_llm)
|
||||
monkeypatch.setattr(
|
||||
"agent.moa_loop._slot_runtime",
|
||||
lambda slot: {"provider": slot["provider"], "model": slot["model"]},
|
||||
)
|
||||
|
||||
result = aggregate_moa_context(
|
||||
user_prompt="do something",
|
||||
api_messages=[{"role": "user", "content": "do something"}],
|
||||
reference_models=[
|
||||
{"provider": "openai", "model": "gpt-4"},
|
||||
{"provider": "anthropic", "model": "claude-opus"},
|
||||
],
|
||||
aggregator={"provider": "openrouter", "model": "anthropic/claude-opus-4.8"},
|
||||
)
|
||||
|
||||
# The aggregator LLM call was never made.
|
||||
assert call_count["n"] == 2 # only the two reference calls
|
||||
# The result carries a sanitized unavailability notice (never raw
|
||||
# provider error text) so the main agent can still act.
|
||||
assert "all reference models failed" in result
|
||||
assert "Reference models unavailable" in result
|
||||
assert "super-secret" not in result
|
||||
|
||||
|
||||
def test_aggregate_skips_aggregator_when_all_references_skipped(monkeypatch):
|
||||
"""References that are skipped (MoA recursion guard) also trigger the early return."""
|
||||
from agent.moa_loop import aggregate_moa_context
|
||||
|
||||
def fake_call_llm(**kwargs):
|
||||
raise AssertionError("aggregator should not be called when all references are skipped")
|
||||
|
||||
monkeypatch.setattr("agent.moa_loop.call_llm", fake_call_llm)
|
||||
|
||||
# Both reference models are "moa" — they hit the recursion guard and are
|
||||
# returned as "[skipped: …]" without calling call_llm at all.
|
||||
result = aggregate_moa_context(
|
||||
user_prompt="do something",
|
||||
api_messages=[{"role": "user", "content": "do something"}],
|
||||
reference_models=[
|
||||
{"provider": "moa", "model": "preset-a"},
|
||||
{"provider": "moa", "model": "preset-b"},
|
||||
],
|
||||
aggregator={"provider": "openrouter", "model": "anthropic/claude-opus-4.8"},
|
||||
)
|
||||
|
||||
assert "all reference models failed" in result
|
||||
assert "Reference models unavailable" in result
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue