diff --git a/backend/apps/web/web.py b/backend/apps/web/web.py index b756ec23..488dd7e6 100644 --- a/backend/apps/web/web.py +++ b/backend/apps/web/web.py @@ -215,10 +215,18 @@ async def search(body: SearchBody) -> Dict: async def try_keyless_engines() -> Optional[Dict]: outcome = await race_keyless( + # Order is quality, not alphabet: race_keyless starts these in sequence and only hedges + # the next one in if the leader stalls, so whoever sits second inherits every ddg + # challenge. Measured 2026-08-13, N=22 fact queries, 12s pacing so nobody is being read + # through their own rate limiter: ddg 21/22 (0.955), bing 6/22 (0.273), brave 2/2 of the + # 2 it served before throttling, startpage 0/22. Bing answers something 100% of the time + # and answers CORRECTLY about a quarter of it, which is the worst shape for a silent + # fallback, so it goes last. The dead rungs cost nothing: record_tier_failure cools them + # out after their first miss. [KeylessEngine(name="ddg", run=try_keyless), - KeylessEngine(name="bing", run=try_bing), KeylessEngine(name="brave", run=try_brave), - KeylessEngine(name="startpage", run=try_startpage)], + KeylessEngine(name="startpage", run=try_startpage), + KeylessEngine(name="bing", run=try_bing)], KEYLESS_TIER_SECONDS, ) keyless_errors.extend(outcome.errors) diff --git a/backend/tests/test_web_search_cascade.py b/backend/tests/test_web_search_cascade.py index e174578f..f68c3322 100644 --- a/backend/tests/test_web_search_cascade.py +++ b/backend/tests/test_web_search_cascade.py @@ -20,6 +20,7 @@ from backend.tests.web_cascade_fixtures import ( # noqa: F401 allow_urls, bing_refuses, bing_returns, + brave_refuses, brave_returns, fresh_breaker, patch_browser_bridge, @@ -210,15 +211,31 @@ async def test_bing_rescues_a_ddg_challenge_before_any_paid_backend(monkeypatch) @pytest.mark.asyncio -async def test_brave_rescues_when_ddg_and_bing_both_refuse(monkeypatch): +async def test_brave_rescues_when_ddg_refuses(monkeypatch): + """Brave now sits directly behind ddg, so it is what catches a ddg challenge.""" ddg_throttled(monkeypatch) - bing_refuses(monkeypatch) brave_returns(monkeypatch, "[1] Independent index\n https://brave-hit.example") res = await search(SearchBody(query="x")) assert res["backend"] == "brave" assert "brave-hit.example" in res["results"] - assert any("Bing" in e for e in res["cascade_errors"]) + + +@pytest.mark.asyncio +async def test_bing_is_the_last_keyless_resort(monkeypatch): + """Bing answers something ~100% of the time and answers CORRECTLY ~27% (N=22, 12s pacing, + 2026-08-13) against ddg's 95.5%, which is the worst possible shape for a silent fallback: the + caller cannot tell a good answer from an off-topic one. So it must serve only when every better + rung has declined, and that ordering is what this pins.""" + ddg_throttled(monkeypatch) + brave_returns(monkeypatch, "[1] Brave result\n https://brave-hit.example") + bing_returns(monkeypatch, "[1] Bing result\n https://bing-hit.example") + + res = await search(SearchBody(query="x")) + assert res["backend"] == "brave", ( + f"bing served while brave was also answering, so it is not last: got {res['backend']}" + ) + assert "bing-hit.example" not in res["results"] @pytest.mark.asyncio