GT5557 commited on
Commit
aabeef8
·
verified ·
1 Parent(s): 686824b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +245 -159
app.py CHANGED
@@ -1,279 +1,365 @@
1
  import os
2
  import time
3
  import threading
4
- import gradio as gr
5
  import requests
6
  import pandas as pd
 
 
7
  from langchain_core.messages import HumanMessage
8
- from agent import build_graph, extract_final_answer, sort_questions_by_difficulty, classify_difficulty, QUESTION_TIMEOUT_SECONDS
9
 
10
- # --- Constants ---
 
 
 
 
 
 
 
 
 
11
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
12
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  # ============================================================
15
- # THREADING-BASED TIMEOUT (works in Gradio worker threads)
16
  # ============================================================
17
 
18
  def run_with_timeout(fn, timeout_seconds):
19
  """
20
- Run fn() in a separate thread with a hard timeout.
21
- Returns (result, timed_out).
22
- Uses threading instead of signal — safe in non-main threads.
23
  """
24
- result_container = {"result": None, "error": None, "done": False}
 
 
 
 
25
 
26
  def target():
27
  try:
28
- result_container["result"] = fn()
29
  except Exception as e:
30
- result_container["error"] = e
31
  finally:
32
- result_container["done"] = True
33
 
34
  thread = threading.Thread(target=target, daemon=True)
35
  thread.start()
36
- thread.join(timeout=timeout_seconds)
37
 
38
- if not result_container["done"]:
39
- # Thread is still running — treat as timeout
40
- # The daemon thread will be killed when the process exits
41
  return None, True
42
 
43
- if result_container["error"] is not None:
44
- raise result_container["error"]
45
 
46
- return result_container["result"], False
47
 
48
 
49
  # ============================================================
50
- # AGENT CLASS
51
  # ============================================================
52
 
53
- class BasicAgent:
54
- """A LangGraph agent with multi-model fallback."""
55
-
56
  def __init__(self):
57
- print("BasicAgent (LangGraph) initialized.")
58
  self.graph = build_graph()
59
 
60
  def __call__(self, question: str) -> str:
61
- print(f"Agent received question: {question[:80]}...")
 
 
62
 
63
- inputs = {"messages": [HumanMessage(content=question)]}
64
  result = self.graph.invoke(inputs)
 
65
  answer = extract_final_answer(result)
66
 
67
  if not answer or not answer.strip():
68
- answer = "N/A"
69
 
70
- print(f"Final answer: {answer}")
71
- return answer
72
 
73
 
74
  # ============================================================
75
- # MAIN EVALUATION FUNCTION
76
  # ============================================================
77
 
