Skip to content
💻 🧠 Code 1001 > LLMs: Articles, Practical Materials, and Resources > any-agent + Tavily for Building Smart Search Agents

any-agent + Tavily for Building Smart Search Agents

In this article, we will explore how to solve this problem using a combination of any-agent (a universal library for working with agents from Mozilla.ai) and Tavily (a specialized search engine for AI).


Part 1: Getting Started with any-agent

What is any-agent?

any-agent is a Python library from Mozilla.ai that provides a unified interface for working with various agent frameworks. The main idea is to allow developers to easily switch between different agent frameworks (AutoGen, CrewAI, LangGraph, etc.) without rewriting code.

Key advantages:

  • Unified API: one interface for all frameworks
  • Standardized logging: consistent traces regardless of the framework
  • Easy comparison: ability to test different agent configurations
  • Extensibility: support for adding new frameworks

System requirements and installation

<em># System requirements</em>
<em># Python 3.11+</em>

<em># Basic installation (TinyAgent only)</em>
pip install any-agent

<em># Installation with support for all frameworks</em>
pip install any-agent[all]

<em># Or with a specific framework</em>
pip install any-agent[langchain]

First agent example

from any_agent import AnyAgent
from any_agent.frameworks import TinyAgent

<em># Creating a simple agent</em>
agent = AnyAgent(
    framework=TinyAgent,
    model="gpt-4",
    tools=[],
    system_prompt="Вы полезный ассистент"
)

<em># Running the agent</em>
result = agent.run("Привет! Расскажи о себе.")
print(result)

Important for Jupyter Notebook: If running in Jupyter Notebook you will need to add the following two lines before running AnyAgent, otherwise you may see the error RuntimeError: This event loop is already running. This is a known limitation of Jupyter Notebooks, see Github Issue

import nest_asyncio
nest_asyncio.apply()
<em># Now you can use any-agent</em>

Part 2: Getting Started with Tavily

What is Tavily?

Tavily is a search engine created specifically for AI agents and RAG systems. Unlike traditional search engines, Tavily is optimized to provide structured, factual results that are easily processed by language models.

Key features of Tavily:

  • AI Optimization: results adapted for LLMs
  • Focus on facts: emphasis on accuracy and verifiability
  • High speed: fast real-time responses
  • Structured output: JSON format with metadata

Registration and obtaining an API key

  1. Go to https://app.tavily.com/
  2. Create an account
  3. Get your API key in your personal account

Basic Tavily usage

<em># Client installation</em>
pip install tavily-python
from tavily import TavilyClient

<em># Client initialization</em>
client = TavilyClient(api_key="tvly-YOUR_API_KEY")

<em># Simple search</em>
response = client.search("Последние новости о Python 3.12")

print(f"Запрос: {response['query']}")
print(f"Ответ: {response['answer']}")
print(f"Источники: {len(response['results'])} результатов")

Tavily response structure

{
  "query": "Ваш поисковый запрос",
  "answer": "Краткий ответ на основе найденной информации",
  "images": [],
  "results": [
    {
      "title": "Заголовок источника",
      "url": "https://example.com",
      "content": "Содержимое...",
      "score": 0.81025416,
      "favicon": "https://example.com/favicon.png"
    }
  ],
  "auto_parameters": {
    "topic": "general",
    "search_depth": "basic"
  },
  "response_time": "1.67"
}

Part 3: Integrating any-agent with Tavily

Approach 1: Creating a Custom Tool

The most flexible way is to create your own search tool:

import os
from any_agent import AnyAgent
from any_agent.frameworks import TinyAgent
from tavily import TavilyClient
from typing import Dict, Optional

class TavilySearchTool:
    """Custom tool for searching via Tavily"""
    
    def __init__(self, api_key: str):
        self.client = TavilyClient(api_key=api_key)
        self.name = "tavily_search"
        self.description = "Search for up-to-date information on the internet"
    
    def __call__(self, query: str, search_depth: str = "basic") -> Dict:
        """Perform search"""
        try:
            response = self.client.search(
                query=query,
                search_depth=search_depth
            )
            return {
                "success": True,
                "data": response
            }
        except Exception as e:
            return {
                "success": False,
                "error": str(e)
            }

<em># Creating an agent with Tavily</em>
tavily_tool = TavilySearchTool(api_key="tvly-YOUR_API_KEY")

agent = AnyAgent(
    framework=TinyAgent,
    model="gpt-4",
    tools=[tavily_tool],
    system_prompt="""
    You are an AI assistant with access to up-to-date information from the internet.
    Use the tavily_search tool to get fresh data.
    Always cite sources of information in your answers.
    """
)

<em># Usage</em>
result = agent.run("Find the latest news on AI development from OpenAI")
print(result)

Approach 2: Integration via LangChain

If you are using a LangChain-compatible framework:

from langchain_tavily import TavilySearchAPIWrapper
from any_agent import AnyAgent

<em># Setting up search via LangChain</em>
search_tool = TavilySearchAPIWrapper(
    tavily_api_key="tvly-YOUR_API_KEY"
)

<em># Creating an agent</em>
agent = AnyAgent(
    framework=YourLangChainFramework,
    model="gpt-4",
    tools=[search_tool],
    system_prompt="Assistant with real-time search access"
)

Let’s create a complete application for working with search agents:

<em>## \file src/agents/smart_search_agent.py</em>
<em># -*- coding: utf-8 -*-</em>
<em>#! .pyenv/bin/python3</em>

"""
Smart Search Agent Module
==============================

Integration of any-agent with Tavily to create an agent
with access to up-to-date information from the internet.
"""

from typing import Dict, List, Optional
from dataclasses import dataclass
import json
from datetime import datetime

from tavily import TavilyClient
from any_agent import AnyAgent
from any_agent.frameworks import TinyAgent


@dataclass
class SearchResult:
    """Search result structure"""
    query: str
    answer: str
    sources: List[Dict]
    timestamp: str
    response_time: float


