SergeyO7 commited on
Commit
3dd1e46
·
verified ·
1 Parent(s): f191142

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +295 -0
app.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ import inspect
5
+ import pandas as pd
6
+ import aiohttp
7
+ import asyncio
8
+ import json
9
+ from agent import MagAgent
10
+ from token_bucket import Limiter, MemoryStorage
11
+ import aiofiles
12
+ from typing import Optional
13
+ import time
14
+
15
+ # --- Constants ---
16
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
17
+
18
+
19
+ # Rate limiting configuration
20
+ MAX_MODEL_CALLS_PER_MINUTE = 14 # Conservative buffer below 15 RPM
21
+ TOKEN_BUCKET_CAPACITY = MAX_MODEL_CALLS_PER_MINUTE
22
+ TOKEN_BUCKET_REFILL_RATE = MAX_MODEL_CALLS_PER_MINUTE / 60.0 # Tokens per second
23
+
24
+ # Initialize global token bucket with MemoryStorage
25
+ storage = MemoryStorage()
26
+ token_bucket = Limiter(rate=TOKEN_BUCKET_REFILL_RATE, capacity=TOKEN_BUCKET_CAPACITY, storage=storage)
27
+
28
+ async def check_n_load_attach(session: aiohttp.ClientSession, task_id: str, question: str, api_url: str = "https://agents-course-unit4-scoring.hf.space") -> Optional[str]:
29
+ file_url = f"{api_url}/files/{task_id}"
30
+ try:
31
+ async with session.get(file_url, timeout=15) as response:
32
+ if response.status == 200:
33
+ content_type = str(response.headers.get("Content-Type", "")).lower()
34
+ content = await response.read() # Read the file content
35
+
36
+ # Determine extension based on content_type, content, or question
37
+ extension = await determine_extension(content_type, content, question)
38
+
39
+ if extension:
40
+ filename = f"{task_id}{extension}"
41
+ local_file_path = os.path.join("downloads", filename)
42
+ os.makedirs("downloads", exist_ok=True)
43
+
44
+ async with aiofiles.open(local_file_path, "wb") as file:
45
+ await file.write(content)
46
+ print(f"File downloaded successfully: {local_file_path}")
47
+ return local_file_path
48
+ else:
49
+ print(f"Unsupported content type: {content_type} for task {task_id}")
50
+ return None
51
+ else:
52
+ print(f"Failed to download file for task {task_id}: HTTP {response.status}")
53
+ return None
54
+ except aiohttp.ClientError as e:
55
+ print(f"Error downloading attachment for task {task_id}: {str(e)}")
56
+ return None
57
+
58
+ async def determine_extension(content_type: str, content: bytes, question: str) -> Optional[str]:
59
+ # Check if the question mentions Excel
60
+ if "excel" in question.lower():
61
+ # Check for XLS signature
62
+ if content.startswith(b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'):
63
+ return ".xls"
64
+ # Check for XLSX signature (ZIP archive)
65
+ elif content.startswith(b'\x50\x4B\x03\x04'):
66
+ return ".xlsx"
67
+ else:
68
+ return ".xlsx" # Default to XLSX if unsure
69
+ # Standard MIME type checks
70
+ if "image/png" in content_type:
71
+ return ".png"
72
+ elif "jpeg" in content_type or "jpg" in content_type:
73
+ return ".jpg"
74
+ elif "spreadsheetml.sheet" in content_type:
75
+ return ".xlsx"
76
+ elif "vnd.ms-excel" in content_type:
77
+ return ".xls"
78
+ elif "audio/mpeg" in content_type:
79
+ return ".mp3"
80
+ elif "application/pdf" in content_type:
81
+ return ".pdf"
82
+ elif "text/x-python" in content_type:
83
+ return ".py"
84
+ else:
85
+ return None
86
+
87
+
88
+ async def fetch_questions(session: aiohttp.ClientSession, questions_url: str) -> list:
89
+ """Fetch questions asynchronously."""
90
+ try:
91
+ async with session.get(questions_url, timeout=15) as response:
92
+ response.raise_for_status()
93
+ questions_data = await response.json()
94
+ if not questions_data:
95
+ print("Fetched questions list is empty.")
96
+ return []
97
+ print(f"Fetched {len(questions_data)} questions.")
98
+ return questions_data
99
+ except aiohttp.ClientError as e:
100
+ print(f"Error fetching questions: {e}")
101
+ return None
102
+ except Exception as e:
103
+ print(f"An unexpected error occurred fetching questions: {e}")
104
+ return None
105
+
106
+ async def submit_answers(session: aiohttp.ClientSession, submit_url: str, submission_data: dict) -> dict:
107
+ """Submit answers asynchronously."""
108
+ try:
109
+ async with session.post(submit_url, json=submission_data, timeout=60) as response:
110
+ response.raise_for_status()
111
+ return await response.json()
112
+ except aiohttp.ClientResponseError as e:
113
+ print(f"Submission Failed: Server responded with status {e.status}. Detail: {e.message}")
114
+ return None
115
+ except aiohttp.ClientError as e:
116
+ print(f"Submission Failed: Network error - {e}")
117
+ return None
118
+ except Exception as e:
119
+ print(f"An unexpected error occurred during submission: {e}")
120
+ return None
121
+
122
+ async def process_question(agent, question_text: str, task_id: str, file_path: Optional[str], results_log: list):
123
+ """Process a single question with global rate limiting and retry logic."""
124
+ submitted_answer = None
125
+ max_retries = 5
126
+ retry_delay = 30 # Initial retry delay in seconds
127
+ atimeout = 300
128
+
129
+ for attempt in range(max_retries +1):
130
+ try:
131
+ while not token_bucket.consume(1):
132
+ print(f"Rate limit reached for task {task_id}. Waiting to retry...")
133
+ await asyncio.sleep(retry_delay)
134
+ print(f"Processing task {task_id} (attempt {attempt + 1})...")
135
+ submitted_answer = await asyncio.wait_for(
136
+ agent(question_text, file_path),
137
+ timeout=atimeout # Increased timeout for audio processing
138
+ )
139
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
140
+ print(f"Completed task {task_id} with answer: {submitted_answer[:50]}...")
141
+
142
+ #Addl sleep
143
+ #await asyncio.sleep(retry_delay)
144
+ return {"task_id": task_id, "submitted_answer": submitted_answer}
145
+ except aiohttp.ClientResponseError as e:
146
+ if e.status == 429:
147
+ print(f"Rate limit hit for task {task_id}. Retrying after {retry_delay}s...")
148
+ retry_delay *= 2 # Exponential backoff
149
+ await asyncio.sleep(retry_delay)
150
+ continue
151
+ else:
152
+ submitted_answer = f"AGENT ERROR: {e}"
153
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
154
+ print(f"Failed task {task_id}: {submitted_answer}")
155
+ return None
156
+ except asyncio.TimeoutError:
157
+ submitted_answer = f"AGENT ERROR: Timeout after {atimeout} seconds"
158
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
159
+ print(f"Failed task {task_id}: {submitted_answer}")
160
+ return None
161
+ except Exception as e:
162
+ submitted_answer = f"AGENT ERROR: {e}"
163
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
164
+ print(f"Failed task {task_id}: {submitted_answer}")
165
+ return None
166
+
167
+ async def run_and_submit_all(profile: gr.OAuthProfile | None):
168
+ """
169
+ Fetches all questions asynchronously, runs the MagAgent on them, submits all answers,
170
+ and displays the results.
171
+ """
172
+ space_id = os.getenv("SPACE_ID")
173
+
174
+ if profile:
175
+ username = f"{profile.username}"
176
+ print(f"User logged in: {username}")
177
+ else:
178
+ print("User not logged in.")
179
+ return "Please Login to Hugging Face with the button.", None
180
+
181
+ api_url = DEFAULT_API_URL
182
+ questions_url = f"{api_url}/questions"
183
+ submit_url = f"{api_url}/submit"
184
+
185
+ try:
186
+ agent = MagAgent(rate_limiter=token_bucket)
187
+ except Exception as e:
188
+ print(f"Error instantiating agent: {e}")
189
+ return f"Error initializing agent: {e}", None
190
+
191
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
192
+ print(agent_code)
193
+
194
+ async with aiohttp.ClientSession() as session:
195
+ questions_data = await fetch_questions(session, questions_url)
196
+ if questions_data is None:
197
+ return "Error fetching questions.", None
198
+ if not questions_data:
199
+ return "Fetched questions list is empty or invalid format.", None
200
+
201
+ results_log = []
202
+ answers_payload = []
203
+ print(f"Running agent on {len(questions_data)} questions...")
204
+
205
+ for item in questions_data:
206
+ task_id = item.get("task_id")
207
+ question_text = item.get("question")
208
+ if not task_id or question_text is None:
209
+ print(f"Skipping item with missing task_id or question: {item}")
210
+ continue
211
+
212
+
213
+ #select question we want to focus
214
+ if "excel" in question_text.lower() or "mp3" in question_text.lower() or "chess" in question_text.lower():
215
+ file_path = await check_n_load_attach(session, task_id, question_text.lower())
216
+ result = await process_question(agent, question_text, task_id, file_path, results_log)
217
+ if result:
218
+ answers_payload.append(result)
219
+ else:
220
+ print("Skipping unrelated question.")
221
+
222
+ if not answers_payload:
223
+ print("Agent did not produce any answers to submit.")
224
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
225
+
226
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
227
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
228
+ print(status_update)
229
+
230
+ result_data = await submit_answers(session, submit_url, submission_data)
231
+ if result_data is None:
232
+ status_message = "Submission Failed."
233
+ print(status_message)
234
+ results_df = pd.DataFrame(results_log)
235
+ return status_message, results_df
236
+
237
+ final_status = (
238
+ f"Submission Successful!\n"
239
+ f"User: {result_data.get('username')}\n"
240
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
241
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
242
+ f"Message: {result_data.get('message', 'No message received.')}"
243
+ )
244
+ print("Submission successful.")
245
+ results_df = pd.DataFrame(results_log)
246
+ return final_status, results_df
247
+
248
+ # --- Build Gradio Interface using Blocks ---
249
+ with gr.Blocks() as demo:
250
+ gr.Markdown("# Magus Agent Evaluation Runner")
251
+ gr.Markdown(
252
+ """
253
+ **Instructions:**
254
+ 1. Log in to your Hugging Face account using the button below.
255
+ 2. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, and submit answers.
256
+ ---
257
+ **Notes:**
258
+ The agent uses asynchronous operations for efficiency. Answers are processed and submitted asynchronously.
259
+ """
260
+ )
261
+
262
+ gr.LoginButton()
263
+
264
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
265
+
266
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
267
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
268
+
269
+ run_button.click(
270
+ fn=run_and_submit_all,
271
+ outputs=[status_output, results_table]
272
+ )
273
+
274
+ if __name__ == "__main__":
275
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
276
+ space_host_startup = os.getenv("SPACE_HOST")
277
+ space_id_startup = os.getenv("SPACE_ID")
278
+
279
+ if space_host_startup:
280
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
281
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
282
+ else:
283
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
284
+
285
+ if space_id_startup:
286
+ print(f"✅ SPACE_ID found: {space_id_startup}")
287
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
288
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
289
+ else:
290
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?). Repo URL cannot be determined.")
291
+
292
+ print("-"*(60 + len(" App Starting ")) + "\n")
293
+
294
+ print("Launching Gradio Interface for Mag Agent Evaluation...")
295
+ demo.launch(debug=True, share=False)