From d3aa209cf6967563e023701acb2d6f4b867762d9 Mon Sep 17 00:00:00 2001 From: Mike Terhar Date: Wed, 8 Apr 2026 09:24:32 -0400 Subject: [PATCH] add kagi web search tool --- nanobot/agent/tools/web.py | 25 +++++++++++++++++++++++++ nanobot/config/schema.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/nanobot/agent/tools/web.py b/nanobot/agent/tools/web.py index 275fcf88c..38fc33d74 100644 --- a/nanobot/agent/tools/web.py +++ b/nanobot/agent/tools/web.py @@ -114,6 +114,8 @@ class WebSearchTool(Tool): return await self._search_jina(query, n) elif provider == "brave": return await self._search_brave(query, n) + elif provider == "kagi": + return await self._search_kagi(query, n) else: return f"Error: unknown search provider '{provider}'" @@ -204,6 +206,29 @@ class WebSearchTool(Tool): logger.warning("Jina search failed ({}), falling back to DuckDuckGo", e) return await self._search_duckduckgo(query, n) + async def _search_kagi(self, query: str, n: int) -> str: + api_key = self.config.api_key or os.environ.get("KAGI_API_KEY", "") + if not api_key: + logger.warning("KAGI_API_KEY not set, falling back to DuckDuckGo") + 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}"}, + 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 + ] + return _format_results(query, items, n) + except Exception as e: + return f"Error: {e}" + async def _search_duckduckgo(self, query: str, n: int) -> str: try: # Note: duckduckgo_search is synchronous and does its own requests diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index 67cce4470..a841fe159 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -159,7 +159,7 @@ class GatewayConfig(Base): class WebSearchConfig(Base): """Web search tool configuration.""" - provider: str = "duckduckgo" # brave, tavily, duckduckgo, searxng, jina + provider: str = "duckduckgo" # brave, tavily, duckduckgo, searxng, jina, kagi api_key: str = "" base_url: str = "" # SearXNG base URL max_results: int = 5