AlanRocha commited on
Commit
7132b77
·
verified ·
1 Parent(s): d79066e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +200 -42
app.py CHANGED
@@ -3,68 +3,226 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
6
-
7
- from smolagents import CodeAgent, InferenceClientModel, WebSearchTool
8
-
 
9
  # (Keep Constants as is)
10
  # --- Constants ---
11
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
12
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  # --- Basic Agent Definition ---
14
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
15
  class BasicAgent:
16
  """
17
  A real agent built with smolagents.
18
- Uses a free Hugging Face hosted model + a web search tool + the
19
- built-in Python code execution that CodeAgent already provides.
 
20
  """
21
  def __init__(self):
22
  print("BasicAgent initializing...")
23
-
24
  # Free model hosted by Hugging Face Inference Providers.
25
  # No paid API key required - just needs the Space's default HF token.
26
  self.model = InferenceClientModel(
27
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
28
  )
29
-
30
  self.agent = CodeAgent(
31
- tools=[WebSearchTool()],
 
 
 
 
 
 
 
32
  model=self.model,
33
  add_base_tools=True, # adds python interpreter + a couple of extra default tools
34
- max_steps=8,
 
 
 
 
35
  )
36
-
37
  print("BasicAgent initialized.")
38
-
39
- def __call__(self, question: str) -> str:
40
  print(f"Agent received question (first 50 chars): {question[:50]}...")
41
-
 
 
 
 
 
 
 
 
 
 
 
 
42
  # Strong instruction to keep answers in the exact-match format
43
  # the GAIA benchmark expects: no "FINAL ANSWER" prefix, no extra
44
  # explanation, just the bare answer.
45
  instructions = (
46
  "You are a general AI assistant answering a benchmark question. "
 
 
 
 
 
47
  "Report your thoughts, then finish with the answer. "
48
  "Your final output must be ONLY the answer itself: "
49
  "no explanations, no extra words, no 'FINAL ANSWER' prefix. "
50
  "If the answer is a number, write only the number (no units unless "
51
  "explicitly requested). If it's a string, give the minimal exact phrase "
52
  "requested, avoiding articles and abbreviations unless asked otherwise. "
53
- "If it's a list, give a comma separated list following the same rules.\n\n"
 
54
  f"Question: {question}"
55
  )
56
-
57
  try:
58
  result = self.agent.run(instructions)
59
  answer = str(result).strip()
60
  except Exception as e:
61
  print(f"Agent error while answering: {e}")
62
  answer = "I don't know."
63
-
64
  print(f"Agent returning answer: {answer}")
65
  return answer
66
-
67
-
68
  def run_and_submit_all( profile: gr.OAuthProfile | None):
69
  """
70
  Fetches all questions, runs the BasicAgent on them, submits all answers,
@@ -72,18 +230,18 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
72
  """
73
  # --- Determine HF Space Runtime URL and Repo URL ---
74
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
75
-
76
  if profile:
77
  username= f"{profile.username}"
78
  print(f"User logged in: {username}")
79
  else:
80
  print("User not logged in.")
81
  return "Please Login to Hugging Face with the button.", None
82
-
83
  api_url = DEFAULT_API_URL
84
  questions_url = f"{api_url}/questions"
85
  submit_url = f"{api_url}/submit"
86
-
87
  # 1. Instantiate Agent ( modify this part to create your agent)
88
  try:
89
  agent = BasicAgent()
@@ -93,7 +251,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
93
  # 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)
94
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
95
  print(agent_code)
96
-
97
  # 2. Fetch Questions
98
  print(f"Fetching questions from: {questions_url}")
99
  try:
@@ -114,7 +272,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
114
  except Exception as e:
115
  print(f"An unexpected error occurred fetching questions: {e}")
116
  return f"An unexpected error occurred fetching questions: {e}", None
117
-
118
  # 3. Run your Agent
119
  results_log = []
120
  answers_payload = []
@@ -126,22 +284,22 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
126
  print(f"Skipping item with missing task_id or question: {item}")
127
  continue
128
  try:
129
- submitted_answer = agent(question_text)
130
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
131
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
132
  except Exception as e:
133
  print(f"Error running agent on task {task_id}: {e}")
134
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
135
-
136
  if not answers_payload:
137
  print("Agent did not produce any answers to submit.")
138
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
139
-
140
  # 4. Prepare Submission
141
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
142
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
143
  print(status_update)
144
-
145
  # 5. Submit
146
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
147
  try:
@@ -184,59 +342,59 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
184
  print(status_message)
185
  results_df = pd.DataFrame(results_log)
