Coverage for web_search / providers / tavily.py: 40%
40 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 02:55 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 02:55 +0800
1import os
2import time
3import requests
4from typing import List, Optional, Dict, Any
5from qrclaw.web_search.types import WebSearchProvider, SearchResult, WebSearchResponse
6from qrclaw.config import TAVILY_API_KEY
7from qrclaw.logger import get_logger
9logger = get_logger("qrclaw.web_search.providers.tavily")
11class TavilySearchProvider(WebSearchProvider):
12 @property
13 def id(self) -> str:
14 return "tavily"
16 @property
17 def name(self) -> str:
18 return "Tavily Search"
20 def is_available(self) -> bool:
21 return bool(TAVILY_API_KEY)
23 def search(self, query: str, max_results: int = 5, **kwargs) -> WebSearchResponse:
24 """
25 执行 Tavily 搜索
27 Args:
28 query: 搜索词
29 max_results: 返回结果数量 (1-20)
30 **kwargs: 其他参数,如 search_depth, include_answer
31 """
32 if not self.is_available():
33 raise ValueError("TAVILY_API_KEY is not configured")
35 start_time = time.time()
37 # 参数处理
38 count = max(1, min(20, int(max_results)))
39 search_depth = kwargs.get("search_depth", "basic")
40 include_answer = kwargs.get("include_answer", False)
42 url = "https://api.tavily.com/search"
43 payload = {
44 "api_key": TAVILY_API_KEY,
45 "query": query,
46 "max_results": count,
47 "search_depth": search_depth,
48 "include_answer": include_answer,
49 "include_images": False,
50 "include_raw_content": False
51 }
53 try:
54 logger.debug(f"Tavily search request: {query} (count={count})")
55 response = requests.post(url, json=payload, timeout=30)
56 response.raise_for_status()
57 data = response.json()
59 results: List[SearchResult] = []
60 raw_results = data.get("results", [])
62 for item in raw_results:
63 results.append(SearchResult(
64 title=item.get("title", ""),
65 url=item.get("url", ""),
66 snippet=item.get("content", "")[:500], # 限制摘要长度
67 score=item.get("score"),
68 published_date=item.get("published_date")
69 ))
71 took_ms = (time.time() - start_time) * 1000
73 return WebSearchResponse(
74 query=query,
75 provider=self.id,
76 count=len(results),
77 took_ms=took_ms,
78 results=results,
79 answer=data.get("answer"),
80 raw_response=data
81 )
83 except requests.exceptions.RequestException as e:
84 logger.error(f"Tavily search failed: {e}")
85 raise RuntimeError(f"Tavily API error: {e}")