0f3dy commited on
Commit
7a86b3e
·
verified ·
1 Parent(s): c13167e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -51
app.py CHANGED
@@ -7,11 +7,42 @@ import time
7
  import re
8
  from markdownify import markdownify
9
  from smolagents import Tool, DuckDuckGoSearchTool, CodeAgent, WikipediaSearchTool, LiteLLMModel
 
 
10
 
11
  # (Keep Constants as is)
12
  # --- Constants ---
13
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  class DownloadTaskAttachmentTool(Tool):
17
  name = "download_file"
@@ -46,7 +77,6 @@ class DownloadTaskAttachmentTool(Tool):
46
  def __init__(self, *args, **kwargs):
47
  self.is_initialized = False
48
 
49
-
50
  class VisitWebpageTool(Tool):
51
  name = "visit_webpage"
52
  description = "Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages."
@@ -58,25 +88,17 @@ class VisitWebpageTool(Tool):
58
  import requests
59
  from markdownify import markdownify
60
  from requests.exceptions import RequestException
61
-
62
  from smolagents.utils import truncate_content
63
  except ImportError as e:
64
  raise ImportError(
65
  "You must install packages `markdownify` and `requests` to run this tool: for instance run `pip install markdownify requests`."
66
  ) from e
67
  try:
68
- # Send a GET request to the URL with a 20-second timeout
69
  response = requests.get(url, timeout=20)
70
- response.raise_for_status() # Raise an exception for bad status codes
71
-
72
- # Convert the HTML content to Markdown
73
  markdown_content = markdownify(response.text).strip()
74
-
75
- # Remove multiple line breaks
76
  markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content)
77
-
78
  return truncate_content(markdown_content, 10000)
79
-
80
  except requests.exceptions.Timeout:
81
  return "The request timed out. Please try again later or check the URL."
82
  except RequestException as e:
@@ -87,11 +109,10 @@ class VisitWebpageTool(Tool):
87
  def __init__(self, *args, **kwargs):
88
  self.is_initialized = False
89
 
90
-
91
- # --- Basic Agent Definition ---
92
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
93
  class BasicAgent:
94
  def __init__(self):
 
95
  self.agent = CodeAgent(
96
  model=LiteLLMModel(model_id="openrouter/meta-llama/llama-4-maverick:free", api_key=os.getenv("OPENROUTER_KEY")),
97
  tools=[DuckDuckGoSearchTool(), WikipediaSearchTool(), VisitWebpageTool(), DownloadTaskAttachmentTool()],
@@ -99,11 +120,38 @@ class BasicAgent:
99
  additional_authorized_imports=['pandas','numpy','csv','subprocess', 'exec']
100
  )
101
  print("BasicAgent initialized.")
102
- def __call__(self, question: str) -> str:
 
103
  print(f"Agent received question (first 50 chars): {question[:50]}...")
104
- agent_answer = self.agent.run(question)
105
- print(f"Agent returning answer: {agent_answer}")
106
- return agent_answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  def download_file(self, task_id: str) -> str:
109
  """
@@ -129,16 +177,15 @@ class BasicAgent:
129
  print(f"Error downloading file for task {task_id}: {e}")
130
  raise
131
 
132
- def run_and_submit_all( profile: gr.OAuthProfile | None):
133
  """
134
  Fetches all questions, runs the BasicAgent on them, submits all answers,