78
- def run_and_submit_all(profile: gr.OAuthProfile | None):
79
- """Fetches questions, sorts by difficulty, runs agent with timeout, submits."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
 
81
  space_id = os.getenv("SPACE_ID")
 
82
  if not space_id:
83
- return "SPACE_ID environment variable is not set.", None
 
 
 
84
 
85
- if profile:
86
- username = profile.username
87
- print(f"Logged in as: {username}")
88
- else:
89
- return "Please Login to Hugging Face with the button.", None
90
 
91
  api_url = DEFAULT_API_URL
 
92
  questions_url = f"{api_url}/questions"
93
  submit_url = f"{api_url}/submit"
 
94
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
95
 
96
- # Instantiate Agent
 
 
 
97
  try:
98
- agent = BasicAgent()
99
  except Exception as e:
100
- return f"Error initializing agent: {e}", None
 
 
 
 
101
 
102
- # Fetch Questions
103
  try:
104
- print(f"Fetching questions from {questions_url}...")
105
- response = requests.get(questions_url, timeout=15)
106
- response.raise_for_status()
107
- questions_data = response.json()
108
- print(f"Fetched {len(questions_data)} questions.")
109
  except Exception as e:
110
- return f"Error fetching questions: {e}", None
 
 
 
 
 
111
 
112
- if not questions_data:
113
- return "No questions received from the server.", None
114
 
115
- # ✅ Sort questions: simple → medium → hard
116
- original_count = len(questions_data)
117
- questions_data = sort_questions_by_difficulty(questions_data)
118
 
119
- print(f"\n📊 Question difficulty breakdown:")
120
- for item in questions_data:
121
- q = item.get("question", "")
122
- d = classify_difficulty(q)
123
- print(f" [{d.upper():6}] {q[:60]}...")
124
 
125
- # Run Agent on Each Question
126
- results_log = []
127
- answers_payload = []
128
- timed_out_count = 0
129
 
130
- for i, item in enumerate(questions_data):
131
  task_id = item.get("task_id")
132
- question_text = item.get("question")
133
- difficulty = classify_difficulty(question_text)
134
 
135
- # Give hard questions slightly more time
136
- timeout = QUESTION_TIMEOUT_SECONDS
137
- if difficulty == "simple":
138
- timeout = 60
139
- elif difficulty == "medium":
140
- timeout = 90
141
- else:
142
- timeout = 120 # hard questions get 2 minutes
143
 
144
- print(f"\n--- Question {i+1}/{original_count} | [{difficulty.upper()}] | timeout={timeout}s | Task ID: {task_id} ---")
145
- print(f"Q: {question_text[:100]}...")
146
 
147
- start_time = time.time()
148
- submitted_answer = "N/A"
149
- status = "SKIPPED"
150
 
151
  try:
152
- def run_agent():
153
- return agent(question_text)
154
 
155
- result, did_timeout = run_with_timeout(run_agent, timeout)
156
- elapsed = time.time() - start_time
 
 
 
 
157
 
158
  if did_timeout:
159
- print(f"⏰ TIMED OUT after {elapsed:.1f}s — submitting N/A.")
160
- timed_out_count += 1
161
- status = f"TIMEOUT ({elapsed:.1f}s)"
162
- submitted_answer = "N/A"
163
  else:
164
- submitted_answer = result if result else "N/A"
165
- print(f"✅ Answered in {elapsed:.1f}s: {submitted_answer}")
166
- status = f"OK ({elapsed:.1f}s)"
 
 
 
167
 
168
  except Exception as e:
169
- elapsed = time.time() - start_time
170
- print(f"❌ Error on task {task_id} after {elapsed:.1f}s: {e}")
171
- submitted_answer = "N/A"
172
- status = f"ERROR ({elapsed:.1f}s)"
173
 
174
- # Always append — even failed questions
175
- answers_payload.append({
176
  "task_id": task_id,
177
- "submitted_answer": str(submitted_answer).strip()
178
  })
179
- results_log.append({
 
 
180
  "Task ID": task_id,
181
  "Difficulty": difficulty.upper(),
182
- "Question": question_text[:80],
183
- "Submitted Answer": submitted_answer,
184
- "Status": status
185
  })
186
 
187
- # Short delay between questions
188
- delay = 2 if difficulty == "simple" else 4
189
- time.sleep(delay)
 
 
 
 
190
 
191
- if not answers_payload:
192
- return "Agent failed on all questions.", pd.DataFrame(results_log)
 
193
 
194
- # Summary before submit
195
- answered = sum(1 for r in results_log if r["Submitted Answer"] != "N/A")
196
- print(f"\n📊 Summary: {answered}/{original_count} answered | {timed_out_count} timed out")
 
 
 
197
 
198
- # Submit Answers
199
- submission_data = {
200
- "username": username.strip(),
 
 
 
201
  "agent_code": agent_code,
202
- "answers": answers_payload
203
  }
204
 
205
- print(f"\nSubmitting {len(answers_payload)} answers for user '{username}'...")
206
-
207
  try:
208
- response = requests.post(submit_url, json=submission_data, timeout=60)
209
- response.raise_for_status()
210
- result_data = response.json()
211
-
212
- final_status = (
213
- f"✅ Submission Successful!\n"
214
- f"User: {result_data.get('username')}\n"
215
- f"Score: {result_data.get('score', 'N/A')}%\n"
216
- f"Correct: {result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')}\n"
217
- f"Answered: {answered}/{original_count} | Timed out: {timed_out_count}\n"
218
- f"Message: {result_data.get('message', '')}"
 
219
  )
220
- print(final_status)
221
- return final_status, pd.DataFrame(results_log)
222
 
223
- except requests.exceptions.HTTPError as e:
224
- try:
225
- error_detail = e.response.json()
226
- except Exception:
227
- error_detail = e.response.text
228
- msg = f"❌ Submission Failed (HTTP {e.response.status_code}):\n{error_detail}"
229
- print(msg)
230
- return msg, pd.DataFrame(results_log)
231
 
232
  except Exception as e:
233
- msg = f"❌ Submission Failed: {e}"
234
- print(msg)
235
- return msg, pd.DataFrame(results_log)
 
 
 
 
 
236
 
237
 
238
  # ============================================================
239
- # GRADIO UI
240
  # ============================================================
241
 
242
  with gr.Blocks() as demo:
243
- gr.Markdown("# 🤖 LangGraph GAIA Benchmark Agent")
244
-
245
- gr.Markdown("""
246
- **Strategy:**
247
- - ✅ Questions sorted: Simple → Medium → Hard
248
- - ⏰ Timeouts: Simple=60s | Medium=90s | Hard=120s
249
- - 🎯 Target: 30%+ success rate on simple questions first
250
- - 🔄 Model stack: Qwen3 32B → Llama 3.3 70B → Llama 3.1 8B
251
-
252
- **Instructions:**
253
- 1. Login with Hugging Face
254
- 2. Click **Run Evaluation**
255
- 3. Agent answers all questions and submits automatically
256
- """)
257
 
258
  gr.LoginButton()
259
 
260
- run_button = gr.Button("Run Evaluation and Submit All Answers", variant="primary", size="lg")
 
 
 
 
261
 
262
- status_output = gr.Textbox(
263
- label="Submission Status",
264
- lines=8,
265
  interactive=False
266
  )
267
 
268
  results_table = gr.DataFrame(
269
- label="Agent Performance per Question",
270
  wrap=True
271
  )
272
 
273
- run_button.click(
274
  fn=run_and_submit_all,
275
- outputs=[status_output, results_table]
276
  )
277
 
 
 
 
 
278
  if __name__ == "__main__":
279
  demo.launch(debug=True)
 
1
  import os
2
  import time
3
  import threading
 
4
  import requests
5
  import pandas as pd
6
+ import gradio as gr
7
+
8
  from langchain_core.messages import HumanMessage
 
9
 
10
+ from agent import (
11
+ build_graph,
12
+ extract_final_answer,
13
+ classify_question,
14
+ )
15
+
16
+ # ============================================================
17
+ # CONFIG
18
+ # ============================================================
19
+
20
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
21
 
22
+ FETCH_RETRIES = 3
23
+ SUBMIT_RETRIES = 3
24
+
25
+ SLEEP_SIMPLE = 0.5
26
+ SLEEP_MEDIUM = 1
27
+ SLEEP_HARD = 1.5
28
+
29
+ TIMEOUTS = {
30
+ "simple": 25,
31
+ "medium": 45,
32
+ "hard": 75
33
+ }
34
 
35
  # ============================================================
36
+ # THREAD TIMEOUT
37
  # ============================================================
38
 
39
  def run_with_timeout(fn, timeout_seconds):
40
  """