class SmartSearchAgent:
    """
    Smart Search Agent with Tavily Integration
    
    The agent is capable of searching for up-to-date information,
    analyzing results, and providing structured answers.
    """
    
    def __init__(
        self, 
        tavily_api_key: str, 
        model: str = "gpt-4",
        search_depth: str = "basic"
    ):
        """
        Agent initialization
        
        Args:
            tavily_api_key: API key for Tavily
            model: Model for the agent
            search_depth: Search depth ('basic' or 'advanced')
        """
        self.tavily_client = TavilyClient(api_key=tavily_api_key)
        self.search_depth = search_depth
        self.agent = self._create_agent(model)
        self.search_history: List[SearchResult] = []
    
    def _create_agent(self, model: str) -> AnyAgent:
        """Create an agent with configured tools"""
        return AnyAgent(
            framework=TinyAgent,
            model=model,
            tools=[self._search_tool],
            system_prompt=self._get_system_prompt()
        )
    
    def _get_system_prompt(self) -> str:
        """System prompt for the agent"""
        return """
        You are an expert AI assistant with access to up-to-date information from the internet.
        
        Your capabilities:
        - Search for up-to-date information via Tavily
        - Analyze and synthesize data from multiple sources
        - Provide factual answers with sources
        
        Operating principles:
        1. Always use search to get up-to-date information
        2. Cite sources for all factual statements
        3. If information is contradictory, point it out
        4. Be accurate and objective
        5. Structure answers for better comprehension
        """
    
    def _search_tool(self, query: str) -> Dict:
        """
        Search tool for the agent
        
        Args:
            query: Search query
            
        Returns:
            Search results in a structured format
        """
        try:
            start_time = datetime.now()
            
            response = self.tavily_client.search(
                query=query,
                search_depth=self.search_depth
            )
            
            end_time = datetime.now()
            response_time = (end_time - start_time).total_seconds()
            
            <em># Saving the result to history</em>
            search_result = SearchResult(
                query=query,
                answer=response.get('answer', ''),
                sources=response.get('results', []),
                timestamp=datetime.now().isoformat(),
                response_time=response_time
            )
            self.search_history.append(search_result)
            
            return {
                "success": True,
                "query": query,
                "answer": response.get('answer', ''),
                "sources": response.get('results', []),
                "source_count": len(response.get('results', [])),
                "response_time": response.get('response_time', response_time)
            }
            
        except Exception as e:
            return {
                "success": False,
                "error": f"Search error: {str(e)}",
                "query": query
            }
    
    def search(self, query: str) -> str:
        """
        Execute a search query via the agent
        
        Args:
            query: User query
            
        Returns:
            Agent's response with up-to-date information
        """
        try:
            result = self.agent.run(query)
            return result
        except Exception as e:
            return f"An error occurred while processing the request: {str(e)}"
    
    def get_search_history(self) -> List[SearchResult]:
        """Get search query history"""
        return self.search_history.copy()
    
    def clear_history(self):
        """Clear search history"""
        self.search_history.clear()
    
    def export_history(self, filename: str):
        """
        Export search history to a JSON file
        
        Args:
            filename: File name for export
        """
        try:
            history_data = [
                {
                    "query": query,
                    "answer": answer,
                    "sources": sources,
                    "timestamp": timestamp,
                    "response_time": response_time
                }
                for query, answer, sources, timestamp, response_time in self.search_history
            ]
            
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(history_data, f, ensure_ascii=False, indent=2)
            
            print(f"History exported to {filename}")
            
        except Exception as e:
            print(f"Export error: {str(e)}")


<em># Example usage</em>
def main():
    """Demonstration of the smart search agent"""
    
    <em># Agent initialization</em>
    agent = SmartSearchAgent(
        tavily_api_key="tvly-YOUR_API_KEY",
        model="gpt-4",
        search_depth="basic"
    )
    
    <em># Example queries</em>
    queries = [
        "Latest news on GPT-5 development",
        "Current prices for Bitcoin and Ethereum",
        "What's new in Python 3.12?",
        "Latest achievements in quantum computing"
    ]
    
    print("=== Smart Search Agent Demonstration ===\n")
    
    for i, query in enumerate(queries, 1):
        print(f"Query {i}: {query}")
        print("-" * 50)
        
        result = agent.search(query)
        print(result)
        print("\n" + "="*80 + "\n")
    
    <em># Export history</em>
    agent.export_history("search_history.json")
    
    <em># Statistics</em>
    history = agent.get_search_history()
    print(f"Total queries executed: {len(history)}")
    
    if history:
        avg_response_time = sum(r.response_time for r in history) / len(history)
        print(f"Average response time: {avg_response_time:.2f} sec")


if __name__ == "__main__":
    main()

Part 5: Advanced Features

Multi-Agent Systems

any-agent supports creating Multi-Agent systems where agents can interact with each other:

class ResearchTeam:
    """Team of research agents"""
    
    def __init__(self, tavily_api_key: str):
        <em># Researcher agent</em>
        self.researcher = SmartSearchAgent(
            tavily_api_key=tavily_api_key,
            model="gpt-4"
        )
        
        <em># Analyst agent</em>
        self.analyst = AnyAgent(
            framework=TinyAgent,
            model="gpt-4",
            system_prompt="""
            You are an expert analyst. Your task is to analyze 
            the information received and draw conclusions.
            """
        )
        
        <em># Writer agent</em>
        self.writer = AnyAgent(
            framework=TinyAgent,
            model="gpt-4",
            system_prompt="""
            You are a professional writer. Create 
            structured reports based on analysis.
            """
        )
    
    def research_topic(self, topic: str) -> str:
        """Comprehensive topic research"""
        
        <em># Stage 1: Information search</em>
        research_data = self.researcher.search(f"Detailed information about {topic}")
        
        <em># Stage 2: Data analysis</em>
        analysis = self.analyst.run(f"Analyze this information: {research_data}")
        
        <em># Stage 3: Report writing</em>
        report = self.writer.run(f"Create a structured report based on the analysis: {analysis}")
        
        return report