135
- and displays the results.
136
  """
137
- # --- Determine HF Space Runtime URL and Repo URL ---
138
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
139
 
140
  if profile:
141
- username= f"{profile.username}"
142
  print(f"User logged in: {username}")
143
  else:
144
  print("User not logged in.")
@@ -148,33 +195,34 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
148
  questions_url = f"{api_url}/questions"
149
  submit_url = f"{api_url}/submit"
150
 
151
- # 1. Instantiate Agent ( modify this part to create your agent)
 
152
  try:
153
  agent = BasicAgent()
154
  except Exception as e:
155
  print(f"Error instantiating agent: {e}")
156
  return f"Error initializing agent: {e}", None
157
- # 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)
158
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
159
  print(agent_code)
160
 
161
  # 2. Fetch Questions
 
162
  print(f"Fetching questions from: {questions_url}")
163
  try:
164
  response = requests.get(questions_url, timeout=15)
165
  response.raise_for_status()
166
  questions_data = response.json()
167
  if not questions_data:
168
- print("Fetched questions list is empty.")
169
- return "Fetched questions list is empty or invalid format.", None
170
  print(f"Fetched {len(questions_data)} questions.")
171
  except requests.exceptions.RequestException as e:
172
  print(f"Error fetching questions: {e}")
173
  return f"Error fetching questions: {e}", None
174
  except requests.exceptions.JSONDecodeError as e:
175
- print(f"Error decoding JSON response from questions endpoint: {e}")
176
- print(f"Response text: {response.text[:500]}")
177
- return f"Error decoding server response for questions: {e}", None
178
  except Exception as e:
179
  print(f"An unexpected error occurred fetching questions: {e}")
180
  return f"An unexpected error occurred fetching questions: {e}", None
@@ -182,8 +230,12 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
182
  # 3. Run your Agent
183
  results_log = []
184
  answers_payload = []
185
- print(f"Running agent on {len(questions_data)} questions...")
186
- for item in questions_data:
 
 
 
 
187
  task_id = item.get("task_id")
188
  question_text = item.get("question")
189
  requires_file = item.get("requires_file", False)
@@ -192,29 +244,41 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
192
  print(f"Skipping item with missing task_id or question: {item}")
193
  continue
194
 
 
 
195
  try:
196
  # Download file if required
197
  if requires_file:
198
  file_path = agent.download_file(task_id)
199
  print(f"File for task {task_id} saved at: {file_path}")
200
- # Optionally, pass the file path to the agent if needed
201
  submitted_answer = agent(f"{question_text} (File: {file_path})")
202
  else:
203
  submitted_answer = agent(question_text)
204
 
 
 
 
 
 
 
 
205
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
206
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
207
- time.sleep(2)
 
 
 
208
  except Exception as e:
 
209
  print(f"Error running agent on task {task_id}: {e}")
210
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
211
-
212
 
213
  if not answers_payload:
214
- print("Agent did not produce any answers to submit.")
215
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
216
 
217
  # 4. Prepare Submission
 
218
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
219
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
220
  print(status_update)
@@ -230,9 +294,12 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
230
  f"User: {result_data.get('username')}\n"
231
  f"Overall Score: {result_data.get('score', 'N/A')}% "
232
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
 
 
233
  f"Message: {result_data.get('message', 'No message received.')}"
234
  )
235
  print("Submission successful.")
 
236
  results_df = pd.DataFrame(results_log)
237
  return final_status, results_df
238
  except requests.exceptions.HTTPError as e:
@@ -262,7 +329,6 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
262
  results_df = pd.DataFrame(results_log)
263
  return status_message, results_df
264
 
265
-
266
  # --- Build Gradio Interface using Blocks ---
267
  with gr.Blocks() as demo:
268
  gr.Markdown("# Basic Agent Evaluation Runner")
@@ -270,14 +336,19 @@ with gr.Blocks() as demo:
270
  """
271
  **Instructions:**
272
 
273
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
274
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
275
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
276
 
277
  ---
278
- **Disclaimers:**
279
- 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).
280
- 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.
 
 
 
 
 
