diff --git a/cookbook/91_tools/nimble_tools.py b/cookbook/91_tools/nimble_tools.py new file mode 100644 index 0000000000..54dd8fc314 --- /dev/null +++ b/cookbook/91_tools/nimble_tools.py @@ -0,0 +1,76 @@ +from agno.agent import Agent +from agno.tools.nimble import NimbleTools + +# ============================================================================ +# SEARCH EXAMPLES +# ============================================================================ + +# Example 1: Basic search with defaults +# The NimbleTools can be configured with global settings like locale and output format +# Search parameters (max_results, deep_search, etc.) are passed in the query +basic_agent = Agent(tools=[NimbleTools()]) + +# Example 2: Plain text output format +# You can set the output format at initialization for all searches +plaintext_agent = Agent(tools=[NimbleTools(output_format="plain_text")]) + +# Example 3: Localized search +# Set locale and country for searches in different regions +spanish_agent = Agent(tools=[NimbleTools(locale="es", country="ES")]) + +# ============================================================================ +# TEST THE AGENTS +# ============================================================================ + +print("=" * 80) +print("EXAMPLE 1: BASIC SEARCH (with default parameters)") +print("=" * 80) +basic_agent.print_response("What are the latest AI developments?", markdown=True) + +print("\n" + "=" * 80) +print("EXAMPLE 2: DEEP SEARCH WITH LLM ANSWER") +print("Parameters: max_results=5, deep_search=True, include_answer=True") +print("=" * 80) +# Parameters can be passed when the agent calls the tool +basic_agent.print_response( + "Compare noise cancelling headphones. " + "Use max_results=5, deep_search=True, and include_answer=True.", + markdown=True, +) + +print("\n" + "=" * 80) +print("EXAMPLE 3: FAST SEARCH (no deep content)") +print("Parameters: max_results=10, deep_search=False") +print("=" * 80) +basic_agent.print_response( + "Find recent climate change news. Use max_results=10 and deep_search=False for faster results.", + markdown=True, +) + +print("\n" + "=" * 80) +print("EXAMPLE 4: PLAIN TEXT OUTPUT FORMAT") +print("=" * 80) +plaintext_agent.print_response("What is quantum computing?", markdown=True) + +print("\n" + "=" * 80) +print("EXAMPLE 5: TIME-FILTERED SEARCH") +print("Parameters: time_range='day'") +print("=" * 80) +basic_agent.print_response( + "What happened in tech news today? Use time_range='day' to get only recent results.", + markdown=True, +) + +print("\n" + "=" * 80) +print("EXAMPLE 6: DOMAIN-FILTERED SEARCH") +print("Parameters: include_domains=['github.com', 'stackoverflow.com']") +print("=" * 80) +basic_agent.print_response( + "Find Python tutorials. Use include_domains=['github.com', 'stackoverflow.com'] to search only those sites.", + markdown=True, +) + +print("\n" + "=" * 80) +print("EXAMPLE 7: LOCALIZED SEARCH (Spanish)") +print("=" * 80) +spanish_agent.print_response("Noticias sobre inteligencia artificial", markdown=True) diff --git a/libs/agno/agno/tools/nimble.py b/libs/agno/agno/tools/nimble.py new file mode 100644 index 0000000000..721d5a80de --- /dev/null +++ b/libs/agno/agno/tools/nimble.py @@ -0,0 +1,133 @@ +import json +from os import getenv +from typing import Any, Dict, List, Literal, Optional + +from agno.tools.toolkit import Toolkit +from agno.utils.log import logger + +# Try to get agno version for tracking +try: + from importlib.metadata import version as get_version + + AGNO_VERSION = get_version("agno") +except Exception: + AGNO_VERSION = "unknown" + +try: + from nimble_python import AsyncNimble, Nimble +except ImportError: + raise ImportError("`nimble_python` not installed. Please install using `pip install nimble_python`") + + +class NimbleTools(Toolkit): + def __init__( + self, + api_key: Optional[str] = None, + enable_search: bool = True, + all: bool = False, + locale: str = "en", + country: str = "US", + output_format: Literal["markdown", "plain_text", "simplified_html"] = "markdown", + **kwargs, + ): + """Initialize NimbleTools with web search capabilities. + + Provides real-time web search powered by the Nimble Search API with support + for deep content extraction and LLM-generated answer summaries. + + Args: + api_key: Nimble API key. If not provided, will use NIMBLE_API_KEY env var. + enable_search: Enable web search functionality. Defaults to True. + all: Enable all available tools. Defaults to False. + locale: Locale for search results (e.g., "en", "es"). Defaults to "en". + country: Country code for search results (e.g., "US", "GB"). Defaults to "US". + output_format: Output format - "markdown", "plain_text", or "simplified_html". Defaults to "markdown". + **kwargs: Additional arguments passed to Toolkit. + """ + self.api_key = api_key or getenv("NIMBLE_API_KEY") + if not self.api_key: + logger.error("NIMBLE_API_KEY not provided") + + # Initialize clients + self.client: Nimble = Nimble(api_key=self.api_key) + self.async_client: AsyncNimble = AsyncNimble(api_key=self.api_key) + + # Store configuration (only things that rarely change per call) + self.locale: str = locale + self.country: str = country + self.output_format: Literal["markdown", "plain_text", "simplified_html"] = output_format + + # Register tools + tools: List[Any] = [] + if enable_search or all: + tools.append(self.web_search_using_nimble) + + super().__init__(name="nimble_tools", tools=tools, **kwargs) + + def web_search_using_nimble( + self, + query: str, + max_results: int = 3, + deep_search: bool = False, + include_answer: bool = False, + time_range: Optional[Literal["hour", "day", "week", "month", "year"]] = None, + include_domains: Optional[List[str]] = None, + exclude_domains: Optional[List[str]] = None, + ) -> str: + """Search the web for real-time information using Nimble's search API. + + Choose the right mode: + - Fast Mode (deep_search=False, default): Best for URL discovery and quick answers. + Returns concise, token-efficient results perfect for agentic loops and initial research. + - Deep Search (deep_search=True): Use when you need comprehensive full-page content + for in-depth analysis, extracting detailed information, or reading entire articles. + + Args: + query: Search query string. + max_results: Number of results to return (1-100). Defaults to 3. + deep_search: Enable full-page content extraction. Defaults to False (fast mode). + include_answer: Generate an LLM-powered summary answer. Defaults to False. + time_range: Filter by recency - "hour", "day", "week", "month", "year". + Use for time-sensitive queries like "latest news" or "recent updates". + include_domains: Restrict search to specific domains (e.g., ["github.com", "docs.python.org"]). + exclude_domains: Exclude specific domains from results. + + Returns: + JSON string with search results formatted according to output_format setting. + """ + try: + # Build search parameters + search_params: Dict[str, Any] = { + "query": query, + "num_results": max_results, + "deep_search": deep_search, + "include_answer": include_answer, + "locale": self.locale, + "country": self.country, + "parsing_type": self.output_format, + } + + # Add optional parameters if specified + if time_range: + search_params["time_range"] = time_range + if include_domains: + search_params["include_domains"] = include_domains + if exclude_domains: + search_params["exclude_domains"] = exclude_domains + + # Call Nimble Search API with tracking headers + response = self.client.search( + **search_params, + extra_headers={ + "X-Client-Source": "agno-tools", + "X-Client-Tool": "NimbleTools", + "X-Client-Version": AGNO_VERSION, + }, + ) + + # Return the response as JSON + return json.dumps(response.model_dump(), indent=2) + + except Exception as e: + logger.error(f"Error searching with Nimble: {e}") + return json.dumps({"error": f"Search failed: {str(e)}"}, indent=2)