feat(tool): add web search proxy
This commit is contained in:
@@ -58,6 +58,7 @@ class AgentLoop:
|
|||||||
memory_window: int = 100,
|
memory_window: int = 100,
|
||||||
reasoning_effort: str | None = None,
|
reasoning_effort: str | None = None,
|
||||||
brave_api_key: str | None = None,
|
brave_api_key: str | 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,
|
||||||
@@ -77,6 +78,7 @@ class AgentLoop:
|
|||||||
self.memory_window = memory_window
|
self.memory_window = memory_window
|
||||||
self.reasoning_effort = reasoning_effort
|
self.reasoning_effort = reasoning_effort
|
||||||
self.brave_api_key = brave_api_key
|
self.brave_api_key = brave_api_key
|
||||||
|
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
|
||||||
@@ -93,6 +95,7 @@ class AgentLoop:
|
|||||||
max_tokens=self.max_tokens,
|
max_tokens=self.max_tokens,
|
||||||
reasoning_effort=reasoning_effort,
|
reasoning_effort=reasoning_effort,
|
||||||
brave_api_key=brave_api_key,
|
brave_api_key=brave_api_key,
|
||||||
|
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,
|
||||||
)
|
)
|
||||||
@@ -120,8 +123,8 @@ 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(api_key=self.brave_api_key))
|
self.tools.register(WebSearchTool(api_key=self.brave_api_key, proxy=self.web_proxy))
|
||||||
self.tools.register(WebFetchTool())
|
self.tools.register(WebFetchTool(proxy=self.web_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:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class SubagentManager:
|
|||||||
max_tokens: int = 4096,
|
max_tokens: int = 4096,
|
||||||
reasoning_effort: str | None = None,
|
reasoning_effort: str | None = None,
|
||||||
brave_api_key: str | None = None,
|
brave_api_key: str | 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,
|
||||||
):
|
):
|
||||||
@@ -43,6 +44,7 @@ class SubagentManager:
|
|||||||
self.max_tokens = max_tokens
|
self.max_tokens = max_tokens
|
||||||
self.reasoning_effort = reasoning_effort
|
self.reasoning_effort = reasoning_effort
|
||||||
self.brave_api_key = brave_api_key
|
self.brave_api_key = brave_api_key
|
||||||
|
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._running_tasks: dict[str, asyncio.Task[None]] = {}
|
self._running_tasks: dict[str, asyncio.Task[None]] = {}
|
||||||
@@ -104,8 +106,8 @@ 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(api_key=self.brave_api_key))
|
tools.register(WebSearchTool(api_key=self.brave_api_key, proxy=self.web_proxy))
|
||||||
tools.register(WebFetchTool())
|
tools.register(WebFetchTool(proxy=self.web_proxy))
|
||||||
|
|
||||||
system_prompt = self._build_subagent_prompt()
|
system_prompt = self._build_subagent_prompt()
|
||||||
messages: list[dict[str, Any]] = [
|
messages: list[dict[str, Any]] = [
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from typing import Any
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from nanobot.agent.tools.base import Tool
|
from nanobot.agent.tools.base import Tool
|
||||||
|
|
||||||
@@ -57,9 +58,10 @@ class WebSearchTool(Tool):
|
|||||||
"required": ["query"]
|
"required": ["query"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, api_key: str | None = None, max_results: int = 5):
|
def __init__(self, api_key: str | None = None, max_results: int = 5, proxy: str | None = None):
|
||||||
self._init_api_key = api_key
|
self._init_api_key = api_key
|
||||||
self.max_results = max_results
|
self.max_results = max_results
|
||||||
|
self.proxy = proxy
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api_key(self) -> str:
|
def api_key(self) -> str:
|
||||||
@@ -71,12 +73,16 @@ class WebSearchTool(Tool):
|
|||||||
return (
|
return (
|
||||||
"Error: Brave Search API key not configured. "
|
"Error: Brave Search API key not configured. "
|
||||||
"Set it in ~/.nanobot/config.json under tools.web.search.apiKey "
|
"Set it in ~/.nanobot/config.json under tools.web.search.apiKey "
|
||||||
"(or export BRAVE_API_KEY), then restart the gateway."
|
"(or export BRAIVE_API_KEY), then restart the gateway."
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
n = min(max(count or self.max_results, 1), 10)
|
n = min(max(count or self.max_results, 1), 10)
|
||||||
async with httpx.AsyncClient() as client:
|
if self.proxy:
|
||||||
|
logger.info("WebSearch: using proxy {} for query: {}", self.proxy, query[:50])
|
||||||
|
else:
|
||||||
|
logger.debug("WebSearch: direct connection for query: {}", query[:50])
|
||||||
|
async with httpx.AsyncClient(proxy=self.proxy) as client:
|
||||||
r = await client.get(
|
r = await client.get(
|
||||||
"https://api.search.brave.com/res/v1/web/search",
|
"https://api.search.brave.com/res/v1/web/search",
|
||||||
params={"q": query, "count": n},
|
params={"q": query, "count": n},
|
||||||
@@ -95,7 +101,11 @@ class WebSearchTool(Tool):
|
|||||||
if desc := item.get("description"):
|
if desc := item.get("description"):
|
||||||
lines.append(f" {desc}")
|
lines.append(f" {desc}")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
except httpx.ProxyError as e:
|
||||||
|
logger.error("WebSearch proxy error: {}", e)
|
||||||
|
return f"Proxy error: {e}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error("WebSearch error: {}", e)
|
||||||
return f"Error: {e}"
|
return f"Error: {e}"
|
||||||
|
|
||||||
|
|
||||||
@@ -114,8 +124,9 @@ class WebFetchTool(Tool):
|
|||||||
"required": ["url"]
|
"required": ["url"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, max_chars: int = 50000):
|
def __init__(self, max_chars: int = 50000, proxy: str | None = None):
|
||||||
self.max_chars = max_chars
|
self.max_chars = max_chars
|
||||||
|
self.proxy = proxy
|
||||||
|
|
||||||
async def execute(self, url: str, extractMode: str = "markdown", maxChars: int | None = None, **kwargs: Any) -> str:
|
async def execute(self, url: str, extractMode: str = "markdown", maxChars: int | None = None, **kwargs: Any) -> str:
|
||||||
from readability import Document
|
from readability import Document
|
||||||
@@ -128,10 +139,15 @@ class WebFetchTool(Tool):
|
|||||||
return json.dumps({"error": f"URL validation failed: {error_msg}", "url": url}, ensure_ascii=False)
|
return json.dumps({"error": f"URL validation failed: {error_msg}", "url": url}, ensure_ascii=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if self.proxy:
|
||||||
|
logger.info("WebFetch: using proxy {} for {}", self.proxy, url)
|
||||||
|
else:
|
||||||
|
logger.debug("WebFetch: direct connection for {}", url)
|
||||||
async with httpx.AsyncClient(
|
async with httpx.AsyncClient(
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
max_redirects=MAX_REDIRECTS,
|
max_redirects=MAX_REDIRECTS,
|
||||||
timeout=30.0
|
timeout=30.0,
|
||||||
|
proxy=self.proxy,
|
||||||
) as client:
|
) as client:
|
||||||
r = await client.get(url, headers={"User-Agent": USER_AGENT})
|
r = await client.get(url, headers={"User-Agent": USER_AGENT})
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -156,7 +172,11 @@ class WebFetchTool(Tool):
|
|||||||
|
|
||||||
return json.dumps({"url": url, "finalUrl": str(r.url), "status": r.status_code,
|
return json.dumps({"url": url, "finalUrl": str(r.url), "status": r.status_code,
|
||||||
"extractor": extractor, "truncated": truncated, "length": len(text), "text": text}, ensure_ascii=False)
|
"extractor": extractor, "truncated": truncated, "length": len(text), "text": text}, ensure_ascii=False)
|
||||||
|
except httpx.ProxyError as e:
|
||||||
|
logger.error("WebFetch proxy error for {}: {}", url, e)
|
||||||
|
return json.dumps({"error": f"Proxy error: {e}", "url": url}, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error("WebFetch error for {}: {}", url, e)
|
||||||
return json.dumps({"error": str(e), "url": url}, ensure_ascii=False)
|
return json.dumps({"error": str(e), "url": url}, ensure_ascii=False)
|
||||||
|
|
||||||
def _to_markdown(self, html: str) -> str:
|
def _to_markdown(self, html: str) -> str:
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ def gateway(
|
|||||||
memory_window=config.agents.defaults.memory_window,
|
memory_window=config.agents.defaults.memory_window,
|
||||||
reasoning_effort=config.agents.defaults.reasoning_effort,
|
reasoning_effort=config.agents.defaults.reasoning_effort,
|
||||||
brave_api_key=config.tools.web.search.api_key or None,
|
brave_api_key=config.tools.web.search.api_key or None,
|
||||||
|
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,
|
||||||
@@ -444,6 +445,7 @@ def agent(
|
|||||||
memory_window=config.agents.defaults.memory_window,
|
memory_window=config.agents.defaults.memory_window,
|
||||||
reasoning_effort=config.agents.defaults.reasoning_effort,
|
reasoning_effort=config.agents.defaults.reasoning_effort,
|
||||||
brave_api_key=config.tools.web.search.api_key or None,
|
brave_api_key=config.tools.web.search.api_key or None,
|
||||||
|
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,
|
||||||
@@ -938,6 +940,7 @@ def cron_run(
|
|||||||
memory_window=config.agents.defaults.memory_window,
|
memory_window=config.agents.defaults.memory_window,
|
||||||
reasoning_effort=config.agents.defaults.reasoning_effort,
|
reasoning_effort=config.agents.defaults.reasoning_effort,
|
||||||
brave_api_key=config.tools.web.search.api_key or None,
|
brave_api_key=config.tools.web.search.api_key or None,
|
||||||
|
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,
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ class WebSearchConfig(Base):
|
|||||||
class WebToolsConfig(Base):
|
class WebToolsConfig(Base):
|
||||||
"""Web tools configuration."""
|
"""Web tools configuration."""
|
||||||
|
|
||||||
|
proxy: str | None = None # HTTP/SOCKS5 proxy URL, e.g. "http://127.0.0.1:7890" or "socks5://127.0.0.1:1080"
|
||||||
search: WebSearchConfig = Field(default_factory=WebSearchConfig)
|
search: WebSearchConfig = Field(default_factory=WebSearchConfig)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user