From 2962ba2b7bb3bf518036aa31ec8e67a2825eb6e3 Mon Sep 17 00:00:00 2001 From: srojk34 <286497132+srojk34@users.noreply.github.com> Date: Wed, 1 Jul 2026 14:01:34 +0300 Subject: [PATCH] fix(auxiliary): treat explicit model:auto sentinel, not just cfg_model 'auto' is a sentinel meaning "inherit from main runtime / auto-detect", not a literal model id -- already handled for cfg_model (config-derived) in _resolve_task_provider_model, but not for the explicit `model` kwarg. MoA reference/aggregator slots (agent/moa_loop.py's _slot_runtime) forward a preset's `model:` field as this explicit argument rather than through auxiliary. config, so a MoA preset configured with `model: auto` (a natural thing to try given the existing auxiliary.*.model: auto convention) reached this function as the explicit `model` arg and took the `model or cfg_model` branch, bypassing the cfg_model-only sentinel check entirely -- sending the literal string "auto" to the wire as a model id. Normalize both the explicit `model` and `cfg_model` the same way, fixing this at the single chokepoint every caller (MoA included) already goes through, rather than patching moa_loop.py separately. --- agent/auxiliary_client.py | 9 +++++ tests/agent/test_auxiliary_client.py | 49 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 7cb0849ea220..c678bc2a3796 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -6369,6 +6369,15 @@ def _resolve_task_provider_model( # which downstream consumers like ContextCompressor accept as the task output. # The provider-side 'auto' is handled in _resolve_auto() via main_runtime # fallback, so dropping cfg_model to None here lets that path do its job. + # + # The explicit `model` kwarg needs the identical normalization: MoA slots + # (agent/moa_loop.py's _slot_runtime) forward a preset's `model:` field as + # this explicit argument rather than through auxiliary. config, so a + # user-configured `model: auto` on a MoA reference/aggregator slot reaches + # this function here, not as cfg_model. Only normalizing cfg_model let that + # literal "auto" slip through via `model or cfg_model` below. + if model and model.lower() == "auto": + model = None if cfg_model and cfg_model.lower() == "auto": cfg_model = None diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index cdc34955e857..9f61ff9668db 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -394,6 +394,55 @@ class TestResolveTaskProviderModel: assert resolved_provider == "anthropic" assert model == "claude-haiku-4-5-20251001" + def test_explicit_model_auto_sentinel_is_normalized(self): + """MoA slots (agent/moa_loop.py's _slot_runtime) forward a preset's + `model:` field as the explicit `model` kwarg here, not through + auxiliary. config. Only cfg_model was normalized before, so a + MoA reference/aggregator slot configured with `model: auto` sent the + literal string "auto" to the wire as a model id.""" + resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model( + provider="anthropic", + model="auto", + ) + + assert resolved_provider == "anthropic" + assert model is None + + def test_explicit_model_auto_sentinel_case_insensitive(self): + resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model( + provider="anthropic", + model="AUTO", + ) + + assert model is None + + def test_explicit_model_auto_falls_back_to_cfg_model(self, monkeypatch): + """When the explicit model is the "auto" sentinel, it must not shadow + a real configured task model — the `model or cfg_model` fallback + chain should still reach cfg_model exactly as if model had been + omitted entirely.""" + monkeypatch.setattr( + "agent.auxiliary_client._get_auxiliary_task_config", + lambda _task: {"provider": "openai", "model": "gpt-real-model"}, + ) + + resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model( + task="moa_reference", + model="auto", + ) + + assert model == "gpt-real-model" + + def test_non_auto_model_is_unaffected(self): + """Regression guard: a real model name must not be touched by the + sentinel normalization.""" + resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model( + provider="openai", + model="gpt-4o-mini", + ) + + assert model == "gpt-4o-mini" + class TestMoaAggregatorSharedResolution: """The shared MoA→aggregator helper and the layers that consume it.