tonko22 commited on
Commit
0e9bb01
·
1 Parent(s): 45b377e

Change instructions

Browse files
.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 analyze_lyrics_tool
 
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(), analyze_lyrics_tool],
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 tools.analysis_tools import analyze_lyrics_tool
9
- from tool.search_tools import ThrottledDuckDuckGoSearchTool
 
 
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
- ThrottledDuckDuckGoSearchTool(),
29
  VisitWebpageTool(),
30
- analyze_lyrics_tool
 
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, specified by the user. Does not use genius.com and AZlyrics.com for scraping since they have protection. Performs lyrics search and analysys using given tools. Provides detailed commentary using FinalAnswerTool meaning with beautiful human-readable format.",
 
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
- model = LiteLLMModel(model_id=model_id)
 
 
 
 
 
 
 
 
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 use_anthropic():
40
- return "claude-3-haiku-20240307"
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 tool
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
- Args:
17
- song_title: Title of the song or music track.
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
- Make sure your response is a properly formatted JSON. Only provide the JSON object, no other text.
 
 
 
 
 
 
 
 
61
 
62
- Here are the lyrics to analyze:
63
- {lyrics}
64
- """
65
 
66
- # Determine which model to use based on configuration
67
- if os.getenv("USE_ANTHROPIC", "false").lower() == "true":
68
- model_to_use = "claude-3-haiku-20240307"
69
- logger.info("Using Anthropic model: {} for lyrics analysis", model_to_use)
70
- else:
71
- model_to_use = "gemini/gemini-2.0-flash"
72
- logger.info("Using Gemini model: {} for lyrics analysis", model_to_use)
73
 
74
- # Use the function with retry mechanism
75
- logger.info("Analyzing lyrics for song: '{}' by '{}'", song_title, artist)
76
- # Get raw analysis as JSON
77
- response = make_api_call_with_retry(model_to_use, prompt)
78
-
79
- # Note: we're returning the raw response which should be a JSON string
80
- # The agent will be responsible for parsing and formatting this data
81
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = 5.0, max_delay: float = 12.0, **kwargs):
21
  """
22
  Initialize the throttled search tool with delay parameters.
23
 
24
  Args:
25
- min_delay: Minimum delay in seconds between requests (default: 2.0)
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
  """