186
  return status_message, results_df
187
-
188
-
189
  # --- Build Gradio Interface using Blocks ---
190
  with gr.Blocks() as demo:
191
  gr.Markdown("# Basic Agent Evaluation Runner")
192
  gr.Markdown(
193
  """
194
  **Instructions:**
195
-
196
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
197
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
198
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
199
-
200
  ---
201
  **Disclaimers:**
202
  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).
203
  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.
204
  """
205
  )
206
-
207
  gr.LoginButton()
208
-
209
  run_button = gr.Button("Run Evaluation & Submit All Answers")
210
-
211
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
212
  # Removed max_rows=10 from DataFrame constructor
213
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
214
-
215
  run_button.click(
216
  fn=run_and_submit_all,
217
  outputs=[status_output, results_table]
218
  )
219
-
220
  if __name__ == "__main__":
221
  print("\n" + "-"*30 + " App Starting " + "-"*30)
222
  # Check for SPACE_HOST and SPACE_ID at startup for information
223
  space_host_startup = os.getenv("SPACE_HOST")
224
  space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
225
-
226
  if space_host_startup:
227
  print(f"✅ SPACE_HOST found: {space_host_startup}")
228
  print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
229
  else:
230
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
231
-
232
  if space_id_startup: # Print repo URLs if SPACE_ID is found
233
  print(f"✅ SPACE_ID found: {space_id_startup}")
234
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
235
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
236
  else:
237
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
238
-
239
  print("-"*(60 + len(" App Starting ")) + "\n")
240
-
241
  print("Launching Gradio Interface for Basic Agent Evaluation...")
242
- demo.launch(debug=True, share=False, ssr_mode=False)
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ import tempfile
7
+
8
+ from smolagents import CodeAgent, InferenceClientModel, WebSearchTool, tool
9
+
10
  # (Keep Constants as is)
11
  # --- Constants ---
12
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
+
14
+
15
+ # --- Custom tools for reading task attachments ---
16
+
17
+ def _download_task_file(task_id: str) -> str:
18
+ """Internal helper: downloads the file attached to a task_id and saves
19
+ it to a temp folder, returning the local file path (or '' if none)."""
20
+ url = f"{DEFAULT_API_URL}/files/{task_id}"
21
+ try:
22
+ response = requests.get(url, timeout=30)
23
+ if response.status_code != 200:
24
+ return ""
25
+ # Try to get a filename from the Content-Disposition header
26
+ cd = response.headers.get("content-disposition", "")
27
+ filename = task_id
28
+ if "filename=" in cd:
29
+ filename = cd.split("filename=")[-1].strip('"; ')
30
+ else:
31
+ # Guess an extension from content-type
32
+ ctype = response.headers.get("content-type", "")
33
+ if "spreadsheet" in ctype or "excel" in ctype:
34
+ filename = f"{task_id}.xlsx"
35
+ elif "audio" in ctype:
36
+ filename = f"{task_id}.mp3"
37
+ elif "pdf" in ctype:
38
+ filename = f"{task_id}.pdf"
39
+ elif "csv" in ctype:
40
+ filename = f"{task_id}.csv"
41
+ tmp_dir = tempfile.gettempdir()
42
+ file_path = os.path.join(tmp_dir, filename)
43
+ with open(file_path, "wb") as f:
44
+ f.write(response.content)
45
+ return file_path
46
+ except Exception as e:
47
+ print(f"Error downloading file for task {task_id}: {e}")
48
+ return ""
49
+
50
+
51
+ @tool
52
+ def download_task_file(task_id: str) -> str:
53
+ """Downloads the file attached to a benchmark question (identified by its task_id)
54
+ and saves it locally. Use this FIRST whenever a question mentions an attached file
55
+ (Excel, CSV, audio, image, PDF, python script, etc).
56
+
57
+ Args:
58
+ task_id: The task_id of the current question.
59
+
60
+ Returns:
61
+ The local file path where the file was saved, or an empty string if there is
62
+ no file for this task_id.
63
+ """
64
+ return _download_task_file(task_id)
65
+
66
+
67
+ @tool
68
+ def read_excel_file(file_path: str) -> str:
69
+ """Reads an Excel (.xlsx/.xls) file and returns its content as readable text
70
+ (one table per sheet). Use this after downloading the file with download_task_file.
71
+
72
+ Args:
73
+ file_path: Local path to the Excel file.
74
+
75
+ Returns:
76
+ A text representation of every sheet in the workbook.
77
+ """
78
+ try:
79
+ sheets = pd.read_excel(file_path, sheet_name=None, engine="openpyxl")
80
+ out = []
81
+ for name, df in sheets.items():
82
+ out.append(f"--- Sheet: {name} ---\n{df.to_string(index=False)}")
83
+ return "\n\n".join(out)
84
+ except Exception as e:
85
+ return f"Error reading Excel file: {e}"
86
+
87
+
88
+ @tool
89
+ def read_csv_file(file_path: str) -> str:
90
+ """Reads a CSV or TSV file and returns its content as readable text.
91
+
92
+ Args:
93
+ file_path: Local path to the CSV file.
94
+
95
+ Returns:
96
+ A text representation of the table.
97
+ """
98
+ try:
99
+ df = pd.read_csv(file_path)
100
+ return df.to_string(index=False)
101
+ except Exception as e:
102
+ return f"Error reading CSV file: {e}"
103
+
104
+
105
+ @tool
106
+ def read_text_file(file_path: str) -> str:
107
+ """Reads a plain text, code, or markdown file and returns its raw content.
108
+
109
+ Args:
110
+ file_path: Local path to the text/code file.
111
+
112
+ Returns:
113
+ The raw text content of the file.
114
+ """
115
+ try:
116
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
117
+ return f.read()
118
+ except Exception as e:
119
+ return f"Error reading text file: {e}"
120
+
121
+
122
+ @tool
123
+ def transcribe_audio_file(file_path: str) -> str:
124
+ """Transcribes a speech audio file (mp3/wav) to text using a Whisper model.
125
+
126
+ Args:
127
+ file_path: Local path to the audio file.
128
+
129
+ Returns:
130
+ The transcribed text content of the audio.
131
+ """
132
+ try:
133
+ from transformers import pipeline
134
+ asr = pipeline("automatic-speech-recognition", model="openai/whisper-base")
135
+ result = asr(file_path)
136
+ return result.get("text", "") if isinstance(result, dict) else str(result)
137
+ except Exception as e:
138
+ return f"Error transcribing audio file: {e}"
139
+
140
+
141
  # --- Basic Agent Definition ---
