You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
7.2 KiB
247 lines
7.2 KiB
#!/usr/bin/env python3 |
|
""" |
|
Tavily AI Search - Optimized search for LLMs and AI applications |
|
Requires: pip install tavily-python |
|
""" |
|
|
|
import argparse |
|
import json |
|
import sys |
|
import os |
|
from typing import Optional, List |
|
|
|
|
|
def search( |
|
query: str, |
|
api_key: str, |
|
search_depth: str = "basic", |
|
topic: str = "general", |
|
max_results: int = 5, |
|
include_answer: bool = True, |
|
include_raw_content: bool = False, |
|
include_images: bool = False, |
|
include_domains: Optional[List[str]] = None, |
|
exclude_domains: Optional[List[str]] = None, |
|
) -> dict: |
|
""" |
|
Execute a Tavily search query. |
|
|
|
Args: |
|
query: Search query string |
|
api_key: Tavily API key (tvly-...) |
|
search_depth: "basic" (fast) or "advanced" (comprehensive) |
|
topic: "general" (default) or "news" (current events) |
|
max_results: Number of results to return (1-10) |
|
include_answer: Include AI-generated answer summary |
|
include_raw_content: Include raw HTML content of sources |
|
include_images: Include relevant images in results |
|
include_domains: List of domains to specifically include |
|
exclude_domains: List of domains to exclude |
|
|
|
Returns: |
|
dict: Tavily API response |
|
""" |
|
try: |
|
from tavily import TavilyClient |
|
except ImportError: |
|
return { |
|
"error": "tavily-python package not installed. Run: pip install tavily-python", |
|
"install_command": "pip install tavily-python" |
|
} |
|
|
|
if not api_key: |
|
return { |
|
"error": "Tavily API key required. Get one at https://tavily.com", |
|
"setup_instructions": "Set TAVILY_API_KEY environment variable or pass --api-key" |
|
} |
|
|
|
try: |
|
client = TavilyClient(api_key=api_key) |
|
|
|
# Build search parameters |
|
search_params = { |
|
"query": query, |
|
"search_depth": search_depth, |
|
"topic": topic, |
|
"max_results": max_results, |
|
"include_answer": include_answer, |
|
"include_raw_content": include_raw_content, |
|
"include_images": include_images, |
|
} |
|
|
|
if include_domains: |
|
search_params["include_domains"] = include_domains |
|
if exclude_domains: |
|
search_params["exclude_domains"] = exclude_domains |
|
|
|
response = client.search(**search_params) |
|
|
|
return { |
|
"success": True, |
|
"query": query, |
|
"answer": response.get("answer"), |
|
"results": response.get("results", []), |
|
"images": response.get("images", []), |
|
"response_time": response.get("response_time"), |
|
"usage": response.get("usage", {}), |
|
} |
|
|
|
except Exception as e: |
|
return { |
|
"error": str(e), |
|
"query": query |
|
} |
|
|
|
|
|
def main(): |
|
parser = argparse.ArgumentParser( |
|
description="Tavily AI Search - Optimized search for LLMs", |
|
formatter_class=argparse.RawDescriptionHelpFormatter, |
|
epilog=""" |
|
Examples: |
|
# Basic search |
|
%(prog)s "What is quantum computing?" |
|
|
|
# Advanced search with more results |
|
%(prog)s "Climate change solutions" --depth advanced --max-results 10 |
|
|
|
# News-focused search |
|
%(prog)s "AI developments" --topic news |
|
|
|
# Domain filtering |
|
%(prog)s "Python tutorials" --include-domains python.org --exclude-domains w3schools.com |
|
|
|
# Include images in results |
|
%(prog)s "Eiffel Tower" --images |
|
|
|
Environment Variables: |
|
TAVILY_API_KEY Your Tavily API key (get one at https://tavily.com) |
|
""" |
|
) |
|
|
|
parser.add_argument( |
|
"query", |
|
help="Search query" |
|
) |
|
|
|
parser.add_argument( |
|
"--api-key", |
|
help="Tavily API key (or set TAVILY_API_KEY env var)" |
|
) |
|
|
|
parser.add_argument( |
|
"--depth", |
|
choices=["basic", "advanced"], |
|
default="basic", |
|
help="Search depth: 'basic' (fast) or 'advanced' (comprehensive)" |
|
) |
|
|
|
parser.add_argument( |
|
"--topic", |
|
choices=["general", "news"], |
|
default="general", |
|
help="Search topic: 'general' or 'news' (current events)" |
|
) |
|
|
|
parser.add_argument( |
|
"--max-results", |
|
type=int, |
|
default=5, |
|
help="Maximum number of results (1-10)" |
|
) |
|
|
|
parser.add_argument( |
|
"--no-answer", |
|
action="store_true", |
|
help="Exclude AI-generated answer summary" |
|
) |
|
|
|
parser.add_argument( |
|
"--raw-content", |
|
action="store_true", |
|
help="Include raw HTML content of sources" |
|
) |
|
|
|
parser.add_argument( |
|
"--images", |
|
action="store_true", |
|
help="Include relevant images in results" |
|
) |
|
|
|
parser.add_argument( |
|
"--include-domains", |
|
nargs="+", |
|
help="List of domains to specifically include" |
|
) |
|
|
|
parser.add_argument( |
|
"--exclude-domains", |
|
nargs="+", |
|
help="List of domains to exclude" |
|
) |
|
|
|
parser.add_argument( |
|
"--json", |
|
action="store_true", |
|
help="Output raw JSON response" |
|
) |
|
|
|
args = parser.parse_args() |
|
|
|
# Get API key from args or environment |
|
api_key = args.api_key or os.getenv("TAVILY_API_KEY") |
|
|
|
result = search( |
|
query=args.query, |
|
api_key=api_key, |
|
search_depth=args.depth, |
|
topic=args.topic, |
|
max_results=args.max_results, |
|
include_answer=not args.no_answer, |
|
include_raw_content=args.raw_content, |
|
include_images=args.images, |
|
include_domains=args.include_domains, |
|
exclude_domains=args.exclude_domains, |
|
) |
|
|
|
if args.json: |
|
print(json.dumps(result, indent=2)) |
|
else: |
|
if "error" in result: |
|
print(f"Error: {result['error']}", file=sys.stderr) |
|
if "install_command" in result: |
|
print(f"\nTo install: {result['install_command']}", file=sys.stderr) |
|
if "setup_instructions" in result: |
|
print(f"\nSetup: {result['setup_instructions']}", file=sys.stderr) |
|
sys.exit(1) |
|
|
|
# Format human-readable output |
|
print(f"Query: {result['query']}") |
|
print(f"Response time: {result.get('response_time', 'N/A')}s") |
|
print(f"Credits used: {result.get('usage', {}).get('credits', 'N/A')}\n") |
|
|
|
if result.get("answer"): |
|
print("=== AI ANSWER ===") |
|
print(result["answer"]) |
|
print() |
|
|
|
if result.get("results"): |
|
print("=== RESULTS ===") |
|
for i, item in enumerate(result["results"], 1): |
|
print(f"\n{i}. {item.get('title', 'No title')}") |
|
print(f" URL: {item.get('url', 'N/A')}") |
|
print(f" Score: {item.get('score', 'N/A'):.3f}") |
|
if item.get("content"): |
|
content = item["content"] |
|
if len(content) > 200: |
|
content = content[:200] + "..." |
|
print(f" {content}") |
|
|
|
if result.get("images"): |
|
print(f"\n=== IMAGES ({len(result['images'])}) ===") |
|
for img_url in result["images"][:5]: # Show first 5 |
|
print(f" {img_url}") |
|
|
|
|
|
if __name__ == "__main__": |
|
main()
|
|
|