281
  """
282
  )
283
 
@@ -285,20 +356,19 @@ with gr.Blocks() as demo:
285
 
286
  run_button = gr.Button("Run Evaluation & Submit All Answers")
287
 
288
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
289
- # Removed max_rows=10 from DataFrame constructor
290
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
291
 
292
  run_button.click(
293
  fn=run_and_submit_all,
294
- outputs=[status_output, results_table]
 
295
  )
296
 
297
  if __name__ == "__main__":
298
  print("\n" + "-"*30 + " App Starting " + "-"*30)
299
- # Check for SPACE_HOST and SPACE_ID at startup for information
300
  space_host_startup = os.getenv("SPACE_HOST")
301
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
302
 
303
  if space_host_startup:
304
  print(f"✅ SPACE_HOST found: {space_host_startup}")
@@ -306,7 +376,7 @@ if __name__ == "__main__":
306
  else:
307
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
308
 
309
- if space_id_startup: # Print repo URLs if SPACE_ID is found
310
  print(f"✅ SPACE_ID found: {space_id_startup}")
311
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
312
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
@@ -316,4 +386,4 @@ if __name__ == "__main__":
316
  print("-"*(60 + len(" App Starting ")) + "\n")
317
 
318
  print("Launching Gradio Interface for Basic Agent Evaluation...")
319
- demo.launch(debug=True, share=False)
 
7
  import re
8
  from markdownify import markdownify
9
  from smolagents import Tool, DuckDuckGoSearchTool, CodeAgent, WikipediaSearchTool, LiteLLMModel
10
+ from datetime import datetime, timedelta
11
+ import threading
12
 
13
  # (Keep Constants as is)
14
  # --- Constants ---
15
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
16
 
17
+ # Rate limiting configuration
18
+ RATE_LIMIT_REQUESTS = 18 # Stay below the 20/min limit
19
+ RATE_LIMIT_WINDOW = 60 # 60 seconds
20
+ REQUEST_DELAY = 4 # Minimum delay between requests (60/18 ≈ 3.33, using 4 for safety)
21
+
22
+ class RateLimiter:
23
+ def __init__(self, max_requests=RATE_LIMIT_REQUESTS, window_seconds=RATE_LIMIT_WINDOW):
24
+ self.max_requests = max_requests
25
+ self.window_seconds = window_seconds
26
+ self.requests = []
27
+ self.lock = threading.Lock()
28
+
29
+ def wait_if_needed(self):
30
+ with self.lock:
31
+ now = datetime.now()
32
+ # Remove requests older than the window
33
+ self.requests = [req_time for req_time in self.requests
34
+ if now - req_time < timedelta(seconds=self.window_seconds)]
35
+
36
+ if len(self.requests) >= self.max_requests:
37
+ # Wait until we can make another request
38
+ oldest_request = min(self.requests)
39
+ wait_time = (oldest_request + timedelta(seconds=self.window_seconds) - now).total_seconds()
40
+ if wait_time > 0:
41
+ print(f"Rate limit reached. Waiting {wait_time:.1f} seconds...")
42
+ time.sleep(wait_time + 1) # Add 1 second buffer
43
+
44
+ # Record this request
45
+ self.requests.append(now)
46
 
47
  class DownloadTaskAttachmentTool(Tool):
48
  name = "download_file"
 
77
  def __init__(self, *args, **kwargs):
78
  self.is_initialized = False
79
 
 
80
  class VisitWebpageTool(Tool):
81
  name = "visit_webpage"
82
  description = "Visits a webpage at the given url and reads its content as a markdown string. Use this to browse webpages."
 
88
  import requests
89
  from markdownify import markdownify
90
  from requests.exceptions import RequestException
 
91
  from smolagents.utils import truncate_content
92
  except ImportError as e:
93
  raise ImportError(
94
  "You must install packages `markdownify` and `requests` to run this tool: for instance run `pip install markdownify requests`."
95
  ) from e
96
  try:
 
97
  response = requests.get(url, timeout=20)
98
+ response.raise_for_status()
 
 
99
  markdown_content = markdownify(response.text).strip()
 
 
100
  markdown_content = re.sub(r"\n{3,}", "\n\n", markdown_content)
 
101
  return truncate_content(markdown_content, 10000)
 
102
  except requests.exceptions.Timeout:
103
  return "The request timed out. Please try again later or check the URL."
104
  except RequestException as e:
 
109
  def __init__(self, *args, **kwargs):
110
  self.is_initialized = False
111
 
112
+ # --- Improved Agent Definition ---
 
 
113
  class BasicAgent:
114
  def __init__(self):
115
+ self.rate_limiter = RateLimiter()
116
  self.agent = CodeAgent(
117
  model=LiteLLMModel(model_id="openrouter/meta-llama/llama-4-maverick:free", api_key=os.getenv("OPENROUTER_KEY")),
118
  tools=[DuckDuckGoSearchTool(), WikipediaSearchTool(), VisitWebpageTool(), DownloadTaskAttachmentTool()],
 
120
  additional_authorized_imports=['pandas','numpy','csv','subprocess', 'exec']
121
  )
122
  print("BasicAgent initialized.")
123
+
124
+ def __call__(self, question: str, max_retries: int = 3) -> str:
125
  print(f"Agent received question (first 50 chars): {question[:50]}...")
126
+
127
+ for attempt in range(max_retries):
128
+ try:
129
+ # Apply rate limiting
130
+ self.rate_limiter.wait_if_needed()
131
+
132
+ # Run the agent
133
+ agent_answer = self.agent.run(question)
134
+ print(f"Agent returning answer: {agent_answer}")
135
+ return agent_answer
136
+
137
+ except Exception as e:
138
+ error_msg = str(e)
139
+ print(f"Attempt {attempt + 1} failed: {error_msg}")
140
+
141
+ # Check if it's a rate limit error
142
+ if "rate limit" in error_msg.lower() or "429" in error_msg:
143
+ if attempt < max_retries - 1:
144
+ wait_time = (attempt + 1) * 30 # Progressive backoff
145
+ print(f"Rate limit hit. Waiting {wait_time} seconds before retry...")
146
+ time.sleep(wait_time)
147
+ continue
148
+ else:
149
+ return f"RATE_LIMIT_ERROR: {error_msg}"
150
+ else:
151
+ # For other errors, return immediately
152
+ return f"AGENT_ERROR: {error_msg}"
153
+
154
+ return "MAX_RETRIES_EXCEEDED"
155
 
156
  def download_file(self, task_id: str) -> str:
157
  """
 