142
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
143
  class BasicAgent:
144
  """
145
  A real agent built with smolagents.
146
+ Uses a free Hugging Face hosted model, web search, and a set of file
147
+ tools (Excel/CSV/text/audio) to handle GAIA-style benchmark questions
148
+ that come with an attachment.
149
  """
150
  def __init__(self):
151
  print("BasicAgent initializing...")
152
+
153
  # Free model hosted by Hugging Face Inference Providers.
154
  # No paid API key required - just needs the Space's default HF token.
155
  self.model = InferenceClientModel(
156
  model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
157
  )
158
+
159
  self.agent = CodeAgent(
160
+ tools=[
161
+ WebSearchTool(),
162
+ download_task_file,
163
+ read_excel_file,
164
+ read_csv_file,
165
+ read_text_file,
166
+ transcribe_audio_file,
167
+ ],
168
  model=self.model,
169
  add_base_tools=True, # adds python interpreter + a couple of extra default tools
170
+ additional_authorized_imports=[
171
+ "pandas", "numpy", "json", "re", "math", "datetime",
172
+ "openpyxl", "io", "csv",
173
+ ],
174
+ max_steps=10,
175
  )
176
+
177
  print("BasicAgent initialized.")
178
+
179
+ def __call__(self, question: str, task_id: str = "") -> str:
180
  print(f"Agent received question (first 50 chars): {question[:50]}...")
181
+
182
+ # Some GAIA questions are written backwards as a "riddle" test.
183
+ # Detect this and flip it back before sending to the model.
184
+ reversed_hint = ""
185
+ if question.strip().endswith(".") and question.strip()[:1].islower():
186
+ # crude heuristic: try reversing and see if it reads like English
187
+ flipped = question.strip()[::-1]
188
+ if flipped[:1].isupper() or flipped.split(" ")[0].isalpha():
189
+ reversed_hint = (
190
+ f"\n\nNote: this question may be written backwards. "
191
+ f"Reversed, it reads: {flipped}"
192
+ )
193
+
194
  # Strong instruction to keep answers in the exact-match format
195
  # the GAIA benchmark expects: no "FINAL ANSWER" prefix, no extra
196
  # explanation, just the bare answer.
