avi080704 commited on
Commit
1b42cf4
·
verified ·
1 Parent(s): 75a2650

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -39
app.py CHANGED
@@ -24,18 +24,20 @@ GROQ_MODELS = [
24
  m.strip()
25
  for m in os.getenv(
26
  "GROQ_MODELS",
27
- "llama-3.1-8b-instant,llama-3.3-70b-versatile",
 
28
  ).split(",")
29
  if m.strip()
30
  ]
31
  GROQ_VISION_MODEL = os.getenv("GROQ_VISION_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct")
32
  GROQ_WHISPER_MODEL = os.getenv("GROQ_WHISPER_MODEL", "whisper-large-v3-turbo")
33
 
34
- MAX_TOOL_ITERATIONS = 5
35
- TOOL_RESULT_MAX_CHARS = 1800 # smaller -> stays under 6K TPM for 70b fallback
36
- HISTORY_TRIM_AFTER = 6 # trim old tool turns once we have this many messages
37
- ANSWER_CACHE_PATH = os.getenv("ANSWER_CACHE_PATH", "answers_cache.json")
38
- INTER_QUESTION_SLEEP = float(os.getenv("INTER_QUESTION_SLEEP", "2"))
 
39
 
40
  # Track downloaded task files so vision/audio tools can re-use them by task_id.
41
  _TASK_FILE_CACHE: dict[str, dict] = {}
@@ -458,30 +460,59 @@ SYSTEM_PROMPT = """You are a careful research agent answering GAIA benchmark que
458
 
459
  Tools: web_search, fetch_url, wikipedia, python, get_task_file, transcribe_audio, view_image, youtube_transcript.
460
 
461
- Decide tools by reading the question carefully:
462
- - ONLY call get_task_file if the question literally says "attached file", "attached image", ".mp3", ".xlsx", ".pdf", ".py", "the file", "the image", "the audio", "the recording", or similar. If get_task_file returns NO_FILE, do NOT call it again — answer from web research.
463
- - If the question references an audio file/recording, call get_task_file then transcribe_audio.
464
- - If it references an image, call get_task_file then view_image with a focused question.
465
- - If it contains a YouTube URL, call youtube_transcript(url) directly. (No need for get_task_file.)
466
- - If the question text looks reversed (e.g. starts with strange punctuation like ".rewsna" or seems like backwards English), use python to reverse it: `result = "<text>"[::-1]` then answer that reversed question.
467
- - For factual lookups, prefer wikipedia first for entities/people/places, web_search + fetch_url for everything else.
468
- - Use python for ALL arithmetic, sums, date math, sorting, alphabetizing. Never compute by hand.
469
-
470
- Be concise with tool calls — you have at most 5 tool turns, so plan well.
471
-
472
- Answer formatting (CRITICAL — grader does an exact-match comparison):
473
- - Reply with ONLY the answer. No preamble, no explanation, no quotes, no trailing period, no markdown.
474
- - Do NOT include the words "FINAL ANSWER", "Answer:", or any label.
475
- - Numbers: digits only, no commas, no units, no $ sign — UNLESS the question asks for the unit.
476
- - Currency with "two decimal places": e.g. "89706.00" not "$89,706" not "89706".
477
- - Strings: no leading articles ("the", "a") unless required; spell out, no abbreviations; write digits as digits.
478
- - For names: just the requested form (first name only / last name only / full name) — read the question carefully.
479
- - Lists: comma-separated, single space after each comma, applying the rules above to each element.
480
- - For "alphabetical order" lists, sort them.
481
- - For "ascending order" numeric lists, sort numerically.
 
 
 
 
 
 
 
 
 
 
 
 
482
  """
483
 
484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  # ---------------------------------------------------------------------------
486
  # Agent
487
  # ---------------------------------------------------------------------------
@@ -570,6 +601,13 @@ class GroqAgent:
570
  return head + [summary] + tail
571
 
572
  def __call__(self, question: str, task_id: str | None = None) -> str:
 
 
 
 
 
 
 
573
  user_content = question
574
  if task_id:
575
  user_content = f"task_id: {task_id}\n\nQuestion: {question}"
@@ -599,7 +637,7 @@ class GroqAgent:
599
 
600
  if not tool_calls:
601
  answer = (msg.content or "").strip()
602
- return self._postprocess_answer(answer, question)
603
 
