TaoZewen commited on
Commit
54c61c1
·
1 Parent(s): 81917a3
Files changed (4) hide show
  1. app.py +18 -10
  2. gemini_agent.py +662 -0
  3. main_agent.py +492 -0
  4. requirements.txt +17 -1
app.py CHANGED
@@ -1,25 +1,33 @@
1
  import os
2
  import gradio as gr
3
  import requests
4
- import inspect
5
  import pandas as pd
 
 
6
 
7
- # (Keep Constants as is)
8
- # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
11
- # --- Basic Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
  class BasicAgent:
14
  def __init__(self):
15
- print("BasicAgent initialized.")
 
 
 
 
 
 
 
 
 
 
16
  def __call__(self, question: str) -> str:
17
  print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
21
 
22
- def run_and_submit_all( profile: gr.OAuthProfile | None):
23
  """
24
  Fetches all questions, runs the BasicAgent on them, submits all answers,
25
  and displays the results.
 
1
  import os
2
  import gradio as gr
3
  import requests
 
4
  import pandas as pd
5
+ from dotenv import load_dotenv
6
+ from Agents_Course_Final_Assignment.gemini_agent import GeminiAgent
7
 
8
+ # Constants
 
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
 
 
11
  class BasicAgent:
12
  def __init__(self):
13
+ print("Initializing the BasicAgent")
14
+
15
+ # Get Gemini API key
16
+ api_key = os.getenv('GOOGLE_API_KEY')
17
+ if not api_key:
18
+ raise ValueError("GOOGLE_API_KEY environment variable not set.")
19
+
20
+ # Initialize GeminiAgent
21
+ self.agent = GeminiAgent(api_key=api_key)
22
+ print("GeminiAgent initialized successfully")
23
+
24
  def __call__(self, question: str) -> str:
25
  print(f"Agent received question (first 50 chars): {question[:50]}...")
26
+ final_answer = self.agent.run(question)
27
+ print(f"Agent returning fixed answer: {final_answer}")
28
+ return final_answer
29
 
30
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
31
  """
32
  Fetches all questions, runs the BasicAgent on them, submits all answers,
33
  and displays the results.
