Spaces:
				
			
			
	
			
			
		Sleeping
		
	
	
	
			
			
	
	
	
	
		
		
		Sleeping
		
	Update rich tool: bugifx,
Browse files- update prompts
- update providers
- agents/single_agent.py +4 -4
- api_utils.py +26 -0
- config.py +6 -5
- run_single_agent.py +9 -13
- tools/analysis_tools.py +18 -4
- tools/formatting_tools.py +186 -169
- tools/search_tools.py +75 -3
    	
        agents/single_agent.py
    CHANGED
    
    | @@ -8,7 +8,7 @@ from smolagents import CodeAgent, VisitWebpageTool, FinalAnswerTool, DuckDuckGoS | |
| 8 | 
             
            from config import load_prompt_templates
         | 
| 9 | 
             
            from tools.analysis_tools import AnalyzeLyricsTool
         | 
| 10 | 
             
            from tools.formatting_tools import FormatAnalysisResultsTool
         | 
| 11 | 
            -
            from tools.search_tools import ThrottledDuckDuckGoSearchTool
         | 
| 12 |  | 
| 13 |  | 
| 14 | 
             
            def create_single_agent(model):
         | 
| @@ -27,12 +27,12 @@ def create_single_agent(model): | |
| 27 | 
             
                # Example usage within the agent
         | 
| 28 | 
             
                agent = CodeAgent(
         | 
| 29 | 
             
                    tools=[
         | 
| 30 | 
            -
                         | 
| 31 | 
            -
                        DuckDuckGoSearchTool(),
         | 
| 32 | 
             
                        # ThrottledDuckDuckGoSearchTool(min_delay=7.0, max_delay=15.0),
         | 
| 33 | 
             
                        VisitWebpageTool(),
         | 
| 34 | 
             
                        AnalyzeLyricsTool(),
         | 
| 35 | 
            -
                        FormatAnalysisResultsTool()
         | 
|  | |
| 36 | 
             
                    ],
         | 
| 37 | 
             
                    model=model,
         | 
| 38 | 
             
                    additional_authorized_imports=['numpy', 'bs4', 'rich'],
         | 
|  | |
| 8 | 
             
            from config import load_prompt_templates
         | 
| 9 | 
             
            from tools.analysis_tools import AnalyzeLyricsTool
         | 
| 10 | 
             
            from tools.formatting_tools import FormatAnalysisResultsTool
         | 
| 11 | 
            +
            from tools.search_tools import ThrottledDuckDuckGoSearchTool, BraveSearchTool
         | 
| 12 |  | 
| 13 |  | 
| 14 | 
             
            def create_single_agent(model):
         | 
|  | |
| 27 | 
             
                # Example usage within the agent
         | 
| 28 | 
             
                agent = CodeAgent(
         | 
| 29 | 
             
                    tools=[
         | 
| 30 | 
            +
                        BraveSearchTool(),
         | 
|  | |
| 31 | 
             
                        # ThrottledDuckDuckGoSearchTool(min_delay=7.0, max_delay=15.0),
         | 
| 32 | 
             
                        VisitWebpageTool(),
         | 
| 33 | 
             
                        AnalyzeLyricsTool(),
         | 
| 34 | 
            +
                        FormatAnalysisResultsTool(),
         | 
| 35 | 
            +
                        FinalAnswerTool(),
         | 
| 36 | 
             
                    ],
         | 
| 37 | 
             
                    model=model,
         | 
| 38 | 
             
                    additional_authorized_imports=['numpy', 'bs4', 'rich'],
         | 
    	
        api_utils.py
    CHANGED
    
    | @@ -40,6 +40,32 @@ def make_api_call_with_retry(model: str, prompt: str) -> str: | |
| 40 | 
             
                            model=model,
         | 
| 41 | 
             
                            messages=[{"role": "user", "content": prompt}],
         | 
| 42 | 
             
                            num_retries=2,  # Built-in retry mechanism of LiteLLM
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 43 | 
             
                        )
         | 
| 44 |  | 
| 45 | 
             
                        # Try to extract the content from the response
         | 
|  | |
| 40 | 
             
                            model=model,
         | 
| 41 | 
             
                            messages=[{"role": "user", "content": prompt}],
         | 
| 42 | 
             
                            num_retries=2,  # Built-in retry mechanism of LiteLLM
         | 
| 43 | 
            +
                            response_format={
         | 
| 44 | 
            +
                                "type": "json_object",
         | 
| 45 | 
            +
                                "schema": {
         | 
| 46 | 
            +
                                    "type": "object",
         | 
| 47 | 
            +
                                    "properties": {
         | 
| 48 | 
            +
                                        "summary": {"type": "string", "description": "Overall analysis of the song vibes, meaning and mood"},
         | 
| 49 | 
            +
                                        "main_themes": {"type": "array", "items": {"type": "string"}, "description": "Main themes identified in the song"},
         | 
| 50 | 
            +
                                        "mood": {"type": "string", "description": "The overall mood/emotion of the song"},
         | 
| 51 | 
            +
                                        "sections_analysis": {
         | 
| 52 | 
            +
                                            "type": "array",
         | 
| 53 | 
            +
                                            "items": {
         | 
| 54 | 
            +
                                                "type": "object",
         | 
| 55 | 
            +
                                                "properties": {
         | 
| 56 | 
            +
                                                    "section_type": {"type": "string", "description": "verse/chorus/bridge/etc."},
         | 
| 57 | 
            +
                                                    "section_number": {"type": "integer", "description": "Sequential number of this section type"},
         | 
| 58 | 
            +
                                                    "lines": {"type": "array", "items": {"type": "string"}, "description": "Lyrics of this section"},
         | 
| 59 | 
            +
                                                    "analysis": {"type": "string", "description": "Analysis of this section with respect to the overall theme"}
         | 
| 60 | 
            +
                                                },
         | 
| 61 | 
            +
                                                "required": ["section_type", "section_number", "lines", "analysis"]
         | 
| 62 | 
            +
                                            }
         | 
| 63 | 
            +
                                        },
         | 
| 64 | 
            +
                                        "conclusion": {"type": "string", "description": "The song vibes and concepts of the underlying meaning, including ideas author may have intended to express"}
         | 
| 65 | 
            +
                                    },
         | 
| 66 | 
            +
                                    "required": ["summary", "main_themes", "mood", "sections_analysis", "conclusion"]
         | 
| 67 | 
            +
                                }
         | 
| 68 | 
            +
                            }
         | 
| 69 | 
             
                        )
         | 