604
  messages.append(
605
  {
@@ -658,12 +696,51 @@ class GroqAgent:
658
  )
659
  try:
660
  resp = self._chat(self._trim_messages(messages), use_tools=False, max_tokens=200)
661
- return self._postprocess_answer(
662
  (resp.choices[0].message.content or "").strip(), question
663
  )
664
  except Exception as e:
665
  return f"AGENT ERROR: {e}"
666
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  @staticmethod
668
  def _postprocess_answer(text: str, question: str = "") -> str:
669
  if not text:
@@ -726,7 +803,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
726
  username = f"{profile.username}"
727
  print(f"User logged in: {username}")
728
  else:
729
- return "Please Login to Hugging Face with the button.", None
730
 
731
  api_url = DEFAULT_API_URL
732
  questions_url = f"{api_url}/questions"
@@ -736,7 +813,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
736
  agent = GroqAgent()
737
  except Exception as e:
738
  print(f"Error instantiating agent: {e}")
739
- return f"Error initializing agent: {e}", None
740
 
741
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
742
  print(agent_code)
@@ -747,12 +824,12 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
747
  response.raise_for_status()
748
  questions_data = response.json()
749
  if not questions_data:
750
- return "Fetched questions list is empty or invalid format.", None
751
  print(f"Fetched {len(questions_data)} questions.")
752
  except requests.exceptions.RequestException as e:
753
- return f"Error fetching questions: {e}", None
754
  except Exception as e:
755
- return f"An unexpected error occurred fetching questions: {e}", None
756
 
757
  results_log = []
758
  answers_payload = []
@@ -788,7 +865,14 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
788
  time.sleep(INTER_QUESTION_SLEEP)
789
 
790
  if not answers_payload:
791
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
 
 
 
 
 
 
 
792
 
793
  submission_data = {
794
  "username": username.strip(),
@@ -810,7 +894,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
810
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
811
  f"Message: {result_data.get('message', 'No message received.')}"
812
  )
813
- return final_status, pd.DataFrame(results_log)
814
  except requests.exceptions.HTTPError as e:
815
  status = e.response.status_code if e.response is not None else "?"
816
  last_error = e
@@ -823,7 +907,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
823
  error_detail += f" Detail: {e.response.json().get('detail', e.response.text)}"
824
  except Exception:
825
  error_detail += f" Response: {e.response.text[:500] if e.response is not None else ''}"
826
- return f"Submission Failed: {error_detail}", pd.DataFrame(results_log)
827
  except requests.exceptions.Timeout as e:
828
  last_error = e
829
  print(f"Submission attempt {attempt + 1} timed out.")
@@ -838,7 +922,8 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
838
  return (
839
  f"Submission Failed after retries: {last_error}. Answers are cached at "
840
  f"{ANSWER_CACHE_PATH} — re-run to retry without re-querying the model.",
841
- pd.DataFrame(results_log),
 
842
  )
843
 
844
 
@@ -865,8 +950,9 @@ with gr.Blocks() as demo:
865
  run_button = gr.Button("Run Evaluation & Submit All Answers")
866
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
867
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
 
868
 
869
- run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
870
 
871
 
872
  if __name__ == "__main__":
 
24
  m.strip()
25
  for m in os.getenv(
26
  "GROQ_MODELS",
27
+ # 70b first — much better reasoning. Trim history aggressively to fit 6K TPM.
28
+ "llama-3.3-70b-versatile,llama-3.1-8b-instant",
29
  ).split(",")
30
  if m.strip()
31
  ]
32
  GROQ_VISION_MODEL = os.getenv("GROQ_VISION_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct")
33
  GROQ_WHISPER_MODEL = os.getenv("GROQ_WHISPER_MODEL", "whisper-large-v3-turbo")
34
 
35
+ MAX_TOOL_ITERATIONS = 6
36
+ TOOL_RESULT_MAX_CHARS = 1400 # tighter -> stays under 6K TPM for 70b
37
+ HISTORY_TRIM_AFTER = 5 # trim aggressively
38
+ ANSWER_CACHE_PATH = os.getenv("ANSWER_CACHE_PATH", "/tmp/answers_cache.json")
39
+ RESULTS_CSV_PATH = "/tmp/gaia_results.csv"
40
+ INTER_QUESTION_SLEEP = float(os.getenv("INTER_QUESTION_SLEEP", "3"))
41
 
42
  # Track downloaded task files so vision/audio tools can re-use them by task_id.
43
  _TASK_FILE_CACHE: dict[str, dict] = {}
 
460
 
461
  Tools: web_search, fetch_url, wikipedia, python, get_task_file, transcribe_audio, view_image, youtube_transcript.
462
 