197
  instructions = (
198
  "You are a general AI assistant answering a benchmark question. "
199
+ f"The task_id for this question is '{task_id}'. If the question "
200
+ "mentions an attached file (Excel, CSV, audio, image, code, etc.), "
201
+ "call download_task_file('" + task_id + "') first to get its local "
202
+ "path, then use the matching reading tool (read_excel_file, "
203
+ "read_csv_file, read_text_file, or transcribe_audio_file) on that path.\n\n"
204
  "Report your thoughts, then finish with the answer. "
205
  "Your final output must be ONLY the answer itself: "
206
  "no explanations, no extra words, no 'FINAL ANSWER' prefix. "
207
  "If the answer is a number, write only the number (no units unless "
208
  "explicitly requested). If it's a string, give the minimal exact phrase "
209
  "requested, avoiding articles and abbreviations unless asked otherwise. "
210
+ "If it's a list, give a comma separated list following the same rules."
211
+ f"{reversed_hint}\n\n"
212
  f"Question: {question}"
213
  )
214
+
215
  try:
216
  result = self.agent.run(instructions)
217
  answer = str(result).strip()
218
  except Exception as e:
219
  print(f"Agent error while answering: {e}")
220
  answer = "I don't know."
221
+
222
  print(f"Agent returning answer: {answer}")
223
  return answer
224
+
225
+
226
  def run_and_submit_all( profile: gr.OAuthProfile | None):
227
  """
228
  Fetches all questions, runs the BasicAgent on them, submits all answers,
 
230
  """
231
  # --- Determine HF Space Runtime URL and Repo URL ---
232
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
233
+
234
  if profile:
235
  username= f"{profile.username}"
236
  print(f"User logged in: {username}")
237
  else:
238
  print("User not logged in.")
239
  return "Please Login to Hugging Face with the button.", None
240
+
241
  api_url = DEFAULT_API_URL
242
  questions_url = f"{api_url}/questions"
243
  submit_url = f"{api_url}/submit"
244
+
245
  # 1. Instantiate Agent ( modify this part to create your agent)
246
  try:
247
  agent = BasicAgent()
 
251
  # 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)
252
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
253
  print(agent_code)
254
+
255
  # 2. Fetch Questions
256
  print(f"Fetching questions from: {questions_url}")
257
  try:
 
272
  except Exception as e:
273
  print(f"An unexpected error occurred fetching questions: {e}")
274
  return f"An unexpected error occurred fetching questions: {e}", None
275
+
276
  # 3. Run your Agent
277
  results_log = []
278
  answers_payload = []
 
284
  print(f"Skipping item with missing task_id or question: {item}")
285
  continue
286
  try:
287
+ submitted_answer = agent(question_text, task_id=task_id)
288
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
289
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
290
  except Exception as e:
291
  print(f"Error running agent on task {task_id}: {e}")
292
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
293
+
294
  if not answers_payload:
295
  print("Agent did not produce any answers to submit.")
296
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
297
+
298
  # 4. Prepare Submission
299
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
300
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
301
  print(status_update)
302
+
303
  # 5. Submit
304
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
305
  try:
 
342
  print(status_message)
343
  results_df = pd.DataFrame(results_log)
344
  return status_message, results_df
345
+
346
+
347
  # --- Build Gradio Interface using Blocks ---
348
  with gr.Blocks() as demo:
349
  gr.Markdown("# Basic Agent Evaluation Runner")
350
  gr.Markdown(
351
  """
352
  **Instructions:**
353
+
354
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
355
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
356
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
357
+
358
  ---
359
  **Disclaimers:**
360
  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).
361
  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.
362
  """
363
  )
364
+
365
  gr.LoginButton()
366
+
367
  run_button = gr.Button("Run Evaluation & Submit All Answers")
368
+
369
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
370
  # Removed max_rows=10 from DataFrame constructor
371
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
372
+
373
  run_button.click(
374
  fn=run_and_submit_all,
375
  outputs=[status_output, results_table]
376
  )
377
+
378
  if __name__ == "__main__":
379
  print("\n" + "-"*30 + " App Starting " + "-"*30)
380
  # Check for SPACE_HOST and SPACE_ID at startup for information
381
  space_host_startup = os.getenv("SPACE_HOST")
382
  space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
383
+
384
  if space_host_startup:
385
  print(f"✅ SPACE_HOST found: {space_host_startup}")
386
  print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
387
  else:
388
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
389
+
390
  if space_id_startup: # Print repo URLs if SPACE_ID is found
391
  print(f"✅ SPACE_ID found: {space_id_startup}")
392
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
393
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
394
  else:
395
  print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
396
+
397
  print("-"*(60 + len(" App Starting ")) + "\n")
398
+
399
  print("Launching Gradio Interface for Basic Agent Evaluation...")
400
+ demo.launch(debug=True, share=False, ssr_mode=False)