Files
Agent-Reach/agent_eyes/fetchers/search.py
T
Panniantong 3a3a0101cf Agent Eyes v1.0.0 — search + read the entire internet
Based on x-reader by @runes_leo (MIT License). Extended with:
- Reddit support (posts + comments, proxy support)
- GitHub support (repos, issues, PRs)
- Web search via Exa semantic search
- Reddit search (bypasses IP blocks via Exa)
- GitHub search (repos by stars)
- Renamed package: x_reader → agent_eyes
- New MCP tools: search, search_reddit, search_github
- Agent-first positioning and documentation
2026-02-24 03:07:50 +01:00

95 lines
2.4 KiB
Python

# -*- coding: utf-8 -*-
"""Search fetcher — semantic web search via Exa API.
Requires EXA_API_KEY env var. Get a free key at https://exa.ai
"""
import os
import requests
from loguru import logger
from typing import Dict, Any, List, Optional
EXA_API_URL = "https://api.exa.ai/search"
async def search_web(
query: str,
num_results: int = 5,
search_type: str = "auto",
) -> List[Dict[str, Any]]:
"""
Search the web using Exa semantic search.
Args:
query: Search query (supports site: prefix, e.g. "site:reddit.com AI agent")
num_results: Number of results to return (default 5, max 10)
search_type: "auto" (default) or "neural" or "keyword"
Returns:
List of search results with title, url, snippet
"""
api_key = os.environ.get("EXA_API_KEY")
if not api_key:
raise ValueError(
"EXA_API_KEY not set. Get a free key at https://exa.ai\n"
"Then: export EXA_API_KEY=your_key_here"
)
logger.info(f"Exa search: {query} (n={num_results})")
resp = requests.post(
EXA_API_URL,
headers={
"Content-Type": "application/json",
"x-api-key": api_key,
},
json={
"query": query,
"numResults": min(num_results, 10),
"type": search_type,
"contents": {
"text": {"maxCharacters": 500},
},
},
timeout=15,
)
resp.raise_for_status()
data = resp.json()
results = []
for item in data.get("results", []):
results.append({
"title": item.get("title", ""),
"url": item.get("url", ""),
"snippet": item.get("text", ""),
"published_date": item.get("publishedDate", ""),
"score": item.get("score", 0),
})
return results
async def search_reddit_via_exa(
query: str,
subreddit: Optional[str] = None,
num_results: int = 10,
) -> List[Dict[str, Any]]:
"""
Search Reddit content via Exa (bypasses Reddit IP blocks).
Args:
query: Search query
subreddit: Optional subreddit to limit search (e.g. "LocalLLaMA")
num_results: Number of results
Returns:
List of Reddit posts found
"""
if subreddit:
full_query = f"site:reddit.com/r/{subreddit} {query}"
else:
full_query = f"site:reddit.com {query}"
return await search_web(full_query, num_results=num_results)