463
+ Decision rules:
464
+ - If the question literally references "attached file/image/audio/Excel/PDF/.mp3/.xlsx/.py/code/recording", call get_task_file FIRST. If it returns NO_FILE, do NOT call it again.
465
+ - Audio file -> transcribe_audio(task_id).
466
+ - Image file -> view_image(task_id, question="<focused question>").
467
+ - YouTube URL -> youtube_transcript(url) directly (no get_task_file needed).
468
+ - For factual lookups about people / places / artists / albums / animals / Wikipedia featured articles, START with wikipedia. Then fetch_url the relevant page if more detail needed.
469
+ - For everything else research-y, web_search then fetch_url.
470
+ - Use python for ALL arithmetic, sums (e.g. summing Excel rows), date math, sorting, alphabetizing, set/group operations, string reversal. Never compute by hand.
471
+ - For Excel/CSV totals, after get_task_file shows you the data, ALWAYS use python to compute the sum precisely.
472
+
473
+ You have 6 tool turns. Be decisive. Do not loop on the same query.
474
+
475
+ ANSWER FORMATTING (the grader does an exact-match comparison; sentence answers ALWAYS lose):
476
+
477
+ Worked examples of correct GAIA format:
478
+ - Q: "How many albums..." -> "3" (NOT "3 albums" or "There were 3 albums")
479
+ - Q: "Express your answer in USD with two decimal places" -> "89706.00" (NOT "$89,706" or "89706")
480
+ - Q: "Give the IOC country code" -> "MLT" (NOT "Malta" or "Malta (MLT)")
481
+ - Q: "Just the city name without abbreviations" -> "Saint Petersburg"
482
+ - Q: "Give only the first name" -> "Bartek" (NOT "Bartlomiej" or "Bartek Kasprzykowski")
483
+ - Q: "Comma separated list ... in alphabetical order" -> "broccoli, celery, fresh basil, lettuce, sweet potatoes, zucchini"
484
+ - Q: "Under what NASA award number..." -> "80NSSC21K1130" (just the code, NO surrounding sentence)
485
+ - Q: "Final numeric output from the attached Python code" -> "0" (just the number)
486
+ - Q: opposite of "left" -> "right" (one word)
487
+
488
+ Strict rules:
489
+ - Reply with ONLY the answer. No preamble. No explanation. No quotes. No trailing period.
490
+ - Do NOT include "FINAL ANSWER", "Answer:", or any label.
491
+ - Numbers: digits only, no commas, no units, no $ — UNLESS the question asks for the unit.
492
+ - Currency "two decimal places": e.g. "89706.00".
493
+ - Strings: no leading articles ("the", "a") unless required; no abbreviations (write "Saint" not "St."); digits as digits.
494
+ - Names: read the question carefully. "First name only" / "last name only" / "surname" / "full name". Match exactly.
495
+ - Lists: comma-separated, ONE space after each comma. Apply formatting rules to each element. Sort if asked.
496
  """
497
 
498
 
499
+ def _maybe_reverse_text(question: str) -> str:
500
+ """If the question text looks reversed, flip it. Returns possibly-modified question."""
501
+ # Heuristic: a normal English sentence has many word-frequencies like 'the', 'a', 'of'.
502
+ # A reversed one has 'eht', 'fo', 'sa', etc., and often starts with punctuation like '.'.
503
+ q = question.strip()
504
+ if not q:
505
+ return question
506
+ starts_with_punct = q[0] in ".,;:!?"
507
+ reversed_text = q[::-1]
508
+ # Look for common English words in the reversed version.
509
+ common = (" the ", " of ", " and ", " to ", " is ", " a ", " in ", " for ")
510
+ hits = sum(1 for w in common if w in (" " + reversed_text.lower() + " "))
511
+ if starts_with_punct and hits >= 2:
512
+ return reversed_text
513
+ return question
514
+
515
+
516
  # ---------------------------------------------------------------------------
517
  # Agent
518
  # ---------------------------------------------------------------------------
 
601
  return head + [summary] + tail
602
 
603
  def __call__(self, question: str, task_id: str | None = None) -> str:
604
+ # Deterministic preprocess: detect & flip reversed-text trick questions.
605
+ original_q = question
606
+ flipped = _maybe_reverse_text(question)
607
+ if flipped != question:
608
+ print("[reversed-text detected, flipping question]")
609
+ question = flipped
610
+
611
  user_content = question
612
  if task_id:
613
  user_content = f"task_id: {task_id}\n\nQuestion: {question}"
 
637
 
638
  if not tool_calls:
639
  answer = (msg.content or "").strip()
640
+ return self._finalize(answer, question)
641
 
642
  messages.append(
643
  {
 
696
  )
697
  try:
698
  resp = self._chat(self._trim_messages(messages), use_tools=False, max_tokens=200)
699
+ return self._finalize(
700
  (resp.choices[0].message.content or "").strip(), question
701
  )
702
  except Exception as e:
703
  return f"AGENT ERROR: {e}"
704
 
705
+ def _finalize(self, raw: str, question: str) -> str:
706
+ """Post-process and, if the answer still looks like a sentence, ask the model to reformat."""
707
+ cleaned = self._postprocess_answer(raw, question)
708
+ if not cleaned:
709
+ return cleaned
710
+ # If the cleaned answer is suspiciously long or contains explanation-y patterns,
711
+ # do a single tiny reformat pass.
712
+ looks_sentence = (
713
+ len(cleaned.split()) > 12
714
+ or re.search(r"\b(because|received|grant|seems|unable|sorry|cannot|provides|indicating)\b", cleaned, re.IGNORECASE)
715
+ )
716
+ if looks_sentence:
717
+ try:
718
+ resp = self._chat(
719
+ [
720
+ {
721
+ "role": "system",
722
+ "content": (
723
+ "Extract ONLY the final answer from the assistant text below, "
724
+ "matching the question's required format exactly. No preamble, "
725
+ "no explanation, no quotes, no trailing period, no labels."
726
+ ),
727
+ },
728
+ {
729
+ "role": "user",
730
+ "content": f"Question: {question}\n\nAssistant text: {cleaned}\n\nFinal answer:",
731
+ },
732
+ ],
733
+ use_tools=False,
734
+ max_tokens=80,
735
+ )
736
+ reformat = (resp.choices[0].message.content or "").strip()
737
+ reformat = self._postprocess_answer(reformat, question)
738
+ if reformat:
739
+ return reformat
740
+ except Exception as e:
741
+ print(f"reformat pass failed: {e}")
742
+ return cleaned
743
+
744
  @staticmethod
745
  def _postprocess_answer(text: str, question: str = "") -> str:
746
  if not text:
 
803
  username = f"{profile.username}"
804
  print(f"User logged in: {username}")
805
  else:
806
+ return "Please Login to Hugging Face with the button.", None, None
807
 
808
  api_url = DEFAULT_API_URL
809
  questions_url = f"{api_url}/questions"
 
813
  agent = GroqAgent()
814
  except Exception as e:
815
  print(f"Error instantiating agent: {e}")
816
+ return f"Error initializing agent: {e}", None, None
817
 
818
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
819
  print(agent_code)
 
824
  response.raise_for_status()
825
  questions_data = response.json()
826
  if not questions_data:
827
+ return "Fetched questions list is empty or invalid format.", None, None
828
  print(f"Fetched {len(questions_data)} questions.")
829
  except requests.exceptions.RequestException as e:
830
+ return f"Error fetching questions: {e}", None, None
831
  except Exception as e:
832
+ return f"An unexpected error occurred fetching questions: {e}", None, None
833
 
834
  results_log = []
835
  answers_payload = []
 
865
  time.sleep(INTER_QUESTION_SLEEP)
866
 
867
  if not answers_payload:
868
+ df = pd.DataFrame(results_log)
869
+ df.to_csv(RESULTS_CSV_PATH, index=False)
870
+ return "Agent did not produce any answers to submit.", df, RESULTS_CSV_PATH
871
+
872
+ # Save results CSV before submission so the user can download even if submit fails.
873
+ df = pd.DataFrame(results_log)
874
+ df.to_csv(RESULTS_CSV_PATH, index=False)
875
+ print(f"Results CSV written to {RESULTS_CSV_PATH}")
876
 
877
  submission_data = {
878
  "username": username.strip(),
 
894
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
895
  f"Message: {result_data.get('message', 'No message received.')}"
896
  )
897
+ return final_status, df, RESULTS_CSV_PATH
898
  except requests.exceptions.HTTPError as e:
899
  status = e.response.status_code if e.response is not None else "?"
900
  last_error = e
 
907
  error_detail += f" Detail: {e.response.json().get('detail', e.response.text)}"
908
  except Exception:
909
  error_detail += f" Response: {e.response.text[:500] if e.response is not None else ''}"
910
+ return f"Submission Failed: {error_detail}", df, RESULTS_CSV_PATH
911
  except requests.exceptions.Timeout as e:
912
  last_error = e
913
  print(f"Submission attempt {attempt + 1} timed out.")
 
922
  return (
923
  f"Submission Failed after retries: {last_error}. Answers are cached at "
924
  f"{ANSWER_CACHE_PATH} — re-run to retry without re-querying the model.",
925
+ df,
926
+ RESULTS_CSV_PATH,
927
  )
928
 
929
 
 
950
  run_button = gr.Button("Run Evaluation & Submit All Answers")
951
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
952
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
953
+ results_csv = gr.File(label="Download Results CSV (paste this back to me for tuning)")
954
 
955
+ run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table, results_csv])
956
 
957
 
958
  if __name__ == "__main__":