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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -38
app.py CHANGED
@@ -24,11 +24,13 @@ GROQ_MODELS = [
24
  m.strip()
25
  for m in os.getenv(
26
  "GROQ_MODELS",
27
- # 70b firstmuch 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
 
@@ -533,15 +535,17 @@ class GroqAgent:
533
  self.exhausted_models: set[str] = set()
534
  print(f"GroqAgent initialized with models={self.models}")
535
 
536
- def _chat(self, messages, use_tools: bool = True, max_tokens: int = 800):
 
537
  last_error: Exception | None = None
538
- for model in self.models:
539
- if model in self.exhausted_models:
 
540
  continue
541
  for attempt in range(3):
542
  try:
543
  kwargs = dict(
544
- model=model,
545
  messages=messages,
546
  temperature=0.0,
547
  max_tokens=max_tokens,
@@ -557,24 +561,23 @@ class GroqAgent:
557
  is_413 = "413" in msg or "too large" in msg.lower()
558
  is_tpd = "per day" in msg.lower() or "tpd" in msg.lower()
559
  if is_413:
560
- # Request is too big for this model's TPM bucket.
561
- # Trim history aggressively and try the next model.
562
- print(f"[{model}] 413 too large; will trim and try next model.")
563
- last_error = e
564
  break
565
  if is_429 and is_tpd:
566
- print(f"[{model}] daily token limit exhausted; switching model.")
567
- self.exhausted_models.add(model)
568
  break
569
  if is_429:
570
  wait = self._parse_retry_seconds(msg)
571
  wait = min(max(wait, 2), 30)
572
- print(f"[{model}] 429; sleeping {wait}s (attempt {attempt + 1}/3)")
573
  time.sleep(wait)
574
  continue
575
- print(f"[{model}] API error: {e}")
576
  break
577
- raise RuntimeError(f"All Groq models failed. Last error: {last_error}")
 
 
578
 
579
  @staticmethod
580
  def _parse_retry_seconds(error_msg: str) -> float:
@@ -617,27 +620,30 @@ class GroqAgent:
617
  {"role": "user", "content": user_content},
618
  ]
619
 
 
 
 
620
  for step in range(MAX_TOOL_ITERATIONS):
621
  try:
622
  resp = self._chat(self._trim_messages(messages), use_tools=True, max_tokens=800)
623
  except Exception as e:
624
- # Try one more time with no tools, heavily trimmed, to at least get a guess.
625
- print(f"chat failed: {e} trying no-tool fallback.")
 
 
 
626
  try:
627
- resp = self._chat(
628
- [messages[0], messages[1]],
629
- use_tools=False,
630
- max_tokens=200,
631
- )
632
  except Exception as e2:
633
- return f"AGENT ERROR: {e2}"
 
634
 
635
  msg = resp.choices[0].message
636
  tool_calls = getattr(msg, "tool_calls", None)
637
 
638
  if not tool_calls:
639
  answer = (msg.content or "").strip()
640
- return self._finalize(answer, question)
641
 
642
  messages.append(
643
  {
@@ -678,6 +684,9 @@ class GroqAgent:
678
  if len(result) > TOOL_RESULT_MAX_CHARS:
679
  result = result[:TOOL_RESULT_MAX_CHARS] + "\n...[truncated]"
680
 
 
 
 
681
  messages.append(
682
  {
683
  "role": "tool",
@@ -687,31 +696,68 @@ class GroqAgent:
687
  }
688
  )
689
 
690
- # Out of tool iterations: ask for a final, no-tool answer.
691
- messages.append(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  {
693
  "role": "user",
694
- "content": "Stop using tools. Reply with ONLY the final answer string per the formatting rules.",
695
- }
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:
 
24
  m.strip()
25
  for m in os.getenv(
26
  "GROQ_MODELS",
27
+ # 8b only30K TPM is plenty. 70b's 6K TPM is too tight for tool-calling agents.
28
+ "llama-3.1-8b-instant",
29
  ).split(",")
30
  if m.strip()
31
  ]
32
+ # Smarter model used ONLY for the final formatting/synthesis pass (one short call -> fits in TPM).
33
+ GROQ_FINAL_MODEL = os.getenv("GROQ_FINAL_MODEL", "llama-3.3-70b-versatile")
34
  GROQ_VISION_MODEL = os.getenv("GROQ_VISION_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct")
35
  GROQ_WHISPER_MODEL = os.getenv("GROQ_WHISPER_MODEL", "whisper-large-v3-turbo")
36
 
 
535
  self.exhausted_models: set[str] = set()
536
  print(f"GroqAgent initialized with models={self.models}")
537
 
538
+ def _chat(self, messages, use_tools: bool = True, max_tokens: int = 800, model: str | None = None):
539
+ """Try the configured models in order. Handles 429 (retry), 413 (trim & next), TPD (skip)."""
540
  last_error: Exception | None = None
541
+ models = [model] if model else self.models
542
+ for m in models:
543
+ if m in self.exhausted_models:
544
  continue
545
  for attempt in range(3):
546
  try:
547
  kwargs = dict(
548
+ model=m,
549
  messages=messages,
550
  temperature=0.0,
551
  max_tokens=max_tokens,
 
561
  is_413 = "413" in msg or "too large" in msg.lower()
562
  is_tpd = "per day" in msg.lower() or "tpd" in msg.lower()
563
  if is_413:
564
+ print(f"[{m}] 413 too large; trying next model.")
 
 
 
565
  break
566
  if is_429 and is_tpd:
567
+ print(f"[{m}] daily token limit exhausted; switching model.")
568
+ self.exhausted_models.add(m)
569
  break
570
  if is_429:
571
  wait = self._parse_retry_seconds(msg)
572
  wait = min(max(wait, 2), 30)
573
+ print(f"[{m}] 429; sleeping {wait}s (attempt {attempt + 1}/3)")
574
  time.sleep(wait)
575
  continue
576
+ print(f"[{m}] API error: {e}")
577
  break
578
+ # Use repr() so empty exception messages still show useful info.
579
+ err_str = repr(last_error) if last_error else "no error captured"
580
+ raise RuntimeError(f"All Groq models failed. {err_str}")
581
 
582
  @staticmethod
583
  def _parse_retry_seconds(error_msg: str) -> float:
 
620
  {"role": "user", "content": user_content},
621
  ]
622
 
623
+ # Track tool outputs to feed into the synthesis pass even if loop fails.
624
+ collected_facts: list[str] = []
625
+
626
  for step in range(MAX_TOOL_ITERATIONS):
627
  try:
628
  resp = self._chat(self._trim_messages(messages), use_tools=True, max_tokens=800)
629
  except Exception as e:
630
+ print(f"chat iteration {step} failed: {e} trimming and retrying once.")
631
+ # Aggressive trim: keep only system + user + last 2 messages.
632
+ short_msgs = [messages[0], messages[1]]
633
+ if len(messages) > 2:
634
+ short_msgs += messages[-2:]
635
  try:
636
+ resp = self._chat(short_msgs, use_tools=True, max_tokens=600)
 
 
 
 
637
  except Exception as e2:
638
+ print(f"retry also failed: {e2}; falling through to synthesis.")
639
+ break
640
 
641
  msg = resp.choices[0].message
642
  tool_calls = getattr(msg, "tool_calls", None)
643
 
644
  if not tool_calls:
645
  answer = (msg.content or "").strip()
646
+ return self._finalize(answer, question, collected_facts)
647
 
648
  messages.append(
649
  {
 
684
  if len(result) > TOOL_RESULT_MAX_CHARS:
685
  result = result[:TOOL_RESULT_MAX_CHARS] + "\n...[truncated]"
686
 
687
+ # Save to facts (cap each at 800 chars for synthesis pass).
688
+ collected_facts.append(f"[{name}] {result[:800]}")
689
+
690
  messages.append(
691
  {
692
  "role": "tool",
 
696
  }
697
  )
698
 
699
+ # Loop ended (either ran out of iterations OR chat repeatedly failed).
700
+ # Do a final synthesis pass on a SHORT context using the smarter model.
701
+ return self._synthesize(question, collected_facts)
702
+
703
+ def _synthesize(self, question: str, facts: list[str]) -> str:
704
+ """Final answer pass on a short context. Uses smarter model if available."""
705
+ # Keep total facts under ~3500 chars to be safe with TPM on 70b.
706
+ joined = "\n\n".join(facts[-6:]) # last 6 tool outputs
707
+ if len(joined) > 3500:
708
+ joined = joined[-3500:]
709
+
710
+ synth_messages = [
711
+ {
712
+ "role": "system",
713
+ "content": (
714
+ "You are a strict GAIA answer formatter. Read the question and the "
715
+ "research notes below, then output ONLY the final answer string. "
716
+ "No preamble, no labels, no explanation, no quotes, no trailing period. "
717
+ "Match the question's required format exactly (number-only / IOC code / "
718
+ "first name only / two-decimal currency / comma-space list / etc.)."
719
+ ),
720
+ },
721
  {
722
  "role": "user",
723
+ "content": (
724
+ f"Question:\n{question}\n\n"
725
+ f"Research notes:\n{joined or '(no notes)'}\n\n"
726
+ f"Final answer:"
727
+ ),
728
+ },
729
+ ]
730
+ # Try the smarter final model first; fall back to the regular pool.
731
+ for model_choice in (GROQ_FINAL_MODEL, *self.models):
732
+ try:
733
+ resp = self._chat(synth_messages, use_tools=False, max_tokens=120, model=model_choice)
734
+ ans = (resp.choices[0].message.content or "").strip()
735
+ ans = self._postprocess_answer(ans, question)
736
+ if ans:
737
+ return ans
738
+ except Exception as e:
739
+ print(f"synth with {model_choice} failed: {e}")
740
+ continue
741
+ return ""
742
 
743
+ def _finalize(self, raw: str, question: str, facts: list[str] | None = None) -> str:
744
  """Post-process and, if the answer still looks like a sentence, ask the model to reformat."""
745
  cleaned = self._postprocess_answer(raw, question)
746
  if not cleaned:
747
+ # Empty answer? Try synthesis from collected facts.
748
+ if facts:
749
+ return self._synthesize(question, facts)
750
  return cleaned
751
  # If the cleaned answer is suspiciously long or contains explanation-y patterns,
752
  # do a single tiny reformat pass.
753
  looks_sentence = (
754
  len(cleaned.split()) > 12
755
+ or re.search(
756
+ r"\b(because|received|grant|seems|unable|sorry|cannot|provides|indicating|"
757
+ r"web_search|youtube_transcript|fetch_url|task_id)\b",
758
+ cleaned,
759
+ re.IGNORECASE,
760
+ )
761
  )
762
  if looks_sentence:
763
  try: