From 7f1dca3186b8497ba1dbf2dc2a629fc71bd3541d Mon Sep 17 00:00:00 2001 From: Shiniese <135589327+Shiniese@users.noreply.github.com> Date: Mon, 30 Mar 2026 15:16:58 +0800 Subject: [PATCH 1/2] feat: unify web tool config under WebToolsConfig + add web tool toggle controls - Rename WebSearchConfig references to the new WebToolsConfig root struct that wraps both search config and global proxy settings - Add 'enable' flag to WebToolsConfig to allow fully disabling all web-related tools (WebSearch, WebFetch) at runtime - Update AgentLoop and SubagentManager to receive the full web config object instead of separate web_search_config/web_proxy parameters - Update CLI command initialization to pass the consolidated web config struct instead of split fields - Change default web search provider from brave to duckduckgo for better out-of-the-box usability (no API key required) --- nanobot/agent/loop.py | 18 ++++++++---------- nanobot/agent/subagent.py | 26 +++++++++++++------------- nanobot/cli/commands.py | 6 ++---- nanobot/config/schema.py | 3 ++- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 63ee92ca5..e4f4ec991 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -33,7 +33,7 @@ from nanobot.providers.base import LLMProvider from nanobot.session.manager import Session, SessionManager 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 @@ -59,8 +59,7 @@ class AgentLoop: model: str | None = None, max_iterations: int = 40, context_window_tokens: int = 65_536, - web_search_config: WebSearchConfig | None = None, - web_proxy: str | None = None, + web_config: WebToolsConfig | None = None, exec_config: ExecToolConfig | None = None, cron_service: CronService | None = None, restrict_to_workspace: bool = False, @@ -69,7 +68,7 @@ class AgentLoop: channels_config: ChannelsConfig | None = None, timezone: str | None = None, ): - from nanobot.config.schema import ExecToolConfig, WebSearchConfig + from nanobot.config.schema import ExecToolConfig, WebToolsConfig self.bus = bus self.channels_config = channels_config @@ -78,8 +77,7 @@ class AgentLoop: self.model = model or provider.get_default_model() self.max_iterations = max_iterations self.context_window_tokens = context_window_tokens - self.web_search_config = web_search_config or WebSearchConfig() - self.web_proxy = web_proxy + self.web_config = web_config or WebToolsConfig() self.exec_config = exec_config or ExecToolConfig() self.cron_service = cron_service self.restrict_to_workspace = restrict_to_workspace @@ -95,8 +93,7 @@ class AgentLoop: workspace=workspace, bus=bus, model=self.model, - web_search_config=self.web_search_config, - web_proxy=web_proxy, + web_config=self.web_config, exec_config=self.exec_config, restrict_to_workspace=restrict_to_workspace, ) @@ -142,8 +139,9 @@ class AgentLoop: restrict_to_workspace=self.restrict_to_workspace, path_append=self.exec_config.path_append, )) - self.tools.register(WebSearchTool(config=self.web_search_config, proxy=self.web_proxy)) - self.tools.register(WebFetchTool(proxy=self.web_proxy)) + if self.web_config.enable: + 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(SpawnTool(manager=self.subagents)) if self.cron_service: diff --git a/nanobot/agent/subagent.py b/nanobot/agent/subagent.py index 5266fc8b1..6487bc11c 100644 --- a/nanobot/agent/subagent.py +++ b/nanobot/agent/subagent.py @@ -17,7 +17,7 @@ from nanobot.agent.tools.shell import ExecTool from nanobot.agent.tools.web import WebFetchTool, WebSearchTool from nanobot.bus.events import InboundMessage 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 @@ -30,8 +30,7 @@ class SubagentManager: workspace: Path, bus: MessageBus, model: str | None = None, - web_search_config: "WebSearchConfig | None" = None, - web_proxy: str | None = None, + web_config: "WebToolsConfig | None" = None, exec_config: "ExecToolConfig | None" = None, restrict_to_workspace: bool = False, ): @@ -41,8 +40,7 @@ class SubagentManager: self.workspace = workspace self.bus = bus self.model = model or provider.get_default_model() - self.web_search_config = web_search_config or WebSearchConfig() - self.web_proxy = web_proxy + self.web_config = web_config or WebToolsConfig() self.exec_config = exec_config or ExecToolConfig() self.restrict_to_workspace = restrict_to_workspace self.runner = AgentRunner(provider) @@ -100,14 +98,16 @@ class SubagentManager: tools.register(WriteFileTool(workspace=self.workspace, allowed_dir=allowed_dir)) tools.register(EditFileTool(workspace=self.workspace, allowed_dir=allowed_dir)) tools.register(ListDirTool(workspace=self.workspace, allowed_dir=allowed_dir)) - tools.register(ExecTool( - working_dir=str(self.workspace), - timeout=self.exec_config.timeout, - restrict_to_workspace=self.restrict_to_workspace, - path_append=self.exec_config.path_append, - )) - tools.register(WebSearchTool(config=self.web_search_config, proxy=self.web_proxy)) - tools.register(WebFetchTool(proxy=self.web_proxy)) + if self.exec_config.enable: + tools.register(ExecTool( + working_dir=str(self.workspace), + timeout=self.exec_config.timeout, + restrict_to_workspace=self.restrict_to_workspace, + path_append=self.exec_config.path_append, + )) + if self.web_config.enable: + 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() messages: list[dict[str, Any]] = [ diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index cacb61ae6..c3727d319 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -541,8 +541,7 @@ def gateway( model=config.agents.defaults.model, max_iterations=config.agents.defaults.max_tool_iterations, context_window_tokens=config.agents.defaults.context_window_tokens, - web_search_config=config.tools.web.search, - web_proxy=config.tools.web.proxy or None, + web_config=config.tools.web, exec_config=config.tools.exec, cron_service=cron, restrict_to_workspace=config.tools.restrict_to_workspace, @@ -747,8 +746,7 @@ def agent( model=config.agents.defaults.model, max_iterations=config.agents.defaults.max_tool_iterations, context_window_tokens=config.agents.defaults.context_window_tokens, - web_search_config=config.tools.web.search, - web_proxy=config.tools.web.proxy or None, + web_config=config.tools.web, exec_config=config.tools.exec, cron_service=cron, restrict_to_workspace=config.tools.restrict_to_workspace, diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index c8b69b42e..1978a17c8 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -107,7 +107,7 @@ class GatewayConfig(Base): class WebSearchConfig(Base): """Web search tool configuration.""" - provider: str = "brave" # brave, tavily, duckduckgo, searxng, jina + provider: str = "duckduckgo" # brave, tavily, duckduckgo, searxng, jina api_key: str = "" base_url: str = "" # SearXNG base URL max_results: int = 5 @@ -116,6 +116,7 @@ class WebSearchConfig(Base): class WebToolsConfig(Base): """Web tools configuration.""" + enable: bool = True proxy: str | None = ( None # HTTP/SOCKS5 proxy URL, e.g. "http://127.0.0.1:7890" or "socks5://127.0.0.1:1080" ) From 400f8eb38e85fefcdcfb1238ac312368428b0769 Mon Sep 17 00:00:00 2001 From: Xubin Ren Date: Fri, 3 Apr 2026 18:44:46 +0000 Subject: [PATCH 2/2] docs: update web search configuration information --- README.md | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index da7346b38..7ca22fd23 100644 --- a/README.md +++ b/README.md @@ -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`. +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 | |----------|--------------|------------------|------| -| `brave` (default) | `apiKey` | `BRAVE_API_KEY` | No | +| `brave` | `apiKey` | `BRAVE_API_KEY` | No | | `tavily` | `apiKey` | `TAVILY_API_KEY` | No | | `jina` | `apiKey` | `JINA_API_KEY` | Free tier (10M tokens) | | `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 { "tools": { @@ -1298,7 +1311,14 @@ When credentials are missing, nanobot automatically falls back to DuckDuckGo. | 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 | | `baseUrl` | string | `""` | Base URL for SearXNG | | `maxResults` | integer | `5` | Results per search (1–10) |