tonko22 commited on
Commit
7539685
·
1 Parent(s): a7b6f49

Update rich tool: bugifx,

Browse files

- update prompts
- update providers

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
- FinalAnswerTool(),
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(use_local=True, provider="gemini"):
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
- else:
47
- # Default fallback
48
- return "ollama/gemma3:4b" if use_local else "gemini/gemini-2.0-flash"
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
- # If using Ollama, we need to specify the API base URL
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 = "John Frusciante - Crowded"
38
 
39
  agent = create_single_agent(model)
40
-
 
 
 
41
  # Agent execution
42
- agent.run(f"""
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
- logger.info("Using Gemini model: {} for lyrics analysis", model_to_use)
 
72
 
73
  # Use the function with retry mechanism
74
  logger.info("Analyzing lyrics for song: '{}' by '{}'", song_title, artist)
75
- response = make_api_call_with_retry(model_to_use, prompt)
76
- return response
 
 
 
 
 
 
 
 
 
 
 
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 string or dictionary containing the analysis results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Expected JSON structure from analysis_tools.py:
40
- # {
41
- # "summary": "Overall analysis of the song vibes, meaning and mood",
42
- # "main_themes": ["theme1", "theme2", ...],
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
- if not pretty:
163
- # Simple text formatting
164
- formatted_text = []
 
 
 
 
 
165
 
166
- # Summary
167
- formatted_text.append("SONG ANALYSIS SUMMARY")
168
- formatted_text.append("====================")
169
- formatted_text.append(analysis.get("summary", "No summary available"))
170
- formatted_text.append("")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
- # Main themes
173
  themes = analysis.get("main_themes", [])
 
174
  if themes:
175
- formatted_text.append("MAIN THEMES")
176
- formatted_text.append("===========")
177
- for theme in themes:
178
- formatted_text.append(f"• {theme}")
179
- formatted_text.append("")
180
 
181
- # Mood
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
- # Sections analysis
189
  sections = analysis.get("sections_analysis", [])
 
190
  if sections:
191
- formatted_text.append("SECTION-BY-SECTION ANALYSIS")
192
- formatted_text.append("==========================")
193
 
194
- for i, section in enumerate(sections):
195
  section_type = section.get("section_type", "Unknown")
196
- section_number = section.get("section_number", i+1)
 
 
 
197
  section_analysis = section.get("analysis", "No analysis available")
 
 
198
 
199
- formatted_text.append(f"{section_type.upper()} {section_number}")
200
- formatted_text.append("-" * (len(section_type) + len(str(section_number)) + 1))
201
 
202
- # Format the section lines
203
- lines = section.get("lines", [])
204
  if lines:
205
- formatted_text.append("Lyrics:")
 
 
 
206
  for line in lines:
207
- formatted_text.append(f"> {line}")
208
- formatted_text.append("")
 
 
 
 
 
 
209
 
210
- formatted_text.append("Analysis:")
211
- formatted_text.append(section_analysis)
212
- formatted_text.append("")
 
 
 
 
 
 
213
 
214
  # We no longer have significant_lines in the new format
215
 
216
  # Conclusion
217
  conclusion = analysis.get("conclusion", "No conclusion available")
218
- formatted_text.append("CONCLUSION")
219
- formatted_text.append("==========")
220
- formatted_text.append(conclusion)
221
 
222
- return "\n".join(formatted_text)
 
 
 
 
 
 
 
 
 
 
223
 
224
- except json.JSONDecodeError:
225
- logger.error("Failed to parse analysis JSON: {}", analysis_json[:100] + "..." if len(analysis_json) > 100 else analysis_json)
226
- return f"Error: Could not parse the analysis result as JSON. Raw output:\n{analysis_json}"
227
- except Exception as e:
228
- logger.error("Error formatting analysis: {}", str(e))
229
- return f"Error formatting analysis: {str(e)}\nRaw output:\n{analysis_json}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 time
6
  import random
7
- from typing import Dict, List, Any
 
 
 
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):