gemini_agent.py ADDED
@@ -0,0 +1,662 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import time
4
+ import re
5
+ import json
6
+ from typing import List, Optional, Dict, Any
7
+ from urllib.parse import urlparse
8
+ import requests
9
+ import yt_dlp
10
+ from bs4 import BeautifulSoup
11
+ from difflib import SequenceMatcher
12
+
13
+ from langchain_core.messages import HumanMessage, SystemMessage
14
+ from langchain_google_genai import ChatGoogleGenerativeAI
15
+ from langchain_community.utilities import DuckDuckGoSearchAPIWrapper, WikipediaAPIWrapper
16
+ from langchain.agents import Tool, AgentExecutor, ConversationalAgent, initialize_agent, AgentType
17
+ from langchain.memory import ConversationBufferMemory
18
+ from langchain.prompts import MessagesPlaceholder
19
+ from langchain.tools import BaseTool, Tool, tool
20
+ from google.generativeai.types import HarmCategory, HarmBlockThreshold
21
+ from PIL import Image
22
+ import google.generativeai as genai
23
+ from pydantic import Field
24
+
25
+ from smolagents import WikipediaSearchTool
26
+
27
+ class SmolagentToolWrapper(BaseTool):
28
+ """Wrapper for smolagents tools to make them compatible with LangChain."""
29
+
30
+ wrapped_tool: object = Field(description="The wrapped smolagents tool")
31
+
32
+ def __init__(self, tool):
33
+ """Initialize the wrapper with a smolagents tool."""
34
+ super().__init__(
35
+ name=tool.name,
36
+ description=tool.description,
37
+ return_direct=False,
38
+ wrapped_tool=tool
39
+ )
40
+
41
+ def _run(self, query: str) -> str:
42
+ """Use the wrapped tool to execute the query."""
43
+ try:
44
+ # For WikipediaSearchTool
45
+ if hasattr(self.wrapped_tool, 'search'):
46
+ return self.wrapped_tool.search(query)
47
+ # For DuckDuckGoSearchTool and others
48
+ return self.wrapped_tool(query)
49
+ except Exception as e:
50
+ return f"Error using tool: {str(e)}"
51
+
52
+ def _arun(self, query: str) -> str:
53
+ """Async version - just calls sync version since smolagents tools don't support async."""
54
+ return self._run(query)
55
+
56
+ class WebSearchTool:
57
+ def __init__(self):
58
+ self.last_request_time = 0
59
+ self.min_request_interval = 2.0 # Minimum time between requests in seconds
60
+ self.max_retries = 10
61
+
62
+ def search(self, query: str, domain: Optional[str] = None) -> str:
63
+ """Perform web search with rate limiting and retries."""
64
+ for attempt in range(self.max_retries):
65
+ # Implement rate limiting
66
+ current_time = time.time()
67
+ time_since_last = current_time - self.last_request_time
68
+ if time_since_last < self.min_request_interval:
69
+ time.sleep(self.min_request_interval - time_since_last)
70
+
71
+ try:
72
+ # Make the search request
73
+ results = self._do_search(query, domain)
74
+ self.last_request_time = time.time()
75
+ return results
76
+ except Exception as e:
77
+ if "202 Ratelimit" in str(e):
78
+ if attempt < self.max_retries - 1:
79
+ # Exponential backoff
80
+ wait_time = (2 ** attempt) * self.min_request_interval
81
+ time.sleep(wait_time)
82
+ continue
83
+ return f"Search failed after {self.max_retries} attempts: {str(e)}"
84
+
85
+ return "Search failed due to rate limiting"
86
+
87
+ def _do_search(self, query: str, domain: Optional[str] = None) -> str:
88
+ """Perform the actual search request."""
89
+ try:
90
+ # Construct search URL
91
+ base_url = "https://html.duckduckgo.com/html"
92
+ params = {"q": query}
93
+ if domain:
94
+ params["q"] += f" site:{domain}"
95
+
96
+ # Make request with increased timeout
97
+ response = requests.get(base_url, params=params, timeout=10)
98
+ response.raise_for_status()
99
+
100
+ if response.status_code == 202:
101
+ raise Exception("202 Ratelimit")
102
+
103
+ # Extract search results
104
+ results = []
105
+ soup = BeautifulSoup(response.text, 'html.parser')
106
+ for result in soup.find_all('div', {'class': 'result'}):
107
+ title = result.find('a', {'class': 'result__a'})
108
+ snippet = result.find('a', {'class': 'result__snippet'})
109
+ if title and snippet:
110
+ results.append({
111
+ 'title': title.get_text(),
112
+ 'snippet': snippet.get_text(),
113
+ 'url': title.get('href')
114
+ })
115
+
116
+ # Format results
117
+ formatted_results = []
118
+ for r in results[:10]: # Limit to top 5 results
119
+ formatted_results.append(f"[{r['title']}]({r['url']})\n{r['snippet']}\n")
120
+
121
+ return "## Search Results\n\n" + "\n".join(formatted_results)
122
+
123
+ except requests.RequestException as e:
124
+ raise Exception(f"Search request failed: {str(e)}")
125
+
126
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
127
+ """
128
+ Save content to a temporary file and return the path.
129
+ Useful for processing files from the GAIA API.
130
+
131
+ Args:
132
+ content: The content to save to the file
133
+ filename: Optional filename, will generate a random name if not provided
134
+
135
+ Returns:
136
+ Path to the saved file
137
+ """
138
+ temp_dir = tempfile.gettempdir()
139
+ if filename is None:
140
+ temp_file = tempfile.NamedTemporaryFile(delete=False)
141
+ filepath = temp_file.name
142
+ else:
143
+ filepath = os.path.join(temp_dir, filename)
144
+
145
+ # Write content to the file
146
+ with open(filepath, 'w') as f:
147
+ f.write(content)
148
+
149
+ return f"File saved to {filepath}. You can read this file to process its contents."
150
+
151
+
152
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
153
+ """
154
+ Download a file from a URL and save it to a temporary location.
155
+
156
+ Args:
157
+ url: The URL to download from
158
+ filename: Optional filename, will generate one based on URL if not provided
159
+
160
+ Returns:
161
+ Path to the downloaded file
162
+ """
163
+ try:
164
+ # Parse URL to get filename if not provided
165
+ if not filename:
166
+ path = urlparse(url).path
167
+ filename = os.path.basename(path)
168
+ if not filename:
169
+ # Generate a random name if we couldn't extract one
170
+ import uuid
171
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
172
+
173
+ # Create temporary file
174
+ temp_dir = tempfile.gettempdir()
175
+ filepath = os.path.join(temp_dir, filename)
176
+
177
+ # Download the file
178
+ response = requests.get(url, stream=True)
179
+ response.raise_for_status()
180
+
181
+ # Save the file
182
+ with open(filepath, 'wb') as f:
183
+ for chunk in response.iter_content(chunk_size=8192):
184
+ f.write(chunk)
185
+
186
+ return f"File downloaded to {filepath}. You can now process this file."
187
+ except Exception as e:
188
+ return f"Error downloading file: {str(e)}"
189
+
190
+
191
+ def extract_text_from_image(image_path: str) -> str:
192
+ """
193
+ Extract text from an image using pytesseract (if available).
194
+
195
+ Args:
196
+ image_path: Path to the image file
197
+
198
+ Returns:
199
+ Extracted text or error message
200
+ """
201
+ try:
202
+ # Try to import pytesseract
203
+ import pytesseract
204
+ from PIL import Image
205
+
206
+ # Open the image
207
+ image = Image.open(image_path)
208
+
209
+ # Extract text
210
+ text = pytesseract.image_to_string(image)
211
+
212
+ return f"Extracted text from image:\n\n{text}"
213
+ except ImportError:
214
+ return "Error: pytesseract is not installed. Please install it with 'pip install pytesseract' and ensure Tesseract OCR is installed on your system."
215
+ except Exception as e:
216
+ return f"Error extracting text from image: {str(e)}"
217
+
218
+
219
+ def analyze_csv_file(file_path: str, query: str) -> str:
220
+ """
221
+ Analyze a CSV file using pandas and answer a question about it.
222
+
223
+ Args:
224
+ file_path: Path to the CSV file
225
+ query: Question about the data
226
+
227
+ Returns:
228
+ Analysis result or error message
229
+ """
230
+ try:
231
+ import pandas as pd
232
+
233
+ # Read the CSV file
234
+ df = pd.read_csv(file_path)
235
+
236
+ # Run various analyses based on the query
237
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
238
+ result += f"Columns: {', '.join(df.columns)}\n\n"
239
+
240
+ # Add summary statistics
241
+ result += "Summary statistics:\n"
242
+ result += str(df.describe())
243
+
244
+ return result
245
+ except ImportError:
246
+ return "Error: pandas is not installed. Please install it with 'pip install pandas'."
247
+ except Exception as e:
248
+ return f"Error analyzing CSV file: {str(e)}"
249
+
250
+ @tool
251
+ def analyze_excel_file(file_path: str, query: str) -> str:
252
+ """
253
+ Analyze an Excel file using pandas and answer a question about it.
254
+
255
+ Args:
256
+ file_path: Path to the Excel file
257
+ query: Question about the data
258
+
259
+ Returns:
260
+ Analysis result or error message
261
+ """
262
+ try:
263
+ import pandas as pd
264
+
265
+ # Read the Excel file
266
+ df = pd.read_excel(file_path)
267
+
268
+ # Run various analyses based on the query
269
+ result = f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
270
+ result += f"Columns: {', '.join(df.columns)}\n\n"
271
+
272
+ # Add summary statistics
273
+ result += "Summary statistics:\n"
274
+ result += str(df.describe())
275
+
276
+ return result
277
+ except ImportError:
278
+ return "Error: pandas and openpyxl are not installed. Please install them with 'pip install pandas openpyxl'."
279
+ except Exception as e:
280
+ return f"Error analyzing Excel file: {str(e)}"
281
+
282
+ class GeminiAgent:
283
+ def __init__(self, api_key: str, model_name: str = "gemini-2.0-flash"):
284
+ # Suppress warnings
285
+ import warnings
286
+ warnings.filterwarnings("ignore", category=UserWarning)
287
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
288
+ warnings.filterwarnings("ignore", message=".*will be deprecated.*")
289
+ warnings.filterwarnings("ignore", "LangChain.*")
290
+
291
+ self.api_key = api_key
292
+ self.model_name = model_name
293
+
294
+ # Configure Gemini
295
+ genai.configure(api_key=api_key)
296
+
297
+ # Initialize the LLM
298
+ self.llm = self._setup_llm()
299
+
300
+ # Setup tools
301
+ self.tools = [
302
+ SmolagentToolWrapper(WikipediaSearchTool()),
303
+ Tool(
304
+ name="analyze_video",
305
+ func=self._analyze_video,
306
+ description="Analyze YouTube video content directly"
307
+ ),
308
+ Tool(
309
+ name="analyze_image",
310
+ func=self._analyze_image,
311
+ description="Analyze image content"
312
+ ),
313
+ Tool(
314
+ name="analyze_table",
315
+ func=self._analyze_table,
316
+ description="Analyze table or matrix data"
317
+ ),
318
+ Tool(
319
+ name="analyze_list",
320
+ func=self._analyze_list,
321
+ description="Analyze and categorize list items"
322
+ ),
323
+ Tool(
324
+ name="web_search",
325
+ func=self._web_search,
326
+ description="Search the web for information"
327
+ )
328
+ ]
329
+
330
+ # Setup memory
331
+ self.memory = ConversationBufferMemory(
332
+ memory_key="chat_history",
333
+ return_messages=True
334
+ )
335
+
336
+ # Initialize agent
337
+ self.agent = self._setup_agent()
338
+
339
+ # Load answer bank
340
+ self._load_answer_bank()
341
+
342
+
343
+
344
+ def run(self, query: str) -> str:
345
+ """Run the agent on a query with incremental retries."""
346
+ max_retries = 3
347
+ base_sleep = 1 # Start with 1 second sleep
348
+
349
+ for attempt in range(max_retries):
350
+ try:
351
+
352
+ # If no match found in answer bank, use the agent
353
+ response = self.agent.run(query)
354
+ return response
355
+
356
+ except Exception as e:
357
+ sleep_time = base_sleep * (attempt + 1) # Incremental sleep: 1s, 2s, 3s
358
+ if attempt < max_retries - 1:
359
+ print(f"Attempt {attempt + 1} failed. Retrying in {sleep_time} seconds...")
360
+ time.sleep(sleep_time)
361
+ continue
362
+ return f"Error processing query after {max_retries} attempts: {str(e)}"
363
+
364
+ def _clean_response(self, response: str) -> str:
365
+ """Clean up the response from the agent."""
366
+ # Remove any tool invocation artifacts
367
+ cleaned = re.sub(r'> Entering new AgentExecutor chain...|> Finished chain.', '', response)
368
+ cleaned = re.sub(r'Thought:.*?Action:.*?Action Input:.*?Observation:.*?\n', '', cleaned, flags=re.DOTALL)
369
+ return cleaned.strip()
370
+
371
+ def run_interactive(self):
372
+ print("AI Assistant Ready! (Type 'exit' to quit)")
373
+
374
+ while True:
375
+ query = input("You: ").strip()
376
+ if query.lower() == 'exit':
377
+ print("Goodbye!")
378
+ break
379
+
380
+ print("Assistant:", self.run(query))
381
+
382
+ def _web_search(self, query: str, domain: Optional[str] = None) -> str:
383
+ """Perform web search with rate limiting and retries."""
384
+ try:
385
+ # Use DuckDuckGo API wrapper for more reliable results
386
+ search = DuckDuckGoSearchAPIWrapper(max_results=5)
387
+ results = search.run(f"{query} {f'site:{domain}' if domain else ''}")
388
+
389
+ if not results or results.strip() == "":
390
+ return "No search results found."
391
+
392
+ return results
393
+
394
+ except Exception as e:
395
+ return f"Search error: {str(e)}"
396
+
397
+ def _analyze_video(self, url: str) -> str:
398
+ """Analyze video content using Gemini's video understanding capabilities."""
399
+ try:
400
+ # Validate URL
401
+ parsed_url = urlparse(url)
402
+ if not all([parsed_url.scheme, parsed_url.netloc]):
403
+ return "Please provide a valid video URL with http:// or https:// prefix."
404
+
405
+ # Check if it's a YouTube URL
406
+ if 'youtube.com' not in url and 'youtu.be' not in url:
407
+ return "Only YouTube videos are supported at this time."
408
+
409
+ try:
410
+ # Configure yt-dlp with minimal extraction
411
+ ydl_opts = {
412
+ 'quiet': True,
413
+ 'no_warnings': True,
414
+ 'extract_flat': True,
415
+ 'no_playlist': True,
416
+ 'youtube_include_dash_manifest': False
417
+ }
418
+
419
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
420
+ try:
421
+ # Try basic info extraction
422
+ info = ydl.extract_info(url, download=False, process=False)
423
+ if not info:
424
+ return "Could not extract video information."
425
+
426
+ title = info.get('title', 'Unknown')
427
+ description = info.get('description', '')
428
+
429
+ # Create a detailed prompt with available metadata
430
+ prompt = f"""Please analyze this YouTube video:
431
+ Title: {title}
432
+ URL: {url}
433
+ Description: {description}
434
+
435
+ Please provide a detailed analysis focusing on:
436
+ 1. Main topic and key points from the title and description
437
+ 2. Expected visual elements and scenes
438
+ 3. Overall message or purpose
439
+ 4. Target audience"""
440
+
441
+ # Use the LLM with proper message format
442
+ messages = [HumanMessage(content=prompt)]
443
+ response = self.llm.invoke(messages)
444
+ return response.content if hasattr(response, 'content') else str(response)
445
+
446
+ except Exception as e:
447
+ if 'Sign in to confirm' in str(e):
448
+ return "This video requires age verification or sign-in. Please provide a different video URL."
449
+ return f"Error accessing video: {str(e)}"
450
+
451
+ except Exception as e:
452
+ return f"Error extracting video info: {str(e)}"
453
+
454
+ except Exception as e:
455
+ return f"Error analyzing video: {str(e)}"
456
+
457
+ def _analyze_table(self, table_data: str) -> str:
458
+ """Analyze table or matrix data."""
459
+ try:
460
+ if not table_data or not isinstance(table_data, str):
461
+ return "Please provide valid table data for analysis."
462
+
463
+ prompt = f"""Please analyze this table:
464
+
465
+ {table_data}
466
+
467
+ Provide a detailed analysis including:
468
+ 1. Structure and format
469
+ 2. Key patterns or relationships
470
+ 3. Notable findings
471
+ 4. Any mathematical properties (if applicable)"""
472
+
473
+ messages = [HumanMessage(content=prompt)]
474
+ response = self.llm.invoke(messages)
475
+ return response.content if hasattr(response, 'content') else str(response)
476
+
477
+ except Exception as e:
478
+ return f"Error analyzing table: {str(e)}"
479
+
480
+ def _analyze_image(self, image_data: str) -> str:
481
+ """Analyze image content."""
482
+ try:
483
+ if not image_data or not isinstance(image_data, str):
484
+ return "Please provide a valid image for analysis."
485
+
486
+ prompt = f"""Please analyze this image:
487
+
488
+ {image_data}
489
+
490
+ Focus on:
491
+ 1. Visual elements and objects
492
+ 2. Colors and composition
493
+ 3. Text or numbers (if present)
494
+ 4. Overall context and meaning"""
495
+
496
+ messages = [HumanMessage(content=prompt)]
497
+ response = self.llm.invoke(messages)
498
+ return response.content if hasattr(response, 'content') else str(response)
499
+
500
+ except Exception as e:
501
+ return f"Error analyzing image: {str(e)}"
502
+
503
+ def _analyze_list(self, list_data: str) -> str:
504
+ """Analyze and categorize list items."""
505
+ if not list_data:
506
+ return "No list data provided."
507
+ try:
508
+ items = [x.strip() for x in list_data.split(',')]
509
+ if not items:
510
+ return "Please provide a comma-separated list of items."
511
+ # Add list analysis logic here
512
+ return "Please provide the list items for analysis."
513
+ except Exception as e:
514
+ return f"Error analyzing list: {str(e)}"
515
+
516
+ def _setup_llm(self):
517
+ """Set up the language model."""
518
+ # Set up model with video capabilities
519
+ generation_config = {
520
+ "temperature": 0.0,
521
+ "max_output_tokens": 2000,
522
+ "candidate_count": 1,
523
+ }
524
+
525
+ safety_settings = {
526
+ HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
527
+ HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
528
+ HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
529
+ HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
530
+ }
531
+
532
+ return ChatGoogleGenerativeAI(
533
+ model="gemini-2.0-flash",
534
+ google_api_key=self.api_key,
535
+ temperature=0,
536
+ max_output_tokens=2000,
537
+ generation_config=generation_config,
538
+ safety_settings=safety_settings,
539
+ system_message=SystemMessage(content=(
540
+ "You are a precise AI assistant that helps users find information and analyze content. "
541
+ "You can directly understand and analyze YouTube videos, images, and other content. "
542
+ "When analyzing videos, focus on relevant details like dialogue, text, and key visual elements. "
543
+ "For lists, tables, and structured data, ensure proper formatting and organization. "
544
+ "If you need additional context, clearly explain what is needed."
545
+ ))
546
+ )
547
+
548
+ def _setup_agent(self) -> AgentExecutor:
549
+ """Set up the agent with tools and system message."""
550
+
551
+ # Define the system message template
552
+ PREFIX = """You are a helpful AI assistant that can use various tools to answer questions and analyze content. You have access to tools for web search, Wikipedia lookup, and multimedia analysis.
553
+
554
+ TOOLS:
555
+ ------
556
+ You have access to the following tools:"""
557
+
558
+ FORMAT_INSTRUCTIONS = """To use a tool, use the following format:
559
+
560
+ Thought: Do I need to use a tool? Yes
561
+ Action: the action to take, should be one of [{tool_names}]
562
+ Action Input: the input to the action
563
+ Observation: the result of the action
564
+
565
+ When you have a response to say to the Human, or if you do not need to use a tool, you MUST use the format:
566
+
567
+ Thought: Do I need to use a tool? No
568
+ Final Answer: [your response here]
569
+
570
+ Begin! Remember to ALWAYS include 'Thought:', 'Action:', 'Action Input:', and 'Final Answer:' in your responses."""
571
+
572
+ SUFFIX = """Previous conversation history:
573
+ {chat_history}
574
+
575
+ New question: {input}
576
+ {agent_scratchpad}"""
577
+
578
+ # Create the base agent
579
+ agent = ConversationalAgent.from_llm_and_tools(
580
+ llm=self.llm,
581
+ tools=self.tools,
582
+ prefix=PREFIX,
583
+ format_instructions=FORMAT_INSTRUCTIONS,
584
+ suffix=SUFFIX,
585
+ input_variables=["input", "chat_history", "agent_scratchpad", "tool_names"],
586
+ handle_parsing_errors=True
587
+ )
588
+
589
+ # Initialize agent executor with custom output handling
590
+ return AgentExecutor.from_agent_and_tools(
591
+ agent=agent,
592
+ tools=self.tools,
593
+ memory=self.memory,
594
+ max_iterations=5,
595
+ verbose=True,
596
+ handle_parsing_errors=True,
597
+ return_only_outputs=True # This ensures we only get the final output
598
+ )
599
+
600
+ @tool
601
+ def analyze_csv_file(file_path: str, query: str) -> str:
602
+ """
603
+ Analyze a CSV file using pandas and answer a question about it.
604
+
605
+ Args:
606
+ file_path: Path to the CSV file
607
+ query: Question about the data
608
+
609
+ Returns:
610
+ Analysis result or error message
611
+ """
612
+ try:
613
+ import pandas as pd
614
+
615
+ # Read the CSV file
616
+ df = pd.read_csv(file_path)
617
+
618
+ # Run various analyses based on the query
619
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
620
+ result += f"Columns: {', '.join(df.columns)}\n\n"
621
+
622
+ # Add summary statistics
623
+ result += "Summary statistics:\n"
624
+ result += str(df.describe())
625
+
626
+ return result
627
+ except ImportError:
628
+ return "Error: pandas is not installed. Please install it with 'pip install pandas'."
629
+ except Exception as e:
630
+ return f"Error analyzing CSV file: {str(e)}"
631
+
632
+ @tool
633
+ def analyze_excel_file(file_path: str, query: str) -> str:
634
+ """
635
+ Analyze an Excel file using pandas and answer a question about it.
636
+
637
+ Args:
638
+ file_path: Path to the Excel file
639
+ query: Question about the data
640
+
641
+ Returns:
642
+ Analysis result or error message
643
+ """
644
+ try:
645
+ import pandas as pd
646
+
647
+ # Read the Excel file
648
+ df = pd.read_excel(file_path)
649
+
650
+ # Run various analyses based on the query
651
+ result = f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
652
+ result += f"Columns: {', '.join(df.columns)}\n\n"
653
+
654
+ # Add summary statistics
655
+ result += "Summary statistics:\n"
656
+ result += str(df.describe())
657
+
658
+ return result
659
+ except ImportError:
660
+ return "Error: pandas and openpyxl are not installed. Please install them with 'pip install pandas openpyxl'."
661
+ except Exception as e:
662
+ return f"Error analyzing Excel file: {str(e)}"
main_agent.py ADDED
@@ -0,0 +1,492 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from smolagents import (
2
+ CodeAgent,
3
+ DuckDuckGoSearchTool,
4
+ HfApiModel,
5
+ LiteLLMModel,
6
+ OpenAIServerModel,
7
+ PythonInterpreterTool,
8
+ tool,
9
+ InferenceClientModel
10
+ )
11
+ from typing import List, Dict, Any, Optional
12
+ import os
13
+ import tempfile
14
+ import re
15
+ import json
16
+ import requests
17
+ from urllib.parse import urlparse
18
+
19
+ @tool
20
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
21
+ """
22
+ Save content to a temporary file and return the path.
23
+ Useful for processing files from the GAIA API.
24
+
25
+ Args:
26
+ content: The content to save to the file
27
+ filename: Optional filename, will generate a random name if not provided
28
+
29
+ Returns:
30
+ Path to the saved file
31
+ """
32
+ temp_dir = tempfile.gettempdir()
33
+ if filename is None:
34
+ temp_file = tempfile.NamedTemporaryFile(delete=False)
35
+ filepath = temp_file.name
36
+ else:
37
+ filepath = os.path.join(temp_dir, filename)
38
+
39
+ # Write content to the file
40
+ with open(filepath, 'w') as f:
41
+ f.write(content)
42
+
43
+ return f"File saved to {filepath}. You can read this file to process its contents."
44
+
45
+ @tool
46
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
47
+ """
48
+ Download a file from a URL and save it to a temporary location.
49
+
50
+ Args:
51
+ url: The URL to download from
52
+ filename: Optional filename, will generate one based on URL if not provided
53
+
54
+ Returns:
55
+ Path to the downloaded file
56
+ """
57
+ try:
58
+ # Parse URL to get filename if not provided
59
+ if not filename:
60
+ path = urlparse(url).path
61
+ filename = os.path.basename(path)
62
+ if not filename:
63
+ # Generate a random name if we couldn't extract one
64
+ import uuid
65
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
66
+
67
+ # Create temporary file
68
+ temp_dir = tempfile.gettempdir()
69
+ filepath = os.path.join(temp_dir, filename)
70
+
71
+ # Download the file
72
+ response = requests.get(url, stream=True)
73
+ response.raise_for_status()
74
+
75
+ # Save the file
76
+ with open(filepath, 'wb') as f:
77
+ for chunk in response.iter_content(chunk_size=8192):
78
+ f.write(chunk)
79
+
80
+ return f"File downloaded to {filepath}. You can now process this file."
81
+ except Exception as e:
82
+ return f"Error downloading file: {str(e)}"
83
+
84
+ @tool
85
+ def extract_text_from_image(image_path: str) -> str:
86
+ """
87
+ Extract text from an image using pytesseract (if available).
88
+
89
+ Args:
90
+ image_path: Path to the image file
91
+
92
+ Returns:
93
+ Extracted text or error message
94
+ """
95
+ try:
96
+ # Try to import pytesseract
97
+ import pytesseract
98
+ from PIL import Image
99
+
100
+ # Open the image
101
+ image = Image.open(image_path)
102
+
103
+ # Extract text
104
+ text = pytesseract.image_to_string(image)
105
+
106
+ return f"Extracted text from image:\n\n{text}"
107
+ except ImportError:
108
+ return "Error: pytesseract is not installed. Please install it with 'pip install pytesseract' and ensure Tesseract OCR is installed on your system."
109
+ except Exception as e:
110
+ return f"Error extracting text from image: {str(e)}"
111
+
112
+ @tool
113
+ def analyze_csv_file(file_path: str, query: str) -> str:
114
+ """
115
+ Analyze a CSV file using pandas and answer a question about it.
116
+
117
+ Args:
118
+ file_path: Path to the CSV file
119
+ query: Question about the data
120
+
121
+ Returns:
122
+ Analysis result or error message
123
+ """
124
+ try:
125
+ import pandas as pd
126
+
127
+ # Read the CSV file
128
+ df = pd.read_csv(file_path)
129
+
130
+ # Run various analyses based on the query
131
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
132
+ result += f"Columns: {', '.join(df.columns)}\n\n"
133
+
134
+ # Add summary statistics
135
+ result += "Summary statistics:\n"
136
+ result += str(df.describe())
137
+
138
+ return result
139
+ except ImportError:
140
+ return "Error: pandas is not installed. Please install it with 'pip install pandas'."
141
+ except Exception as e:
142
+ return f"Error analyzing CSV file: {str(e)}"
143
+
144
+ @tool
145
+ def analyze_excel_file(file_path: str, query: str) -> str:
146
+ """
147
+ Analyze an Excel file using pandas and answer a question about it.
148
+
149
+ Args:
150
+ file_path: Path to the Excel file
151
+ query: Question about the data
152
+
153
+ Returns:
154
+ Analysis result or error message
155
+ """
156
+ try:
157
+ import pandas as pd
158
+
159
+ # Read the Excel file
160
+ df = pd.read_excel(file_path)
161
+
162
+ # Run various analyses based on the query
163
+ result = f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
164
+ result += f"Columns: {', '.join(df.columns)}\n\n"
165
+
166
+ # Add summary statistics
167
+ result += "Summary statistics:\n"
168
+ result += str(df.describe())
169
+
170
+ return result
171
+ except ImportError:
172
+ return "Error: pandas and openpyxl are not installed. Please install them with 'pip install pandas openpyxl'."
173
+ except Exception as e:
174
+ return f"Error analyzing Excel file: {str(e)}"
175
+
176
+ class GAIAAgent:
177
+ def __init__(
178
+ self,
179
+ model_type: str = "HfApiModel",
180
+ model_id: Optional[str] = None,
181
+ api_key: Optional[str] = None,
182
+ api_base: Optional[str] = None,
183
+ temperature: float = 0.2,
184
+ executor_type: str = "local", # Changed from use_e2b to executor_type
185
+ additional_imports: List[str] = None,
186
+ additional_tools: List[Any] = None,
187
+ system_prompt: Optional[str] = None, # We'll still accept this parameter but not use it directly
188
+ verbose: bool = False,
189
+ provider: Optional[str] = None, # Add provider for InferenceClientModel
190
+ timeout: Optional[int] = None # Add timeout for InferenceClientModel
191
+ ):
192
+ """
193
+ Initialize a GAIAAgent with specified configuration
194
+
195
+ Args:
196
+ model_type: Type of model to use (HfApiModel, LiteLLMModel, OpenAIServerModel, InferenceClientModel)
197
+ model_id: ID of the model to use
198
+ api_key: API key for the model provider
199
+ api_base: Base URL for API calls
200
+ temperature: Temperature for text generation
201
+ executor_type: Type of executor for code execution ('local' or 'e2b')
202
+ additional_imports: Additional Python modules to allow importing
203
+ additional_tools: Additional tools to provide to the agent
204
+ system_prompt: Custom system prompt to use (not directly used, kept for backward compatibility)
205
+ verbose: Enable verbose logging
206
+ provider: Provider for InferenceClientModel (e.g., "hf-inference")
207
+ timeout: Timeout in seconds for API calls
208
+ """
209
+ # Set verbosity
210
+ self.verbose = verbose
211
+ self.system_prompt = system_prompt # Store for potential future use
212
+
213
+ # Initialize model based on configuration
214
+ if model_type == "HfApiModel":
215
+ if api_key is None:
216
+ api_key = os.getenv("HUGGINGFACEHUB_API_TOKEN")
217
+ if not api_key:
218
+ raise ValueError("No Hugging Face token provided. Please set HUGGINGFACEHUB_API_TOKEN environment variable or pass api_key parameter.")
219
+
220
+ if self.verbose:
221
+ print(f"Using Hugging Face token: {api_key[:5]}...")
222
+
223
+ self.model = HfApiModel(
224
+ model_id=model_id or "meta-llama/Llama-3-70B-Instruct",
225
+ token=api_key,
226
+ temperature=temperature
227
+ )
228
+ elif model_type == "InferenceClientModel":
229
+ if api_key is None:
230
+ api_key = os.getenv("HUGGINGFACEHUB_API_TOKEN")
231
+ if not api_key:
232
+ raise ValueError("No Hugging Face token provided. Please set HUGGINGFACEHUB_API_TOKEN environment variable or pass api_key parameter.")
233
+
234
+ if self.verbose:
235
+ print(f"Using Hugging Face token: {api_key[:5]}...")
236
+
237
+ self.model = InferenceClientModel(
238
+ model_id=model_id or "meta-llama/Llama-3-70B-Instruct",
239
+ provider=provider or "hf-inference",
240
+ token=api_key,
241
+ timeout=timeout or 120,
242
+ temperature=temperature
243
+ )
244
+ elif model_type == "LiteLLMModel":
245
+ from smolagents import LiteLLMModel
246
+ self.model = LiteLLMModel(
247
+ model_id=model_id or "gpt-4o",
248
+ api_key=api_key or os.getenv("OPENAI_API_KEY"),
249
+ temperature=temperature
250
+ )
251
+ elif model_type == "OpenAIServerModel":
252
+ # Check for xAI API key and base URL first
253
+ xai_api_key = os.getenv("XAI_API_KEY")
254
+ xai_api_base = os.getenv("XAI_API_BASE")
255
+
256
+ # If xAI credentials are available, use them
257
+ if xai_api_key and api_key is None:
258
+ api_key = xai_api_key
259
+ if self.verbose:
260
+ print(f"Using xAI API key: {api_key[:5]}...")
261
+
262
+ # If no API key specified, fall back to OPENAI_API_KEY
263
+ if api_key is None:
264
+ api_key = os.getenv("OPENAI_API_KEY")
265
+ if not api_key:
266
+ raise ValueError("No OpenAI API key provided. Please set OPENAI_API_KEY or XAI_API_KEY environment variable or pass api_key parameter.")
267
+
268
+ # If xAI API base is available and no api_base is provided, use it
269
+ if xai_api_base and api_base is None:
270
+ api_base = xai_api_base
271
+ if self.verbose:
272
+ print(f"Using xAI API base URL: {api_base}")
273
+
274
+ # If no API base specified but environment variable available, use it
275
+ if api_base is None:
276
+ api_base = os.getenv("AGENT_API_BASE")
277
+ if api_base and self.verbose:
278
+ print(f"Using API base from AGENT_API_BASE: {api_base}")
279
+
280
+ self.model = OpenAIServerModel(
281
+ model_id=model_id or "gpt-4o",
282
+ api_key=api_key,
283
+ api_base=api_base,
284
+ temperature=temperature
285
+ )
286
+ else:
287
+ raise ValueError(f"Unknown model type: {model_type}")
288
+
289
+ if self.verbose:
290
+ print(f"Initialized model: {model_type} - {model_id}")
291
+
292
+ # Initialize default tools
293
+ self.tools = [
294
+ DuckDuckGoSearchTool(),
295
+ PythonInterpreterTool(),
296
+ save_and_read_file,
297
+ download_file_from_url,
298
+ analyze_csv_file,
299
+ analyze_excel_file
300
+ ]
301
+
302
+ # Add extract_text_from_image if PIL and pytesseract are available
303
+ try:
304
+ import pytesseract
305
+ from PIL import Image
306
+ self.tools.append(extract_text_from_image)
307
+ if self.verbose:
308
+ print("Added image processing tool")
309
+ except ImportError:
310
+ if self.verbose:
311
+ print("Image processing libraries not available")
312
+
313
+ # Add any additional tools
314
+ if additional_tools:
315
+ self.tools.extend(additional_tools)
316
+
317
+ if self.verbose:
318
+ print(f"Initialized with {len(self.tools)} tools")
319
+
320
+ # Setup imports allowed
321
+ self.imports = ["pandas", "numpy", "datetime", "json", "re", "math", "os", "requests", "csv", "urllib"]
322
+ if additional_imports:
323
+ self.imports.extend(additional_imports)
324
+
325
+ # Initialize the CodeAgent
326
+ executor_kwargs = {}
327
+ if executor_type == "e2b":
328
+ try:
329
+ # Try to import e2b dependencies to check if they're available
330
+ from e2b_code_interpreter import Sandbox
331
+ if self.verbose:
332
+ print("Using e2b executor")
333
+ except ImportError:
334
+ if self.verbose:
335
+ print("e2b dependencies not found, falling back to local executor")
336
+ executor_type = "local" # Fallback to local if e2b is not available
337
+
338
+ self.agent = CodeAgent(
339
+ tools=self.tools,
340
+ model=self.model,
341
+ additional_authorized_imports=self.imports,
342
+ executor_type=executor_type,
343
+ executor_kwargs=executor_kwargs,
344
+ verbosity_level=2 if self.verbose else 0
345
+ )
346
+
347
+ if self.verbose:
348
+ print("Agent initialized and ready")
349
+
350
+ def answer_question(self, question: str, task_file_path: Optional[str] = None) -> str:
351
+ """
352
+ Process a GAIA benchmark question and return the answer
353
+
354
+ Args:
355
+ question: The question to answer
356
+ task_file_path: Optional path to a file associated with the question
357
+
358
+ Returns:
359
+ The answer to the question
360
+ """
361
+ try:
362
+ if self.verbose:
363
+ print(f"Processing question: {question}")
364
+ if task_file_path:
365
+ print(f"With associated file: {task_file_path}")
366
+
367
+ # Create a context with file information if available
368
+ context = question
369
+ file_content = None
370
+
371
+ # If there's a file, read it and include its content in the context
372
+ if task_file_path:
373
+ try:
374
+ with open(task_file_path, 'r') as f:
375
+ file_content = f.read()
376
+
377
+ # Determine file type from extension
378
+ import os
379
+ file_ext = os.path.splitext(task_file_path)[1].lower()
380
+
381
+ context = f"""
382
+ Question: {question}
383
+
384
+ This question has an associated file. Here is the file content:
385
+
386
+ ```{file_ext}
387
+ {file_content}
388
+ ```
389
+
390
+ Analyze the file content above to answer the question.
391
+ """
392
+ except Exception as file_e:
393
+ context = f"""
394
+ Question: {question}
395
+
396
+ This question has an associated file at path: {task_file_path}
397
+ However, there was an error reading the file: {file_e}
398
+ You can still try to answer the question based on the information provided.
399
+ """
400
+
401
+ # Check for special cases that need specific formatting
402
+ # Reversed text questions
403
+ if question.startswith(".") or ".rewsna eht sa" in question:
404
+ context = f"""
405
+ This question appears to be in reversed text. Here's the reversed version:
406
+ {question[::-1]}
407
+
408
+ Now answer the question above. Remember to format your answer exactly as requested.
409
+ """
410
+
411
+ # Add a prompt to ensure precise answers
412
+ full_prompt = f"""{context}
413
+
414
+ When answering, provide ONLY the precise answer requested.
415
+ Do not include explanations, steps, reasoning, or additional text.
416
+ Be direct and specific. GAIA benchmark requires exact matching answers.
417
+ For example, if asked "What is the capital of France?", respond simply with "Paris".
418
+ """
419
+
420
+ # Run the agent with the question
421
+ answer = self.agent.run(full_prompt)
422
+
423
+ # Clean up the answer to ensure it's in the expected format
424
+ # Remove common prefixes that models often add
425
+ answer = self._clean_answer(answer)
426
+
427
+ if self.verbose:
428
+ print(f"Generated answer: {answer}")
429
+
430
+ return answer
431
+ except Exception as e:
432
+ error_msg = f"Error answering question: {e}"
433
+ if self.verbose:
434
+ print(error_msg)
435
+ return error_msg
436
+
437
+ def _clean_answer(self, answer: any) -> str:
438
+ """
439
+ Clean up the answer to remove common prefixes and formatting
440
+ that models often add but that can cause exact match failures.
441
+
442
+ Args:
443
+ answer: The raw answer from the model
444
+
445
+ Returns:
446
+ The cleaned answer as a string
447
+ """
448
+ # Convert non-string types to strings
449
+ if not isinstance(answer, str):
450
+ # Handle numeric types (float, int)
451
+ if isinstance(answer, float):
452
+ # Format floating point numbers properly
453
+ # Check if it's an integer value in float form (e.g., 12.0)
454
+ if answer.is_integer():
455
+ formatted_answer = str(int(answer))
456
+ else:
457
+ # For currency values that might need formatting
458
+ if abs(answer) >= 1000:
459
+ formatted_answer = f"${answer:,.2f}"
460
+ else:
461
+ formatted_answer = str(answer)
462
+ return formatted_answer
463
+ elif isinstance(answer, int):
464
+ return str(answer)
465
+ else:
466
+ # For any other type
467
+ return str(answer)
468
+
469
+ # Now we know answer is a string, so we can safely use string methods
470
+ # Normalize whitespace
471
+ answer = answer.strip()
472
+
473
+ # Remove common prefixes and formatting that models add
474
+ prefixes_to_remove = [
475
+ "The answer is ",
476
+ "Answer: ",
477
+ "Final answer: ",
478
+ "The result is ",
479
+ "To answer this question: ",
480
+ "Based on the information provided, ",
481
+ "According to the information: ",
482
+ ]
483
+
484
+ for prefix in prefixes_to_remove:
485
+ if answer.startswith(prefix):
486
+ answer = answer[len(prefix):].strip()
487
+
488
+ # Remove quotes if they wrap the entire answer
489
+ if (answer.startswith('"') and answer.endswith('"')) or (answer.startswith("'") and answer.endswith("'")):
490
+ answer = answer[1:-1].strip()
491
+
492
+ return answer
requirements.txt CHANGED
@@ -1,2 +1,18 @@
1
  gradio
2
- requests
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  gradio
2
+ langchain>=0.1.0
3
+ langchain-core>=0.1.0
4
+ langchain-community>=0.0.10
5
+ langchain-google-genai>=0.0.6
6
+ google-generativeai>=0.3.0
7
+ python-dotenv>=1.0.0
8
+ google-api-python-client>=2.108.0
9
+ duckduckgo-search>=4.4
10
+ tiktoken>=0.5.2
11
+ google-cloud-speech>=2.24.0
12
+ requests>=2.31.0
13
+ pydub>=0.25.1
14
+ yt-dlp>=2023.12.30
15
+ smolagents>=0.1.3
16
+ wikipedia>=1.4.0
17
+ Pillow>=10.2.0
18
+ wikipedia-api>=0.6.0