feat(web): configurable web search providers with fallback

Add multi-provider web search support: Brave (default), Tavily,
DuckDuckGo, and SearXNG. Falls back to DuckDuckGo when provider
credentials are missing. Providers are dispatched via a map with
register_provider() for plugin extensibility.

- WebSearchConfig with env-var resolution and from_legacy() bridge
- Config migration for legacy flat keys (tavilyApiKey, searxngBaseUrl)
- SearXNG URL validation, explicit error for unknown providers
- ddgs package (replaces deprecated duckduckgo-search)
- 16 tests covering all providers, fallback, env resolution, edge cases
- docs/web-search.md with full config reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Alexander
2026-02-10 13:06:56 +00:00
parent 99b896f5d4
commit 71d90de31b
10 changed files with 722 additions and 97 deletions

View File

@@ -1,8 +1,10 @@
from typing import Any
from nanobot.agent.tools.web import WebSearchTool
from nanobot.agent.tools.base import Tool
from nanobot.agent.tools.registry import ToolRegistry
from nanobot.agent.tools.shell import ExecTool
from nanobot.config.schema import WebSearchConfig
class SampleTool(Tool):
@@ -337,3 +339,16 @@ def test_cast_params_single_value_not_auto_wrapped_to_array() -> None:
assert result["items"] == 5 # Not wrapped to [5]
result = tool.cast_params({"items": "text"})
assert result["items"] == "text" # Not wrapped to ["text"]
async def test_web_search_no_fallback_returns_provider_error() -> None:
tool = WebSearchTool(
config=WebSearchConfig(
provider="brave",
api_key="",
fallback_to_duckduckgo=False,
)
)
result = await tool.execute(query="fallback", count=1)
assert result == "Error: BRAVE_API_KEY not configured"