Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions cookbook/91_tools/nimble_tools.py
Original file line number Diff line number Diff line change
@@ -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)
133 changes: 133 additions & 0 deletions libs/agno/agno/tools/nimble.py
Original file line number Diff line number Diff line change
@@ -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)