fix(ai): chaine de repli web_search (SearXNG -> DuckDuckGo -> Bing, BUG-051)
This commit is contained in:
+72
-3
@@ -33,6 +33,40 @@ def _ctx() -> ToolContext:
|
||||
return ToolContext(user={"username": "tester", "vaults": []}, mode=ToolMode.IN_APP)
|
||||
|
||||
|
||||
def _html_get(routes: dict[str, str], searxng: Any = None):
|
||||
"""Dispatch httpx.get by URL marker: searxng JSON, HTML providers, else error."""
|
||||
|
||||
def fake_get(url, params=None, **kw):
|
||||
url = str(url)
|
||||
if "search.dracodev.net" in url:
|
||||
if searxng is not None:
|
||||
return searxng
|
||||
raise web.httpx.ConnectError("searxng down")
|
||||
for marker, body in routes.items():
|
||||
if marker in url:
|
||||
return FakeResponse(content=body.encode("utf-8"), url=url)
|
||||
raise web.httpx.ConnectError(f"no route: {url}")
|
||||
|
||||
return fake_get
|
||||
|
||||
|
||||
DDG_HTML = (
|
||||
'<div class="result">'
|
||||
'<a rel="nofollow" class="result__a" '
|
||||
'href="//duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.com%2Fpage&rut=1">'
|
||||
"Example <b>Page</b></a>"
|
||||
'<a class="result__snippet" href="#">A useful snippet</a>'
|
||||
"</div>"
|
||||
)
|
||||
|
||||
BING_HTML = (
|
||||
'<h2 class=""><a target="_blank" '
|
||||
'href="https://www.bing.com/ck/a?u=a1aHR0cHM6Ly9leGFtcGxlLmNvbS9iaW5n&ntb=1">'
|
||||
"Bing <strong>Result</strong></a></h2>"
|
||||
'<p class="b_lineclamp2">Bing snippet here</p>'
|
||||
)
|
||||
|
||||
|
||||
class TestRegistration:
|
||||
def test_tools_registered_read_only_in_app(self):
|
||||
for name in ("web_search", "fetch_url"):
|
||||
@@ -58,7 +92,7 @@ class TestWebSearch:
|
||||
|
||||
monkeypatch.setattr(web.httpx, "get", fake_get)
|
||||
out = web.web_search(_ctx(), web.WebSearchInput(query="pizza", max_results=3))
|
||||
assert out["engine"] == "searxng"
|
||||
assert out["provider"] == "searxng"
|
||||
assert out["count"] == 3
|
||||
assert len(out["results"][0]["snippet"]) <= 600
|
||||
assert captured["params"]["q"] == "pizza"
|
||||
@@ -70,11 +104,11 @@ class TestWebSearch:
|
||||
def test_empty_result_set_warns_about_blocked_engines(self, monkeypatch):
|
||||
"""An all-blocked instance answers 200 with no results: the model must
|
||||
be told instead of retrying the same search until the quota burns."""
|
||||
monkeypatch.setattr(web.httpx, "get", lambda *a, **kw: FakeResponse(json_data={
|
||||
monkeypatch.setattr(web.httpx, "get", _html_get({}, searxng=FakeResponse(json_data={
|
||||
"results": [],
|
||||
"number_of_results": 0,
|
||||
"unresponsive_engines": [["duckduckgo", "CAPTCHA"], ["google", "access denied"]],
|
||||
}))
|
||||
})))
|
||||
out = web.web_search(_ctx(), web.WebSearchInput(query="meteo montreal"))
|
||||
assert out["count"] == 0
|
||||
assert out["unresponsive_engines"] == ["duckduckgo", "google"]
|
||||
@@ -100,6 +134,41 @@ class TestWebSearch:
|
||||
web.web_search(_ctx(), web.WebSearchInput(query="x"))
|
||||
assert ei.value.code == "web_search_unavailable"
|
||||
|
||||
def test_falls_back_to_duckduckgo_when_searxng_empty(self, monkeypatch):
|
||||
monkeypatch.setattr(web.httpx, "get", _html_get(
|
||||
{"html.duckduckgo.com": DDG_HTML},
|
||||
searxng=FakeResponse(json_data={"results": []}),
|
||||
))
|
||||
out = web.web_search(_ctx(), web.WebSearchInput(query="python release"))
|
||||
assert out["provider"] == "duckduckgo"
|
||||
assert out["count"] == 1
|
||||
assert out["results"][0]["title"] == "Example Page"
|
||||
assert out["results"][0]["url"] == "https://example.com/page"
|
||||
assert out["results"][0]["snippet"] == "A useful snippet"
|
||||
|
||||
def test_falls_back_to_bing_when_searxng_unreachable(self, monkeypatch):
|
||||
monkeypatch.setattr(web.httpx, "get", _html_get(
|
||||
{"bing.com": BING_HTML},
|
||||
searxng=None,
|
||||
))
|
||||
out = web.web_search(_ctx(), web.WebSearchInput(query="python release"))
|
||||
assert out["provider"] == "bing"
|
||||
assert out["count"] == 1
|
||||
assert out["results"][0]["title"] == "Bing Result"
|
||||
assert out["results"][0]["url"] == "https://example.com/bing"
|
||||
assert out["results"][0]["snippet"] == "Bing snippet here"
|
||||
|
||||
def test_fallback_can_be_disabled(self, monkeypatch):
|
||||
monkeypatch.setattr(web, "WEB_FALLBACK_ENABLED", False)
|
||||
monkeypatch.setattr(web.httpx, "get", _html_get(
|
||||
{"html.duckduckgo.com": DDG_HTML, "bing.com": BING_HTML},
|
||||
searxng=FakeResponse(json_data={"results": []}),
|
||||
))
|
||||
out = web.web_search(_ctx(), web.WebSearchInput(query="python release"))
|
||||
assert out["count"] == 0
|
||||
assert out["provider"] == "searxng"
|
||||
assert "warning" in out
|
||||
|
||||
|
||||
class TestFetchUrl:
|
||||
def test_html_converted_to_text(self, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user