<em># Usage</em>
team = ResearchTeam("tvly-YOUR_API_KEY")
report = team.research_topic("The impact of AI on the labor market in 2024")
print(report)

Configuring Search Depth

Tavily offers different levels of search depth:

<em># Basic search</em>
basic_agent = SmartSearchAgent(
    tavily_api_key="your-key",
    search_depth="basic"  <em># Faster, fewer sources</em>
)

<em># Advanced search</em>
advanced_agent = SmartSearchAgent(
    tavily_api_key="your-key", 
    search_depth="advanced"  <em># Slower, more sources</em>
)

Filtering Results

def create_specialized_search_tool(domains: List[str] = None):
    """Create a specialized search tool"""
    
    def search_tool(query: str) -> Dict:
        client = TavilyClient(api_key="your-key")
        
        search_params = {
            "query": query,
            "search_depth": "advanced"
        }
        
        <em># Adding domain filters</em>
        if domains:
            search_params["include_domains"] = domains
        
        response = client.search(**search_params)
        return response
    
    return search_tool

<em># Creating an agent for searching only scientific sources</em>
science_agent = AnyAgent(
    framework=TinyAgent,
    model="gpt-4",
    tools=[create_specialized_search_tool([
        "arxiv.org", 
        "nature.com", 
        "science.org"
    ])],
    system_prompt="Expert in scientific information"
)

Part 6: Best Practices and Recommendations

Safety and Limitations

⚠️ Important warnings:

  1. Computational resources: Agent systems require significantly more resources than simple LLM calls
  2. Security: Agents can perform unpredictable actions
  3. Cost: Each search query and LLM call is charged

Performance Optimization

class OptimizedSearchAgent:
    """Optimized search agent"""
    
    def __init__(self, tavily_api_key: str):
        self.client = TavilyClient(api_key=tavily_api_key)
        self.cache = {}  <em># Cache for repetitive queries</em>
        self.rate_limit_delay = 1  <em># Delay between requests</em>
    
    def search_with_cache(self, query: str) -> Dict:
        """Search with result caching"""
        
        <em># Check cache</em>
        if query in self.cache:
            print(f"Result taken from cache for: {query}")
            return self.cache[query]
        
        <em># Perform search</em>
        time.sleep(self.rate_limit_delay)  <em># Observe rate limits</em>
        result = self.client.search(query)
        
        <em># Save to cache</em>
        self.cache[query] = result
        
        return result

Error Handling

class RobustSearchAgent:
    """Error-resilient search agent"""
    
    def __init__(self, tavily_api_key: str, max_retries: int = 3):
        self.client = TavilyClient(api_key=tavily_api_key)
        self.max_retries = max_retries
    
    def search_with_retry(self, query: str) -> Dict:
        """Search with retries"""
        
        for attempt in range(self.max_retries):
            try:
                return self.client.search(query)
            
            except Exception as e:
                print(f"Attempt {attempt + 1} failed: {str(e)}")
                
                if attempt == self.max_retries - 1:
                    return {
                        "error": f"Search failed after {self.max_retries} attempts",
                        "query": query
                    }
                
                time.sleep(2 ** attempt)  <em># Exponential backoff</em>

Monitoring and Logging

import logging
from datetime import datetime

