diff --git a/backend/packages/harness/deerflow/community/ddg_search/__init__.py b/backend/packages/harness/deerflow/community/ddg_search/__init__.py new file mode 100644 index 0000000..8761678 --- /dev/null +++ b/backend/packages/harness/deerflow/community/ddg_search/__init__.py @@ -0,0 +1,3 @@ +from .tools import web_search_tool + +__all__ = ["web_search_tool"] diff --git a/backend/packages/harness/deerflow/community/ddg_search/tools.py b/backend/packages/harness/deerflow/community/ddg_search/tools.py new file mode 100644 index 0000000..7639fe8 --- /dev/null +++ b/backend/packages/harness/deerflow/community/ddg_search/tools.py @@ -0,0 +1,95 @@ +""" +Web Search Tool - Search the web using DuckDuckGo (no API key required). +""" + +import json +import logging + +from langchain.tools import tool + +from deerflow.config import get_app_config + +logger = logging.getLogger(__name__) + + +def _search_text( + query: str, + max_results: int = 5, + region: str = "wt-wt", + safesearch: str = "moderate", +) -> list[dict]: + """ + Execute text search using DuckDuckGo. + + Args: + query: Search keywords + max_results: Maximum number of results + region: Search region + safesearch: Safe search level + + Returns: + List of search results + """ + try: + from ddgs import DDGS + except ImportError: + logger.error("ddgs library not installed. Run: pip install ddgs") + return [] + + ddgs = DDGS(timeout=30) + + try: + results = ddgs.text( + query, + region=region, + safesearch=safesearch, + max_results=max_results, + ) + return list(results) if results else [] + + except Exception as e: + logger.error(f"Failed to search web: {e}") + return [] + + +@tool("web_search", parse_docstring=True) +def web_search_tool( + query: str, + max_results: int = 5, +) -> str: + """Search the web for information. Use this tool to find current information, news, articles, and facts from the internet. + + Args: + query: Search keywords describing what you want to find. Be specific for better results. + max_results: Maximum number of results to return. Default is 5. + """ + config = get_app_config().get_tool_config("web_search") + + # Override max_results from config if set + if config is not None and "max_results" in config.model_extra: + max_results = config.model_extra.get("max_results", max_results) + + results = _search_text( + query=query, + max_results=max_results, + ) + + if not results: + return json.dumps({"error": "No results found", "query": query}, ensure_ascii=False) + + normalized_results = [ + { + "title": r.get("title", ""), + "url": r.get("href", r.get("link", "")), + "content": r.get("body", r.get("snippet", "")), + } + for r in results + ] + + output = { + "query": query, + "total_results": len(normalized_results), + "results": normalized_results, + } + + return json.dumps(output, indent=2, ensure_ascii=False) diff --git a/config.example.yaml b/config.example.yaml index 5b75602..f0acefa 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -231,12 +231,18 @@ tool_groups: # Configure available tools for the agent to use tools: - # Web search tool (requires Tavily API key) + # Web search tool (uses DuckDuckGo, no API key required) - name: web_search group: web - use: deerflow.community.tavily.tools:web_search_tool + use: deerflow.community.ddg_search.tools:web_search_tool max_results: 5 - # api_key: $TAVILY_API_KEY # Set if needed + + # Web search tool (requires Tavily API key) + # - name: web_search + # group: web + # use: deerflow.community.tavily.tools:web_search_tool + # max_results: 5 + # # api_key: $TAVILY_API_KEY # Set if needed # Web search tool (uses InfoQuest, requires InfoQuest API key) # - name: web_search