41
+ Run fn() in background thread.
42
+ Returns (result, timed_out)
 
43
  """
44
+ state = {
45
+ "done": False,
46
+ "result": None,
47
+ "error": None
48
+ }
49
 
50
  def target():
51
  try:
52
+ state["result"] = fn()
53
  except Exception as e:
54
+ state["error"] = e
55
  finally:
56
+ state["done"] = True
57
 
58
  thread = threading.Thread(target=target, daemon=True)
59
  thread.start()
60
+ thread.join(timeout_seconds)
61
 
62
+ if not state["done"]:
 
 
63
  return None, True
64
 
65
+ if state["error"]:
66
+ raise state["error"]
67
 
68
+ return state["result"], False
69
 
70
 
71
  # ============================================================
72
+ # AGENT WRAPPER
73
  # ============================================================
74
 
75
+ class BenchmarkAgent:
 
 
76
  def __init__(self):
 
77
  self.graph = build_graph()
78
 
79
  def __call__(self, question: str) -> str:
80
+ inputs = {
81
+ "messages": [HumanMessage(content=question)]
82
+ }
83
 
 
84
  result = self.graph.invoke(inputs)
85
+
86
  answer = extract_final_answer(result)
87
 
88
  if not answer or not answer.strip():
89
+ return "N/A"
90
 
91
+ return answer.strip()
 
92
 
93
 
94
  # ============================================================
95
+ # HELPERS
96
  # ============================================================
97
 
98
+ def fetch_questions(url):
99
+ last_err = None
100
+
101
+ for i in range(FETCH_RETRIES):
102
+ try:
103
+ r = requests.get(url, timeout=20)
104
+ r.raise_for_status()
105
+ return r.json()
106
+
107
+ except Exception as e:
108
+ last_err = e
109
+ time.sleep(2)
110
+
111
+ raise RuntimeError(last_err)
112
+
113
+
114
+ def submit_answers(url, payload):
115
+ last_err = None
116
+
117
+ for i in range(SUBMIT_RETRIES):
118
+ try:
119
+ r = requests.post(url, json=payload, timeout=60)
120
+ r.raise_for_status()
121
+ return r.json()
122
+
123
+ except Exception as e:
124
+ last_err = e
125
+ time.sleep(3)
126
+
127
+ raise RuntimeError(last_err)
128
+
129
+
130
+ def sort_questions(questions):
131
+ order = {
132
+ "simple": 0,
133
+ "medium": 1,
134
+ "hard": 2
135
+ }
136
+
137
+ return sorted(
138
+ questions,
139
+ key=lambda x: order.get(
140
+ classify_question(x.get("question", "")),
141
+ 9
142
+ )
143
+ )
144
+
145
+
146
+ # ============================================================
147
+ # MAIN RUNNER
148
+ # ============================================================
149
 
150
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
151
  space_id = os.getenv("SPACE_ID")
152
+
153
  if not space_id:
154
+ return "SPACE_ID not set.", None
155
+
156
+ if not profile:
157
+ return "Please login with Hugging Face.", None
158
 
159
+ username = profile.username.strip()
 
 
 
 
160
 
161
  api_url = DEFAULT_API_URL
162
+
163
  questions_url = f"{api_url}/questions"
164
  submit_url = f"{api_url}/submit"
165
+
166
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
167
 
168
+ # --------------------------------------------------------
169
+ # INIT AGENT
170
+ # --------------------------------------------------------
171
+
172
  try:
173
+ agent = BenchmarkAgent()
174
  except Exception as e:
175
+ return f"Agent init failed: {e}", None
176
+
177
+ # --------------------------------------------------------
178
+ # FETCH QUESTIONS
179
+ # --------------------------------------------------------
180
 
 
181
  try:
182
+ questions = fetch_questions(questions_url)
 
 
 
 
183
  except Exception as e:
184
+ return f"Failed to fetch questions: {e}", None
185
+
186
+ if not questions:
187
+ return "No questions returned.", None
188
+
189
+ questions = sort_questions(questions)
190
 
191
+ total = len(questions)
 
192
 
193
+ # --------------------------------------------------------
194
+ # RUN QUESTIONS
195
+ # --------------------------------------------------------
196
 
197
+ logs = []
198
+ answers = []
 
 
 
199
 
200
+ answered = 0
201
+ timed_out = 0
202
+ errors = 0
 
203
 
204
+ for idx, item in enumerate(questions, start=1):
205
  task_id = item.get("task_id")
206
+ question = item.get("question", "")
 
207
 
208
+ difficulty = classify_question(question)
209
+ timeout = TIMEOUTS[difficulty]
 
 
 
 
 
 
210
 
211
+ start = time.time()
 
212
 
213
+ answer = "N/A"
214
+ status = "UNKNOWN"
 
215
 
216
  try:
217
+ def solve():
218
+ return agent(question)
219
 
220
+ result, did_timeout = run_with_timeout(
221
+ solve,
222
+ timeout
223
+ )
224
+
225
+ elapsed = round(time.time() - start, 1)
226
 
227
  if did_timeout:
228
+ timed_out += 1
229
+ status = f"TIMEOUT ({elapsed}s)"
230
+ answer = "N/A"
231
+
232
  else:
233
+ answer = result if result else "N/A"
234
+
235
+ if answer != "N/A":
236
+ answered += 1
237
+
238
+ status = f"OK ({elapsed}s)"
239
 
240
  except Exception as e:
241
+ elapsed = round(time.time() - start, 1)
242
+ errors += 1
243
+ status = f"ERROR ({elapsed}s)"
244
+ answer = "N/A"
245
 
246
+ answers.append({
 
247
  "task_id": task_id,
248
+ "submitted_answer": str(answer).strip()
249
  })
250
+
251
+ logs.append({
252
+ "No": idx,
253
  "Task ID": task_id,
254
  "Difficulty": difficulty.upper(),
255
+ "Submitted Answer": answer,
256
+ "Status": status,
257
+ "Question": question[:120]
258
  })
259
 
260
+ # small pacing delay
261
+ if difficulty == "simple":
262
+ time.sleep(SLEEP_SIMPLE)
263
+ elif difficulty == "medium":
264
+ time.sleep(SLEEP_MEDIUM)
265
+ else:
266
+ time.sleep(SLEEP_HARD)
267
 
268
+ # --------------------------------------------------------
269
+ # SAVE LOCAL CSV
270
+ # --------------------------------------------------------
271
 
272
+ df = pd.DataFrame(logs)
273
+
274
+ try:
275
+ df.to_csv("last_run_results.csv", index=False)
276
+ except Exception:
277
+ pass
278
 
279
+ # --------------------------------------------------------
280
+ # SUBMIT
281
+ # --------------------------------------------------------
282
+
283
+ payload = {
284
+ "username": username,
285
  "agent_code": agent_code,
286
+ "answers": answers
287
  }
288
 
 
 
289
  try:
290
+ result = submit_answers(submit_url, payload)
291
+
292
+ status = (
293
+ f"SUCCESS\n\n"
294
+ f"User: {result.get('username')}\n"
295
+ f"Score: {result.get('score', 'N/A')}%\n"
296
+ f"Correct: {result.get('correct_count', '?')}/"
297
+ f"{result.get('total_attempted', '?')}\n"
298
+ f"Answered: {answered}/{total}\n"
299
+ f"Timeouts: {timed_out}\n"
300
+ f"Errors: {errors}\n"
301
+ f"{result.get('message', '')}"
302
  )
 
 
303
 
304
+ return status, df
 
 
 
 
 
 
 
305
 
306
  except Exception as e:
307
+ fail = (
308
+ f"Submission failed:\n{e}\n\n"
309
+ f"Answered: {answered}/{total}\n"
310
+ f"Timeouts: {timed_out}\n"
311
+ f"Errors: {errors}"
312
+ )
313
+
314
+ return fail, df
315
 
316
 
317
  # ============================================================
318
+ # UI
319
  # ============================================================
320
 
321
  with gr.Blocks() as demo:
322
+ gr.Markdown("# Benchmark Agent Submission Tool")
323
+
324
+ gr.Markdown(
325
+ """
326
+ ### Current Strategy
327
+
328
+ - Simple -> Medium -> Hard
329
+ - Fast model routing in backend
330
+ - Auto timeouts
331
+ - Auto submit
332
+ - CSV logs saved
333
+ """
334
+ )
 
335
 
336
  gr.LoginButton()
337
 
338
+ run_btn = gr.Button(
339
+ "Run Evaluation and Submit",
340
+ variant="primary",
341
+ size="lg"
342
+ )
343
 
344
+ status_box = gr.Textbox(
345
+ label="Status",
346
+ lines=12,
347
  interactive=False
348
  )
349
 
350
  results_table = gr.DataFrame(
351
+ label="Per Question Results",
352
  wrap=True
353
  )
354
 
355
+ run_btn.click(
356
  fn=run_and_submit_all,
357
+ outputs=[status_box, results_table]
358
  )
359
 
360
+ # ============================================================
361
+ # MAIN
362
+ # ============================================================
363
+
364
  if __name__ == "__main__":
365
  demo.launch(debug=True)