Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
OPENROUTER_API_KEY=sk-or-...

# ── Web Search ────────────────────────────────────────────────────────────
# TAVILY_API_KEY=tvly-... # Tavily Search API (optional — enables tavily_search tool)

# ── Messaging ─────────────────────────────────────────────────────────────────
TELEGRAM_BOT_TOKEN= # get from @BotFather on Telegram

Expand Down
6 changes: 5 additions & 1 deletion core/toolsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
],
"search": [
"web_search",
"tavily_search",
],
"git": [
"git_ops",
Expand All @@ -47,6 +48,7 @@
],
"web": [
"web_search",
"tavily_search",
"http_client",
"browser",
],
Expand Down Expand Up @@ -79,6 +81,7 @@
"knowledge_ops",
"file_search",
"web_search",
"tavily_search",
],
"media": [
"vision",
Expand All @@ -97,13 +100,14 @@
TOOLSETS: dict[str, list[str]] = {
"core": [
"shell_exec", "file_ops", "file_search", "web_search",
"tavily_search",
],
"coding": [
"shell_exec", "file_ops", "file_search", "git_ops",
"code_exec", "cloud_exec", "apply_patch", "llm_task",
],
"research": [
"web_search", "http_client", "browser", "file_ops",
"web_search", "tavily_search", "http_client", "browser", "file_ops",
"file_search", "vision", "llm_task",
],
"data": [
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ reportlab>=4.0.0 # PDF generation
playwright>=1.40.0 # headless browser — then: python -m core.bootstrap --browser
paramiko>=3.4.0 # SSH/SFTP; falls back to system ssh binary if absent

# ── Optional: Tavily web search ────────────────────────────────────────────
# tavily-python>=0.3.0 # Tavily Search API (requires TAVILY_API_KEY)

# ── Optional: OpenAI / Anthropic vision, TTS, image gen ──────────────────────
# openai>=1.30.0
# anthropic>=0.25.0
Expand Down
16 changes: 13 additions & 3 deletions tools/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
file_delete, dir_list, file_exists, file_info,
)
from tools.shell_exec import shell_exec
from tools.web_search import duckduckgo_search, web_scrape, x_search
from tools.web_search import duckduckgo_search, web_scrape, x_search, tavily_search
from tools.code_exec import python_exec
from tools.http_client import http_request
from tools.file_search import file_search
Expand Down Expand Up @@ -234,6 +234,14 @@ def _td_required(td: dict) -> set:
"max_results": "integer — number of results, default 6 (optional)",
},
},
{
"name": "tavily_search",
"description": "Search the web via the Tavily Search API. Returns titles, URLs, and snippets. Requires TAVILY_API_KEY.",
"params": {
"query": "string — search query (required)",
"max_results": "integer — number of results, default 6 (optional)",
},
},
{
"name": "web_scrape",
"description": "Fetch a URL and extract its readable text content. Good for documentation, articles, GitHub READMEs.",
Expand Down Expand Up @@ -2087,6 +2095,7 @@ def _td_required(td: dict) -> set:
"shell_exec": shell_exec,
# Web
"duckduckgo_search": duckduckgo_search,
"tavily_search": tavily_search,
"web_scrape": web_scrape,
# Code
"python_exec": python_exec,
Expand Down Expand Up @@ -2399,7 +2408,7 @@ def spawn_agent(persona: str = "generalist", objective: str = "",
"filesystem": ["file_read", "file_write", "file_append", "file_patch",
"file_delete", "dir_list", "file_exists", "file_info", "file_search"],
"shell": ["shell_exec", "python_exec"],
"web": ["duckduckgo_search", "web_scrape", "x_search", "http_request"],
"web": ["duckduckgo_search", "tavily_search", "web_scrape", "x_search", "http_request"],
"email": ["email_draft"],
"browser": ["browser_navigate", "browser_snapshot", "browser_screenshot",
"browser_click", "browser_type", "browser_scroll", "browser_hover",
Expand Down Expand Up @@ -2471,7 +2480,8 @@ def _get_original_fn(tool_name: str):
"file_read": file_read, "file_write": file_write, "file_append": file_append,
"file_patch": file_patch, "file_delete": file_delete, "dir_list": dir_list,
"file_exists": file_exists, "file_info": file_info, "shell_exec": shell_exec,
"duckduckgo_search": duckduckgo_search, "web_scrape": web_scrape,
"duckduckgo_search": duckduckgo_search, "tavily_search": tavily_search,
"web_scrape": web_scrape,
"x_search": x_search, "python_exec": python_exec, "http_request": http_request,
"file_search": file_search, "email_draft": email_draft,
"browser_navigate": browser_navigate, "browser_snapshot": browser_snapshot,
Expand Down
46 changes: 46 additions & 0 deletions tools/web_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@

1. duckduckgo_search — real-time DuckDuckGo search results (no API key)
2. web_scrape — fetch and extract clean text from any URL
3. tavily_search — Tavily Search API (requires TAVILY_API_KEY)
"""

import os
import re
import urllib.parse
import requests
from typing import Optional

try:
from tavily import TavilyClient
_TAVILY = True
except ImportError:
_TAVILY = False

try:
from bs4 import BeautifulSoup
_BS4 = True
Expand Down Expand Up @@ -217,3 +225,41 @@ def web_scrape(url: str, max_chars: int = 8000) -> dict:
"content": text[:max_chars],
"error": "",
}


def tavily_search(query: str, max_results: int = 6) -> dict:
"""
Search the web via the Tavily Search API and return structured results.
Requires TAVILY_API_KEY environment variable.

Returns:
{"success": bool, "results": [{"title", "url", "snippet"}, ...], "error": str}
"""
api_key = os.environ.get("TAVILY_API_KEY")
if not api_key:
return {
"success": False,
"results": [],
"error": "TAVILY_API_KEY environment variable not set.",
}
if not _TAVILY:
return {
"success": False,
"results": [],
"error": "tavily-python not installed. Run: pip install tavily-python",
}

try:
client = TavilyClient(api_key=api_key)
response = client.search(query=query, max_results=max_results)
results = [
{
"title": r.get("title", ""),
"url": r.get("url", ""),
"snippet": r.get("content", ""),
}
for r in response.get("results", [])
]
return {"success": True, "results": results, "error": ""}
except Exception as e:
return {"success": False, "results": [], "error": str(e)}