Spaces:
Runtime error
Runtime error
Change instructions
Browse files- .gitignore +1 -0
- agents/analysis_agent.py +3 -2
- agents/single_agent.py +12 -7
- app.py +9 -1
- config.py +9 -13
- prompts.yaml → prompts/prompts.yaml +0 -0
- prompts_hf.yaml → prompts/prompts_hf.yaml +0 -0
- tools/analysis_tools.py +85 -67
- tools/formatting_tools.py +230 -0
- tools/search_tools.py +3 -2
.gitignore
CHANGED
@@ -5,3 +5,4 @@ uv.lock
|
|
5 |
.gradio
|
6 |
__pycache__/
|
7 |
*.pyc
|
|
|
|
5 |
.gradio
|
6 |
__pycache__/
|
7 |
*.pyc
|
8 |
+
.vscode
|
agents/analysis_agent.py
CHANGED
@@ -10,7 +10,8 @@ from loguru import logger
|
|
10 |
|
11 |
from config import load_prompt_templates
|
12 |
from tools.search_tools import ThrottledDuckDuckGoSearchTool
|
13 |
-
from tools.analysis_tools import
|
|
|
14 |
|
15 |
|
16 |
def create_analysis_agent(model):
|
@@ -42,7 +43,7 @@ def create_analysis_agent(model):
|
|
42 |
|
43 |
agent = CodeAgent(
|
44 |
model=model,
|
45 |
-
tools=[throttled_search_tool, VisitWebpageTool(),
|
46 |
name="lyrics_analysis_agent",
|
47 |
description="Specialized agent for analyzing song lyrics and providing detailed commentary",
|
48 |
additional_authorized_imports=["numpy", "bs4", "json", "re"],
|
|
|
10 |
|
11 |
from config import load_prompt_templates
|
12 |
from tools.search_tools import ThrottledDuckDuckGoSearchTool
|
13 |
+
from tools.analysis_tools import AnalyzeLyricsTool
|
14 |
+
from tools.formatting_tools import FormatAnalysisResultsTool
|
15 |
|
16 |
|
17 |
def create_analysis_agent(model):
|
|
|
43 |
|
44 |
agent = CodeAgent(
|
45 |
model=model,
|
46 |
+
tools=[throttled_search_tool, VisitWebpageTool(), AnalyzeLyricsTool(), FormatAnalysisResultsTool()],
|
47 |
name="lyrics_analysis_agent",
|
48 |
description="Specialized agent for analyzing song lyrics and providing detailed commentary",
|
49 |
additional_authorized_imports=["numpy", "bs4", "json", "re"],
|
agents/single_agent.py
CHANGED
@@ -3,10 +3,12 @@ Web agent for finding and extracting song lyrics from online sources.
|
|
3 |
"""
|
4 |
|
5 |
from loguru import logger
|
6 |
-
from smolagents import CodeAgent, VisitWebpageTool, FinalAnswerTool
|
7 |
|
8 |
-
from
|
9 |
-
from
|
|
|
|
|
10 |
|
11 |
|
12 |
def create_single_agent(model):
|
@@ -19,21 +21,24 @@ def create_single_agent(model):
|
|
19 |
Returns:
|
20 |
A configured CodeAgent for web searches
|
21 |
"""
|
22 |
-
|
|
|
23 |
# Define agent parameters directly in the code instead of using config
|
24 |
# Example usage within the agent
|
25 |
agent = CodeAgent(
|
26 |
tools=[
|
27 |
FinalAnswerTool(),
|
28 |
-
|
29 |
VisitWebpageTool(),
|
30 |
-
|
|
|
31 |
],
|
32 |
model=model,
|
33 |
additional_authorized_imports=['numpy', 'bs4', 'rich'],
|
34 |
max_steps=25,
|
35 |
verbosity_level=2,
|
36 |
-
description="Specialized agent for finding and extracting song
|
|
|
37 |
)
|
38 |
|
39 |
logger.info("Web agent (lyrics search) created successfully")
|
|
|
3 |
"""
|
4 |
|
5 |
from loguru import logger
|
6 |
+
from smolagents import CodeAgent, VisitWebpageTool, FinalAnswerTool, DuckDuckGoSearchTool
|
7 |
|
8 |
+
from config import load_prompt_templates
|
9 |
+
from tools.search_tools import ThrottledDuckDuckGoSearchTool
|
10 |
+
from tools.analysis_tools import AnalyzeLyricsTool
|
11 |
+
from tools.formatting_tools import FormatAnalysisResultsTool
|
12 |
|
13 |
|
14 |
def create_single_agent(model):
|
|
|
21 |
Returns:
|
22 |
A configured CodeAgent for web searches
|
23 |
"""
|
24 |
+
prompt_templates = load_prompt_templates()
|
25 |
+
|
26 |
# Define agent parameters directly in the code instead of using config
|
27 |
# Example usage within the agent
|
28 |
agent = CodeAgent(
|
29 |
tools=[
|
30 |
FinalAnswerTool(),
|
31 |
+
DuckDuckGoSearchTool(),
|
32 |
VisitWebpageTool(),
|
33 |
+
AnalyzeLyricsTool(),
|
34 |
+
FormatAnalysisResultsTool()
|
35 |
],
|
36 |
model=model,
|
37 |
additional_authorized_imports=['numpy', 'bs4', 'rich'],
|
38 |
max_steps=25,
|
39 |
verbosity_level=2,
|
40 |
+
description="Specialized agent for finding and extracting song lyrics specified by the user. Does not use genius.com and AZlyrics.com for scraping since they have protection mechanisms. Performs lyrics search and analysis using given tools. Provides detailed commentary with beautiful human-readable format using rich library formatting. Can format analysis results in either rich colorful output or simple text format.",
|
41 |
+
prompt_templates=prompt_templates
|
42 |
)
|
43 |
|
44 |
logger.info("Web agent (lyrics search) created successfully")
|
app.py
CHANGED
@@ -29,7 +29,15 @@ def main():
|
|
29 |
# Initialize the LLM model based on configuration
|
30 |
model_id = get_model_id()
|
31 |
logger.info(f"Initializing with model: {model_id}")
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
# Create the manager agent which will create and manage the other agents
|
35 |
single_agent = create_single_agent(model)
|
|
|
29 |
# Initialize the LLM model based on configuration
|
30 |
model_id = get_model_id()
|
31 |
logger.info(f"Initializing with model: {model_id}")
|
32 |
+
|
33 |
+
# If using Ollama, we need to specify the API base URL
|
34 |
+
if "ollama" in model_id:
|
35 |
+
from config import get_ollama_api_base
|
36 |
+
api_base = get_ollama_api_base()
|
37 |
+
logger.info(f"Using Ollama API base: {api_base}")
|
38 |
+
model = LiteLLMModel(model_id=model_id, api_base=api_base)
|
39 |
+
else:
|
40 |
+
model = LiteLLMModel(model_id=model_id)
|
41 |
|
42 |
# Create the manager agent which will create and manage the other agents
|
43 |
single_agent = create_single_agent(model)
|
config.py
CHANGED
@@ -24,28 +24,24 @@ def load_api_keys():
|
|
24 |
"""Load API keys from environment variables."""
|
25 |
# Gemini API is the default
|
26 |
os.environ["GEMINI_API_KEY"] = os.getenv("GEMINI_API_KEY")
|
27 |
-
|
28 |
-
# Anthropic API for local testing
|
29 |
-
if use_anthropic():
|
30 |
-
os.environ["ANTHROPIC_API_KEY"] = os.getenv("ANTHROPIC_API_KEY")
|
31 |
-
|
32 |
-
# Default model configuration
|
33 |
-
def use_anthropic():
|
34 |
-
"""Check if we should use Anthropic instead of Gemini."""
|
35 |
-
return os.getenv("USE_ANTHROPIC", "false").lower() == "true"
|
36 |
|
37 |
-
def get_model_id():
|
38 |
"""Get the appropriate model ID based on configuration."""
|
39 |
-
if
|
40 |
-
return "
|
41 |
else:
|
42 |
return "gemini/gemini-2.0-flash"
|
43 |
|
|
|
|
|
|
|
|
|
44 |
# Load prompts from YAML
|
45 |
def load_prompt_templates():
|
46 |
"""Load prompt templates from YAML file."""
|
47 |
try:
|
48 |
-
with open("prompts_hf.yaml", 'r') as stream:
|
49 |
return yaml.safe_load(stream)
|
50 |
except (FileNotFoundError, yaml.YAMLError) as e:
|
51 |
logger.error(f"Error loading prompts.yaml: {e}")
|
|
|
24 |
"""Load API keys from environment variables."""
|
25 |
# Gemini API is the default
|
26 |
os.environ["GEMINI_API_KEY"] = os.getenv("GEMINI_API_KEY")
|
27 |
+
os.environ["ANTHROPIC_API_KEY"] = os.getenv("ANTHROPIC_API_KEY")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
+
def get_model_id(is_test=True):
|
30 |
"""Get the appropriate model ID based on configuration."""
|
31 |
+
if is_test:
|
32 |
+
return "ollama/gemma3:4b" # Using local Ollama with Gemma 3:4B instead of Claude
|
33 |
else:
|
34 |
return "gemini/gemini-2.0-flash"
|
35 |
|
36 |
+
def get_ollama_api_base():
|
37 |
+
"""Get the API base URL for Ollama."""
|
38 |
+
return "http://localhost:11434"
|
39 |
+
|
40 |
# Load prompts from YAML
|
41 |
def load_prompt_templates():
|
42 |
"""Load prompt templates from YAML file."""
|
43 |
try:
|
44 |
+
with open("prompts/prompts_hf.yaml", 'r') as stream:
|
45 |
return yaml.safe_load(stream)
|
46 |
except (FileNotFoundError, yaml.YAMLError) as e:
|
47 |
logger.error(f"Error loading prompts.yaml: {e}")
|
prompts.yaml → prompts/prompts.yaml
RENAMED
File without changes
|
prompts_hf.yaml → prompts/prompts_hf.yaml
RENAMED
File without changes
|
tools/analysis_tools.py
CHANGED
@@ -4,78 +4,96 @@ Analysis tools for understanding and interpreting song lyrics.
|
|
4 |
|
5 |
import os
|
6 |
from loguru import logger
|
7 |
-
from smolagents import
|
8 |
|
9 |
from api_utils import make_api_call_with_retry
|
|
|
10 |
|
11 |
-
@tool
|
12 |
-
def analyze_lyrics_tool(song_title: str, artist: str, lyrics: str) -> str:
|
13 |
-
"""
|
14 |
-
Performs a deep analysis of the musical track, given its metadata.
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
artist: The name of the artist.
|
19 |
-
lyrics: The lyrics of the song.
|
20 |
-
|
21 |
-
Returns:
|
22 |
-
A summary of the song's meaning in English.
|
23 |
-
"""
|
24 |
-
|
25 |
-
prompt = f"""You are an expert in songs and their meanings.
|
26 |
-
|
27 |
-
Analyze the song "{song_title}" by {artist}. Return a structured JSON with the following information:
|
28 |
-
|
29 |
-
1. Overall analysis of the song including themes, mood, meaning, and context.
|
30 |
-
2. Section-by-section analysis of each part of the song (verses, chorus, bridge, etc.).
|
31 |
-
3. Line-specific insights for particularly significant lines.
|
32 |
-
4. A conclusion about what makes this song unique or significant.
|
33 |
-
|
34 |
-
Format your response as a valid JSON object with the following structure:
|
35 |
-
```
|
36 |
-
{{
|
37 |
-
"summary": "Overall analysis of the song themes, meaning and mood",
|
38 |
-
"main_themes": ["theme1", "theme2", ...],
|
39 |
-
"mood": "The overall mood/emotion of the song",
|
40 |
-
"sections_analysis": [
|
41 |
-
{{
|
42 |
-
"section_type": "verse/chorus/bridge/etc.",
|
43 |
-
"section_number": 1,
|
44 |
-
"lines": ["line1", "line2", ...],
|
45 |
-
"analysis": "Analysis of this section"
|
46 |
-
}},
|
47 |
-
...
|
48 |
-
],
|
49 |
-
"significant_lines": [
|
50 |
-
{{
|
51 |
-
"line": "A specific important line",
|
52 |
-
"significance": "Why this line is important"
|
53 |
-
}},
|
54 |
-
...
|
55 |
-
],
|
56 |
-
"conclusion": "What makes this song significant or unique"
|
57 |
-
}}
|
58 |
-
```
|
59 |
|
60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
model_to_use = "gemini/gemini-2.0-flash"
|
72 |
-
logger.info("Using Gemini model: {} for lyrics analysis", model_to_use)
|
73 |
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
import os
|
6 |
from loguru import logger
|
7 |
+
from smolagents import Tool
|
8 |
|
9 |
from api_utils import make_api_call_with_retry
|
10 |
+
from tools.formatting_tools import FormatAnalysisResultsTool
|
11 |
|
|
|
|
|
|
|
|
|
12 |
|
13 |
+
class AnalyzeLyricsTool(Tool):
|
14 |
+
"""Tool for analyzing song lyrics"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
+
name = "analyze_lyrics"
|
17 |
+
description = "Analyzes song lyrics and returns a detailed analysis"
|
18 |
+
inputs = {
|
19 |
+
"song_title": {"type": "string", "description": "Title of the song"},
|
20 |
+
"artist": {"type": "string", "description": "Name of the artist"},
|
21 |
+
"lyrics": {"type": "string", "description": "Lyrics of the song"},
|
22 |
+
"format_output": {"type": "boolean", "description": "Whether to format the output with rich styling", "nullable": True}
|
23 |
+
}
|
24 |
+
output_type = "string"
|
25 |
|
26 |
+
def forward(self, song_title: str, artist: str, lyrics: str, format_output: bool = True) -> str:
|
27 |
+
"""
|
28 |
+
Performs a deep analysis of the musical track, given its metadata.
|
29 |
|
30 |
+
Args:
|
31 |
+
song_title: Title of the song or music track.
|
32 |
+
artist: The name of the artist.
|
33 |
+
lyrics: The lyrics of the song.
|
34 |
+
format_output: Whether to format the output using rich formatting (default: True).
|
|
|
|
|
35 |
|
36 |
+
Returns:
|
37 |
+
A formatted analysis of the song's meaning in English or raw JSON if format_output is False.
|
38 |
+
"""
|
39 |
+
|
40 |
+
prompt = f"""You are an expert in songs and their meanings.
|
41 |
+
|
42 |
+
Analyze the song "{song_title}" by {artist}. Return a structured JSON with the following information:
|
43 |
+
|
44 |
+
1. Overall analysis of the song including themes, mood, meaning, and context.
|
45 |
+
2. Section-by-section analysis of each part of the song (verses, chorus, bridge, etc.).
|
46 |
+
3. Line-specific insights for particularly significant lines.
|
47 |
+
4. A conclusion about what makes this song unique or significant.
|
48 |
+
|
49 |
+
Format your response as a valid JSON object with the following structure:
|
50 |
+
```
|
51 |
+
{{
|
52 |
+
"summary": "Overall analysis of the song themes, meaning and mood",
|
53 |
+
"main_themes": ["theme1", "theme2", ...],
|
54 |
+
"mood": "The overall mood/emotion of the song",
|
55 |
+
"sections_analysis": [
|
56 |
+
{{
|
57 |
+
"section_type": "verse/chorus/bridge/etc.",
|
58 |
+
"section_number": 1,
|
59 |
+
"lines": ["line1", "line2", ...],
|
60 |
+
"analysis": "Analysis of this section"
|
61 |
+
}},
|
62 |
+
...
|
63 |
+
],
|
64 |
+
"significant_lines": [
|
65 |
+
{{
|
66 |
+
"line": "A specific important line",
|
67 |
+
"significance": "Why this line is important"
|
68 |
+
}},
|
69 |
+
...
|
70 |
+
],
|
71 |
+
"conclusion": "What makes this song significant or unique"
|
72 |
+
}}
|
73 |
+
```
|
74 |
+
|
75 |
+
Make sure your response is a properly formatted JSON. Only provide the JSON object, no other text.
|
76 |
+
|
77 |
+
Here are the lyrics to analyze:
|
78 |
+
{lyrics}
|
79 |
+
"""
|
80 |
+
|
81 |
+
# Determine which model to use based on configuration
|
82 |
+
if os.getenv("USE_ANTHROPIC", "false").lower() == "true":
|
83 |
+
model_to_use = "claude-3-haiku-20240307"
|
84 |
+
logger.info("Using Anthropic model: {} for lyrics analysis", model_to_use)
|
85 |
+
else:
|
86 |
+
model_to_use = "gemini/gemini-2.0-flash"
|
87 |
+
logger.info("Using Gemini model: {} for lyrics analysis", model_to_use)
|
88 |
+
|
89 |
+
# Use the function with retry mechanism
|
90 |
+
logger.info("Analyzing lyrics for song: '{}' by '{}'", song_title, artist)
|
91 |
+
# Get raw analysis as JSON
|
92 |
+
response = make_api_call_with_retry(model_to_use, prompt)
|
93 |
+
|
94 |
+
# Format the response if requested
|
95 |
+
if format_output:
|
96 |
+
formatter = FormatAnalysisResultsTool()
|
97 |
+
return formatter.forward(response)
|
98 |
+
else:
|
99 |
+
return response
|
tools/formatting_tools.py
ADDED
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
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
|
9 |
+
from rich.panel import Panel
|
10 |
+
from rich.text import Text
|
11 |
+
from rich.table import Table
|
12 |
+
from rich.markdown import Markdown
|
13 |
+
from rich.theme import Theme
|
14 |
+
from rich.box import ROUNDED
|
15 |
+
from rich.console import Group
|
16 |
+
|
17 |
+
|
18 |
+
class FormatAnalysisResultsTool(Tool):
|
19 |
+
"""Tool for formatting analysis results"""
|
20 |
+
|
21 |
+
name = "format_analysis_results"
|
22 |
+
description = "Formats the analysis results for better readability"
|
23 |
+
inputs = {
|
24 |
+
"analysis_json": {"type": "any", "description": "JSON string or dictionary containing the analysis results"},
|
25 |
+
"pretty": {"type": "boolean", "description": "Whether to use rich formatting (True) or simple text formatting (False)", "nullable": True}
|
26 |
+
}
|
27 |
+
output_type = "string"
|
28 |
+
|
29 |
+
def forward(self, analysis_json, pretty: bool = True) -> str:
|
30 |
+
"""
|
31 |
+
Formats the analysis results for better readability.
|
32 |
+
|
33 |
+
Args:
|
34 |
+
analysis_json: JSON string or dictionary containing the analysis results
|
35 |
+
pretty: Whether to use rich formatting (True) or simple text formatting (False)
|
36 |
+
|
37 |
+
Returns:
|
38 |
+
A formatted string representation of the analysis
|
39 |
+
"""
|
40 |
+
try:
|
41 |
+
# Parse the JSON string into a Python dictionary if it's a string
|
42 |
+
if isinstance(analysis_json, str):
|
43 |
+
analysis = json.loads(analysis_json)
|
44 |
+
else:
|
45 |
+
analysis = analysis_json
|
46 |
+
|
47 |
+
if pretty:
|
48 |
+
# Rich formatting with the rich library
|
49 |
+
try:
|
50 |
+
# Create a string buffer to capture the rich output
|
51 |
+
console = Console(record=True, width=100)
|
52 |
+
|
53 |
+
# Create a custom theme for consistent styling
|
54 |
+
custom_theme = Theme({
|
55 |
+
"heading": "bold cyan underline",
|
56 |
+
"subheading": "bold blue",
|
57 |
+
"highlight": "bold yellow",
|
58 |
+
"positive": "green",
|
59 |
+
"negative": "red",
|
60 |
+
"neutral": "magenta",
|
61 |
+
"section": "cyan",
|
62 |
+
"quote": "italic yellow",
|
63 |
+
"metadata": "dim white"
|
64 |
+
})
|
65 |
+
|
66 |
+
# Apply the theme to our console
|
67 |
+
console.theme = custom_theme
|
68 |
+
|
69 |
+
# Format the summary section
|
70 |
+
summary = analysis.get("summary", "No summary available")
|
71 |
+
console.print(Panel(Text(summary, justify="center"), title="[heading]Song Analysis[/]", subtitle="[metadata]Summary[/]"))
|
72 |
+
|
73 |
+
# Create a table for the main themes and mood
|
74 |
+
info_table = Table(show_header=False, box=ROUNDED, expand=True)
|
75 |
+
info_table.add_column("Key", style="subheading")
|
76 |
+
info_table.add_column("Value")
|
77 |
+
|
78 |
+
# Add the mood
|
79 |
+
mood = analysis.get("mood", "Not specified")
|
80 |
+
info_table.add_row("Mood", mood)
|
81 |
+
|
82 |
+
# Add the themes as a comma-separated list
|
83 |
+
themes = analysis.get("main_themes", [])
|
84 |
+
if themes:
|
85 |
+
themes_text = ", ".join([f"[highlight]{theme}[/]" for theme in themes])
|
86 |
+
info_table.add_row("Main Themes", themes_text)
|
87 |
+
|
88 |
+
console.print(info_table)
|
89 |
+
|
90 |
+
# Section-by-section analysis
|
91 |
+
sections = analysis.get("sections_analysis", [])
|
92 |
+
if sections:
|
93 |
+
console.print("\n[heading]Section-by-Section Analysis[/]")
|
94 |
+
|
95 |
+
for section in sections:
|
96 |
+
section_type = section.get("section_type", "Unknown")
|
97 |
+
section_number = section.get("section_number", "")
|
98 |
+
section_title = f"{section_type.title()} {section_number}" if section_number else section_type.title()
|
99 |
+
|
100 |
+
section_analysis = section.get("analysis", "No analysis available")
|
101 |
+
lines = section.get("lines", [])
|
102 |
+
|
103 |
+
# Create a group with the lyrics and analysis
|
104 |
+
section_content = []
|
105 |
+
|
106 |
+
if lines:
|
107 |
+
lyrics_text = "\n".join([f"> [quote]{line}[/]" for line in lines])
|
108 |
+
section_content.append(Text("Lyrics:", style="subheading"))
|
109 |
+
section_content.append(Markdown(lyrics_text))
|
110 |
+
|
111 |
+
section_content.append(Text("Analysis:", style="subheading"))
|
112 |
+
section_content.append(Text(section_analysis))
|
113 |
+
|
114 |
+
# Add the section panel
|
115 |
+
console.print(Panel(
|
116 |
+
Group(*section_content),
|
117 |
+
title=f"[section]{section_title}[/]",
|
118 |
+
border_style="section"
|
119 |
+
))
|
120 |
+
|
121 |
+
# Significant lines
|
122 |
+
sig_lines = analysis.get("significant_lines", [])
|
123 |
+
if sig_lines:
|
124 |
+
console.print("\n[heading]Significant Lines[/]")
|
125 |
+
|
126 |
+
for i, line_data in enumerate(sig_lines):
|
127 |
+
line = line_data.get("line", "")
|
128 |
+
significance = line_data.get("significance", "")
|
129 |
+
|
130 |
+
console.print(Panel(
|
131 |
+
f"[quote]\"{line}\"[/]\n\n[subheading]Significance:[/] {significance}",
|
132 |
+
title=f"[highlight]Key Line #{i+1}[/]",
|
133 |
+
border_style="highlight"
|
134 |
+
))
|
135 |
+
|
136 |
+
# Conclusion
|
137 |
+
conclusion = analysis.get("conclusion", "No conclusion available")
|
138 |
+
console.print("\n[heading]Conclusion[/]")
|
139 |
+
console.print(Panel(conclusion, border_style="neutral"))
|
140 |
+
|
141 |
+
# Export the rich text as a string
|
142 |
+
return console.export_text()
|
143 |
+
|
144 |
+
except Exception as e:
|
145 |
+
logger.error("Error in rich formatting: {}", str(e))
|
146 |
+
# Fall back to simple formatting if rich formatting fails
|
147 |
+
logger.info("Falling back to simple text formatting")
|
148 |
+
pretty = False
|
149 |
+
|
150 |
+
if not pretty:
|
151 |
+
# Simple text formatting
|
152 |
+
formatted_text = []
|
153 |
+
|
154 |
+
# Summary
|
155 |
+
formatted_text.append("SONG ANALYSIS SUMMARY")
|
156 |
+
formatted_text.append("====================")
|
157 |
+
formatted_text.append(analysis.get("summary", "No summary available"))
|
158 |
+
formatted_text.append("")
|
159 |
+
|
160 |
+
# Main themes
|
161 |
+
themes = analysis.get("main_themes", [])
|
162 |
+
if themes:
|
163 |
+
formatted_text.append("MAIN THEMES")
|
164 |
+
formatted_text.append("===========")
|
165 |
+
for theme in themes:
|
166 |
+
formatted_text.append(f"• {theme}")
|
167 |
+
formatted_text.append("")
|
168 |
+
|
169 |
+
# Mood
|
170 |
+
mood = analysis.get("mood", "Not specified")
|
171 |
+
formatted_text.append("MOOD")
|
172 |
+
formatted_text.append("====")
|
173 |
+
formatted_text.append(mood)
|
174 |
+
formatted_text.append("")
|
175 |
+
|
176 |
+
# Sections analysis
|
177 |
+
sections = analysis.get("sections_analysis", [])
|
178 |
+
if sections:
|
179 |
+
formatted_text.append("SECTION-BY-SECTION ANALYSIS")
|
180 |
+
formatted_text.append("==========================")
|
181 |
+
|
182 |
+
for i, section in enumerate(sections):
|
183 |
+
section_type = section.get("section_type", "Unknown")
|
184 |
+
section_number = section.get("section_number", i+1)
|
185 |
+
section_analysis = section.get("analysis", "No analysis available")
|
186 |
+
|
187 |
+
formatted_text.append(f"{section_type.upper()} {section_number}")
|
188 |
+
formatted_text.append("-" * (len(section_type) + len(str(section_number)) + 1))
|
189 |
+
|
190 |
+
# Format the section lines
|
191 |
+
lines = section.get("lines", [])
|
192 |
+
if lines:
|
193 |
+
formatted_text.append("Lyrics:")
|
194 |
+
for line in lines:
|
195 |
+
formatted_text.append(f"> {line}")
|
196 |
+
formatted_text.append("")
|
197 |
+
|
198 |
+
formatted_text.append("Analysis:")
|
199 |
+
formatted_text.append(section_analysis)
|
200 |
+
formatted_text.append("")
|
201 |
+
|
202 |
+
# Significant lines
|
203 |
+
sig_lines = analysis.get("significant_lines", [])
|
204 |
+
if sig_lines:
|
205 |
+
formatted_text.append("SIGNIFICANT LINES")
|
206 |
+
formatted_text.append("=================")
|
207 |
+
|
208 |
+
for i, line_data in enumerate(sig_lines):
|
209 |
+
line = line_data.get("line", "")
|
210 |
+
significance = line_data.get("significance", "")
|
211 |
+
|
212 |
+
formatted_text.append(f"Key Line #{i+1}:")
|
213 |
+
formatted_text.append(f'"{line}"')
|
214 |
+
formatted_text.append(f"Significance: {significance}")
|
215 |
+
formatted_text.append("")
|
216 |
+
|
217 |
+
# Conclusion
|
218 |
+
conclusion = analysis.get("conclusion", "No conclusion available")
|
219 |
+
formatted_text.append("CONCLUSION")
|
220 |
+
formatted_text.append("==========")
|
221 |
+
formatted_text.append(conclusion)
|
222 |
+
|
223 |
+
return "\n".join(formatted_text)
|
224 |
+
|
225 |
+
except json.JSONDecodeError:
|
226 |
+
logger.error("Failed to parse analysis JSON: {}", analysis_json[:100] + "..." if len(analysis_json) > 100 else analysis_json)
|
227 |
+
return f"Error: Could not parse the analysis result as JSON. Raw output:\n{analysis_json}"
|
228 |
+
except Exception as e:
|
229 |
+
logger.error("Error formatting analysis: {}", str(e))
|
230 |
+
return f"Error formatting analysis: {str(e)}\nRaw output:\n{analysis_json}"
|
tools/search_tools.py
CHANGED
@@ -8,6 +8,7 @@ from typing import Dict, List, Any
|
|
8 |
from loguru import logger
|
9 |
from smolagents import DuckDuckGoSearchTool
|
10 |
|
|
|
11 |
class ThrottledDuckDuckGoSearchTool(DuckDuckGoSearchTool):
|
12 |
"""
|
13 |
A wrapper around DuckDuckGoSearchTool that adds a delay between requests
|
@@ -17,12 +18,12 @@ class ThrottledDuckDuckGoSearchTool(DuckDuckGoSearchTool):
|
|
17 |
Each search request will be followed by a random delay within the specified range.
|
18 |
"""
|
19 |
|
20 |
-
def __init__(self, min_delay: float =
|
21 |
"""
|
22 |
Initialize the throttled search tool with delay parameters.
|
23 |
|
24 |
Args:
|
25 |
-
min_delay: Minimum delay in seconds between requests (default:
|
26 |
max_delay: Maximum delay in seconds between requests (default: 5.0)
|
27 |
**kwargs: Additional arguments to pass to DuckDuckGoSearchTool
|
28 |
"""
|
|
|
8 |
from loguru import logger
|
9 |
from smolagents import DuckDuckGoSearchTool
|
10 |
|
11 |
+
|
12 |
class ThrottledDuckDuckGoSearchTool(DuckDuckGoSearchTool):
|
13 |
"""
|
14 |
A wrapper around DuckDuckGoSearchTool that adds a delay between requests
|
|
|
18 |
Each search request will be followed by a random delay within the specified range.
|
19 |
"""
|
20 |
|
21 |
+
def __init__(self, min_delay: float = 7.0, max_delay: float = 15.0, **kwargs):
|
22 |
"""
|
23 |
Initialize the throttled search tool with delay parameters.
|
24 |
|
25 |
Args:
|
26 |
+
min_delay: Minimum delay in seconds between requests (default: 5.0)
|
27 |
max_delay: Maximum delay in seconds between requests (default: 5.0)
|
28 |
**kwargs: Additional arguments to pass to DuckDuckGoSearchTool
|
29 |
"""
|