avi080704 commited on
Commit
6d91d29
·
verified ·
1 Parent(s): 908c372

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -62
app.py CHANGED
@@ -18,6 +18,7 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
18
  GROQ_MODELS = [
19
  m.strip()
20
  for m in os.getenv(
 
21
  "llama-3.3-70b-versatile,llama-3.1-8b-instant",
22
  ).split(",")
23
  if m.strip()
@@ -47,7 +48,7 @@ def tool_web_search(query: str, max_results: int = 5) -> str:
47
  return f"web_search error: {e}"
48
 
49
 
50
- def tool_fetch_url(url: str, max_chars: int = 6000) -> str:
51
  """Fetch a URL and return readable text (HTML stripped)."""
52
  try:
53
  from bs4 import BeautifulSoup
@@ -75,7 +76,7 @@ def tool_fetch_url(url: str, max_chars: int = 6000) -> str:
75
  return f"fetch_url error: {e}"
76
 
77
 
78
- def tool_wikipedia(query: str, sentences: int = 6) -> str:
79
  """Look up a topic on Wikipedia and return a summary."""
80
  try:
81
  import wikipedia
@@ -133,7 +134,7 @@ def tool_get_task_file(task_id: str, api_url: str = DEFAULT_API_URL) -> str:
133
  text = resp.content.decode("utf-8", errors="replace")
134
  except Exception:
135
  text = resp.text
136
- return info + "\n--- preview ---\n" + text[:6000]
137
 
138
  if suffix in {".xlsx", ".xls"}:
139
  try:
@@ -146,8 +147,8 @@ def tool_get_task_file(task_id: str, api_url: str = DEFAULT_API_URL) -> str:
146
  try:
147
  from pypdf import PdfReader
148
  reader = PdfReader(tmp.name)
149
- pages = [p.extract_text() or "" for p in reader.pages[:10]]
150
- return info + "\n--- pdf text (first 10 pages) ---\n" + "\n".join(pages)[:6000]
151
  except Exception as e:
152
  return info + f"\n(pdf parse error: {e})"
153
 
@@ -190,7 +191,7 @@ TOOLS_SPEC = [
190
  "type": "object",
191
  "properties": {
192
  "url": {"type": "string"},
193
- "max_chars": {"type": "integer", "default": 6000},
194
  },
195
  "required": ["url"],
196
  },
@@ -205,7 +206,7 @@ TOOLS_SPEC = [
205
  "type": "object",
206
  "properties": {
207
  "query": {"type": "string"},
208
- "sentences": {"type": "integer", "default": 6},
209
  },
210
  "required": ["query"],
211
  },
@@ -239,8 +240,8 @@ TOOLS_SPEC = [
239
 
240
  TOOL_FUNCTIONS = {
241
  "web_search": lambda args: tool_web_search(args["query"], int(args.get("max_results", 5))),
242
- "fetch_url": lambda args: tool_fetch_url(args["url"], int(args.get("max_chars", 6000))),
243
- "wikipedia": lambda args: tool_wikipedia(args["query"], int(args.get("sentences", 6))),
244
  "python": lambda args: tool_python(args["code"]),
245
  "get_task_file": lambda args: tool_get_task_file(args["task_id"]),
246
  }
@@ -283,9 +284,61 @@ class GroqAgent:
283
  "GROQ_API_KEY is not set. Add it as a Secret in your HF Space settings."
284
  )
285
  self.client = Groq(api_key=api_key)
286
- self.model = GROQ_MODEL
287
- print(f"GroqAgent initialized with model={self.model}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
 
 
 
 
 
 
 
 
 
 
 
289
  def __call__(self, question: str, task_id: str | None = None) -> str:
290
  user_content = question
291
  if task_id:
@@ -298,16 +351,8 @@ class GroqAgent:
298
 
299
  for step in range(MAX_TOOL_ITERATIONS):
300
  try:
301
- resp = self.client.chat.completions.create(
302
- model=self.model,
303
- messages=messages,
304
- tools=TOOLS_SPEC,
305
- tool_choice="auto",
306
- temperature=0.0,
307
- max_tokens=1024,
308
- )
309
  except Exception as e:
310
- print(f"Groq API error: {e}")
311
  return f"AGENT ERROR: {e}"
312
 
313
  msg = resp.choices[0].message
@@ -317,7 +362,6 @@ class GroqAgent:
317
  answer = (msg.content or "").strip()
318
  return self._postprocess_answer(answer)
319
 
320
- # Append assistant message with the tool calls
321
  messages.append(
322
  {
323
  "role": "assistant",
@@ -354,8 +398,8 @@ class GroqAgent:
354
 
355
  if not isinstance(result, str):
356
  result = str(result)
357
- if len(result) > 8000:
358
- result = result[:8000] + "\n...[truncated]"
359
 
360
  messages.append(
361
  {
@@ -366,7 +410,7 @@ class GroqAgent:
366
  }
367
  )
368
 
369
- # Out of iterations: ask for a final, no-tool answer
370
  messages.append(
371
  {
372
  "role": "user",
@@ -374,12 +418,7 @@ class GroqAgent:
374
  }
375
  )
376
  try:
377
- resp = self.client.chat.completions.create(
378
- model=self.model,
379
- messages=messages,
380
- temperature=0.0,
381
- max_tokens=256,
382
- )
383
  return self._postprocess_answer((resp.choices[0].message.content or "").strip())
384
  except Exception as e:
385
  return f"AGENT ERROR: {e}"
@@ -388,15 +427,32 @@ class GroqAgent:
388
  def _postprocess_answer(text: str) -> str:
389
  if not text:
390
  return ""
391
- # Strip common prefixes the model may sneak in despite instructions.
392
  text = text.strip()
393
  text = re.sub(r"^(final answer|answer)\s*:\s*", "", text, flags=re.IGNORECASE)
394
- # Remove surrounding quotes/backticks
395
  if len(text) >= 2 and text[0] == text[-1] and text[0] in {'"', "'", "`"}:
396
  text = text[1:-1].strip()
397
  return text
398
 
399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  # ---------------------------------------------------------------------------
401
  # Gradio submission flow
402
  # ---------------------------------------------------------------------------
@@ -437,6 +493,9 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
437
 
438
  results_log = []
439
  answers_payload = []
 
 
 
440
  print(f"Running agent on {len(questions_data)} questions...")
441
  for idx, item in enumerate(questions_data, 1):
442
  task_id = item.get("task_id")
@@ -445,11 +504,18 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
445
  print(f"Skipping item with missing task_id or question: {item}")
446
  continue
447
  print(f"\n=== [{idx}/{len(questions_data)}] task_id={task_id} ===")
448
- try:
449
- submitted_answer = agent(question_text, task_id=task_id)
450
- except Exception as e:
451
- print(f"Error running agent on task {task_id}: {e}")
452
- submitted_answer = f"AGENT ERROR: {e}"
 
 
 
 
 
 
 
453
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
454
  results_log.append(
455
  {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}
@@ -465,31 +531,50 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
465
  }
466
  print(f"Submitting {len(answers_payload)} answers for user '{username}'...")
467
 
468
- try:
469
- response = requests.post(submit_url, json=submission_data, timeout=120)
470
- response.raise_for_status()
471
- result_data = response.json()
472
- final_status = (
473
- f"Submission Successful!\n"
474
- f"User: {result_data.get('username')}\n"
475
- f"Overall Score: {result_data.get('score', 'N/A')}% "
476
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
477
- f"Message: {result_data.get('message', 'No message received.')}"
478
- )
479
- return final_status, pd.DataFrame(results_log)
480
- except requests.exceptions.HTTPError as e:
481
- error_detail = f"Server responded with status {e.response.status_code}."
482
  try:
483
- error_detail += f" Detail: {e.response.json().get('detail', e.response.text)}"
484
- except requests.exceptions.JSONDecodeError:
485
- error_detail += f" Response: {e.response.text[:500]}"
486
- return f"Submission Failed: {error_detail}", pd.DataFrame(results_log)
487
- except requests.exceptions.Timeout:
488
- return "Submission Failed: The request timed out.", pd.DataFrame(results_log)
489
- except requests.exceptions.RequestException as e:
490
- return f"Submission Failed: Network error - {e}", pd.DataFrame(results_log)
491
- except Exception as e:
492
- return f"An unexpected error occurred during submission: {e}", pd.DataFrame(results_log)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
 
494
 
495
  # --- Gradio UI ---
@@ -499,10 +584,11 @@ with gr.Blocks() as demo:
499
  """
500
  **Setup**
501
  1. Add a Space secret named `GROQ_API_KEY` with your Groq API key.
502
- 2. Optional: set `GROQ_MODEL` (default `llama-3.3-70b-versatile`).
503
  3. Log in to Hugging Face below and click **Run Evaluation & Submit All Answers**.
504
 
505
  Tools available to the agent: `web_search`, `fetch_url`, `wikipedia`, `python`, `get_task_file`.
 
506
  """
507
  )
508
 
 
18
  GROQ_MODELS = [
19
  m.strip()
20
  for m in os.getenv(
21
+ "GROQ_MODELS",
22
  "llama-3.3-70b-versatile,llama-3.1-8b-instant",
23
  ).split(",")
24
  if m.strip()
 
48
  return f"web_search error: {e}"
49
 
50
 
51
+ def tool_fetch_url(url: str, max_chars: int = 3000) -> str:
52
  """Fetch a URL and return readable text (HTML stripped)."""
53
  try:
54
  from bs4 import BeautifulSoup
 
76
  return f"fetch_url error: {e}"
77
 
78
 
79
+ def tool_wikipedia(query: str, sentences: int = 4) -> str:
80
  """Look up a topic on Wikipedia and return a summary."""
81
  try:
82
  import wikipedia
 
134
  text = resp.content.decode("utf-8", errors="replace")
135
  except Exception:
136
  text = resp.text
137
+ return info + "\n--- preview ---\n" + text[:3000]
138
 
139
  if suffix in {".xlsx", ".xls"}:
140
  try:
 
147
  try:
148
  from pypdf import PdfReader
149
  reader = PdfReader(tmp.name)
150
+ pages = [p.extract_text() or "" for p in reader.pages[:8]]
151
+ return info + "\n--- pdf text (first 8 pages) ---\n" + "\n".join(pages)[:3000]
152
  except Exception as e:
153
  return info + f"\n(pdf parse error: {e})"
154
 
 
191
  "type": "object",
192
  "properties": {
193
  "url": {"type": "string"},
194
+ "max_chars": {"type": "integer", "default": 3000},
195
  },
196
  "required": ["url"],
197
  },
 
206
  "type": "object",
207
  "properties": {
208
  "query": {"type": "string"},
209
+ "sentences": {"type": "integer", "default": 4},
210
  },
211
  "required": ["query"],
212
  },
 
240
 
241
  TOOL_FUNCTIONS = {
242
  "web_search": lambda args: tool_web_search(args["query"], int(args.get("max_results", 5))),
243
+ "fetch_url": lambda args: tool_fetch_url(args["url"], int(args.get("max_chars", 3000))),
244
+ "wikipedia": lambda args: tool_wikipedia(args["query"], int(args.get("sentences", 4))),
245
  "python": lambda args: tool_python(args["code"]),
246
  "get_task_file": lambda args: tool_get_task_file(args["task_id"]),
247
  }
 
284
  "GROQ_API_KEY is not set. Add it as a Secret in your HF Space settings."
285
  )
286
  self.client = Groq(api_key=api_key)
287
+ self.models = list(GROQ_MODELS)
288
+ # Track models that hit a daily-token cap; skip them for the rest of the run.
289
+ self.exhausted_models: set[str] = set()
290
+ print(f"GroqAgent initialized with models={self.models}")
291
+
292
+ # ---- Groq call with model fallback + 429 handling -------------------
293
+ def _chat(self, messages, use_tools: bool = True, max_tokens: int = 1024):
294
+ last_error: Exception | None = None
295
+ for model in self.models:
296
+ if model in self.exhausted_models:
297
+ continue
298
+ for attempt in range(3):
299
+ try:
300
+ kwargs = dict(
301
+ model=model,
302
+ messages=messages,
303
+ temperature=0.0,
304
+ max_tokens=max_tokens,
305
+ )
306
+ if use_tools:
307
+ kwargs["tools"] = TOOLS_SPEC
308
+ kwargs["tool_choice"] = "auto"
309
+ return self.client.chat.completions.create(**kwargs)
310
+ except Exception as e:
311
+ msg = str(e)
312
+ last_error = e
313
+ is_429 = "429" in msg or "rate_limit" in msg.lower()
314
+ is_tpd = "per day" in msg.lower() or "tpd" in msg.lower()
315
+ if is_429 and is_tpd:
316
+ # Daily quota gone — switch model permanently for this run.
317
+ print(f"[{model}] daily token limit exhausted; switching model.")
318
+ self.exhausted_models.add(model)
319
+ break
320
+ if is_429:
321
+ wait = self._parse_retry_seconds(msg)
322
+ wait = min(max(wait, 2), 30)
323
+ print(f"[{model}] 429 rate limit; sleeping {wait}s (attempt {attempt + 1}/3)")
324
+ time.sleep(wait)
325
+ continue
326
+ # Non-429 error: don't retry on the same model.
327
+ print(f"[{model}] API error: {e}")
328
+ break
329
+ raise RuntimeError(f"All Groq models failed. Last error: {last_error}")
330
 
331
+ @staticmethod
332
+ def _parse_retry_seconds(error_msg: str) -> float:
333
+ # Examples in Groq error: "Please try again in 7m18.912s." or "in 12.3s"
334
+ m = re.search(r"in\s+(?:(\d+)m)?([\d.]+)s", error_msg)
335
+ if not m:
336
+ return 5.0
337
+ minutes = int(m.group(1)) if m.group(1) else 0
338
+ seconds = float(m.group(2)) if m.group(2) else 0.0
339
+ return minutes * 60 + seconds
340
+
341
+ # ---- Main entrypoint ------------------------------------------------
342
  def __call__(self, question: str, task_id: str | None = None) -> str:
343
  user_content = question
344
  if task_id:
 
351
 
352
  for step in range(MAX_TOOL_ITERATIONS):
353
  try:
354
+ resp = self._chat(messages, use_tools=True, max_tokens=1024)
 
 
 
 
 
 
 
355
  except Exception as e:
 
356
  return f"AGENT ERROR: {e}"
357
 
358
  msg = resp.choices[0].message
 
362
  answer = (msg.content or "").strip()
363
  return self._postprocess_answer(answer)
364
 
 
365
  messages.append(
366
  {
367
  "role": "assistant",
 
398
 
399
  if not isinstance(result, str):
400
  result = str(result)
401
+ if len(result) > TOOL_RESULT_MAX_CHARS:
402
+ result = result[:TOOL_RESULT_MAX_CHARS] + "\n...[truncated]"
403
 
404
  messages.append(
405
  {
 
410
  }
411
  )
412
 
413
+ # Out of tool iterations: ask for a final, no-tool answer.
414
  messages.append(
415
  {
416
  "role": "user",
 
418
  }
419
  )
420
  try:
421
+ resp = self._chat(messages, use_tools=False, max_tokens=256)
 
 
 
 
 
422
  return self._postprocess_answer((resp.choices[0].message.content or "").strip())
423
  except Exception as e:
424
  return f"AGENT ERROR: {e}"
 
427
  def _postprocess_answer(text: str) -> str:
428
  if not text:
429
  return ""
 
430
  text = text.strip()
431
  text = re.sub(r"^(final answer|answer)\s*:\s*", "", text, flags=re.IGNORECASE)
 
432
  if len(text) >= 2 and text[0] == text[-1] and text[0] in {'"', "'", "`"}:
433
  text = text[1:-1].strip()
434
  return text
435
 
436
 
437
+ # ---------------------------------------------------------------------------
438
+ # Answer cache so a failed run doesn't waste tokens
439
+ # ---------------------------------------------------------------------------
440
+ def _load_cache() -> dict:
441
+ try:
442
+ with open(ANSWER_CACHE_PATH, "r", encoding="utf-8") as f:
443
+ return json.load(f)
444
+ except (FileNotFoundError, json.JSONDecodeError):
445
+ return {}
446
+
447
+
448
+ def _save_cache(cache: dict) -> None:
449
+ try:
450
+ with open(ANSWER_CACHE_PATH, "w", encoding="utf-8") as f:
451
+ json.dump(cache, f, ensure_ascii=False, indent=2)
452
+ except Exception as e:
453
+ print(f"cache save error: {e}")
454
+
455
+
456
  # ---------------------------------------------------------------------------
457
  # Gradio submission flow
458
  # ---------------------------------------------------------------------------
 
493
 
494
  results_log = []
495
  answers_payload = []
496
+ cache = _load_cache()
497
+ if cache:
498
+ print(f"Loaded {len(cache)} cached answers from {ANSWER_CACHE_PATH}")
499
  print(f"Running agent on {len(questions_data)} questions...")
500
  for idx, item in enumerate(questions_data, 1):
501
  task_id = item.get("task_id")
 
504
  print(f"Skipping item with missing task_id or question: {item}")
505
  continue
506
  print(f"\n=== [{idx}/{len(questions_data)}] task_id={task_id} ===")
507
+ cached = cache.get(task_id)
508
+ if cached and not str(cached).startswith("AGENT ERROR"):
509
+ submitted_answer = cached
510
+ print(f"(cache hit) {submitted_answer[:80]}")
511
+ else:
512
+ try:
513
+ submitted_answer = agent(question_text, task_id=task_id)
514
+ except Exception as e:
515
+ print(f"Error running agent on task {task_id}: {e}")
516
+ submitted_answer = f"AGENT ERROR: {e}"
517
+ cache[task_id] = submitted_answer
518
+ _save_cache(cache)
519
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
520
  results_log.append(
521
  {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}
 
531
  }
532
  print(f"Submitting {len(answers_payload)} answers for user '{username}'...")
533
 
534
+ # Retry submission a few times — the leaderboard's HF dataset write is flaky.
535
+ last_error = None
536
+ for attempt in range(3):
 
 
 
 
 
 
 
 
 
 
 
537
  try:
538
+ response = requests.post(submit_url, json=submission_data, timeout=120)
539
+ response.raise_for_status()
540
+ result_data = response.json()
541
+ final_status = (
542
+ f"Submission Successful!\n"
543
+ f"User: {result_data.get('username')}\n"
544
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
545
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
546
+ f"Message: {result_data.get('message', 'No message received.')}"
547
+ )
548
+ return final_status, pd.DataFrame(results_log)
549
+ except requests.exceptions.HTTPError as e:
550
+ status = e.response.status_code if e.response is not None else "?"
551
+ last_error = e
552
+ print(f"Submission attempt {attempt + 1} failed: HTTP {status}")
553
+ if status and 500 <= int(status) < 600:
554
+ time.sleep(5 * (attempt + 1))
555
+ continue
556
+ error_detail = f"Server responded with status {status}."
557
+ try:
558
+ error_detail += f" Detail: {e.response.json().get('detail', e.response.text)}"
559
+ except Exception:
560
+ error_detail += f" Response: {e.response.text[:500] if e.response is not None else ''}"
561
+ return f"Submission Failed: {error_detail}", pd.DataFrame(results_log)
562
+ except requests.exceptions.Timeout as e:
563
+ last_error = e
564
+ print(f"Submission attempt {attempt + 1} timed out.")
565
+ time.sleep(5 * (attempt + 1))
566
+ continue
567
+ except requests.exceptions.RequestException as e:
568
+ last_error = e
569
+ print(f"Submission attempt {attempt + 1} network error: {e}")
570
+ time.sleep(5 * (attempt + 1))
571
+ continue
572
+
573
+ return (
574
+ f"Submission Failed after retries: {last_error}. Answers are cached at "
575
+ f"{ANSWER_CACHE_PATH} — re-run to retry without re-querying the model.",
576
+ pd.DataFrame(results_log),
577
+ )
578
 
579
 
580
  # --- Gradio UI ---
 
584
  """
585
  **Setup**
586
  1. Add a Space secret named `GROQ_API_KEY` with your Groq API key.
587
+ 2. Optional: set `GROQ_MODELS` (comma-separated, default `llama-3.3-70b-versatile,llama-3.1-8b-instant`).
588
  3. Log in to Hugging Face below and click **Run Evaluation & Submit All Answers**.
589
 
590
  Tools available to the agent: `web_search`, `fetch_url`, `wikipedia`, `python`, `get_task_file`.
591
+ Answers are cached locally, so a failed submission can be retried without re-running the agent.
592
  """
593
  )
594