177
  print(f"Error downloading file for task {task_id}: {e}")
178
  raise
179
 
180
+ def run_and_submit_all(profile: gr.OAuthProfile | None, progress=gr.Progress()):
181
  """
182
  Fetches all questions, runs the BasicAgent on them, submits all answers,
183
+ and displays the results with progress tracking.
184
  """
185
+ space_id = os.getenv("SPACE_ID")
 
186
 
187
  if profile:
188
+ username = f"{profile.username}"
189
  print(f"User logged in: {username}")
190
  else:
191
  print("User not logged in.")
 
195
  questions_url = f"{api_url}/questions"
196
  submit_url = f"{api_url}/submit"
197
 
198
+ # 1. Instantiate Agent
199
+ progress(0, desc="Initializing agent...")
200
  try:
201
  agent = BasicAgent()
202
  except Exception as e:
203
  print(f"Error instantiating agent: {e}")
204
  return f"Error initializing agent: {e}", None
205
+
206
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
207
  print(agent_code)
208
 
209
  # 2. Fetch Questions
210
+ progress(0.1, desc="Fetching questions...")
211
  print(f"Fetching questions from: {questions_url}")
212
  try:
213
  response = requests.get(questions_url, timeout=15)
214
  response.raise_for_status()
215
  questions_data = response.json()
216
  if not questions_data:
217
+ print("Fetched questions list is empty.")
218
+ return "Fetched questions list is empty or invalid format.", None
219
  print(f"Fetched {len(questions_data)} questions.")
220
  except requests.exceptions.RequestException as e:
221
  print(f"Error fetching questions: {e}")
222
  return f"Error fetching questions: {e}", None
223
  except requests.exceptions.JSONDecodeError as e:
224
+ print(f"Error decoding JSON response from questions endpoint: {e}")
225
+ return f"Error decoding server response for questions: {e}", None
 
226
  except Exception as e:
227
  print(f"An unexpected error occurred fetching questions: {e}")
228
  return f"An unexpected error occurred fetching questions: {e}", None
 
230
  # 3. Run your Agent
231
  results_log = []
232
  answers_payload = []
233
+ total_questions = len(questions_data)
234
+ print(f"Running agent on {total_questions} questions...")
235
+
236
+ for i, item in enumerate(questions_data):
237
+ progress((0.1 + 0.8 * i / total_questions), desc=f"Processing question {i+1}/{total_questions}")
238
+
239
  task_id = item.get("task_id")
