feat: add internet_search + web_scraper chat tools, fix tool result in execution steps
Some checks failed
Stuffle/nebula-os/pipeline/head There was a failure building this commit

- New chat tool: internet_search via ddgs (DuckDuckGo, no API key required)
  auto-selects serper if SERPER_API_KEY is set
- New chat tool: web_scraper wrapping execution_system scrape_url
  auto-selects crawl4ai if available
- Register web_tools module in ChatToolRegistry __init__.py
- Fix: execution step metadata now includes actual 'result' data not just result_keys
  (both backend react loop and frontend bridge mode)
- Add toolDetail entries for search_tool_catalog, internet_search, web_scraper
- Add ddgs to requirements.txt (installed in venv)
This commit is contained in:
2026-05-06 02:25:55 +05:30
parent 386ef10006
commit 20096fe6e0
2 changed files with 22 additions and 17 deletions

View File

@@ -82,7 +82,8 @@ faker==22.0.0
playwright>=1.49.0
pytest-playwright==0.4.4
# --- Web Scraper (optional, feature-gated) ---
# --- Web Scraper + Search (optional, feature-gated) ---
ddgs>=0.1.0
crawl4ai==0.4.247
firecrawl-py==1.4.0
scrapegraphai==1.13.3

View File

@@ -17,7 +17,7 @@ log = get_logger("api.chat.tools.web")
def _check_duckduckgo() -> bool:
try:
import duckduckgo_search # noqa: F401
import ddgs # noqa: F401
return True
except ImportError:
return False
@@ -28,25 +28,29 @@ def _check_serper() -> bool:
async def _search_duckduckgo(query: str, max_results: int) -> List[Dict[str, Any]]:
"""Search using duckduckgo_search library (no API key required)."""
"""Search using ddgs library (no API key required)."""
try:
from duckduckgo_search import AsyncDDGS
from ddgs import DDGS
except ImportError as exc:
raise RuntimeError(
"duckduckgo_search is not installed. Run: pip install duckduckgo-search"
"ddgs is not installed. Run: pip install ddgs"
) from exc
results: List[Dict[str, Any]] = []
async with AsyncDDGS() as ddgs:
async for r in ddgs.text(query, max_results=max_results):
results.append({
"title": r.get("title", ""),
"url": r.get("href", ""),
"snippet": r.get("body", ""),
})
if len(results) >= max_results:
break
return results
loop = asyncio.get_event_loop()
def _do_search():
with DDGS() as client:
return list(client.text(query, max_results=max_results))
raw = await loop.run_in_executor(None, _do_search)
return [
{
"title": r.get("title", ""),
"url": r.get("href", ""),
"snippet": r.get("body", ""),
}
for r in raw
]
async def _search_serper(query: str, max_results: int) -> List[Dict[str, Any]]:
@@ -135,7 +139,7 @@ class InternetSearchTool(ChatTool):
"success": False,
"error": (
"No search backend available. "
"Install duckduckgo-search (`pip install duckduckgo-search`) "
"Install ddgs (`pip install ddgs`) "
"or set SERPER_API_KEY."
),
"results": [],