MrArray22 commited on
Commit
0f51c29
·
verified ·
1 Parent(s): 481b5b8

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +203 -453
  2. gemini_agent.py +660 -0
  3. main_agent.py +492 -0
app.py CHANGED
@@ -1,454 +1,204 @@
1
- import os
2
- import gradio as gr
3
- import requests
4
- import pandas as pd
5
- from smolagents import CodeAgent, DuckDuckGoSearchTool, OpenAIServerModel, Tool, PythonInterpreterTool
6
-
7
- # --- Constants ---
8
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
9
-
10
- # Custom file reading tool
11
- class FileReadTool(Tool):
12
- name = "file_reader"
13
- description = """
14
- This tool reads the content of text files.
15
- It's useful for processing plain text files (.txt, .csv, .json, etc).
16
- """
17
- inputs = {
18
- "file_path": {
19
- "type": "string",
20
- "description": "The path to the file to read",
21
- }
22
- }
23
- output_type = "string"
24
-
25
- def forward(self, file_path: str) -> str:
26
- """
27
- Reads the content of the given file.
28
- """
29
- try:
30
- # Check if the file exists
31
- if not os.path.exists(file_path):
32
- return f"Error: File not found at {file_path}"
33
-
34
- # Read the file
35
- with open(file_path, 'r', encoding='utf-8') as file:
36
- content = file.read()
37
-
38
- # If the content is too long, truncate it
39
- if len(content) > 10000:
40
- content = content[:10000] + "...\n[Text truncated due to length]"
41
-
42
- return content or "File is empty."
43
-
44
- except Exception as e:
45
- return f"Error reading file: {str(e)}"
46
-
47
- class PDFReaderTool(Tool):
48
- name = "pdf_reader"
49
- description = """
50
- This tool extracts text content from PDF files.
51
- It's useful for reading research papers, reports, or other document types.
52
- """
53
- inputs = {
54
- "pdf_path": {
55
- "type": "string",
56
- "description": "The path to the PDF file to read",
57
- }
58
- }
59
- output_type = "string"
60
-
61
- def forward(self, pdf_path: str) -> str:
62
- """
63
- Extracts text from the given PDF file.
64
- """
65
- try:
66
- # Check if the file exists
67
- if not os.path.exists(pdf_path):
68
- return f"Error: PDF file not found at {pdf_path}"
69
-
70
- import PyPDF2
71
-
72
- # Open the PDF file
73
- with open(pdf_path, 'rb') as file:
74
- # Create a PDF reader object
75
- pdf_reader = PyPDF2.PdfReader(file)
76
-
77
- # Get the number of pages
78
- num_pages = len(pdf_reader.pages)
79
-
80
- # Extract text from all pages
81
- text = ""
82
- for page_num in range(num_pages):
83
- page = pdf_reader.pages[page_num]
84
- text += page.extract_text() + "\n\n"
85
-
86
- # If the text is too long, truncate it
87
- if len(text) > 10000:
88
- text = text[:10000] + "...\n[Text truncated due to length]"
89
-
90
- return text or "No text could be extracted from the PDF."
91
-
92
- except Exception as e:
93
- return f"Error reading PDF: {str(e)}"
94
-
95
- class ExcelReaderTool(Tool):
96
- name = "excel_reader"
97
- description = """
98
- This tool reads and processes Excel files (.xlsx, .xls).
99
- It can extract data, calculate statistics, and perform data analysis on spreadsheets.
100
- """
101
- inputs = {
102
- "excel_path": {
103
- "type": "string",
104
- "description": "The path to the Excel file to read",
105
- },
106
- "sheet_name": {
107
- "type": "string",
108
- "description": "The name of the sheet to read (optional, defaults to first sheet)",
109
- "nullable": True
110
- }
111
- }
112
- output_type = "string"
113
-
114
- def forward(self, excel_path: str, sheet_name: str = None) -> str:
115
- """
116
- Reads and processes the given Excel file.
117
- """
118
- try:
119
- # Check if the file exists
120
- if not os.path.exists(excel_path):
121
- return f"Error: Excel file not found at {excel_path}"
122
-
123
- import pandas as pd
124
-
125
- # Read the Excel file
126
- if sheet_name:
127
- df = pd.read_excel(excel_path, sheet_name=sheet_name)
128
- else:
129
- df = pd.read_excel(excel_path)
130
-
131
- # Get basic info about the data
132
- info = {
133
- "shape": df.shape,
134
- "columns": list(df.columns),
135
- "dtypes": df.dtypes.to_dict(),
136
- "head": df.head(5).to_dict()
137
- }
138
-
139
- # Return formatted info
140
- result = f"Excel file: {excel_path}\n"
141
- result += f"Shape: {info['shape'][0]} rows × {info['shape'][1]} columns\n\n"
142
- result += "Columns:\n"
143
- for col in info['columns']:
144
- result += f"- {col} ({info['dtypes'].get(col)})\n"
145
-
146
- result += "\nPreview (first 5 rows):\n"
147
- result += df.head(5).to_string()
148
-
149
- return result
150
-
151
- except Exception as e:
152
- return f"Error reading Excel file: {str(e)}"
153
-
154
- class ImageAnalysisTool(Tool):
155
- name = "image_analysis"
156
- description = """
157
- This tool analyzes an image and extracts relevant information from it.
158
- It can describe image content, extract text from images, identify objects, etc.
159
- """
160
- inputs = {
161
- "image_path": {
162
- "type": "string",
163
- "description": "The path to the image file to analyze",
164
- }
165
- }
166
- output_type = "string"
167
-
168
- def forward(self, image_path: str) -> str:
169
- """
170
- Analyzes the given image and returns relevant information using OpenAI's ChatGPT API.
171
- """
172
- try:
173
- # Check if the file exists
174
- if not os.path.exists(image_path):
175
- return f"Error: Image file not found at {image_path}"
176
-
177
- import requests
178
- import base64
179
- import json
180
- from PIL import Image
181
-
182
- # Load the image
183
- with open(image_path, "rb") as image_file:
184
- image_bytes = image_file.read()
185
-
186
- # Convert to base64 for OpenAI API
187
- encoded_image = base64.b64encode(image_bytes).decode('utf-8')
188
-
189
- # Get API key from environment
190
- api_key = os.getenv('OPENAI_API_KEY', '')
191
- if not api_key:
192
- return "OpenAI API key not configured. Please add the OPENAI_API_KEY to your environment variables."
193
-
194
- # Prepare the API request for ChatGPT with vision capabilities
195
- api_url = "https://api.openai.com/v1/chat/completions"
196
- headers = {
197
- "Content-Type": "application/json",
198
- "Authorization": f"Bearer {api_key}"
199
- }
200
-
201
- payload = {
202
- "model": "gpt-4o-mini-2024-07-18", # Vision-capable model
203
- "messages": [
204
- {
205
- "role": "user",
206
- "content": [
207
- {
208
- "type": "text",
209
- "text": "Analyze this image in detail. Describe what you see, including main subjects, activities, background elements, colors, and any text visible in the image. If there's text in the image, please extract it."
210
- },
211
- {
212
- "type": "image_url",
213
- "image_url": {
214
- "url": f"data:image/jpeg;base64,{encoded_image}"
215
- }
216
- }
217
- ]
218
- }
219
- ],
220
- "max_tokens": 500
221
- }
222
-
223
- response = requests.post(
224
- api_url,
225
- headers=headers,
226
- json=payload
227
- )
228
-
229
- if response.status_code != 200:
230
- return f"Error: OpenAI API returned status code {response.status_code}. Details: {response.text}"
231
-
232
- result = response.json()
233
-
234
- # Extract the response content
235
- if "choices" in result and len(result["choices"]) > 0:
236
- analysis = result["choices"][0]["message"]["content"]
237
- return f"Image analysis result: {analysis}"
238
- else:
239
- return f"Error: Unexpected response format from OpenAI API: {result}"
240
-
241
- except Exception as e:
242
- return f"Error analyzing image: {str(e)}"
243
-
244
- # --- Basic Agent Definition ---
245
- class BasicAgent:
246
- def __init__(self):
247
- print("BasicAgent initialized.")
248
- # Initialize the model
249
- model = OpenAIServerModel(model_id="openai/gpt-4o-mini",api_key=os.environ["API_KEY"],api_base="https://models.github.ai/inference")
250
-
251
- # Initialize tools
252
- self.tools = [
253
- DuckDuckGoSearchTool(), # Built-in web search tool
254
- FileReadTool(), # Custom file reader
255
- PDFReaderTool(), # PDF reader
256
- ExcelReaderTool(), # Excel reader
257
- ImageAnalysisTool(), # Image analysis
258
- # Code execution
259
- ]
260
-
261
- # Initialize Agent
262
- self.agent = CodeAgent(
263
- model=model,
264
- tools=self.tools,
265
- add_base_tools=True # Add basic tools like math
266
- )
267
-
268
- def __call__(self, question: str) -> str:
269
- print(f"Agent received question (first 50 chars): {question[:50]}...")
270
- try:
271
- answer = self.agent.run(question)
272
- print(f"Agent returned answer (first 50 chars): {answer[:50]}...")
273
- return answer
274
- except Exception as e:
275
- error_msg = f"Error running agent: {str(e)}"
276
- print(error_msg)
277
- return f"I encountered an issue while processing your question: {str(e)}"
278
-
279
- def run_and_submit_all(profile: gr.OAuthProfile | None):
280
- """
281
- Fetches all questions, runs the BasicAgent on them, submits all answers,
282
- and displays the results.
283
- """
284
- # --- Determine HF Space Runtime URL and Repo URL ---
285
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
286
-
287
- if profile:
288
- username = f"{profile.username}"
289
- print(f"User logged in: {username}")
290
- else:
291
- print("User not logged in.")
292
- return "Please Login to Hugging Face with the button.", None
293
-
294
- api_url = DEFAULT_API_URL
295
- questions_url = f"{api_url}/questions"
296
- submit_url = f"{api_url}/submit"
297
-
298
- # 1. Instantiate Agent
299
- try:
300
- agent = BasicAgent()
301
- except Exception as e:
302
- print(f"Error instantiating agent: {e}")
303
- return f"Error initializing agent: {e}", None
304
-
305
- # In the case of an app running as a Hugging Face space, this link points toward your codebase
306
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
307
- print(agent_code)
308
-
309
- # 2. Fetch Questions
310
- print(f"Fetching questions from: {questions_url}")
311
- try:
312
- response = requests.get(questions_url, timeout=15)
313
- response.raise_for_status()
314
- questions_data = response.json()
315
- if not questions_data:
316
- print("Fetched questions list is empty.")
317
- return "Fetched questions list is empty or invalid format.", None
318
- print(f"Fetched {len(questions_data)} questions.")
319
- except requests.exceptions.RequestException as e:
320
- print(f"Error fetching questions: {e}")
321
- return f"Error fetching questions: {e}", None
322
- except requests.exceptions.JSONDecodeError as e:
323
- print(f"Error decoding JSON response from questions endpoint: {e}")
324
- print(f"Response text: {response.text[:500]}")
325
- return f"Error decoding server response for questions: {e}", None
326
- except Exception as e:
327
- print(f"An unexpected error occurred fetching questions: {e}")
328
- return f"An unexpected error occurred fetching questions: {e}", None
329
-
330
- # 3. Run your Agent
331
- results_log = []
332
- answers_payload = []
333
- print(f"Running agent on {len(questions_data)} questions...")
334
- for item in questions_data:
335
- task_id = item.get("task_id")
336
- question_text = item.get("question")
337
- if not task_id or question_text is None:
338
- print(f"Skipping item with missing task_id or question: {item}")
339
- continue
340
- try:
341
- print(f"Processing task {task_id}: {question_text[:50]}...")
342
- submitted_answer = agent(question_text)
343
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
344
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
345
- print(f"Completed task {task_id}")
346
- except Exception as e:
347
- print(f"Error running agent on task {task_id}: {e}")
348
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
349
-
350
- if not answers_payload:
351
- print("Agent did not produce any answers to submit.")
352
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
353
-
354
- # 4. Prepare Submission
355
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
356
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
357
- print(status_update)
358
-
359
- # 5. Submit
360
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
361
- try:
362
- response = requests.post(submit_url, json=submission_data, timeout=60)
363
- response.raise_for_status()
364
- result_data = response.json()
365
- final_status = (
366
- f"Submission Successful!\n"
367
- f"User: {result_data.get('username')}\n"
368
- f"Overall Score: {result_data.get('score', 'N/A')}% "
369
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
370
- f"Message: {result_data.get('message', 'No message received.')}"
371
- )
372
- print("Submission successful.")
373
- results_df = pd.DataFrame(results_log)
374
- return final_status, results_df
375
- except requests.exceptions.HTTPError as e:
376
- error_detail = f"Server responded with status {e.response.status_code}."
377
- try:
378
- error_json = e.response.json()
379
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
380
- except requests.exceptions.JSONDecodeError:
381
- error_detail += f" Response: {e.response.text[:500]}"
382
- status_message = f"Submission Failed: {error_detail}"
383
- print(status_message)
384
- results_df = pd.DataFrame(results_log)
385
- return status_message, results_df
386
- except requests.exceptions.Timeout:
387
- status_message = "Submission Failed: The request timed out."
388
- print(status_message)
389
- results_df = pd.DataFrame(results_log)
390
- return status_message, results_df
391
- except requests.exceptions.RequestException as e:
392
- status_message = f"Submission Failed: Network error - {e}"
393
- print(status_message)
394
- results_df = pd.DataFrame(results_log)
395
- return status_message, results_df
396
- except Exception as e:
397
- status_message = f"An unexpected error occurred during submission: {e}"
398
- print(status_message)
399
- results_df = pd.DataFrame(results_log)
400
- return status_message, results_df
401
-
402
-
403
- # --- Build Gradio Interface using Blocks ---
404
- with gr.Blocks() as demo:
405
- gr.Markdown("# Advanced Agent Evaluation Runner")
406
- gr.Markdown(
407
- """
408
- **Instructions:**
409
-
410
- 1. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
411
- 2. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
412
-
413
- ---
414
- **Note:**
415
- Once you click on the "submit" button, it may take quite some time as the agent processes all the questions.
416
- The agent is using SmolaAgents with multiple tools including web search, file processing, and code execution.
417
- """
418
- )
419
-
420
- gr.LoginButton()
421
-
422
- run_button = gr.Button("Run Evaluation & Submit All Answers")
423
-
424
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
425
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
426
-
427
- run_button.click(
428
- fn=run_and_submit_all,
429
- outputs=[status_output, results_table]
430
- )
431
-
432
- if __name__ == "__main__":
433
- print("\n" + "-"*30 + " App Starting " + "-"*30)
434
- # Check for SPACE_HOST and SPACE_ID at startup for information
435
- space_host_startup = os.getenv("SPACE_HOST")
436
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
437
-
438
- if space_host_startup:
439
- print(f"✅ SPACE_HOST found: {space_host_startup}")
440
- print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
441
- else:
442
- print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
443
-
444
- if space_id_startup: # Print repo URLs if SPACE_ID is found
445
- print(f"✅ SPACE_ID found: {space_id_startup}")
446
- print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
447
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
448
- else:
449
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
450
-
451
- print("-"*(60 + len(" App Starting ")) + "\n")
452
-
453
- print("Launching Gradio Interface for Advanced Agent Evaluation...")
454
  demo.launch(debug=True, share=False)
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ import pandas as pd
5
+ from dotenv import load_dotenv
6
+ from 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.
34
+ """
35
+ # --- Determine HF Space Runtime URL and Repo URL ---
36
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
37
+
38
+ if profile:
39
+ username= f"{profile.username}"
40
+ print(f"User logged in: {username}")
41
+ else:
42
+ print("User not logged in.")
43
+ return "Please Login to Hugging Face with the button.", None
44
+
45
+ api_url = DEFAULT_API_URL
46
+ questions_url = f"{api_url}/questions"
47
+ submit_url = f"{api_url}/submit"
48
+
49
+ # 1. Instantiate Agent ( modify this part to create your agent)
50
+ try:
51
+ agent = BasicAgent()
52
+ except Exception as e:
53
+ print(f"Error instantiating agent: {e}")
54
+ return f"Error initializing agent: {e}", None
55
+ # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
56
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
57
+ print(agent_code)
58
+
59
+ # 2. Fetch Questions
60
+ print(f"Fetching questions from: {questions_url}")
61
+ try:
62
+ response = requests.get(questions_url, timeout=15)
63
+ response.raise_for_status()
64
+ questions_data = response.json()
65
+ if not questions_data:
66
+ print("Fetched questions list is empty.")
67
+ return "Fetched questions list is empty or invalid format.", None
68
+ print(f"Fetched {len(questions_data)} questions.")
69
+ except requests.exceptions.RequestException as e:
70
+ print(f"Error fetching questions: {e}")
71
+ return f"Error fetching questions: {e}", None
72
+ except requests.exceptions.JSONDecodeError as e:
73
+ print(f"Error decoding JSON response from questions endpoint: {e}")
74
+ print(f"Response text: {response.text[:500]}")
75
+ return f"Error decoding server response for questions: {e}", None
76
+ except Exception as e:
77
+ print(f"An unexpected error occurred fetching questions: {e}")
78
+ return f"An unexpected error occurred fetching questions: {e}", None
79
+
80
+ # 3. Run your Agent
81
+ results_log = []
82
+ answers_payload = []
83
+ print(f"Running agent on {len(questions_data)} questions...")
84
+ for item in questions_data:
85
+ task_id = item.get("task_id")
86
+ question_text = item.get("question")
87
+ if not task_id or question_text is None:
88
+ print(f"Skipping item with missing task_id or question: {item}")
89
+ continue
90
+ try:
91
+ submitted_answer = agent(question_text)
92
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
93
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
94
+ except Exception as e:
95
+ print(f"Error running agent on task {task_id}: {e}")
96
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
97
+
98
+ if not answers_payload:
99
+ print("Agent did not produce any answers to submit.")
100
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
101
+
102
+ # 4. Prepare Submission
103
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
104
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
105
+ print(status_update)
106
+
107
+ # 5. Submit
108
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
109
+ try:
110
+ response = requests.post(submit_url, json=submission_data, timeout=60)
111
+ response.raise_for_status()
112
+ result_data = response.json()
113
+ final_status = (
114
+ f"Submission Successful!\n"
115
+ f"User: {result_data.get('username')}\n"
116
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
117
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
118
+ f"Message: {result_data.get('message', 'No message received.')}"
119
+ )
120
+ print("Submission successful.")
121
+ results_df = pd.DataFrame(results_log)
122
+ return final_status, results_df
123
+ except requests.exceptions.HTTPError as e:
124
+ error_detail = f"Server responded with status {e.response.status_code}."
125
+ try:
126
+ error_json = e.response.json()
127
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
128
+ except requests.exceptions.JSONDecodeError:
129
+ error_detail += f" Response: {e.response.text[:500]}"
130
+ status_message = f"Submission Failed: {error_detail}"
131
+ print(status_message)
132
+ results_df = pd.DataFrame(results_log)
133
+ return status_message, results_df
134
+ except requests.exceptions.Timeout:
135
+ status_message = "Submission Failed: The request timed out."
136
+ print(status_message)
137
+ results_df = pd.DataFrame(results_log)
138
+ return status_message, results_df
139
+ except requests.exceptions.RequestException as e:
140
+ status_message = f"Submission Failed: Network error - {e}"
141
+ print(status_message)
142
+ results_df = pd.DataFrame(results_log)
143
+ return status_message, results_df
144
+ except Exception as e:
145
+ status_message = f"An unexpected error occurred during submission: {e}"
146
+ print(status_message)
147
+ results_df = pd.DataFrame(results_log)
148
+ return status_message, results_df
149
+
150
+
151
+ # --- Build Gradio Interface using Blocks ---
152
+ with gr.Blocks() as demo:
153
+ gr.Markdown("# Basic Agent Evaluation Runner")
154
+ gr.Markdown(
155
+ """
156
+ **Instructions:**
157
+
158
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
159
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
160
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
161
+
162
+ ---
163
+ **Disclaimers:**
164
+ Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
165
+ This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
166
+ """
167
+ )
168
+
169
+ gr.LoginButton()
170
+
171
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
172
+
173
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
174
+ # Removed max_rows=10 from DataFrame constructor
175
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
176
+
177
+ run_button.click(
178
+ fn=run_and_submit_all,
179
+ outputs=[status_output, results_table]
180
+ )
181
+
182
+ if __name__ == "__main__":
183
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
184
+ # Check for SPACE_HOST and SPACE_ID at startup for information
185
+ space_host_startup = os.getenv("SPACE_HOST")
186
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
187
+
188
+ if space_host_startup:
189
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
190
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
191
+ else:
192
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
193
+
194
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
195
+ print(f"✅ SPACE_ID found: {space_id_startup}")
196
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
197
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
198
+ else:
199
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
200
+
201
+ print("-"*(60 + len(" App Starting ")) + "\n")
202
+
203
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  demo.launch(debug=True, share=False)
gemini_agent.py ADDED
@@ -0,0 +1,660 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
340
+ def run(self, query: str) -> str:
341
+ """Run the agent on a query with incremental retries."""
342
+ max_retries = 3
343
+ base_sleep = 1 # Start with 1 second sleep
344
+
345
+ for attempt in range(max_retries):
346
+ try:
347
+
348
+ # If no match found in answer bank, use the agent
349
+ response = self.agent.run(query)
350
+ return response
351
+
352
+ except Exception as e:
353
+ sleep_time = base_sleep * (attempt + 1) # Incremental sleep: 1s, 2s, 3s
354
+ if attempt < max_retries - 1:
355
+ print(f"Attempt {attempt + 1} failed. Retrying in {sleep_time} seconds...")
356
+ time.sleep(sleep_time)
357
+ continue
358
+ return f"Error processing query after {max_retries} attempts: {str(e)}"
359
+
360
+ print("Agent processed all queries!")
361
+
362
+ def _clean_response(self, response: str) -> str:
363
+ """Clean up the response from the agent."""
364
+ # Remove any tool invocation artifacts
365
+ cleaned = re.sub(r'> Entering new AgentExecutor chain...|> Finished chain.', '', response)
366
+ cleaned = re.sub(r'Thought:.*?Action:.*?Action Input:.*?Observation:.*?\n', '', cleaned, flags=re.DOTALL)
367
+ return cleaned.strip()
368
+
369
+ def run_interactive(self):
370
+ print("AI Assistant Ready! (Type 'exit' to quit)")
371
+
372
+ while True:
373
+ query = input("You: ").strip()
374
+ if query.lower() == 'exit':
375
+ print("Goodbye!")
376
+ break
377
+
378
+ print("Assistant:", self.run(query))
379
+
380
+ def _web_search(self, query: str, domain: Optional[str] = None) -> str:
381
+ """Perform web search with rate limiting and retries."""
382
+ try:
383
+ # Use DuckDuckGo API wrapper for more reliable results
384
+ search = DuckDuckGoSearchAPIWrapper(max_results=5)
385
+ results = search.run(f"{query} {f'site:{domain}' if domain else ''}")
386
+
387
+ if not results or results.strip() == "":
388
+ return "No search results found."
389
+
390
+ return results
391
+
392
+ except Exception as e:
393
+ return f"Search error: {str(e)}"
394
+
395
+ def _analyze_video(self, url: str) -> str:
396
+ """Analyze video content using Gemini's video understanding capabilities."""
397
+ try:
398
+ # Validate URL
399
+ parsed_url = urlparse(url)
400
+ if not all([parsed_url.scheme, parsed_url.netloc]):
401
+ return "Please provide a valid video URL with http:// or https:// prefix."
402
+
403
+ # Check if it's a YouTube URL
404
+ if 'youtube.com' not in url and 'youtu.be' not in url:
405
+ return "Only YouTube videos are supported at this time."
406
+
407
+ try:
408
+ # Configure yt-dlp with minimal extraction
409
+ ydl_opts = {
410
+ 'quiet': True,
411
+ 'no_warnings': True,
412
+ 'extract_flat': True,
413
+ 'no_playlist': True,
414
+ 'youtube_include_dash_manifest': False
415
+ }
416
+
417
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
418
+ try:
419
+ # Try basic info extraction
420
+ info = ydl.extract_info(url, download=False, process=False)
421
+ if not info:
422
+ return "Could not extract video information."
423
+
424
+ title = info.get('title', 'Unknown')
425
+ description = info.get('description', '')
426
+
427
+ # Create a detailed prompt with available metadata
428
+ prompt = f"""Please analyze this YouTube video:
429
+ Title: {title}
430
+ URL: {url}
431
+ Description: {description}
432
+
433
+ Please provide a detailed analysis focusing on:
434
+ 1. Main topic and key points from the title and description
435
+ 2. Expected visual elements and scenes
436
+ 3. Overall message or purpose
437
+ 4. Target audience"""
438
+
439
+ # Use the LLM with proper message format
440
+ messages = [HumanMessage(content=prompt)]
441
+ response = self.llm.invoke(messages)
442
+ return response.content if hasattr(response, 'content') else str(response)
443
+
444
+ except Exception as e:
445
+ if 'Sign in to confirm' in str(e):
446
+ return "This video requires age verification or sign-in. Please provide a different video URL."
447
+ return f"Error accessing video: {str(e)}"
448
+
449
+ except Exception as e:
450
+ return f"Error extracting video info: {str(e)}"
451
+
452
+ except Exception as e:
453
+ return f"Error analyzing video: {str(e)}"
454
+
455
+ def _analyze_table(self, table_data: str) -> str:
456
+ """Analyze table or matrix data."""
457
+ try:
458
+ if not table_data or not isinstance(table_data, str):
459
+ return "Please provide valid table data for analysis."
460
+
461
+ prompt = f"""Please analyze this table:
462
+
463
+ {table_data}
464
+
465
+ Provide a detailed analysis including:
466
+ 1. Structure and format
467
+ 2. Key patterns or relationships
468
+ 3. Notable findings
469
+ 4. Any mathematical properties (if applicable)"""
470
+
471
+ messages = [HumanMessage(content=prompt)]
472
+ response = self.llm.invoke(messages)
473
+ return response.content if hasattr(response, 'content') else str(response)
474
+
475
+ except Exception as e:
476
+ return f"Error analyzing table: {str(e)}"
477
+
478
+ def _analyze_image(self, image_data: str) -> str:
479
+ """Analyze image content."""
480
+ try:
481
+ if not image_data or not isinstance(image_data, str):
482
+ return "Please provide a valid image for analysis."
483
+
484
+ prompt = f"""Please analyze this image:
485
+
486
+ {image_data}
487
+
488
+ Focus on:
489
+ 1. Visual elements and objects
490
+ 2. Colors and composition
491
+ 3. Text or numbers (if present)
492
+ 4. Overall context and meaning"""
493
+
494
+ messages = [HumanMessage(content=prompt)]
495
+ response = self.llm.invoke(messages)
496
+ return response.content if hasattr(response, 'content') else str(response)
497
+
498
+ except Exception as e:
499
+ return f"Error analyzing image: {str(e)}"
500
+
501
+ def _analyze_list(self, list_data: str) -> str:
502
+ """Analyze and categorize list items."""
503
+ if not list_data:
504
+ return "No list data provided."
505
+ try:
506
+ items = [x.strip() for x in list_data.split(',')]
507
+ if not items:
508
+ return "Please provide a comma-separated list of items."
509
+ # Add list analysis logic here
510
+ return "Please provide the list items for analysis."
511
+ except Exception as e:
512
+ return f"Error analyzing list: {str(e)}"
513
+
514
+ def _setup_llm(self):
515
+ """Set up the language model."""
516
+ # Set up model with video capabilities
517
+ generation_config = {
518
+ "temperature": 0.0,
519
+ "max_output_tokens": 2000,
520
+ "candidate_count": 1,
521
+ }
522
+
523
+ safety_settings = {
524
+ HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
525
+ HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
526
+ HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
527
+ HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
528
+ }
529
+
530
+ return ChatGoogleGenerativeAI(
531
+ model="gemini-2.0-flash",
532
+ google_api_key=self.api_key,
533
+ temperature=0,
534
+ max_output_tokens=2000,
535
+ generation_config=generation_config,
536
+ safety_settings=safety_settings,
537
+ system_message=SystemMessage(content=(
538
+ "You are a precise AI assistant that helps users find information and analyze content. "
539
+ "You can directly understand and analyze YouTube videos, images, and other content. "
540
+ "When analyzing videos, focus on relevant details like dialogue, text, and key visual elements. "
541
+ "For lists, tables, and structured data, ensure proper formatting and organization. "
542
+ "If you need additional context, clearly explain what is needed."
543
+ ))
544
+ )
545
+
546
+ def _setup_agent(self) -> AgentExecutor:
547
+ """Set up the agent with tools and system message."""
548
+
549
+ # Define the system message template
550
+ 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.
551
+
552
+ TOOLS:
553
+ ------
554
+ You have access to the following tools:"""
555
+
556
+ FORMAT_INSTRUCTIONS = """To use a tool, use the following format:
557
+
558
+ Thought: Do I need to use a tool? Yes
559
+ Action: the action to take, should be one of [{tool_names}]
560
+ Action Input: the input to the action
561
+ Observation: the result of the action
562
+
563
+ 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:
564
+
565
+ Thought: Do I need to use a tool? No
566
+ Final Answer: [your response here]
567
+
568
+ Begin! Remember to ALWAYS include 'Thought:', 'Action:', 'Action Input:', and 'Final Answer:' in your responses."""
569
+
570
+ SUFFIX = """Previous conversation history:
571
+ {chat_history}
572
+
573
+ New question: {input}
574
+ {agent_scratchpad}"""
575
+
576
+ # Create the base agent
577
+ agent = ConversationalAgent.from_llm_and_tools(
578
+ llm=self.llm,
579
+ tools=self.tools,
580
+ prefix=PREFIX,
581
+ format_instructions=FORMAT_INSTRUCTIONS,
582
+ suffix=SUFFIX,
583
+ input_variables=["input", "chat_history", "agent_scratchpad", "tool_names"],
584
+ handle_parsing_errors=True
585
+ )
586
+
587
+ # Initialize agent executor with custom output handling
588
+ return AgentExecutor.from_agent_and_tools(
589
+ agent=agent,
590
+ tools=self.tools,
591
+ memory=self.memory,
592
+ max_iterations=5,
593
+ verbose=True,
594
+ handle_parsing_errors=True,
595
+ return_only_outputs=True # This ensures we only get the final output
596
+ )
597
+
598
+ @tool
599
+ def analyze_csv_file(file_path: str, query: str) -> str:
600
+ """
601
+ Analyze a CSV file using pandas and answer a question about it.
602
+
603
+ Args:
604
+ file_path: Path to the CSV file
605
+ query: Question about the data
606
+
607
+ Returns:
608
+ Analysis result or error message
609
+ """
610
+ try:
611
+ import pandas as pd
612
+
613
+ # Read the CSV file
614
+ df = pd.read_csv(file_path)
615
+
616
+ # Run various analyses based on the query
617
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
618
+ result += f"Columns: {', '.join(df.columns)}\n\n"
619
+
620
+ # Add summary statistics
621
+ result += "Summary statistics:\n"
622
+ result += str(df.describe())
623
+
624
+ return result
625
+ except ImportError:
626
+ return "Error: pandas is not installed. Please install it with 'pip install pandas'."
627
+ except Exception as e:
628
+ return f"Error analyzing CSV file: {str(e)}"
629
+
630
+ @tool
631
+ def analyze_excel_file(file_path: str, query: str) -> str:
632
+ """
633
+ Analyze an Excel file using pandas and answer a question about it.
634
+
635
+ Args:
636
+ file_path: Path to the Excel file
637
+ query: Question about the data
638
+
639
+ Returns:
640
+ Analysis result or error message
641
+ """
642
+ try:
643
+ import pandas as pd
644
+
645
+ # Read the Excel file
646
+ df = pd.read_excel(file_path)
647
+
648
+ # Run various analyses based on the query
649
+ result = f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
650
+ result += f"Columns: {', '.join(df.columns)}\n\n"
651
+
652
+ # Add summary statistics
653
+ result += "Summary statistics:\n"
654
+ result += str(df.describe())
655
+
656
+ return result
657
+ except ImportError:
658
+ return "Error: pandas and openpyxl are not installed. Please install them with 'pip install pandas openpyxl'."
659
+ except Exception as e:
660
+ 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