fix(auxiliary): reach the main agent model when a sibling aux model fails on the same provider

Widen okalentiev's failed_model narrowing (#59561) to
_try_main_agent_model_fallback. The safety-net layer still skipped on a
provider-label match alone, so single-provider users whose aux compression
model and main model share one custom endpoint had ZERO fallbacks: the aux
model timing out exhausted the chain in one hop and compression aborted.

Real incident (0.19.0 debug dump): aux zai-org/glm-5.2 hung 324s and timed
out while main mindai/macaron-v1-venti on the SAME endpoint was serving
448K-token turns — the label-only skip discarded the one viable summarizer,
the session wedged over threshold, and the anti-thrash breaker tripped.

Same convention as the chain fix: model-specific failures (timeout,
connection, rate limit) pass failed_model so only the exact failed model is
skipped; provider-wide failures (auth 401 / payment 402) pass None and keep
the whole-provider skip. Both sync and async call_llm sites pass it.

Sabotage-verified: the new regression test fails on the provider-only skip.
This commit is contained in:
teknium1 2026-07-26 21:20:17 -07:00 • committed by Teknium
parent 83f20e07e0
commit 0a2c245cd6
3 changed files with 70 additions and 6 deletions

View file

@ -3167,6 +3167,47 @@ class TestTryMainAgentModelFallback:
client, model, label = _try_main_agent_model_fallback("glm", task="vision")
assert client is None
def test_same_provider_different_model_falls_back_when_failed_model_given(self):
"""Self-hosted shape: aux model and main model share one custom
provider label. A timeout on the aux model must still reach the main
model — same provider, DIFFERENT model (real incident: aux glm-5.2
timed out while main macaron-v1-venti on the same endpoint was
healthy; the provider-label skip discarded the working fallback)."""
from agent.auxiliary_client import _try_main_agent_model_fallback
fake_client = MagicMock()
with patch("agent.auxiliary_client._read_main_provider", return_value="custom"), \
patch("agent.auxiliary_client._read_main_model", return_value="mindai/macaron-v1-venti"), \
patch("agent.auxiliary_client._is_provider_unhealthy", return_value=False), \
patch("agent.auxiliary_client.resolve_provider_client",
return_value=(fake_client, "mindai/macaron-v1-venti")):
client, model, label = _try_main_agent_model_fallback(
"custom", task="compression", failed_model="zai-org/glm-5.2")
assert client is fake_client
assert model == "mindai/macaron-v1-venti"
assert label == "main-agent(custom)"
def test_same_provider_same_model_still_skips_with_failed_model(self):
"""When the model that failed IS the main model, there is nothing to
fall back to — the narrowed skip must not regress into a self-retry."""
from agent.auxiliary_client import _try_main_agent_model_fallback
with patch("agent.auxiliary_client._read_main_provider", return_value="custom"), \
patch("agent.auxiliary_client._read_main_model", return_value="mindai/macaron-v1-venti"):
client, model, label = _try_main_agent_model_fallback(
"custom", task="compression",
failed_model="MindAI/Macaron-V1-Venti") # case-insensitive match
assert client is None and model is None and label == ""
def test_same_provider_no_failed_model_keeps_provider_wide_skip(self):
"""Legacy / provider-wide callers (auth 401, payment 402) pass no
failed_model: the whole-provider skip must be preserved so broken
shared credentials don't trigger a doomed main-model attempt."""
from agent.auxiliary_client import _try_main_agent_model_fallback
with patch("agent.auxiliary_client._read_main_provider", return_value="custom"), \
patch("agent.auxiliary_client._read_main_model", return_value="mindai/macaron-v1-venti"):
client, model, label = _try_main_agent_model_fallback(
"custom", task="compression")
assert client is None and model is None and label == ""
# ---------------------------------------------------------------------------
# Gate: _resolve_api_key_provider must skip anthropic when not configured