tavily_search_tool.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """Tavily web search tool for hello_agents framework"""
  2. import logging
  3. import re
  4. from typing import Dict, Any, List
  5. from hello_agents.tools.base import Tool, ToolParameter
  6. logger = logging.getLogger("game.tools")
  7. class TavilySearchTool(Tool):
  8. """Tavily web search tool - search-only, no AI answer generation"""
  9. def __init__(self, api_key: str):
  10. super().__init__(
  11. name="tavily_search",
  12. description=(
  13. "Search the web for information about a historical figure. "
  14. "Input the figure's name to retrieve relevant biographical information."
  15. )
  16. )
  17. if not api_key:
  18. raise ValueError("TAVILY_API_KEY is required for TavilySearchTool")
  19. from tavily import TavilyClient
  20. self._client = TavilyClient(api_key=api_key)
  21. logger.info("[TOOL] TavilySearchTool initialized")
  22. def run(self, parameters: Dict[str, Any]) -> str:
  23. """
  24. Execute web search
  25. Args:
  26. parameters: dict with key 'query' - the search query string
  27. Returns:
  28. Concatenated search result snippets as a single string
  29. """
  30. query = parameters.get("query", "").strip()
  31. if not query:
  32. return "Error: search query cannot be empty"
  33. logger.info(f"[TOOL] Tavily search | query={query!r}")
  34. try:
  35. response = self._client.search(
  36. query=query,
  37. search_depth="basic",
  38. max_results=5,
  39. include_answer=False, # raw search only, no AI answer
  40. )
  41. results = response.get("results", [])
  42. if not results:
  43. logger.warning("[TOOL] Tavily returned no results")
  44. return "No search results found."
  45. # Take top 1 result, clean and truncate content to 300 chars
  46. MAX_RESULTS = 1
  47. MAX_CONTENT_LEN = 300
  48. def _clean(text: str) -> str:
  49. """Remove noise: extra whitespace, URLs, repeated punctuation."""
  50. text = re.sub(r'https?://\S+', '', text) # strip URLs
  51. text = re.sub(r'\s+', ' ', text) # collapse whitespace
  52. text = re.sub(r'[。,、]{2,}', '。', text) # deduplicate punctuation
  53. text = re.sub(r'[\.]{3,}', '...', text) # normalize ellipsis
  54. return text.strip()
  55. snippets = []
  56. for item in results[:MAX_RESULTS]:
  57. content = _clean(item.get("content", ""))
  58. if content:
  59. if len(content) > MAX_CONTENT_LEN:
  60. content = content[:MAX_CONTENT_LEN] + "..."
  61. snippets.append(content)
  62. combined = "\n".join(snippets)
  63. logger.info(
  64. f"[TOOL] Tavily search completed | results={len(results)} "
  65. f"used={len(snippets)} total_chars={len(combined)}"
  66. )
  67. return combined
  68. except Exception as e:
  69. logger.error(f"[TOOL] Tavily search failed: {e}", exc_info=True)
  70. return f"Search failed: {str(e)}"
  71. def get_parameters(self) -> List[ToolParameter]:
  72. return [
  73. ToolParameter(
  74. name="query",
  75. type="string",
  76. description="Search query, e.g. the name of a historical figure",
  77. required=True
  78. )
  79. ]