From 172ec4d4c4a9617e6992d6ace8a9bba75f8d2154 Mon Sep 17 00:00:00 2001 From: "A.G. Bocsardi" Date: Mon, 25 May 2026 20:38:45 +0200 Subject: [PATCH] fix(web): update Kagi search API integration Use Kagi's documented v1 Search API shape from the OpenAPI spec: POST /search, Bearer auth, JSON query payload, and data.search results. --- nanobot/agent/tools/web.py | 11 +++++------ tests/tools/test_web_search_tool.py | 22 +++++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/nanobot/agent/tools/web.py b/nanobot/agent/tools/web.py index dc87ea6a2..042418387 100644 --- a/nanobot/agent/tools/web.py +++ b/nanobot/agent/tools/web.py @@ -455,17 +455,16 @@ class WebSearchTool(Tool): return await self._search_duckduckgo(query, n) try: async with httpx.AsyncClient(proxy=self.proxy) as client: - r = await client.get( - "https://kagi.com/api/v0/search", - params={"q": query, "limit": n}, - headers={"Authorization": f"Bot {api_key}", "User-Agent": self.user_agent}, + r = await client.post( + "https://kagi.com/api/v1/search", + json={"query": query, "limit": n}, + headers={"Authorization": f"Bearer {api_key}", "User-Agent": self.user_agent}, timeout=10.0, ) r.raise_for_status() - # t=0 items are search results; other values are related searches, etc. items = [ {"title": d.get("title", ""), "url": d.get("url", ""), "content": d.get("snippet", "")} - for d in r.json().get("data", []) if d.get("t") == 0 + for d in r.json().get("data", {}).get("search", []) ] return _format_results(query, items, n) except Exception as e: diff --git a/tests/tools/test_web_search_tool.py b/tests/tools/test_web_search_tool.py index a7b11928e..39784ef33 100644 --- a/tests/tools/test_web_search_tool.py +++ b/tests/tools/test_web_search_tool.py @@ -202,19 +202,23 @@ async def test_jina_search(monkeypatch): @pytest.mark.asyncio async def test_kagi_search(monkeypatch): - async def mock_get(self, url, **kw): - assert "kagi.com/api/v0/search" in url - assert kw["headers"]["Authorization"] == "Bot kagi-key" + async def mock_post(self, url, **kw): + assert "kagi.com/api/v1/search" in url + assert kw["headers"]["Authorization"] == "Bearer kagi-key" assert kw["headers"]["User-Agent"] == "nanobot-search-test" - assert kw["params"] == {"q": "test", "limit": 2} + assert kw["json"] == {"query": "test", "limit": 2} return _response(json={ - "data": [ - {"t": 0, "title": "Kagi Result", "url": "https://kagi.com", "snippet": "Premium search"}, - {"t": 1, "list": ["ignored related search"]}, - ] + "data": { + "search": [ + {"title": "Kagi Result", "url": "https://kagi.com", "snippet": "Premium search"}, + ], + "related_search": [ + {"title": "ignored related search", "url": "", "snippet": ""}, + ], + } }) - monkeypatch.setattr(httpx.AsyncClient, "get", mock_get) + monkeypatch.setattr(httpx.AsyncClient, "post", mock_post) tool = _tool(provider="kagi", api_key="kagi-key", user_agent="nanobot-search-test") result = await tool.execute(query="test", count=2) assert "Kagi Result" in result