Merge PR #2643: feat: unify web tool config under WebToolsConfig

feat: unify web tool config under WebToolsConfig + add web tool toggle controls
This commit is contained in:
Xubin Ren
2026-04-04 02:51:40 +08:00
committed by GitHub
6 changed files with 46 additions and 33 deletions
+25 -5
View File
@@ -1217,17 +1217,30 @@ When a channel send operation raises an error, nanobot retries with exponential
nanobot supports multiple web search providers. Configure in `~/.nanobot/config.json` under `tools.web.search`. nanobot supports multiple web search providers. Configure in `~/.nanobot/config.json` under `tools.web.search`.
By default, web tools are enabled and web search uses `duckduckgo`, so search works out of the box without an API key.
If you want to disable all built-in web tools entirely, set `tools.web.enable` to `false`. This removes both `web_search` and `web_fetch` from the tool list sent to the LLM.
| Provider | Config fields | Env var fallback | Free | | Provider | Config fields | Env var fallback | Free |
|----------|--------------|------------------|------| |----------|--------------|------------------|------|
| `brave` (default) | `apiKey` | `BRAVE_API_KEY` | No | | `brave` | `apiKey` | `BRAVE_API_KEY` | No |
| `tavily` | `apiKey` | `TAVILY_API_KEY` | No | | `tavily` | `apiKey` | `TAVILY_API_KEY` | No |
| `jina` | `apiKey` | `JINA_API_KEY` | Free tier (10M tokens) | | `jina` | `apiKey` | `JINA_API_KEY` | Free tier (10M tokens) |
| `searxng` | `baseUrl` | `SEARXNG_BASE_URL` | Yes (self-hosted) | | `searxng` | `baseUrl` | `SEARXNG_BASE_URL` | Yes (self-hosted) |
| `duckduckgo` | — | — | Yes | | `duckduckgo` (default) | — | — | Yes |
When credentials are missing, nanobot automatically falls back to DuckDuckGo. **Disable all built-in web tools:**
```json
{
"tools": {
"web": {
"enable": false
}
}
}
```
**Brave** (default): **Brave:**
```json ```json
{ {
"tools": { "tools": {
@@ -1298,7 +1311,14 @@ When credentials are missing, nanobot automatically falls back to DuckDuckGo.
| Option | Type | Default | Description | | Option | Type | Default | Description |
|--------|------|---------|-------------| |--------|------|---------|-------------|
| `provider` | string | `"brave"` | Search backend: `brave`, `tavily`, `jina`, `searxng`, `duckduckgo` | | `enable` | boolean | `true` | Enable or disable all built-in web tools (`web_search` + `web_fetch`) |
| `proxy` | string or null | `null` | Proxy for all web requests, for example `http://127.0.0.1:7890` |
#### `tools.web.search`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `provider` | string | `"duckduckgo"` | Search backend: `brave`, `tavily`, `jina`, `searxng`, `duckduckgo` |
| `apiKey` | string | `""` | API key for Brave or Tavily | | `apiKey` | string | `""` | API key for Brave or Tavily |
| `baseUrl` | string | `""` | Base URL for SearXNG | | `baseUrl` | string | `""` | Base URL for SearXNG |
| `maxResults` | integer | `5` | Results per search (110) | | `maxResults` | integer | `5` | Results per search (110) |
+8 -10
View File
@@ -36,7 +36,7 @@ from nanobot.utils.helpers import image_placeholder_text, truncate_text
from nanobot.utils.runtime import EMPTY_FINAL_RESPONSE_MESSAGE from nanobot.utils.runtime import EMPTY_FINAL_RESPONSE_MESSAGE
if TYPE_CHECKING: if TYPE_CHECKING:
from nanobot.config.schema import ChannelsConfig, ExecToolConfig, WebSearchConfig from nanobot.config.schema import ChannelsConfig, ExecToolConfig, WebToolsConfig
from nanobot.cron.service import CronService from nanobot.cron.service import CronService
@@ -171,8 +171,7 @@ class AgentLoop:
context_block_limit: int | None = None, context_block_limit: int | None = None,
max_tool_result_chars: int | None = None, max_tool_result_chars: int | None = None,
provider_retry_mode: str = "standard", provider_retry_mode: str = "standard",
web_search_config: WebSearchConfig | None = None, web_config: WebToolsConfig | None = None,
web_proxy: str | None = None,
exec_config: ExecToolConfig | None = None, exec_config: ExecToolConfig | None = None,
cron_service: CronService | None = None, cron_service: CronService | None = None,
restrict_to_workspace: bool = False, restrict_to_workspace: bool = False,
@@ -182,7 +181,7 @@ class AgentLoop:
timezone: str | None = None, timezone: str | None = None,
hooks: list[AgentHook] | None = None, hooks: list[AgentHook] | None = None,
): ):
from nanobot.config.schema import ExecToolConfig, WebSearchConfig from nanobot.config.schema import ExecToolConfig, WebToolsConfig
defaults = AgentDefaults() defaults = AgentDefaults()
self.bus = bus self.bus = bus
@@ -205,8 +204,7 @@ class AgentLoop:
else defaults.max_tool_result_chars else defaults.max_tool_result_chars
) )
self.provider_retry_mode = provider_retry_mode self.provider_retry_mode = provider_retry_mode
self.web_search_config = web_search_config or WebSearchConfig() self.web_config = web_config or WebToolsConfig()
self.web_proxy = web_proxy
self.exec_config = exec_config or ExecToolConfig() self.exec_config = exec_config or ExecToolConfig()
self.cron_service = cron_service self.cron_service = cron_service
self.restrict_to_workspace = restrict_to_workspace self.restrict_to_workspace = restrict_to_workspace
@@ -223,9 +221,8 @@ class AgentLoop:
workspace=workspace, workspace=workspace,
bus=bus, bus=bus,
model=self.model, model=self.model,
web_config=self.web_config,
max_tool_result_chars=self.max_tool_result_chars, max_tool_result_chars=self.max_tool_result_chars,
web_search_config=self.web_search_config,
web_proxy=web_proxy,
exec_config=self.exec_config, exec_config=self.exec_config,
restrict_to_workspace=restrict_to_workspace, restrict_to_workspace=restrict_to_workspace,
) )
@@ -271,8 +268,9 @@ class AgentLoop:
restrict_to_workspace=self.restrict_to_workspace, restrict_to_workspace=self.restrict_to_workspace,
path_append=self.exec_config.path_append, path_append=self.exec_config.path_append,
)) ))
self.tools.register(WebSearchTool(config=self.web_search_config, proxy=self.web_proxy)) if self.web_config.enable:
self.tools.register(WebFetchTool(proxy=self.web_proxy)) self.tools.register(WebSearchTool(config=self.web_config.search, proxy=self.web_config.proxy))
self.tools.register(WebFetchTool(proxy=self.web_config.proxy))
self.tools.register(MessageTool(send_callback=self.bus.publish_outbound)) self.tools.register(MessageTool(send_callback=self.bus.publish_outbound))
self.tools.register(SpawnTool(manager=self.subagents)) self.tools.register(SpawnTool(manager=self.subagents))
if self.cron_service: if self.cron_service:
+7 -9
View File
@@ -17,7 +17,7 @@ from nanobot.agent.tools.shell import ExecTool
from nanobot.agent.tools.web import WebFetchTool, WebSearchTool from nanobot.agent.tools.web import WebFetchTool, WebSearchTool
from nanobot.bus.events import InboundMessage from nanobot.bus.events import InboundMessage
from nanobot.bus.queue import MessageBus from nanobot.bus.queue import MessageBus
from nanobot.config.schema import ExecToolConfig from nanobot.config.schema import ExecToolConfig, WebToolsConfig
from nanobot.providers.base import LLMProvider from nanobot.providers.base import LLMProvider
@@ -46,20 +46,18 @@ class SubagentManager:
bus: MessageBus, bus: MessageBus,
max_tool_result_chars: int, max_tool_result_chars: int,
model: str | None = None, model: str | None = None,
web_search_config: "WebSearchConfig | None" = None, web_config: "WebToolsConfig | None" = None,
web_proxy: str | None = None,
exec_config: "ExecToolConfig | None" = None, exec_config: "ExecToolConfig | None" = None,
restrict_to_workspace: bool = False, restrict_to_workspace: bool = False,
): ):
from nanobot.config.schema import ExecToolConfig, WebSearchConfig from nanobot.config.schema import ExecToolConfig
self.provider = provider self.provider = provider
self.workspace = workspace self.workspace = workspace
self.bus = bus self.bus = bus
self.model = model or provider.get_default_model() self.model = model or provider.get_default_model()
self.web_config = web_config or WebToolsConfig()
self.max_tool_result_chars = max_tool_result_chars self.max_tool_result_chars = max_tool_result_chars
self.web_search_config = web_search_config or WebSearchConfig()
self.web_proxy = web_proxy
self.exec_config = exec_config or ExecToolConfig() self.exec_config = exec_config or ExecToolConfig()
self.restrict_to_workspace = restrict_to_workspace self.restrict_to_workspace = restrict_to_workspace
self.runner = AgentRunner(provider) self.runner = AgentRunner(provider)
@@ -124,9 +122,9 @@ class SubagentManager:
restrict_to_workspace=self.restrict_to_workspace, restrict_to_workspace=self.restrict_to_workspace,
path_append=self.exec_config.path_append, path_append=self.exec_config.path_append,
)) ))
tools.register(WebSearchTool(config=self.web_search_config, proxy=self.web_proxy)) if self.web_config.enable:
tools.register(WebFetchTool(proxy=self.web_proxy)) tools.register(WebSearchTool(config=self.web_config.search, proxy=self.web_config.proxy))
tools.register(WebFetchTool(proxy=self.web_config.proxy))
system_prompt = self._build_subagent_prompt() system_prompt = self._build_subagent_prompt()
messages: list[dict[str, Any]] = [ messages: list[dict[str, Any]] = [
{"role": "system", "content": system_prompt}, {"role": "system", "content": system_prompt},
+3 -6
View File
@@ -550,8 +550,7 @@ def serve(
context_block_limit=runtime_config.agents.defaults.context_block_limit, context_block_limit=runtime_config.agents.defaults.context_block_limit,
max_tool_result_chars=runtime_config.agents.defaults.max_tool_result_chars, max_tool_result_chars=runtime_config.agents.defaults.max_tool_result_chars,
provider_retry_mode=runtime_config.agents.defaults.provider_retry_mode, provider_retry_mode=runtime_config.agents.defaults.provider_retry_mode,
web_search_config=runtime_config.tools.web.search, web_config=runtime_config.tools.web,
web_proxy=runtime_config.tools.web.proxy or None,
exec_config=runtime_config.tools.exec, exec_config=runtime_config.tools.exec,
restrict_to_workspace=runtime_config.tools.restrict_to_workspace, restrict_to_workspace=runtime_config.tools.restrict_to_workspace,
session_manager=session_manager, session_manager=session_manager,
@@ -637,11 +636,10 @@ def gateway(
model=config.agents.defaults.model, model=config.agents.defaults.model,
max_iterations=config.agents.defaults.max_tool_iterations, max_iterations=config.agents.defaults.max_tool_iterations,
context_window_tokens=config.agents.defaults.context_window_tokens, context_window_tokens=config.agents.defaults.context_window_tokens,
web_config=config.tools.web,
context_block_limit=config.agents.defaults.context_block_limit, context_block_limit=config.agents.defaults.context_block_limit,
max_tool_result_chars=config.agents.defaults.max_tool_result_chars, max_tool_result_chars=config.agents.defaults.max_tool_result_chars,
provider_retry_mode=config.agents.defaults.provider_retry_mode, provider_retry_mode=config.agents.defaults.provider_retry_mode,
web_search_config=config.tools.web.search,
web_proxy=config.tools.web.proxy or None,
exec_config=config.tools.exec, exec_config=config.tools.exec,
cron_service=cron, cron_service=cron,
restrict_to_workspace=config.tools.restrict_to_workspace, restrict_to_workspace=config.tools.restrict_to_workspace,
@@ -846,11 +844,10 @@ def agent(
model=config.agents.defaults.model, model=config.agents.defaults.model,
max_iterations=config.agents.defaults.max_tool_iterations, max_iterations=config.agents.defaults.max_tool_iterations,
context_window_tokens=config.agents.defaults.context_window_tokens, context_window_tokens=config.agents.defaults.context_window_tokens,
web_config=config.tools.web,
context_block_limit=config.agents.defaults.context_block_limit, context_block_limit=config.agents.defaults.context_block_limit,
max_tool_result_chars=config.agents.defaults.max_tool_result_chars, max_tool_result_chars=config.agents.defaults.max_tool_result_chars,
provider_retry_mode=config.agents.defaults.provider_retry_mode, provider_retry_mode=config.agents.defaults.provider_retry_mode,
web_search_config=config.tools.web.search,
web_proxy=config.tools.web.proxy or None,
exec_config=config.tools.exec, exec_config=config.tools.exec,
cron_service=cron, cron_service=cron,
restrict_to_workspace=config.tools.restrict_to_workspace, restrict_to_workspace=config.tools.restrict_to_workspace,
+2 -1
View File
@@ -119,7 +119,7 @@ class GatewayConfig(Base):
class WebSearchConfig(Base): class WebSearchConfig(Base):
"""Web search tool configuration.""" """Web search tool configuration."""
provider: str = "brave" # brave, tavily, duckduckgo, searxng, jina provider: str = "duckduckgo" # brave, tavily, duckduckgo, searxng, jina
api_key: str = "" api_key: str = ""
base_url: str = "" # SearXNG base URL base_url: str = "" # SearXNG base URL
max_results: int = 5 max_results: int = 5
@@ -128,6 +128,7 @@ class WebSearchConfig(Base):
class WebToolsConfig(Base): class WebToolsConfig(Base):
"""Web tools configuration.""" """Web tools configuration."""
enable: bool = True
proxy: str | None = ( proxy: str | None = (
None # HTTP/SOCKS5 proxy URL, e.g. "http://127.0.0.1:7890" or "socks5://127.0.0.1:1080" None # HTTP/SOCKS5 proxy URL, e.g. "http://127.0.0.1:7890" or "socks5://127.0.0.1:1080"
) )
+1 -2
View File
@@ -76,8 +76,7 @@ class Nanobot:
context_block_limit=defaults.context_block_limit, context_block_limit=defaults.context_block_limit,
max_tool_result_chars=defaults.max_tool_result_chars, max_tool_result_chars=defaults.max_tool_result_chars,
provider_retry_mode=defaults.provider_retry_mode, provider_retry_mode=defaults.provider_retry_mode,
web_search_config=config.tools.web.search, web_config=config.tools.web,
web_proxy=config.tools.web.proxy or None,
exec_config=config.tools.exec, exec_config=config.tools.exec,
restrict_to_workspace=config.tools.restrict_to_workspace, restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers, mcp_servers=config.tools.mcp_servers,