| 70 |  | 
| 71 | 
             
                        # Try to extract the content from the response
         | 
    	
        config.py
    CHANGED
    
    | @@ -26,7 +26,7 @@ def load_api_keys(): | |
| 26 | 
             
                os.environ["GEMINI_API_KEY"] = os.getenv("GEMINI_API_KEY")
         | 
| 27 |  | 
| 28 |  | 
| 29 | 
            -
            def get_model_id( | 
| 30 | 
             
                """Get the appropriate model ID based on configuration.
         | 
| 31 |  | 
| 32 | 
             
                Args:
         | 
| @@ -38,14 +38,15 @@ def get_model_id(use_local=True, provider="gemini"): | |
| 38 | 
             
                    String with model ID for the specified provider.
         | 
| 39 | 
             
                """
         | 
| 40 | 
             
                if provider == "ollama":
         | 
| 41 | 
            -
                    return "ollama/gemma3:4b"  # Using local Ollama with Gemma 3:4B
         | 
|  | |
| 42 | 
             
                elif provider == "gemini":
         | 
| 43 | 
             
                    return "gemini/gemini-2.0-flash"
         | 
| 44 | 
             
                elif provider == "openrouter":
         | 
| 45 | 
             
                    return "openrouter/google/gemini-2.0-flash-lite-preview-02-05:free"  # OpenRouter Claude 3 Opus
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                    #  | 
| 48 | 
            -
                    return " | 
| 49 |  | 
| 50 | 
             
            def get_ollama_api_base():
         | 
| 51 | 
             
                """Get the API base URL for Ollama."""
         | 
|  | |
| 26 | 
             
                os.environ["GEMINI_API_KEY"] = os.getenv("GEMINI_API_KEY")
         | 
| 27 |  | 
| 28 |  | 
| 29 | 
            +
            def get_model_id(provider="gemini"):
         | 
| 30 | 
             
                """Get the appropriate model ID based on configuration.
         | 
| 31 |  | 
| 32 | 
             
                Args:
         | 
|  | |
| 38 | 
             
                    String with model ID for the specified provider.
         | 
| 39 | 
             
                """
         | 
| 40 | 
             
                if provider == "ollama":
         | 
| 41 | 
            +
                    # return "ollama/gemma3:4b"  # Using local Ollama with Gemma 3:4B
         | 
| 42 | 
            +
                    return "ollama/qwen2.5-coder:7b"
         | 
| 43 | 
             
                elif provider == "gemini":
         | 
| 44 | 
             
                    return "gemini/gemini-2.0-flash"
         | 
| 45 | 
             
                elif provider == "openrouter":
         | 
| 46 | 
             
                    return "openrouter/google/gemini-2.0-flash-lite-preview-02-05:free"  # OpenRouter Claude 3 Opus
         | 
| 47 | 
            +
                    # return "openrouter/mistralai/mistral-small-3.1-24b-instruct:free"
         | 
| 48 | 
            +
                    #return "openrouter/rekaai/reka-flash-3:free"
         | 
| 49 | 
            +
                    # return "openrouter/deepseek/deepseek-chat:free"
         | 
| 50 |  | 
| 51 | 
             
            def get_ollama_api_base():
         | 
| 52 | 
             
                """Get the API base URL for Ollama."""
         | 
    	
        run_single_agent.py
    CHANGED
    
    | @@ -14,16 +14,12 @@ os.environ["GEMINI_API_KEY"] = str(os.getenv("GEMINI_API_KEY")) | |
| 14 |  | 
| 15 | 
             
            use_local = False
         | 
| 16 |  | 
| 17 | 
            -
             | 
| 18 | 
            -
            # Initialize the LLM model based on configuration
         | 
| 19 | 
            -
            if use_local:
         | 
| 20 | 
            -
                model_id = "openrouter/google/gemini-2.0-flash-lite-preview-02-05:free"
         | 
| 21 | 
            -
            else:
         | 
| 22 | 
            -
                model_id = get_model_id(use_local=use_local)
         | 
| 23 | 
            -
             | 
| 24 | 
             
            logger.info(f"Initializing with model: {model_id}")
         | 
| 25 |  | 
| 26 | 
             
            if use_local:
         | 
|  | |
|  | |
| 27 | 
             
                api_base = get_ollama_api_base()
         | 
| 28 | 
             
                logger.info(f"Using Ollama API base: {api_base}")
         | 
| 29 | 
             
                model = LiteLLMModel(model_id=model_id, api_base=api_base)
         | 
| @@ -34,12 +30,12 @@ else: | |
| 34 | 
             
            # model_id='https://pflgm2locj2t89co.us-east-1.aws.endpoints.huggingface.cloud' 
         | 
| 35 |  | 
| 36 | 
             
            # Prompt the user for the song name
         | 
| 37 | 
            -
            song_data = " | 
| 38 |  | 
| 39 | 
             
            agent = create_single_agent(model)
         | 
| 40 | 
            -
             | 
|  | |
|  | |
|  | |
| 41 | 
             
            # Agent execution
         | 
| 42 | 
            -
            agent.run( | 
| 43 | 
            -
            1. Find and extract the lyrics of the song, {song_data}. Don't try to scrape from azlyrics.com or genius.com, others are ok.
         | 
| 44 | 
            -
            2. Perform deep lyrics analysis and return full lyrics and analysis results in a pretty human-readable format.
         | 
| 45 | 
            -
            """)
         | 
|  | |
| 14 |  | 
| 15 | 
             
            use_local = False
         | 
| 16 |  | 
| 17 | 
            +
            model_id = get_model_id(provider='openrouter')
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 18 | 
             
            logger.info(f"Initializing with model: {model_id}")
         | 
| 19 |  | 
| 20 | 
             
            if use_local:
         | 
| 21 | 
            +
                # If using Ollama, we need to specify the API base URL
         | 
| 22 | 
            +
                # Initialize the LLM model based onx configuration
         | 
| 23 | 
             
                api_base = get_ollama_api_base()
         | 
| 24 | 
             
                logger.info(f"Using Ollama API base: {api_base}")
         | 
| 25 | 
             
                model = LiteLLMModel(model_id=model_id, api_base=api_base)
         | 
|  | |
| 30 | 
             
            # model_id='https://pflgm2locj2t89co.us-east-1.aws.endpoints.huggingface.cloud' 
         | 
| 31 |  | 
| 32 | 
             
            # Prompt the user for the song name
         | 
| 33 | 
            +
            song_data = "Felix Da Housecat - Everyone is someone in LA"
         | 
| 34 |  | 
| 35 | 
             
            agent = create_single_agent(model)
         | 
| 36 | 
            +
            prompt = f"""1. Find and extract the lyrics of the song: {song_data}.
         | 
| 37 | 
            +
            2. Perform deep lyrics analysis and return full lyrics and analysis results 
         | 
| 38 | 
            +
              in a pretty human-readable format.
         | 
| 39 | 
            +
            """
         | 
| 40 | 
             
            # Agent execution
         | 
| 41 | 
            +
            agent.run(prompt)
         | 
|  | |
|  | |
|  | 
    	
        tools/analysis_tools.py
    CHANGED
    
    | @@ -4,6 +4,8 @@ Analysis tools for understanding and interpreting song lyrics. | |
| 4 |  | 
| 5 | 
             
            from loguru import logger
         | 
| 6 | 
             
            from smolagents import Tool
         | 
|  | |
|  | |
| 7 |  | 
| 8 | 
             
            from api_utils import make_api_call_with_retry
         | 
| 9 |  | 
| @@ -67,11 +69,23 @@ class AnalyzeLyricsTool(Tool): | |
| 67 | 
             
                    {lyrics}
         | 
| 68 | 
             
                    """
         | 
| 69 |  | 
| 70 | 
            -
                    model_to_use = "gemini/gemini-2.0-flash"
         | 
| 71 | 
            -
                     | 
|  | |
| 72 |  | 
| 73 | 
             
                    # Use the function with retry mechanism
         | 
| 74 | 
             
                    logger.info("Analyzing lyrics for song: '{}' by '{}'", song_title, artist)
         | 
| 75 | 
            -
                     | 
| 76 | 
            -
                     | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 77 |  | 
|  | |
| 4 |  | 
| 5 | 
             
            from loguru import logger
         | 
| 6 | 
             
            from smolagents import Tool
         | 
| 7 | 
            +
            import json
         | 
| 8 | 
            +
            from typing import Dict
         | 
| 9 |  | 
| 10 | 
             
            from api_utils import make_api_call_with_retry
         | 
| 11 |  | 
|  | |
| 69 | 
             
                    {lyrics}
         | 
| 70 | 
             
                    """
         | 
| 71 |  | 
| 72 | 
            +
                    # model_to_use = "gemini/gemini-2.0-flash"
         | 
| 73 | 
            +
                    model_to_use = "openrouter/google/gemini-2.0-flash-lite-preview-02-05:free"
         | 
| 74 | 
            +
                    logger.info("Using {} for lyrics analysis", model_to_use)
         | 
| 75 |  | 
| 76 | 
             
                    # Use the function with retry mechanism
         | 
| 77 | 
             
                    logger.info("Analyzing lyrics for song: '{}' by '{}'", song_title, artist)
         | 
| 78 | 
            +
                    response_text = make_api_call_with_retry(model_to_use, prompt)
         | 
| 79 | 
            +
                    
         | 
| 80 | 
            +
                    try:
         | 
| 81 | 
            +
                        # Parse the string response into a JSON object (dictionary)
         | 
| 82 | 
            +
                        logger.debug(f"Parsing JSON response for {song_title}")
         | 
| 83 | 
            +
                        response_json = json.loads(response_text)
         | 
| 84 | 
            +
                        return response_json
         | 
| 85 | 
            +
                    except json.JSONDecodeError as e:
         | 
| 86 | 
            +
                        logger.error(f"Failed to parse lyrics analysis response as JSON: {str(e)}")
         | 
| 87 | 
            +
                        # Return the raw text response if parsing fails
         | 
| 88 | 
            +
                        # This will likely cause an error in the formatting step,
         | 
| 89 | 
            +
                        # but at least we'll have the raw output for debugging
         | 
| 90 | 
            +
                        return response_text
         | 
| 91 |  | 
    	
        tools/formatting_tools.py
    CHANGED
    
    | @@ -3,6 +3,8 @@ Formatting tools for displaying analysis results with rich formatting. | |
| 3 | 
             
            """
         | 
| 4 |  | 
| 5 | 
             
            import json
         | 
|  | |
|  | |
| 6 | 
             
            from loguru import logger
         | 
| 7 | 
             
            from smolagents import Tool
         | 
| 8 | 
             
            from rich.console import Console
         | 
| @@ -25,205 +27,220 @@ class FormatAnalysisResultsTool(Tool): | |
| 25 | 
             
                }
         | 
| 26 | 
             
                output_type = "string"
         | 
| 27 |  | 
| 28 | 
            -
                def forward(self, analysis_json, pretty: bool = True) -> str:
         | 
| 29 | 
             
                    """
         | 
| 30 | 
             
                    Formats the analysis results for better readability.
         | 
| 31 |  | 
| 32 | 
             
                    Args:
         | 
| 33 | 
            -
                        analysis_json: JSON  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 34 | 
             
                        pretty: Whether to use rich formatting (True) or simple text formatting (False)
         | 
| 35 |  | 
| 36 | 
             
                    Returns:
         | 
| 37 | 
             
                        A formatted string representation of the analysis
         | 
| 38 | 
             
                    """
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                     | 
| 41 | 
            -
                    # | 
| 42 | 
            -
                     | 
| 43 | 
            -
                    #     "mood": "The overall mood/emotion of the song",
         | 
| 44 | 
            -
                    #     "sections_analysis": [
         | 
| 45 | 
            -
                    #         {
         | 
| 46 | 
            -
                    #             "section_type": "verse/chorus/bridge/etc.",
         | 
| 47 | 
            -
                    #             "section_number": 1,
         | 
| 48 | 
            -
                    #             "lines": ["line1", "line2", ...],
         | 
| 49 | 
            -
                    #             "analysis": "Analysis of this section whith respect to the overall theme"
         | 
| 50 | 
            -
                    #         },
         | 
| 51 | 
            -
                    #         ...
         | 
| 52 | 
            -
                    #     ],
         | 
| 53 | 
            -
                    #     "conclusion": "The song vibes and concepts of the underlying meaning"
         | 
| 54 | 
            -
                    # }
         | 
| 55 | 
            -
                    try:
         | 
| 56 | 
            -
                        # Parse the JSON string into a Python dictionary if it's a string
         | 
| 57 | 
            -
                        if isinstance(analysis_json, str):
         | 
| 58 | 
            -
                            analysis = json.loads(analysis_json)
         | 
| 59 | 
            -
                        else:
         | 
| 60 | 
            -
                            analysis = analysis_json
         | 
| 61 | 
            -
                            
         | 
| 62 | 
            -
                        if pretty:
         | 
| 63 | 
            -
                            # Rich formatting with the rich library
         | 
| 64 | 
            -
                            try:
         | 
| 65 | 
            -
                                # Create a string buffer to capture the rich output
         | 
| 66 | 
            -
                                console = Console(record=True, width=100)
         | 
| 67 | 
            -
                                
         | 
| 68 | 
            -
                                # Create a custom theme for consistent styling
         | 
| 69 | 
            -
                                # Using direct style definitions instead of named styles
         | 
| 70 | 
            -
                                custom_theme = Theme({
         | 
| 71 | 
            -
                                    "heading": "bold cyan underline",
         | 
| 72 | 
            -
                                    "highlight": "bold yellow",
         | 
| 73 | 
            -
                                    "positive": "green",
         | 
| 74 | 
            -
                                    "negative": "red",
         | 
| 75 | 
            -
                                    "neutral": "magenta",
         | 
| 76 | 
            -
                                    "quote": "italic yellow",
         | 
| 77 | 
            -
                                    "metadata": "dim white",
         | 
| 78 | 
            -
                                    "conclusion": "bold magenta"  # Add style for conclusion
         | 
| 79 | 
            -
                                })
         | 
| 80 | 
            -
                                
         | 
| 81 | 
            -
                                # Apply the theme to our console
         | 
| 82 | 
            -
                                console.theme = custom_theme
         | 
| 83 | 
            -
                                
         | 
| 84 | 
            -
                                # Format the summary section
         | 
| 85 | 
            -
                                summary = analysis.get("summary", "No summary available")
         | 
| 86 | 
            -
                                console.print(Panel(Text(summary, justify="center"), title="[heading]Song Analysis[/]", subtitle="[metadata]Summary[/]"))
         | 
| 87 | 
            -
                                
         | 
| 88 | 
            -
                                # Create a table for the main themes and mood
         | 
| 89 | 
            -
                                info_table = Table(show_header=False, box=ROUNDED, expand=True)
         | 
| 90 | 
            -
                                info_table.add_column("Key", style="bold blue")
         | 
| 91 | 
            -
                                info_table.add_column("Value")
         | 
| 92 | 
            -
                                
         | 
| 93 | 
            -
                                # Add the mood
         | 
| 94 | 
            -
                                mood = analysis.get("mood", "Not specified")
         | 
| 95 | 
            -
                                info_table.add_row("Mood", mood)
         | 
| 96 | 
            -
                                
         | 
| 97 | 
            -
                                # Add the themes as a comma-separated list
         | 
| 98 | 
            -
                                themes = analysis.get("main_themes", [])
         | 
| 99 | 
            -
                                if themes:
         | 
| 100 | 
            -
                                    themes_text = ", ".join([f"[highlight]{theme}[/]" for theme in themes])
         | 
| 101 | 
            -
                                    info_table.add_row("Main Themes", themes_text)
         | 
| 102 | 
            -
                                
         | 
| 103 | 
            -
                                console.print(info_table)
         | 
| 104 | 
            -
                                
         | 
| 105 | 
            -
                                # Section-by-section analysis
         | 
| 106 | 
            -
                                sections = analysis.get("sections_analysis", [])
         | 
| 107 | 
            -
                                if sections:
         | 
| 108 | 
            -
                                    console.print("\n[heading]Section-by-Section Analysis[/]")
         | 
| 109 | 
            -
                                    
         | 
| 110 | 
            -
                                    for section in sections:
         | 
| 111 | 
            -
                                        section_type = section.get("section_type", "Unknown")
         | 
| 112 | 
            -
                                        section_number = section.get("section_number", "")
         | 
| 113 | 
            -
                                        section_title = f"{section_type.title()} {section_number}" if section_number else section_type.title()
         | 
| 114 | 
            -
                                        
         | 
| 115 | 
            -
                                        section_analysis = section.get("analysis", "No analysis available")
         | 
| 116 | 
            -
                                        lines = section.get("lines", [])
         | 
| 117 | 
            -
                                        
         | 
| 118 | 
            -
                                        # Create a group with the lyrics and analysis
         | 
| 119 | 
            -
                                        section_content = []
         | 
| 120 | 
            -
                                        
         | 
| 121 | 
            -
                                        if lines:
         | 
| 122 | 
            -
                                            # Format lyrics in a more readable way
         | 
| 123 | 
            -
                                            section_content.append(Text("Lyrics:", style="bold blue"))
         | 
| 124 | 
            -
                                            # Форматируем каждую строку лирики с стилем quote
         | 
| 125 | 
            -
                                            lyrics_lines = []
         | 
| 126 | 
            -
                                            for line in lines:
         | 
| 127 | 
            -
                                                lyrics_lines.append(f"[quote]{line}[/]")
         | 
| 128 | 
            -
                                            
         | 
| 129 | 
            -
                                            lyrics_panel = Panel(
         | 
| 130 | 
            -
                                                "\n".join(lyrics_lines),
         | 
| 131 | 
            -
                                                border_style="blue",
         | 
| 132 | 
            -
                                                padding=(1, 2)
         | 
| 133 | 
            -
                                            )
         | 
| 134 | 
            -
                                            section_content.append(lyrics_panel)
         | 
| 135 | 
            -
                                        
         | 
| 136 | 
            -
                                        section_content.append(Text("Analysis:", style="bold blue"))
         | 
| 137 | 
            -
                                        section_content.append(Text(section_analysis))
         | 
| 138 | 
            -
                                        
         | 
| 139 | 
            -
                                        # Add the section panel
         | 
| 140 | 
            -
                                        console.print(Panel(
         | 
| 141 | 
            -
                                            Group(*section_content),
         | 
| 142 | 
            -
                                            title=f"[bold cyan]{section_title}[/]",
         | 
| 143 | 
            -
                                            border_style="cyan"
         | 
| 144 | 
            -
                                        ))
         | 
| 145 | 
            -
                                
         | 
| 146 | 
            -
                                # We no longer have significant_lines in the new format
         | 
| 147 | 
            -
                                
         | 
| 148 | 
            -
                                # Conclusion
         | 
| 149 | 
            -
                                conclusion = analysis.get("conclusion", "No conclusion available")
         | 
| 150 | 
            -
                                console.print("\n[heading]Conclusion[/]")
         | 
| 151 | 
            -
                                console.print(Panel(conclusion, border_style="magenta"))
         | 
| 152 | 
            -
                                
         | 
| 153 | 
            -
                                # Export the rich text as a string
         | 
| 154 | 
            -
                                return console.export_text()
         | 
| 155 | 
            -
                                
         | 
| 156 | 
            -
                            except Exception as e:
         | 
| 157 | 
            -
                                logger.error("Error in rich formatting: {}", str(e))
         | 
| 158 | 
            -
                                # Fall back to simple formatting if rich formatting fails
         | 
| 159 | 
            -
                                logger.info("Falling back to simple text formatting")
         | 
| 160 | 
            -
                                pretty = False
         | 
| 161 |  | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 165 |  | 
| 166 | 
            -
                            #  | 
| 167 | 
            -
                             | 
| 168 | 
            -
                             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 171 |  | 
| 172 | 
            -
                            #  | 
| 173 | 
             
                            themes = analysis.get("main_themes", [])
         | 
|  | |
| 174 | 
             
                            if themes:
         | 
| 175 | 
            -
                                 | 
| 176 | 
            -
                                 | 
| 177 | 
            -
                                for theme in themes:
         | 
| 178 | 
            -
                                    formatted_text.append(f"• {theme}")
         | 
| 179 | 
            -
                                formatted_text.append("")
         | 
| 180 |  | 
| 181 | 
            -
                             | 
| 182 | 
            -
                            mood = analysis.get("mood", "Not specified")
         | 
| 183 | 
            -
                            formatted_text.append("MOOD")
         | 
| 184 | 
            -
                            formatted_text.append("====")
         | 
| 185 | 
            -
                            formatted_text.append(mood)
         | 
| 186 | 
            -
                            formatted_text.append("")
         | 
| 187 |  | 
| 188 | 
            -
                            #  | 
| 189 | 
             
                            sections = analysis.get("sections_analysis", [])
         | 
|  | |
| 190 | 
             
                            if sections:
         | 
| 191 | 
            -
                                 | 
| 192 | 
            -
                                formatted_text.append("==========================")
         | 
| 193 |  | 
| 194 | 
            -
                                for  | 
| 195 | 
             
                                    section_type = section.get("section_type", "Unknown")
         | 
| 196 | 
            -
                                    section_number = section.get("section_number",  | 
|  | |
|  | |
|  | |
| 197 | 
             
                                    section_analysis = section.get("analysis", "No analysis available")
         | 
|  | |
|  | |
| 198 |  | 
| 199 | 
            -
                                     | 
| 200 | 
            -
                                     | 
| 201 |  | 
| 202 | 
            -
                                    # Format the section lines
         | 
| 203 | 
            -
                                    lines = section.get("lines", [])
         | 
| 204 | 
             
                                    if lines:
         | 
| 205 | 
            -
                                         | 
|  | |
|  | |
|  | |
| 206 | 
             
                                        for line in lines:
         | 
| 207 | 
            -
                                             | 
| 208 | 
            -
                                         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 209 |  | 
| 210 | 
            -
                                     | 
| 211 | 
            -
                                     | 
| 212 | 
            -
                                     | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 213 |  | 
| 214 | 
             
                            # We no longer have significant_lines in the new format
         | 
| 215 |  | 
| 216 | 
             
                            # Conclusion
         | 
| 217 | 
             
                            conclusion = analysis.get("conclusion", "No conclusion available")
         | 
| 218 | 
            -
                             | 
| 219 | 
            -
                             | 
| 220 | 
            -
                             | 
| 221 |  | 
| 222 | 
            -
                             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 223 |  | 
| 224 | 
            -
             | 
| 225 | 
            -
             | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 228 | 
            -
             | 
| 229 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 3 | 
             
            """
         | 
| 4 |  | 
| 5 | 
             
            import json
         | 
| 6 | 
            +
            import traceback
         | 
| 7 | 
            +
            from typing import Dict, Any, List, Union
         | 
| 8 | 
             
            from loguru import logger
         | 
| 9 | 
             
            from smolagents import Tool
         | 
| 10 | 
             
            from rich.console import Console
         | 
|  | |
| 27 | 
             
                }
         | 
| 28 | 
             
                output_type = "string"
         | 
| 29 |  | 
| 30 | 
            +
                def forward(self, analysis_json: Dict, pretty: bool = True) -> str:
         | 
| 31 | 
             
                    """
         | 
| 32 | 
             
                    Formats the analysis results for better readability.
         | 
| 33 |  | 
| 34 | 
             
                    Args:
         | 
| 35 | 
            +
                        analysis_json: Dics JSON containing the analysis results
         | 
| 36 | 
            +
                        # Format:
         | 
| 37 | 
            +
                        # {
         | 
| 38 | 
            +
                        #     "summary": "Overall analysis of the song vibes, meaning and mood",
         | 
| 39 | 
            +
                        #     "main_themes": ["theme1", "theme2", ...],
         | 
| 40 | 
            +
                        #     "mood": "The overall mood/emotion of the song",
         | 
| 41 | 
            +
                        #     "sections_analysis": [
         | 
| 42 | 
            +
                        #         {
         | 
| 43 | 
            +
                        #             "section_type": "verse/chorus/bridge/etc.",
         | 
| 44 | 
            +
                        #             "section_number": 1,
         | 
| 45 | 
            +
                        #             "lines": ["line1", "line2", ...],
         | 
| 46 | 
            +
                        #             "analysis": "Analysis of this section whith respect to the overall theme"
         | 
| 47 | 
            +
                        #         },
         | 
| 48 | 
            +
                        #         ...
         | 
| 49 | 
            +
                        #     ],
         | 
| 50 | 
            +
                        #     "conclusion": "The song vibes and concepts of the underlying meaning"
         | 
| 51 | 
            +
                        # }
         | 
| 52 | 
             
                        pretty: Whether to use rich formatting (True) or simple text formatting (False)
         | 
| 53 |  | 
| 54 | 
             
                    Returns:
         | 
| 55 | 
             
                        A formatted string representation of the analysis
         | 
| 56 | 
             
                    """
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    analysis = analysis_json
         | 
| 59 | 
            +
                    # Log the structure of the parsed analysis
         | 
| 60 | 
            +
                    logger.debug(f"Analysis structure keys: {list(analysis.keys()) if isinstance(analysis, dict) else 'Not a dictionary'}")
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 61 |  | 
| 62 | 
            +
                    if pretty:
         | 
| 63 | 
            +
                        # Rich formatting with the rich library
         | 
| 64 | 
            +
                        try:
         | 
| 65 | 
            +
                            logger.debug("Starting rich formatting")
         | 
| 66 | 
            +
                            # Create a console that outputs to a temporary file
         | 
| 67 | 
            +
                            import tempfile
         | 
| 68 | 
            +
                            temp_file = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)
         | 
| 69 | 
            +
                            console = Console(file=temp_file, width=100)
         | 
| 70 |  | 
| 71 | 
            +
                            # Create a custom theme for consistent styling
         | 
| 72 | 
            +
                            # Using direct style definitions instead of named styles
         | 
| 73 | 
            +
                            custom_theme = Theme({
         | 
| 74 | 
            +
                                "heading": "bold cyan underline",
         | 
| 75 | 
            +
                                "highlight": "bold yellow",
         | 
| 76 | 
            +
                                "positive": "green",
         | 
| 77 | 
            +
                                "negative": "red",
         | 
| 78 | 
            +
                                "neutral": "magenta",
         | 
| 79 | 
            +
                                "quote": "italic yellow",
         | 
| 80 | 
            +
                                "metadata": "dim white",
         | 
| 81 | 
            +
                                "conclusion": "bold magenta"  # Add style for conclusion
         | 
| 82 | 
            +
                            })
         | 
| 83 | 
            +
                            
         | 
| 84 | 
            +
                            # Apply the theme to our console
         | 
| 85 | 
            +
                            console.theme = custom_theme
         | 
| 86 | 
            +
                            
         | 
| 87 | 
            +
                            # Format the summary section
         | 
| 88 | 
            +
                            summary = analysis.get("summary", "No summary available")
         | 
| 89 | 
            +
                            logger.debug(f"Summary content: {summary if summary else 'Not available'}")
         | 
| 90 | 
            +
                            console.print(Panel(Text(summary, justify="center"), title="[heading]Song Analysis[/]", subtitle="[metadata]Summary[/]"))
         | 
| 91 | 
            +
                            
         | 
| 92 | 
            +
                            # Create a table for the main themes and mood
         | 
| 93 | 
            +
                            info_table = Table(show_header=False, box=ROUNDED, expand=True)
         | 
| 94 | 
            +
                            info_table.add_column("Key", style="bold blue")
         | 
| 95 | 
            +
                            info_table.add_column("Value")
         | 
| 96 | 
            +
                            
         | 
| 97 | 
            +
                            # Add the mood
         | 
| 98 | 
            +
                            mood = analysis.get("mood", "Not specified")
         | 
| 99 | 
            +
                            logger.debug(f"Mood content: {mood if mood else 'Not specified'}")
         | 
| 100 | 
            +
                            info_table.add_row("Mood", mood)
         | 
| 101 |  | 
| 102 | 
            +
                            # Add the themes as a comma-separated list
         | 
| 103 | 
             
                            themes = analysis.get("main_themes", [])
         | 
| 104 | 
            +
                            logger.debug(f"Themes: {themes if themes else 'Not available'}")
         | 
| 105 | 
             
                            if themes:
         | 
| 106 | 
            +
                                themes_text = ", ".join([f"[highlight]{theme}[/]" for theme in themes])
         | 
| 107 | 
            +
                                info_table.add_row("Main Themes", themes_text)
         | 
|  | |
|  | |
|  | |
| 108 |  | 
| 109 | 
            +
                            console.print(info_table)
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 110 |  | 
| 111 | 
            +
                            # Section-by-section analysis
         | 
| 112 | 
             
                            sections = analysis.get("sections_analysis", [])
         | 
| 113 | 
            +
                            logger.debug(f"Sections count: {len(sections) if sections else 0}")
         | 
| 114 | 
             
                            if sections:
         | 
| 115 | 
            +
                                console.print("\n[heading]Section-by-Section Analysis[/]")
         | 
|  | |
| 116 |  | 
| 117 | 
            +
                                for section in sections:
         | 
| 118 | 
             
                                    section_type = section.get("section_type", "Unknown")
         | 
| 119 | 
            +
                                    section_number = section.get("section_number", "")
         | 
| 120 | 
            +
                                    logger.debug(f"Processing section: {section_type} {section_number}")
         | 
| 121 | 
            +
                                    section_title = f"{section_type.title()} {section_number}" if section_number else section_type.title()
         | 
| 122 | 
            +
                                    
         | 
| 123 | 
             
                                    section_analysis = section.get("analysis", "No analysis available")
         | 
| 124 | 
            +
                                    lines = section.get("lines", [])
         | 
| 125 | 
            +
                                    logger.debug(f"Section lines count: {len(lines) if lines else 0}")
         | 
| 126 |  | 
| 127 | 
            +
                                    # Create a group with the lyrics and analysis
         | 
| 128 | 
            +
                                    section_content = []
         | 
| 129 |  | 
|  | |
|  | |
| 130 | 
             
                                    if lines:
         | 
| 131 | 
            +
                                        # Format lyrics in a more readable way
         | 
| 132 | 
            +
                                        section_content.append(Text("Lyrics:", style="bold blue"))
         | 
| 133 | 
            +
                                        # Форматируем каждую строку лирики с стилем quote
         | 
| 134 | 
            +
                                        lyrics_lines = []
         | 
| 135 | 
             
                                        for line in lines:
         | 
| 136 | 
            +
                                            lyrics_lines.append(f"[quote]{line}[/]")
         | 
| 137 | 
            +
                                        
         | 
| 138 | 
            +
                                        lyrics_panel = Panel(
         | 
| 139 | 
            +
                                            "\n".join(lyrics_lines),
         | 
| 140 | 
            +
                                            border_style="blue",
         | 
| 141 | 
            +
                                            padding=(1, 2)
         | 
| 142 | 
            +
                                        )
         | 
| 143 | 
            +
                                        section_content.append(lyrics_panel)
         | 
| 144 |  | 
| 145 | 
            +
                                    section_content.append(Text("Analysis:", style="bold blue"))
         | 
| 146 | 
            +
                                    section_content.append(Text(section_analysis))
         | 
| 147 | 
            +
                                    
         | 
| 148 | 
            +
                                    # Add the section panel
         | 
| 149 | 
            +
                                    console.print(Panel(
         | 
| 150 | 
            +
                                        Group(*section_content),
         | 
| 151 | 
            +
                                        title=f"[bold cyan]{section_title}[/]",
         | 
| 152 | 
            +
                                        border_style="cyan"
         | 
| 153 | 
            +
                                    ))
         | 
| 154 |  | 
| 155 | 
             
                            # We no longer have significant_lines in the new format
         | 
| 156 |  | 
| 157 | 
             
                            # Conclusion
         | 
| 158 | 
             
                            conclusion = analysis.get("conclusion", "No conclusion available")
         | 
| 159 | 
            +
                            logger.debug(f"Conclusion content: {conclusion if conclusion else 'Not available'}")
         | 
| 160 | 
            +
                            console.print("\n[heading]Conclusion[/]")
         | 
| 161 | 
            +
                            console.print(Panel(conclusion, border_style="magenta"))
         | 
| 162 |  | 
| 163 | 
            +
                            # Save output to file and read back as string
         | 
| 164 | 
            +
                            temp_file.close()
         | 
| 165 | 
            +
                            with open(temp_file.name, 'r', encoding='utf-8') as f:
         | 
| 166 | 
            +
                                result = f.read()
         | 
| 167 | 
            +
                            # Clean up the temp file
         | 
| 168 | 
            +
                            import os
         | 
| 169 | 
            +
                            try:
         | 
| 170 | 
            +
                                os.unlink(temp_file.name)
         | 
| 171 | 
            +
                            except Exception as e:
         | 
| 172 | 
            +
                                logger.warning(f"Could not delete temporary file {temp_file.name}: {str(e)}")
         | 
| 173 | 
            +
                            return result
         | 
| 174 |  | 
| 175 | 
            +
                        except Exception as e:
         | 
| 176 | 
            +
                            error_traceback = traceback.format_exc()
         | 
| 177 | 
            +
                            logger.error(f"Error in rich formatting: {str(e)}\nTraceback:\n{error_traceback}")
         | 
| 178 | 
            +
                            # Log the current state of the analysis object for debugging
         | 
| 179 | 
            +
                            if isinstance(analysis, dict):
         | 
| 180 | 
            +
                                for key, value in analysis.items():
         | 
| 181 | 
            +
                                    logger.debug(f"Key: {key}, Value type: {type(value)}, Value: {str(value)[:100]}{'...' if len(str(value)) > 100 else ''}")
         | 
| 182 | 
            +
                            # Fall back to simple formatting if rich formatting fails
         | 
| 183 | 
            +
                            logger.info("Falling back to simple text formatting")
         | 
| 184 | 
            +
                            pretty = False
         | 
| 185 | 
            +
                    
         | 
| 186 | 
            +
                    if not pretty:
         | 
| 187 | 
            +
                        # Simple text formatting
         | 
| 188 | 
            +
                        formatted_text = []
         | 
| 189 | 
            +
                        
         | 
| 190 | 
            +
                        # Summary
         | 
| 191 | 
            +
                        formatted_text.append("SONG ANALYSIS SUMMARY")
         | 
| 192 | 
            +
                        formatted_text.append("====================")
         | 
| 193 | 
            +
                        formatted_text.append(analysis.get("summary", "No summary available"))
         | 
| 194 | 
            +
                        formatted_text.append("")
         | 
| 195 | 
            +
                        
         | 
| 196 | 
            +
                        # Main themes
         | 
| 197 | 
            +
                        themes = analysis.get("main_themes", [])
         | 
| 198 | 
            +
                        if themes:
         | 
| 199 | 
            +
                            formatted_text.append("MAIN THEMES")
         | 
| 200 | 
            +
                            formatted_text.append("===========")
         | 
| 201 | 
            +
                            for theme in themes:
         | 
| 202 | 
            +
                                formatted_text.append(f"• {theme}")
         | 
| 203 | 
            +
                            formatted_text.append("")
         | 
| 204 | 
            +
                        
         | 
| 205 | 
            +
                        # Mood
         | 
| 206 | 
            +
                        mood = analysis.get("mood", "Not specified")
         | 
| 207 | 
            +
                        formatted_text.append("MOOD")
         | 
| 208 | 
            +
                        formatted_text.append("====")
         | 
| 209 | 
            +
                        formatted_text.append(mood)
         | 
| 210 | 
            +
                        formatted_text.append("")
         | 
| 211 | 
            +
                        
         | 
| 212 | 
            +
                        # Sections analysis
         | 
| 213 | 
            +
                        sections = analysis.get("sections_analysis", [])
         | 
| 214 | 
            +
                        if sections:
         | 
| 215 | 
            +
                            formatted_text.append("SECTION-BY-SECTION ANALYSIS")
         | 
| 216 | 
            +
                            formatted_text.append("==========================")
         | 
| 217 | 
            +
                            
         | 
| 218 | 
            +
                            for i, section in enumerate(sections):
         | 
| 219 | 
            +
                                section_type = section.get("section_type", "Unknown")
         | 
| 220 | 
            +
                                section_number = section.get("section_number", i+1)
         | 
| 221 | 
            +
                                section_analysis = section.get("analysis", "No analysis available")
         | 
| 222 | 
            +
                                
         | 
| 223 | 
            +
                                formatted_text.append(f"{section_type.upper()} {section_number}")
         | 
| 224 | 
            +
                                formatted_text.append("-" * (len(section_type) + len(str(section_number)) + 1))
         | 
| 225 | 
            +
                                
         | 
| 226 | 
            +
                                # Format the section lines
         | 
| 227 | 
            +
                                lines = section.get("lines", [])
         | 
| 228 | 
            +
                                if lines:
         | 
| 229 | 
            +
                                    formatted_text.append("Lyrics:")
         | 
| 230 | 
            +
                                    for line in lines:
         | 
| 231 | 
            +
                                        formatted_text.append(f"> {line}")
         | 
| 232 | 
            +
                                    formatted_text.append("")
         | 
| 233 | 
            +
                                
         | 
| 234 | 
            +
                                formatted_text.append("Analysis:")
         | 
| 235 | 
            +
                                formatted_text.append(section_analysis)
         | 
| 236 | 
            +
                                formatted_text.append("")
         | 
| 237 | 
            +
                        
         | 
| 238 | 
            +
                        # We no longer have significant_lines in the new format
         | 
| 239 | 
            +
                        
         | 
| 240 | 
            +
                        # Conclusion
         | 
| 241 | 
            +
                        conclusion = analysis.get("conclusion", "No conclusion available")
         | 
| 242 | 
            +
                        formatted_text.append("CONCLUSION")
         | 
| 243 | 
            +
                        formatted_text.append("==========")
         | 
| 244 | 
            +
                        formatted_text.append(conclusion)
         | 
| 245 | 
            +
                        
         | 
| 246 | 
            +
                        return "\n".join(formatted_text)
         | 
    	
        tools/search_tools.py
    CHANGED
    
    | @@ -2,11 +2,83 @@ | |
| 2 | 
             
            Search tools for finding song lyrics and related information.
         | 
| 3 | 
             
            """
         | 
| 4 |  | 
| 5 | 
            -
            import  | 
| 6 | 
             
            import random
         | 
| 7 | 
            -
             | 
|  | |
|  | |
|  | |
| 8 | 
             
            from loguru import logger
         | 
| 9 | 
            -
            from smolagents import DuckDuckGoSearchTool
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 10 |  | 
| 11 |  | 
| 12 | 
             
            class ThrottledDuckDuckGoSearchTool(DuckDuckGoSearchTool):
         | 
|  | |
| 2 | 
             
            Search tools for finding song lyrics and related information.
         | 
| 3 | 
             
            """
         | 
| 4 |  | 
| 5 | 
            +
            import os
         | 
| 6 | 
             
            import random
         | 
| 7 | 
            +
            import time
         | 
| 8 | 
            +
            from typing import Any, Dict, List
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            import requests
         | 
| 11 | 
             
            from loguru import logger
         | 
| 12 | 
            +
            from smolagents import DuckDuckGoSearchTool, Tool
         | 
| 13 | 
            +
             | 
| 14 | 
            +
             | 
| 15 | 
            +
            class BraveSearchTool(Tool):
         | 
| 16 | 
            +
                """
         | 
| 17 | 
            +
                A tool for performing web searches using the Brave Search API.
         | 
| 18 | 
            +
                
         | 
| 19 | 
            +
                This tool requires a Brave Search API key to be set in the environment
         | 
| 20 | 
            +
                variable BRAVE_API_KEY or passed directly to the constructor.
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
                Documentation: https://api.search.brave.com/app/documentation
         | 
| 23 | 
            +
                """
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                def __init__(self, max_results: int = 10, **kwargs):
         | 
| 26 | 
            +
                    """
         | 
| 27 | 
            +
                    Initialize the Brave Search tool.
         | 
| 28 | 
            +
                    
         | 
| 29 | 
            +
                    Args:
         | 
| 30 | 
            +
                        max_results: Maximum number of results to return (default: 10)
         | 
| 31 | 
            +
                    """
         | 
| 32 | 
            +
                    super().__init__(**kwargs)
         | 
| 33 | 
            +
                    self.api_key = os.environ.get("BRAVE_API_KEY")
         | 
| 34 | 
            +
                    if not self.api_key:
         | 
| 35 | 
            +
                        logger.warning("No Brave API key found. Set BRAVE_API_KEY environment variable or pass api_key parameter.")
         | 
| 36 | 
            +
                    self.max_results = max_results
         | 
| 37 | 
            +
                    self.name = "brave_search"
         | 
| 38 | 
            +
                    self.description = "Search the web using Brave Search API"
         | 
| 39 | 
            +
                    self.inputs = {"query": {"type": "string", "description": "The search query string"}}
         | 
| 40 | 
            +
                    self.output_type = "string"
         | 
| 41 | 
            +
                    logger.info(f"Initialized BraveSearchTool with max_results={max_results}")
         | 
| 42 | 
            +
                
         | 
| 43 | 
            +
                def forward(self, query: str) -> List[Dict[str, Any]]:
         | 
| 44 | 
            +
                    """
         | 
| 45 | 
            +
                    Execute a search using the Brave Search API.
         | 
| 46 | 
            +
                    
         | 
| 47 | 
            +
                    Args:
         | 
| 48 | 
            +
                        query: The search query string
         | 
| 49 | 
            +
                        
         | 
| 50 | 
            +
                    Returns:
         | 
| 51 | 
            +
                        List of search results in the format:
         | 
| 52 | 
            +
                        [{"title": str, "href": str, "body": str}, ...]
         | 
| 53 | 
            +
                    """
         | 
| 54 | 
            +
                    if not self.api_key:
         | 
| 55 | 
            +
                        logger.error("Brave Search API key is not set")
         | 
| 56 | 
            +
                        return [{"title": "API Key Error", "href": "", "body": "Brave Search API key is not set"}]
         | 
| 57 | 
            +
                    
         | 
| 58 | 
            +
                    url = "https://api.search.brave.com/res/v1/web/search"
         | 
| 59 | 
            +
                    headers = {"Accept": "application/json", "X-Subscription-Token": self.api_key}
         | 
| 60 | 
            +
                    params = {"q": query, "count": self.max_results}
         | 
| 61 | 
            +
                    
         | 
| 62 | 
            +
                    try:
         | 
| 63 | 
            +
                        logger.info(f"Performing Brave search for query: '{query}'")
         | 
| 64 | 
            +
                        response = requests.get(url, headers=headers, params=params)
         | 
| 65 | 
            +
                        response.raise_for_status()
         | 
| 66 | 
            +
                        data = response.json()
         | 
| 67 | 
            +
                        
         | 
| 68 | 
            +
                        results = []
         | 
| 69 | 
            +
                        if "web" in data and "results" in data["web"]:
         | 
| 70 | 
            +
                            for result in data["web"]["results"]:
         | 
| 71 | 
            +
                                results.append({
         | 
| 72 | 
            +
                                    "title": result.get("title", ""),
         | 
| 73 | 
            +
                                    "href": result.get("url", ""),
         | 
| 74 | 
            +
                                    "body": result.get("description", "")
         | 
| 75 | 
            +
                                })
         | 
| 76 | 
            +
                        
         | 
| 77 | 
            +
                        logger.info(f"Found {len(results)} results for query: '{query}'")
         | 
| 78 | 
            +
                        return results
         | 
| 79 | 
            +
                    except Exception as e:
         | 
| 80 | 
            +
                        logger.error(f"Error in Brave search: {str(e)}")
         | 
| 81 | 
            +
                        return [{"title": "Search error", "href": "", "body": f"Error performing search: {str(e)}"}]
         | 
| 82 |  | 
| 83 |  | 
| 84 | 
             
            class ThrottledDuckDuckGoSearchTool(DuckDuckGoSearchTool):
         |