240
  question_text = item.get("question")
241
  requires_file = item.get("requires_file", False)
 
244
  print(f"Skipping item with missing task_id or question: {item}")
245
  continue
246
 
247
+ print(f"Processing task {task_id} ({i+1}/{total_questions})")
248
+
249
  try:
250
  # Download file if required
251
  if requires_file:
252
  file_path = agent.download_file(task_id)
253
  print(f"File for task {task_id} saved at: {file_path}")
 
254
  submitted_answer = agent(f"{question_text} (File: {file_path})")
255
  else:
256
  submitted_answer = agent(question_text)
257
 
258
+ # Check if the answer indicates an error
259
+ if submitted_answer.startswith(("RATE_LIMIT_ERROR", "AGENT_ERROR", "MAX_RETRIES_EXCEEDED")):
260
+ print(f"Error processing task {task_id}: {submitted_answer}")
261
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
262
+ # Don't add to answers_payload for submission if it's an error
263
+ continue
264
+
265
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
266
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
267
+
268
+ # Add delay between requests
269
+ time.sleep(REQUEST_DELAY)
270
+
271
  except Exception as e:
272
+ error_msg = f"PROCESSING_ERROR: {e}"
273
  print(f"Error running agent on task {task_id}: {e}")
274
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": error_msg})
 
275
 
276
  if not answers_payload:
277
+ print("Agent did not produce any valid answers to submit.")
278
+ return "Agent did not produce any valid answers to submit. Check the results table for errors.", pd.DataFrame(results_log)
279
 
280
  # 4. Prepare Submission
281
+ progress(0.9, desc="Submitting answers...")
282
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
283
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
284
  print(status_update)
 
294
  f"User: {result_data.get('username')}\n"
295
  f"Overall Score: {result_data.get('score', 'N/A')}% "
296
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
297
+ f"Processed: {len(results_log)} questions\n"
298
+ f"Successfully submitted: {len(answers_payload)} answers\n"
299
  f"Message: {result_data.get('message', 'No message received.')}"
300
  )
301
  print("Submission successful.")
302
+ progress(1.0, desc="Complete!")
303
  results_df = pd.DataFrame(results_log)
304
  return final_status, results_df
305
  except requests.exceptions.HTTPError as e:
 
329
  results_df = pd.DataFrame(results_log)
330
  return status_message, results_df
331
 
 
332
  # --- Build Gradio Interface using Blocks ---
333
  with gr.Blocks() as demo:
334
  gr.Markdown("# Basic Agent Evaluation Runner")
 
336
  """
337
  **Instructions:**
338
 
339
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc.
340
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
341
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
342
 
343
  ---
344
+ **Improvements:**
345
+ - Rate limiting to prevent API errors
346
+ - Progressive retry logic with backoff
347
+ - ✅ Better error handling and categorization
348
+ - ✅ Progress tracking during execution
349
+ - ✅ Detailed status reporting
350
+
351
+ **Note:** This improved version includes rate limiting to stay within the free tier limits of 20 requests per minute.
352
  """
353
  )
354
 
 
356
 
357
  run_button = gr.Button("Run Evaluation & Submit All Answers")
358
 
359
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=8, interactive=False)
 
360
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
361
 
362
  run_button.click(
363
  fn=run_and_submit_all,
364
+ outputs=[status_output, results_table],
365
+ show_progress=True
366
  )
367
 
368
  if __name__ == "__main__":
369
  print("\n" + "-"*30 + " App Starting " + "-"*30)
 
370
  space_host_startup = os.getenv("SPACE_HOST")
371
+ space_id_startup = os.getenv("SPACE_ID")
372
 
373
  if space_host_startup:
374
  print(f"✅ SPACE_HOST found: {space_host_startup}")
 
376
  else:
377
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
378
 
379
+ if space_id_startup:
380
  print(f"✅ SPACE_ID found: {space_id_startup}")
381
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
382
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
 
386
  print("-"*(60 + len(" App Starting ")) + "\n")
387
 
388
  print("Launching Gradio Interface for Basic Agent Evaluation...")
389
+ demo.launch(debug=True, share=False)