class MonitoredSearchAgent:
    """Agent with monitoring and logging"""
    
    def __init__(self, tavily_api_key: str):
        self.client = TavilyClient(api_key=tavily_api_key)
        self.setup_logging()
        self.stats = {
            "total_searches": 0,
            "successful_searches": 0,
            "failed_searches": 0,
            "total_response_time": 0
        }
    
    def setup_logging(self):
        """Configure logging system"""
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('search_agent.log'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger('SearchAgent')
    
    def search_with_monitoring(self, query: str) -> Dict:
        """Search with full monitoring"""
        start_time = datetime.now()
        self.stats["total_searches"] += 1
        
        try:
            self.logger.info(f"Starting search: {query}")
            
            result = self.client.search(query)
            
            end_time = datetime.now()
            response_time = (end_time - start_time).total_seconds()
            
            self.stats["successful_searches"] += 1
            self.stats["total_response_time"] += response_time
            
            self.logger.info(f"Search successful in {response_time:.2f}s: {query}")
            
            return result
            
        except Exception as e:
            self.stats["failed_searches"] += 1
            self.logger.error(f"Search error {query}: {str(e)}")
            
            return {"error": str(e), "query": query}
    
    def get_statistics(self) -> Dict:
        """Get operational statistics"""
        success_rate = (
            self.stats["successful_searches"] / self.stats["total_searches"] * 100
            if self.stats["total_searches"] > 0 else 0
        )
        
        avg_response_time = (
            self.stats["total_response_time"] / self.stats["successful_searches"]
            if self.stats["successful_searches"] > 0 else 0
        )
        
        return {
            **self.stats,
            "success_rate": f"{success_rate:.1f}%",
            "average_response_time": f"{avg_response_time:.2f}s"
        }

Part 7: Practical Use Cases

Case 1: News Aggregator

class NewsAggregatorAgent:
    """Agent for aggregating news by topics"""
    
    def __init__(self, tavily_api_key: str):
        self.search_agent = SmartSearchAgent(tavily_api_key)
    
    def get_daily_digest(self, topics: List[str]) -> str:
        """Create a daily news digest"""
        
        digest_parts = []
        
        for topic in topics:
            query = f"Latest news today: {topic}"
            news = self.search_agent.search(query)
            
            digest_parts.append(f"## {topic}\n{news}\n")
        
        return "\n".join(digest_parts)

<em># Usage</em>
news_agent = NewsAggregatorAgent("your-api-key")
digest = news_agent.get_daily_digest([
    "AI Technologies",
    "Cryptocurrencies", 
    "Space Exploration"
])

Case 2: Research Assistant

class ResearchAssistant:
    """Assistant for scientific research"""
    
    def __init__(self, tavily_api_key: str):
        self.agent = SmartSearchAgent(tavily_api_key)
    
    def literature_review(self, topic: str, num_sources: int = 10) -> Dict:
        """Literature review on a topic"""
        
        queries = [
            f"{topic} recent research 2024",
            f"{topic} scientific papers",
            f"{topic} review articles",
            f"{topic} latest findings"
        ]
        
        all_sources = []
        
        for query in queries:
            result = self.agent.search(query)
            <em># Extract sources from result</em>
            <em># ... processing logic</em>
        
        return {
            "topic": topic,
            "sources_found": len(all_sources),
            "summary": "...",
            "key_findings": "...",
            "sources": all_sources
        }

Case 3: Business Analyst

class BusinessAnalyst:
    """Agent for business analysis"""
    
    def __init__(self, tavily_api_key: str):
        self.agent = SmartSearchAgent(tavily_api_key)
    
    def market_analysis(self, company: str, competitors: List[str]) -> str:
        """Market and competitor analysis"""
        
        <em># Analysis of the main company</em>
        company_analysis = self.agent.search(
            f"Financial results and news {company} 2024"
        )
        
        <em># Competitor analysis</em>
        competitor_analyses = []
        for competitor in competitors:
            analysis = self.agent.search(
                f"Financial results {competitor} comparison with {company}"
            )
            competitor_analyses.append(analysis)
        
        <em># Report synthesis</em>
        report_query = f"""
        Create a business report based on the data:
        Company: {company_analysis}
        Competitors: {competitor_analyses}
        """
        
        return self.agent.search(report_query)

Conclusion

The integration of any-agent with Tavily opens up broad possibilities for creating smart agents with access to up-to-date information. This combination is especially useful for:

Key advantages:

  • Timeliness: access to fresh information in real-time
  • Flexibility: a unified interface for different agent frameworks
  • Reliability: specialized search optimized for AI
  • Scalability: ability to create complex multi-agent systems

Usage recommendations:

  1. Start simple: master basic integration before moving to complex systems
  2. Monitor costs: keep track of API usage and computational resources
  3. Test thoroughly: agent systems can behave unpredictably
  4. Use caching: avoid repetitive queries for the same information
  5. Plan error handling: always foresee fallback scenarios

Next steps:

  • Explore additional frameworks supported by any-agent
  • Experiment with different Tavily search depth settings
  • Consider integration with other tools (databases, external service APIs)
  • Explore opportunities to create specialized agents for your domain

This technological combination represents a powerful tool for developing modern AI applications capable of working with current data and providing users with relevant information.


Appendix A: Full Code for a Production Agent

<em>## \file src/agents/production_search_agent.py</em>
<em># -*- coding: utf-8 -*-</em>
<em>#! .pyenv/bin/python3</em>
"""
Production Search Agent
===============================
A full-featured agent for production use with a complete
set of capabilities: monitoring, caching, error handling.
"""
import asyncio
import json
import time
import hashlib
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Any, Union
from dataclasses import dataclass, asdict
from enum import Enum
import logging
from tavily import TavilyClient
from any_agent import AnyAgent
from any_agent.frameworks import TinyAgent
class SearchDepth(Enum):
    """Search depth"""
    BASIC = "basic"
    ADVANCED = "advanced"
class SearchStatus(Enum):
    """Search request status"""
    SUCCESS = "success"
    ERROR = "error"
    CACHED = "cached"
    RATE_LIMITED = "rate_limited"
@dataclass
class SearchRequest:
    """Search request structure"""
    query: str
    depth: SearchDepth = SearchDepth.BASIC
    timestamp: str = None
    user_id: Optional[str] = None
    session_id: Optional[str] = None
    
    def __post_init__(self):
        if not self.timestamp:
            self.timestamp = datetime.now().isoformat()
    
    def get_cache_key(self) -> str:
        """Generate key for caching"""
        content = f"{self.query}:{self.depth.value}"
        return hashlib.md5(content.encode()).hexdigest()
@dataclass
class SearchResponse:
    """Search response structure"""
    request: SearchRequest
    status: SearchStatus
    answer: str = ""
    sources: List[Dict] = None
    error_message: str = ""
    response_time: float = 0.0
    from_cache: bool = False
    
    def __post_init__(self):
        if self.sources is None:
            self.sources = []
class CacheManager:
    """Cache manager for search queries"""
    
    def __init__(self, cache_dir: str = "./cache", ttl_hours: int = 24):
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(exist_ok=True)
        self.ttl = timedelta(hours=ttl_hours)
    
    def _get_cache_file(self, cache_key: str) -> Path:
        """Get path to cache file"""
        return self.cache_dir / f"{cache_key}.json"
    
    def get(self, cache_key: str) -> Optional[Dict]:
        """Get data from cache"""
        cache_file = self._get_cache_file(cache_key)
        
        if not cache_file.exists():
            return None
        
        try:
            with open(cache_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            <em># Check TTL</em>
            cached_time = datetime.fromisoformat(data['cached_at'])
            if datetime.now() - cached_time > self.ttl:
                cache_file.unlink()  <em># Delete expired cache</em>
                return None
            
            return data['content']
            
        except Exception:
            return None
    
    def set(self, cache_key: str, content: Dict):
        """Save data to cache"""
        cache_file = self._get_cache_file(cache_key)
        
        try:
            cache_data = {
                'cached_at': datetime.now().isoformat(),
                'content': content
            }
            
            with open(cache_file, 'w', encoding='utf-8') as f:
                json.dump(cache_data, f, ensure_ascii=False, indent=2)
                
        except Exception as e:
            logging.warning(f"Failed to save to cache: {e}")
    
    def clear_expired(self):
        """Clear expired cache"""
        for cache_file in self.cache_dir.glob("*.json"):
            try:
                with open(cache_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                
                cached_time = datetime.fromisoformat(data['cached_at'])
                if datetime.now() - cached_time > self.ttl:
                    cache_file.unlink()
                    
            except Exception:
                continue
class RateLimiter:
    """Request rate limiter"""
    
    def __init__(self, max_requests: int = 60, time_window: int = 60):
        self.max_requests = max_requests
        self.time_window = time_window
        self.requests = []
    
    def can_make_request(self) -> bool:
        """Check if a request can be made"""
        now = time.time()
        
        <em># Clear expired requests</em>
        self.requests = [req_time for req_time in self.requests 
                        if now - req_time < self.time_window]
        
        return len(self.requests) < self.max_requests
    
    def register_request(self):
        """Register a completed request"""
        self.requests.append(time.time())
    
    def get_wait_time(self) -> float:
        """Get wait time until the next request"""
        if not self.requests:
            return 0.0
        
        oldest_request = min(self.requests)
        wait_time = self.time_window - (time.time() - oldest_request)
        
        return max(0.0, wait_time)
class ProductionSearchAgent:
    """
    Production Search Agent
    
    A full-featured agent for production use with:
    - Result caching
    - Rate limiting
    - Monitoring and logging
    - Error handling
    - Usage statistics
    """
    
    def __init__(
        self,
        tavily_api_key: str,
        model: str = "gpt-4",
        cache_ttl_hours: int = 24,
        rate_limit_per_minute: int = 60,
        max_retries: int = 3,
        log_level: str = "INFO"
    ):
        <em># Component initialization</em>
        self.tavily_client = TavilyClient(api_key=tavily_api_key)
        self.cache_manager = CacheManager(ttl_hours=cache_ttl_hours)
        self.rate_limiter = RateLimiter(max_requests=rate_limit_per_minute)
        self.max_retries = max_retries
        
        <em># Logging configuration</em>
        self.setup_logging(log_level)
        
        <em># Agent creation</em>
        self.agent = self._create_agent(model)
        
        <em># Statistics</em>
        self.stats = {
            "total_requests": 0,
            "successful_requests": 0,
            "cached_requests": 0,
            "failed_requests": 0,
            "rate_limited_requests": 0,
            "total_response_time": 0.0,
            "start_time": datetime.now().isoformat()
        }
    
    def setup_logging(self, log_level: str):
        """Configure logging system"""
        logging.basicConfig(
            level=getattr(logging, log_level.upper()),
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('production_search_agent.log'),
                logging.StreamHandler()
            ]
        )
        self.logger = logging.getLogger('ProductionSearchAgent')
    
    def _create_agent(self, model: str) -> AnyAgent:
        """Create an agent with production settings"""
        return AnyAgent(
            framework=TinyAgent,
            model=model,
            tools=[self._search_tool],
            system_prompt=self._get_production_system_prompt()
        )
    
    def _get_production_system_prompt(self) -> str:
        """System prompt for production"""
        return """
        You are a professional enterprise-level AI assistant with access 
        to up-to-date information from the internet.
        Your operating principles:
        1. ACCURACY: Always verify facts through search
        2. SOURCES: Cite sources for all statements
        3. OBJECTIVITY: Present different viewpoints
        4. STRUCTURE: Organize information logically
        5. TIMELINESS: Use only fresh data
        Response format:
        - Brief summary (2-3 sentences)
        - Key information with sources
        - Additional sources for further study
        If information is contradictory or unreliable - be sure to point it out.
        """
    
    def _search_tool(self, query: str, depth: str = "basic") -> Dict:
        """Search tool with full error handling"""
        search_request = SearchRequest(
            query=query,
            depth=SearchDepth(depth)
        )
        
        response = self._execute_search(search_request)
        
        if response.status == SearchStatus.SUCCESS:
            return {
                "success": True,
                "query": query,
                "answer": response.answer,
                "sources": response.sources,
                "from_cache": response.from_cache,
                "response_time": response.response_time
            }
        else:
            return {
                "success": False,
                "query": query,
                "error": response.error_message,
                "status": response.status.value
            }
    
    def _execute_search(self, request: SearchRequest) -> SearchResponse:
        """Perform search with full handling"""
        start_time = time.time()
        self.stats["total_requests"] += 1
        
        try:
            <em># Check cache</em>
            cache_key = request.get_cache_key()
            cached_result = self.cache_manager.get(cache_key)
            
            if cached_result:
                self.stats["cached_requests"] += 1
                self.logger.info(f"Result obtained from cache: {request.query}")
                
                return SearchResponse(
                    request=request,
                    status=SearchStatus.CACHED,
                    answer=cached_result.get('answer', ''),
                    sources=cached_result.get('sources', []),
                    response_time=time.time() - start_time,
                    from_cache=True
                )
            
            <em># Check rate limit</em>
            if not self.rate_limiter.can_make_request():
                wait_time = self.rate_limiter.get_wait_time()
                self.stats["rate_limited_requests"] += 1
                self.logger.warning(f"Rate limit exceeded. Wait time: {wait_time:.1f}s")
                
                return SearchResponse(
                    request=request,
                    status=SearchStatus.RATE_LIMITED,
                    error_message=f"Rate limit exceeded. Try again in {wait_time:.1f} seconds",
                    response_time=time.time() - start_time
                )
            
            <em># Perform search with retries</em>
            result = None
            last_error = None
            
            for attempt in range(self.max_retries):
                try:
                    self.rate_limiter.register_request()
                    
                    result = self.tavily_client.search(
                        query=request.query,
                        search_depth=request.depth.value
                    )
                    break
                    
                except Exception as e:
                    last_error = e
                    if attempt < self.max_retries - 1:
                        wait_time = (2 ** attempt) * 1  <em># Exponential backoff</em>
                        self.logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying in {wait_time}s...")
                        time.sleep(wait_time)
            
            if result is None:
                self.stats["failed_requests"] += 1
                self.logger.error(f"All search attempts failed for: {request.query}")
                
                return SearchResponse(
                    request=request,
                    status=SearchStatus.ERROR,
                    error_message=f"Search failed after {self.max_retries} attempts: {last_error}",
                    response_time=time.time() - start_time
                )
            
            <em># Handle successful result</em>
            response_time = time.time() - start_time
            self.stats["successful_requests"] += 1
            self.stats["total_response_time"] += response_time
            
            <em># Save to cache</em>
            cache_data = {
                'answer': result.get('answer', ''),
                'sources': result.get('results', [])
            }
            self.cache_manager.set(cache_key, cache_data)
            
            self.logger.info(f"Search successful in {response_time:.2f}s: {request.query}")
            
            return SearchResponse(
                request=request,
                status=SearchStatus.SUCCESS,
                answer=result.get('answer', ''),
                sources=result.get('results', []),
                response_time=response_time,
                from_cache=False
            )
            
        except Exception as e:
            self.stats["failed_requests"] += 1
            self.logger.error(f"Unexpected error during search: {e}", exc_info=True)
            
            return SearchResponse(
                request=request,
                status=SearchStatus.ERROR,
                error_message=f"Unexpected error: {str(e)}",
                response_time=time.time() - start_time
            )
    
    def search(self, query: str, depth: SearchDepth = SearchDepth.BASIC) -> str:
        """
        Main search method for users
        
        Args:
            query: Search query
            depth: Search depth (basic/advanced)
            
        Returns:
            Agent's response with found information
        """
        try:
            self.logger.info(f"Processing user query: {query}")
            
            <em># Create context for the agent</em>
            search_context = f"Perform a search and provide a detailed answer to the query: {query}"
            if depth == SearchDepth.ADVANCED:
                search_context += " (use advanced search for deeper analysis)"
            
            result = self.agent.run(search_context)
            return result
            
        except Exception as e:
            self.logger.error(f"Error processing query: {e}", exc_info=True)
            return f"Sorry, an error occurred while processing the request: {str(e)}"
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get detailed operational statistics"""
        uptime = datetime.now() - datetime.fromisoformat(self.stats["start_time"])
        
        success_rate = (
            self.stats["successful_requests"] / self.stats["total_requests"] * 100
            if self.stats["total_requests"] > 0 else 0
        )
        
        cache_hit_rate = (
            self.stats["cached_requests"] / self.stats["total_requests"] * 100
            if self.stats["total_requests"] > 0 else 0
        )
        
        avg_response_time = (
            self.stats["total_response_time"] / self.stats["successful_requests"]
            if self.stats["successful_requests"] > 0 else 0
        )
        
        return {
            **self.stats,
            "uptime": str(uptime),
            "success_rate": f"{success_rate:.1f}%",
            "cache_hit_rate": f"{cache_hit_rate:.1f}%",
            "average_response_time": f"{avg_response_time:.3f}s",
            "requests_per_hour": self.stats["total_requests"] / (uptime.total_seconds() / 3600) if uptime.total_seconds() > 0 else 0
        }
    
    def health_check(self) -> Dict[str, Any]:
        """System health check"""
        try:
            <em># Check basic functionality</em>
            test_response = self._execute_search(SearchRequest("test query"))
            
            return {
                "status": "healthy",
                "timestamp": datetime.now().isoformat(),
                "tavily_api": "connected",
                "cache": "operational",
                "rate_limiter": "operational",
                "last_test_status": test_response.status.value
            }
            
        except Exception as e:
            return {
                "status": "unhealthy",
                "timestamp": datetime.now().isoformat(),
                "error": str(e)
            }
    
    def cleanup(self):
        """Clean up resources upon shutdown"""
        self.logger.info("Starting cleanup process...")
        
        <em># Clear expired cache</em>
        self.cache_manager.clear_expired()
        
        <em># Save statistics</em>
        stats_file = Path("agent_statistics.json")
        try:
            with open(stats_file, 'w', encoding='utf-8') as f:
                json.dump(self.get_statistics(), f, ensure_ascii=False, indent=2)
            self.logger.info(f"Statistics saved to {stats_file}")
        except Exception as e:
            self.logger.error(f"Failed to save statistics: {e}")
        
        self.logger.info("Cleanup completed")
<em># Example usage in production</em>
async def main():
    """Demonstration of the production agent"""
    
    <em># Agent initialization</em>
    agent = ProductionSearchAgent(
        tavily_api_key="tvly-YOUR_API_KEY",
        model="mistral/mistral-small-latest",
        cache_ttl_hours=12,
        rate_limit_per_minute=30,
        log_level="INFO"
    )
    
    try:
        <em># Health check</em>
        health = agent.health_check()
        print(f"Agent health: {health['status']}")
        
        <em># Test queries</em>
        test_queries = [
            "Latest achievements in quantum computing",
            "Current trends in AI development 2024",
            "Impact of climate change on the economy",
        ]
        
        print("\n=== Production Agent Demonstration ===\n")
        
        for i, query in enumerate(test_queries, 1):
            print(f"Query {i}: {query}")
            print("-" * 60)
            
            result = agent.search(query, SearchDepth.BASIC)
            print(result[:500] + "..." if len(result) > 500 else result)
            
            print(f"\nStatistics:")
            stats = agent.get_statistics()
            print(f"- Total requests: {stats['total_requests']}")
            print(f"- Successful: {stats['success_rate']}")
            print(f"- From cache: {stats['cache_hit_rate']}")
            print(f"- Average response time: {stats['average_response_time']}")
            print("\n" + "="*80 + "\n")
        
        <em># Final statistics</em>
        print("Final statistics:")
        final_stats = agent.get_statistics()
        for key, value in final_stats.items():
            print(f"{key}: {value}")
    
    finally:
        <em># Resource cleanup</em>
        agent.cleanup()
if __name__ == "__main__":
    asyncio.run(main())

Appendix B: Configuration Files

config/agent_config.json

{
  "tavily": {
    "api_key": "tvly-YOUR_API_KEY",
    "default_search_depth": "basic",
    "timeout_seconds": 30
  },
  "agent": {
    "model": "gpt-4",
    "framework": "TinyAgent",
    "max_tokens": 4000,
    "temperature": 0.1
  },
  "cache": {
    "enabled": true,
    "ttl_hours": 24,
    "max_size_mb": 100,
    "directory": "./cache"
  },
  "rate_limiting": {
    "enabled": true,
    "requests_per_minute": 60,
    "burst_allowance": 10
  },
  "logging": {
    "level": "INFO",
    "file": "agent.log",
    "max_file_size_mb": 10,
    "backup_count": 5
  },
  "monitoring": {
    "enabled": true,
    "metrics_interval_seconds": 300,
    "health_check_interval_seconds": 60
  }
}

docker/Dockerfile

FROM python:3.11-slim

WORKDIR /app

<em># Install system dependencies</em>
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

<em># Copy dependency files</em>
COPY requirements.txt .

<em># Install Python dependencies</em>
RUN pip install --no-cache-dir -r requirements.txt

<em># Copy application code</em>
COPY . .

<em># Create necessary directories</em>
RUN mkdir -p /app/cache /app/logs

<em># Configure non-root user</em>
RUN useradd -m -u 1000 agent && \
    chown -R agent:agent /app
USER agent

<em># Expose ports</em>
EXPOSE 8000

<em># Startup command</em>
CMD ["python", "-m", "src.agents.production_search_agent"]

docker-compose.yml

version: '3.8'

services:
  search-agent:
    build: .
    container_name: production-search-agent
    environment:
      - TAVILY_API_KEY=${TAVILY_API_KEY}
      - LOG_LEVEL=INFO
    volumes:
      - ./cache:/app/cache
      - ./logs:/app/logs
      - ./config:/app/config
    ports:
      - "8000:8000"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/health')"]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:7-alpine
    container_name: agent-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    restart: unless-stopped

  prometheus:
    image: prom/prometheus:latest
    container_name: agent-prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    restart: unless-stopped

volumes:
  redis_data:

Appendix B: Testing

tests/test_production_agent.py

import pytest
import asyncio
from unittest.mock import Mock, patch
from src.agents.production_search_agent import (
    ProductionSearchAgent,
    SearchRequest,
    SearchDepth,
    SearchStatus
)


@pytest.fixture
def mock_agent():
    """Fixture for creating a mock agent"""
    with patch('src.agents.production_search_agent.TavilyClient'):
        agent = ProductionSearchAgent(
            tavily_api_key="test-key",
            model="gpt-4"
        )
        return agent


class TestProductionSearchAgent:
    
    def test_agent_initialization(self, mock_agent):
        """Test agent initialization"""
        assert mock_agent is not None
        assert mock_agent.stats["total_requests"] == 0
        assert mock_agent.max_retries == 3
    
    def test_search_request_creation(self):
        """Test search request creation"""
        request = SearchRequest(
            query="test query",
            depth=SearchDepth.BASIC
        )
        
        assert request.query == "test query"
        assert request.depth == SearchDepth.BASIC
        assert request.timestamp is not None
        
        cache_key = request.get_cache_key()
        assert len(cache_key) == 32  <em># MD5 hash</em>
    
    @patch('src.agents.production_search_agent.TavilyClient')
    def test_successful_search(self, mock_tavily, mock_agent):
        """Test successful search"""
        <em># Mock setup</em>
        mock_tavily_instance = Mock()
        mock_tavily.return_value = mock_tavily_instance
        mock_tavily_instance.search.return_value = {
            'answer': 'Test answer',
            'results': [{'title': 'Test', 'url': 'http://test.com'}]
        }
        
        <em># Perform search</em>
        request = SearchRequest("test query")
        response = mock_agent._execute_search(request)
        
        <em># Assertions</em>
        assert response.status == SearchStatus.SUCCESS
        assert response.answer == 'Test answer'
        assert len(response.sources) == 1
        assert not response.from_cache
    
    def test_cache_functionality(self, mock_agent):
        """Test cache functionality"""
        cache_key = "test_key"
        test_data = {"answer": "cached answer", "sources": []}
        
        <em># Save to cache</em>
        mock_agent.cache_manager.set(cache_key, test_data)
        
        <em># Get from cache</em>
        cached_data = mock_agent.cache_manager.get(cache_key)
        
        assert cached_data is not None
        assert cached_data["answer"] == "cached answer"
    
    def test_rate_limiting(self, mock_agent):
        """Test request rate limiting"""
        rate_limiter = mock_agent.rate_limiter
        
        <em># Fill limit</em>
        for _ in range(rate_limiter.max_requests):
            rate_limiter.register_request()
        
        <em># Check blocking</em>
        assert not rate_limiter.can_make_request()
        
        <em># Check wait time</em>
        wait_time = rate_limiter.get_wait_time()
        assert wait_time > 0
    
    def test_statistics_tracking(self, mock_agent):
        """Test statistics tracking"""
        initial_stats = mock_agent.get_statistics()
        assert initial_stats["total_requests"] == 0
        assert initial_stats["success_rate"] == "0.0%"
        
        <em># Simulate requests</em>
        mock_agent.stats["total_requests"] = 10
        mock_agent.stats["successful_requests"] = 8
        mock_agent.stats["cached_requests"] = 2
        
        updated_stats = mock_agent.get_statistics()
        assert updated_stats["total_requests"] == 10
        assert updated_stats["success_rate"] == "80.0%"
        assert updated_stats["cache_hit_rate"] == "20.0%"
    
    def test_health_check(self, mock_agent):
        """Test system health check"""
        health = mock_agent.health_check()
        
        assert "status" in health
        assert "timestamp" in health
        assert health["status"] in ["healthy", "unhealthy"]


@pytest.mark.asyncio
async def test_async_functionality():
    """Test asynchronous functionality"""
    <em># Asynchronous operations can be tested here</em>
    <em># if they are added in the future</em>
    pass


if __name__ == "__main__":
    pytest.main([__file__, "-v"])

Final Recommendations

1. Production Deployment

Containerization: Use Docker for dependency isolation and simplified deployment.

Monitoring: Set up a monitoring system (Prometheus + Grafana) to track performance.

Logging: Use centralized logging (ELK stack or similar).

2. Security

<em># Secure storage of API keys</em>
import os
from cryptography.fernet import Fernet

class SecureConfig:
    """Secure configuration management"""
    
    def __init__(self, key_file: str = ".encryption_key"):
        self.key_file = key_file
        self.key = self._load_or_create_key()
        self.cipher = Fernet(self.key)
    
    def _load_or_create_key(self) -> bytes:
        """Load or create encryption key"""
        if os.path.exists(self.key_file):
            with open(self.key_file, 'rb') as f:
                return f.read()
        else:
            key = Fernet.generate_key()
            with open(self.key_file, 'wb') as f:
                f.write(key)
            return key
    
    def encrypt_api_key(self, api_key: str) -> str:
        """Encrypt API key"""
        return self.cipher.encrypt(api_key.encode()).decode()
    
    def decrypt_api_key(self, encrypted_key: str) -> str:
        """Decrypt API key"""
        return self.cipher.decrypt(encrypted_key.encode()).decode()

<em># Usage</em>
config = SecureConfig()
encrypted_key = config.encrypt_api_key("tvly-YOUR_API_KEY")
<em># Save encrypted_key in config</em>
<em># When using:</em>
api_key = config.decrypt_api_key(encrypted_key)

3. Scaling

<em># Example microservice architecture</em>
from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel
import uvicorn

app = FastAPI(title="Search Agent API", version="1.0.0")

class SearchQuery(BaseModel):
    query: str
    depth: str = "basic"
    user_id: str = None

class SearchResponse(BaseModel):
    query: str
    answer: str
    sources: list
    response_time: float
    from_cache: bool

<em># Global agent instance</em>
agent = ProductionSearchAgent(tavily_api_key=os.getenv("TAVILY_API_KEY"))

@app.post("/search", response_model=SearchResponse)
async def search_endpoint(query: SearchQuery, background_tasks: BackgroundTasks):
    """Endpoint for search query"""
    try:
        result = agent.search(query.query, SearchDepth(query.depth))
        
        <em># Asynchronous logging</em>
        background_tasks.add_task(log_search_request, query, result)
        
        return SearchResponse(
            query=query.query,
            answer=result,
            sources=[],  <em># Populate with actual sources</em>
            response_time=0.0,  <em># Populate with actual time</em>
            from_cache=False  <em># Populate with actual status</em>
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health_check():
    """Service health check"""
    return agent.health_check()

@app.get("/stats")
async def get_statistics():
    """Get statistics"""
    return agent.get_statistics()

def log_search_request(query: SearchQuery, result: str):
    """Asynchronous logging of request"""
    <em># Log to database or external system</em>
    pass

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

4. Monitoring and Alerting

<em># monitoring/prometheus.yml</em>
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'search-agent'
    static_configs:
      - targets: ['search-agent:8000']
    metrics_path: '/metrics'
    scrape_interval: 30s

rule_files:
  - "alert_rules.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          - alertmanager:9093
<em># monitoring/alert_rules.yml</em>
groups:
  - name: search_agent_alerts
    rules:
      - alert: HighErrorRate
        expr: (rate(search_agent_failed_requests_total[5m]) / rate(search_agent_total_requests_total[5m])) > 0.1
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value | humanizePercentage }}"

      - alert: SlowResponseTime
        expr: search_agent_avg_response_time_seconds > 5
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Slow response time"
          description: "Average response time is {{ $value }}s"

5. Continuous Integration

<em># .github/workflows/ci.yml</em>
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install -r requirements-test.txt
      
      - name: Run tests
        run: |
          pytest tests/ --cov=src --cov-report=xml
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run security scan
        run: |
          pip install bandit safety
          bandit -r src/
          safety check

  build:
    needs: [test, security]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v3
      
      - name: Build Docker image
        run: |
          docker build -t search-agent:latest .
      
      - name: Push to registry
        run: |
          echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
          docker push search-agent:latest

6. API Documentation

<em># Extended FastAPI documentation</em>
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi

def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema
    
    openapi_schema = get_openapi(
        title="Smart Search Agent API",
        version="2.0.0",
        description="""
        ## Smart Search Agent with Tavily Integration

        This API provides access to a smart search agent,
        which uses up-to-date information from the internet.

        ### Capabilities:
        - Real-time search for up-to-date information
        - Result caching for improved performance
        - Request rate limiting
        - Detailed usage statistics
        - System health monitoring

        ### Authentication:
        Use the API key in the `X-API-Key` header
        """,
        routes=app.routes,
    )
    
    openapi_schema["info"]["x-logo"] = {
        "url": "https://example.com/logo.png"
    }
    
    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi

Concluding Remarks

The integration of any-agent with Tavily creates a powerful platform for developing modern AI applications. This technological combination solves the critical problem of access to up-to-date information and opens up new possibilities for creating intelligent systems.

Key Achievements:

🎯 Versatility: A unified interface for working with various agent frameworks

⚡ Timeliness: Access to fresh information in real-time through a specialized search engine

🔧 Flexibility: Ability to create both simple agents and complex multi-agent systems

📊 Reliability: A complete set of tools for production: monitoring, caching, error handling

🚀 Scalability: Ready for deployment in an enterprise environment

Application in Real Projects:

  • Corporate assistants with access to up-to-date information
  • Market analysis systems for financial organizations
  • Research platforms for scientific institutions
  • News aggregators and analytical systems
  • Educational assistants with current knowledge

Future Development:

AI agent technologies are rapidly evolving. The combination of any-agent + Tavily creates a strong foundation for adapting to new frameworks and capabilities, ensuring long-term value for development investments.


This guide provides a complete development cycle – from basic examples to production solutions. Use it as a reference for creating your own smart agents with access to up-to-date information.

Good luck with your development! 🤖✨

Leave a Reply

Your email address will not be published. Required fields are marked *