[aidan] fix/web-search: nudge model to browser fallback when search backends fail

This commit is contained in:
abccodes
2026-06-30 14:14:33 -07:00
parent ab982afcea
commit a8de20482f
2 changed files with 28 additions and 2 deletions
+12 -2
View File
@@ -74,6 +74,15 @@ P_GROUNDED_ATTEMPT_TIMEOUT = 48.0 # just above the providers' own 45s httpx tim
# Local httpx + trafilatura fetch of a real page; the fast path for /fetch (normal pages return in <2s). Set just above WebFetchTool's own 30s httpx ceiling so a valid-but-slow page still completes locally instead of being clipped down to a grounded summary; only a truly hung server gets cut.
P_LOCAL_FETCH_TIMEOUT = 32.0
# When every search backend fails, point the model at the in-product browser (always-on CreateBrowserAgent tool) instead of telling it to "wait and retry", which it can't do and just relays as a dead end. The real Chromium renders pages and isn't subject to the DDG scrape throttle.
def p_browser_fallback_nudge(query: str) -> str:
return (
"Don't stop here: fall back to the in-product browser, which renders real pages and "
"isn't subject to this rate limit. Call CreateBrowserAgent with a task like: "
f'"Search the web for: {query}. Report the top results with their titles and URLs, '
'plus a direct answer if you find one."'
)
async def p_gemini_grounded_call(api_key: str, prompt: str, *, use_url_context: bool) -> dict:
"""Call Gemini with googleSearch (+ optionally urlContext) grounding.
@@ -470,11 +479,12 @@ async def search(body: SearchBody) -> dict:
else:
tail = (
"DuckDuckGo is rate-limiting this network and every configured provider "
"errored (see details below). Wait a moment and retry."
"errored (see details below)."
)
nudge = p_browser_fallback_nudge(body.query)
return {
"query": body.query,
"results": f"No results for: {body.query}\n\n{tail}",
"results": f"No results for: {body.query}\n\n{tail}\n\n{nudge}",
"backend": "none",
"cascade_errors": errors,
}
+16
View File
@@ -128,6 +128,22 @@ async def test_everything_fails_is_honest_not_empty(monkeypatch):
assert "Settings" in res["results"] or "API key" in res["results"]
@pytest.mark.asyncio
async def test_everything_fails_nudges_browser_not_retry(monkeypatch):
# All-fail must hand the model the browser as an escape hatch, not a dead-end "wait and retry".
p_ddg_throttled(monkeypatch)
monkeypatch.setattr(W, "p_resolve_openai_api_key", lambda: "okey") # configured but errors
async def p_openai_boom(*a, **k):
raise RuntimeError("openai down")
monkeypatch.setattr(W, "p_openai_websearch", p_openai_boom)
res = await search(SearchBody(query="sony zv-e10 price"))
assert res["backend"] == "none"
assert "CreateBrowserAgent" in res["results"]
assert "retry" not in res["results"].lower()
# -------------------------------------------------------------------------- /fetch mirrors /search: local httpx + trafilatura is the fast path, grounded fetchers are the fallback for JS/paywalled pages, every attempt is bounded. --------------------------------------------------------------------------
from backend.apps.web.web import fetch, FetchBody