diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 60ebe1b552..df7f42d9a7 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -4468,43 +4468,71 @@ async def _call_fallback_candidate_async( task or "call", fb_label, fb_timeout, effective_timeout, ) effective_timeout = fb_timeout - fb_base = str(getattr(fb_client, "base_url", "") or "") + destination = _fallback_destination(task, fb_client, fb_model, fb_label) + fallback_messages, fallback_tools = _replan_synchronous_cache_sections( + messages, + tools, + destination=destination, + ) fb_kwargs = _build_call_kwargs( - fb_label, fb_model, messages, + destination.provider, destination.model, fallback_messages, temperature=temperature, max_tokens=max_tokens, - tools=tools, timeout=effective_timeout, + tools=fallback_tools, timeout=effective_timeout, extra_body=effective_extra_body, reasoning_config=reasoning_config, - base_url=fb_base, task=task) + base_url=destination.base_url, task=task) try: return _validate_llm_response( await _relay_async_completion( fb_client, fb_kwargs, - provider=fb_label, + provider=destination.provider, + api_mode=destination.api_mode, ), task, ) except Exception as fb_err: if not _is_auth_error(fb_err): raise - fb_provider = _auth_refresh_provider_for_route(fb_label, fb_base) + fb_provider = _auth_refresh_provider_for_route( + destination.provider, destination.base_url + ) if fb_provider not in {"auto", "", None} and _refresh_provider_credentials(fb_provider): retry_client, retry_model = _get_cached_client( - fb_provider, fb_model, async_mode=True) + fb_provider, + destination.model, + async_mode=True, + base_url=destination.base_url or None, + api_mode=destination.api_mode, + ) if retry_client is not None: + retry_destination = _FallbackDestination( + fb_provider, + destination.base_url + or str(getattr(retry_client, "base_url", "") or ""), + destination.api_mode, + retry_model or destination.model, + ) + retry_messages, retry_tools = _replan_synchronous_cache_sections( + messages, + tools, + destination=retry_destination, + ) retry_kwargs = _build_call_kwargs( - fb_provider, retry_model or fb_model, messages, + retry_destination.provider, + retry_destination.model, + retry_messages, temperature=temperature, max_tokens=max_tokens, - tools=tools, timeout=effective_timeout, + tools=retry_tools, timeout=effective_timeout, extra_body=effective_extra_body, reasoning_config=reasoning_config, - base_url=str(getattr(retry_client, "base_url", "") or fb_base), task=task) + base_url=retry_destination.base_url, task=task) try: return _validate_llm_response( await _relay_async_completion( retry_client, retry_kwargs, - provider=fb_provider, + provider=retry_destination.provider, + api_mode=retry_destination.api_mode, ), task, ) diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index 0e60ea3dd7..9882b26f80 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -4213,3 +4213,66 @@ class TestSynchronousFallbackCachePlans: for part in (message.get("content") if isinstance(message.get("content"), list) else []) ) + + +class TestAsynchronousFallbackCachePlans: + @pytest.mark.asyncio + async def test_async_fallback_replans_cache_sections_like_sync(self, monkeypatch): + """Async mirror parity: per-destination cache replan, not verbatim pass-through.""" + from agent.auxiliary_client import ( + _call_fallback_candidate_async, + _try_configured_fallback_chain, + ) + + entry = { + "provider": "anthropic", + "model": "claude-sonnet-4-6", + "base_url": "https://api.anthropic.com", + "api_mode": "anthropic_messages", + } + client = MagicMock() + client.base_url = entry["base_url"] + + async def _create(**kwargs): + return _DummyResponse() + + client.chat.completions.create = MagicMock(side_effect=_create) + monkeypatch.setattr( + "agent.auxiliary_client.resolve_provider_client", + lambda provider, model=None, **kwargs: (client, model), + ) + monkeypatch.setattr( + "agent.auxiliary_client._get_auxiliary_task_config", + lambda task: {"fallback_chain": [entry]}, + ) + fallback_client, fallback_model, label = _try_configured_fallback_chain( + task="moa_aggregator", + failed_provider="primary", + ) + tools = [{ + "type": "function", + "function": { + "name": "lookup", + "parameters": {"type": "object", "properties": {}}, + }, + }] + await _call_fallback_candidate_async( + fallback_client, + fallback_model, + label, + task="moa_aggregator", + messages=[ + {"role": "system", "content": "stable prefix"}, + {"role": "user", "content": "lookup"}, + ], + temperature=None, + max_tokens=None, + tools=tools, + effective_timeout=30.0, + effective_extra_body={}, + reasoning_config=None, + ) + + wire_tools = client.chat.completions.create.call_args.kwargs["tools"] + assert "cache_control" in wire_tools[-1] + assert "cache_control" not in tools[-1]