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: 2 additions & 1 deletion forgegod/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,11 @@ class ReconConfig(BaseModel):
enabled: bool = False
max_searches: int = 15
max_fetch_chars: int = 3000 # per-page content limit
search_provider: str = "searxng" # searxng, brave, exa
search_provider: str = "searxng" # searxng, duckduckgo, brave, exa, tavily
searxng_url: str = "http://localhost:8888"
brave_api_key: str = ""
exa_api_key: str = ""
tavily_api_key: str = ""
debate_rounds: int = 3
min_approval_score: float = 7.0 # 0-10, plan must score above this
cache_results: bool = True
Expand Down
2 changes: 2 additions & 0 deletions forgegod/researcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ async def _execute_searches(
searxng_url = self.recon.searxng_url
brave_key = self.recon.brave_api_key
exa_key = self.recon.exa_api_key
tavily_key = self.recon.tavily_api_key

async def _search_one(q: SearchQuery) -> list[SearchResult]:
raw = await web_search(
Expand All @@ -209,6 +210,7 @@ async def _search_one(q: SearchQuery) -> list[SearchResult]:
searxng_url=searxng_url,
brave_api_key=brave_key,
exa_api_key=exa_key,
tavily_api_key=tavily_key,
)
try:
items = json.loads(raw)
Expand Down
31 changes: 29 additions & 2 deletions forgegod/tools/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,30 @@ async def _search_exa(
return []


async def _search_tavily(
query: str, api_key: str, max_results: int = 5
) -> list[dict]:
"""Search via Tavily API (optimised for LLM pipelines)."""
if not api_key:
return []
try:
from tavily import AsyncTavilyClient

client = AsyncTavilyClient(api_key=api_key)
data = await client.search(query=query, max_results=max_results)
results = []
for r in data.get("results", [])[:max_results]:
results.append({
"url": r.get("url", ""),
"title": r.get("title", ""),
"snippet": (r.get("content") or "")[:500],
})
return results
except Exception as e:
logger.warning("Tavily search failed: %s", e)
return []


async def _search_duckduckgo(
query: str, max_results: int = 5
) -> list[dict]:
Expand Down Expand Up @@ -174,15 +198,16 @@ async def web_search(
query: str, provider: str = "searxng", max_results: int = 5,
searxng_url: str = "http://localhost:8888",
brave_api_key: str = "", exa_api_key: str = "",
tavily_api_key: str = "",
) -> str:
"""Search the web. Returns JSON array of {url, title, snippet}.

Tries providers in order: requested → SearXNG → Brave → Exa.
Tries providers in order: requested → SearXNG → DuckDuckGo → Brave → Exa → Tavily.
"""
results: list[dict] = []

# Try requested provider first, then fallback chain
providers = [provider, "searxng", "duckduckgo", "brave", "exa"]
providers = [provider, "searxng", "duckduckgo", "brave", "exa", "tavily"]
seen = set()

for p in providers:
Expand All @@ -198,6 +223,8 @@ async def web_search(
results = await _search_brave(query, brave_api_key, max_results)
elif p == "exa":
results = await _search_exa(query, exa_api_key, max_results)
elif p == "tavily":
results = await _search_tavily(query, tavily_api_key, max_results)

if not results:
return json.dumps({"error": "All search providers failed", "query": query})
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ anthropic = ["anthropic>=0.52"]
dotenv = ["python-dotenv>=1.0"]
taste = ["taste-agent>=0.1.0"]
effort = ["effort-agent>=0.1.0"]
all = ["anthropic>=0.52", "python-dotenv>=1.0", "taste-agent>=0.1.0", "effort-agent>=0.1.0"]
tavily = ["tavily-python>=0.5"]
all = ["anthropic>=0.52", "python-dotenv>=1.0", "taste-agent>=0.1.0", "effort-agent>=0.1.0", "tavily-python>=0.5"]
dev = ["pytest>=8", "pytest-asyncio>=0.25", "ruff>=0.11"]

[project.scripts]
Expand Down