vladd19 commited on
Commit
75784cd
·
verified ·
1 Parent(s): eb1d8de

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +177 -31
app.py CHANGED
@@ -31,15 +31,15 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
31
  GROQ_TEXT_MODEL = os.getenv("GROQ_TEXT_MODEL", "llama-3.1-8b-instant")
32
  GROQ_FINAL_MODEL = os.getenv("GROQ_FINAL_MODEL", "llama-3.1-8b-instant")
33
  GROQ_STRONG_MODEL = os.getenv("GROQ_STRONG_MODEL", "openai/gpt-oss-20b")
34
- GROQ_RESEARCH_MODEL = os.getenv("GROQ_RESEARCH_MODEL", "llama-3.1-8b-instant")
35
  GROQ_VISION_MODEL = os.getenv("GROQ_VISION_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct")
36
  GROQ_AUDIO_MODEL = os.getenv("GROQ_AUDIO_MODEL", "whisper-large-v3-turbo")
37
 
38
  GAIA_DIR = os.getenv("GAIA_DIR", "./data/gaia")
39
  ALLOW_CODE_EXECUTION = 1
40
 
41
- MAX_CONTEXT_CHARS = 18_000
42
- MAX_SEARCH_CONTEXT_CHARS = 12_000
43
 
44
 
45
  def get_groq_client() -> Groq:
@@ -196,6 +196,12 @@ def analyze_image(task_id: str, question: str = "") -> str:
196
  b64 = base64.standard_b64encode(data).decode("utf-8")
197
  mime = _image_mime(data, ct)
198
  prompt = question or "Describe this image. Extract all visible text, numbers, symbols, and key details."
 
 
 
 
 
 
199
 
200
  try:
201
  client = get_groq_client()
@@ -211,7 +217,7 @@ def analyze_image(task_id: str, question: str = "") -> str:
211
  }
212
  ],
213
  temperature=0,
214
- max_tokens=384,
215
  )
216
  return resp.choices[0].message.content.strip()
217
  except Exception as e:
@@ -318,13 +324,56 @@ def read_spreadsheet_context(path: Path) -> str:
318
  return truncate_text("\n".join(parts), MAX_CONTEXT_CHARS)
319
 
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  def read_code_context(path: Path) -> str:
322
  source = path.read_text(encoding="utf-8", errors="replace")
323
  parts = [f"Code file: {path.name}", "--- Source code ---", source]
324
  return truncate_text("\n".join(parts), MAX_CONTEXT_CHARS)
325
 
326
 
327
- def run_python_file(path: Path, timeout_seconds: int = 8) -> str:
328
  if not ALLOW_CODE_EXECUTION:
329
  return "execution skipped"
330
  if path.suffix.lower() != ".py":
@@ -397,6 +446,15 @@ def fetch_url_text(url: str, limit: int = 8000) -> str:
397
  return f"[fetch error: {type(e).__name__}: {e}]"
398
 
399
 
 
 
 
 
 
 
 
 
 
400
  def ddg_search(query: str, max_results: int = 5) -> list[dict[str, str]]:
401
  try:
402
  results = DDGS().text(query, max_results=max_results)
@@ -416,7 +474,7 @@ def ddg_search(query: str, max_results: int = 5) -> list[dict[str, str]]:
416
 
417
 
418
  def build_research_queries(question: str, base_query: str) -> list[str]:
419
- queries: list[str] = [base_query, question]
420
  quoted_phrases = re.findall(r'["“]([^"”]{3,120})["”]', question)
421
  if quoted_phrases:
422
  queries.append(" ".join(f'"{phrase}"' for phrase in quoted_phrases[:4]))
@@ -436,7 +494,18 @@ def build_research_queries(question: str, base_query: str) -> list[str]:
436
 
437
  years = re.findall(r"\b(?:19|20)\d{2}\b", question)
438
  if years:
439
- queries.append(f"{base_query} {' '.join(years[:4])}")
 
 
 
 
 
 
 
 
 
 
 
440
 
441
  deduped: list[str] = []
442
  for query in queries:
@@ -446,6 +515,32 @@ def build_research_queries(question: str, base_query: str) -> list[str]:
446
  return deduped[:5]
447
 
448
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  def fetch_youtube_timedtext(video_id: str) -> str:
450
  urls = [
451
  f"https://video.google.com/timedtext?lang=en&v={video_id}",
@@ -470,12 +565,17 @@ def fetch_youtube_timedtext(video_id: str) -> str:
470
 
471
  def build_youtube_context(question: str, video_id: str | None) -> str:
472
  queries: list[str] = []
 
473
  if video_id:
474
  queries += [
475
  f'"{video_id}" transcript',
476
  f'"{video_id}" subtitles',
477
  f'"{video_id}"',
478
  ]
 
 
 
 
479
 
480
  queries.append(question)
481
 
@@ -516,31 +616,66 @@ def build_youtube_context(question: str, video_id: str | None) -> str:
516
  def build_research_context(question: str, base_query: str) -> str:
517
  parts = [f"Question: {question}", f"Primary query: {base_query}"]
518
  seen_urls: set[str] = set()
 
519
 
520
- for query in build_research_queries(question, base_query):
521
  parts.append(f"\n=== Search query: {query} ===")
522
- results = ddg_search(query, max_results=5)
523
  if not results:
524
  parts.append(safe_tool_run(web_search_tool, query, limit=2000))
525
  continue
526
 
 
527
  for i, result in enumerate(results, 1):
528
  url = result["url"]
529
  title = result["title"]
530
  body = result["body"]
531
  parts.append(f"[{i}] {title}\nURL: {url}\nSnippet: {body}")
532
 
533
- parsed = urlparse(url)
534
  if not url or url in seen_urls:
535
  continue
536
- if parsed.scheme not in {"http", "https"}:
537
  continue
538
- if any(skip in parsed.netloc for skip in ["youtube.com", "youtu.be", "facebook.com", "x.com"]):
539
  continue
540
 
541
  seen_urls.add(url)
542
  fetched = fetch_url_text(url, limit=5000)
543
  if fetched and not fetched.startswith("[fetch error"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  parts.append(f"Fetched text from {url}:\n{fetched}")
545
  if len("\n".join(parts)) > MAX_SEARCH_CONTEXT_CHARS:
546
  return truncate_text("\n".join(parts), MAX_SEARCH_CONTEXT_CHARS)
@@ -690,8 +825,6 @@ def direct_question(question: str) -> bool:
690
  "grocery list",
691
  "shopping list",
692
  "given this table",
693
- "alphabetical order",
694
- "sort",
695
  "opposite of",
696
  "reverse",
697
  "what is the final numeric output",
@@ -731,7 +864,8 @@ class BasicAgent:
731
  def __init__(self):
732
  self.answer_llm = make_chat_model(GROQ_TEXT_MODEL, max_tokens=256)
733
  self.final_llm = make_chat_model(GROQ_FINAL_MODEL, max_tokens=48)
734
- self.research_llm = make_chat_model(GROQ_RESEARCH_MODEL, max_tokens=384)
 
735
 
736
  self.graph = self.build_graph()
737
 
@@ -844,7 +978,7 @@ class BasicAgent:
844
  question=question,
845
  context=context,
846
  context_label="Image analysis",
847
- llm=self.answer_llm,
848
  )
849
  return {"context": context, "raw_answer": raw_answer}
850
 
@@ -866,23 +1000,16 @@ class BasicAgent:
866
  path = state["local_path"]
867
  question = state["question"]
868
 
869
- xls = pd.ExcelFile(path)
870
- parts = []
871
-
872
- for sheet in xls.sheet_names:
873
- df = pd.read_excel(path, sheet_name=sheet)
874
- parts.append(f"Sheet: {sheet}")
875
- parts.append(f"Columns: {list(df.columns)}")
876
- parts.append(f"Shape: {df.shape}")
877
- parts.append(df.head(20).to_csv(index=False))
878
-
879
- context = "\n\n".join(parts)
880
 
881
  raw_answer = self.answer_from_context(
882
  question=question,
883
- context=context[:12000],
884
- context_label="spreadsheet preview",
885
- llm=self.answer_llm,
886
  )
887
 
888
  return {"context": context, "raw_answer": raw_answer}
@@ -909,7 +1036,7 @@ class BasicAgent:
909
  question=question,
910
  context=context,
911
  context_label="Code and execution result",
912
- llm=self.answer_llm,
913
  )
914
  return {"context": context, "raw_answer": raw_answer}
915
 
@@ -957,6 +1084,16 @@ class BasicAgent:
957
  llm=self.research_llm,
958
  )
959
 
 
 
 
 
 
 
 
 
 
 
960
  print(f"[research raw_answer] {repr(raw_answer[:500])}")
961
  return {"context": context, "raw_answer": raw_answer}
962
 
@@ -973,6 +1110,15 @@ class BasicAgent:
973
  llm=self.research_llm,
974
  )
975
 
 
 
 
 
 
 
 
 
 
976
  return {
977
  "context": context,
978
  "raw_answer": raw_answer,
 
31
  GROQ_TEXT_MODEL = os.getenv("GROQ_TEXT_MODEL", "llama-3.1-8b-instant")
32
  GROQ_FINAL_MODEL = os.getenv("GROQ_FINAL_MODEL", "llama-3.1-8b-instant")
33
  GROQ_STRONG_MODEL = os.getenv("GROQ_STRONG_MODEL", "openai/gpt-oss-20b")
34
+ GROQ_RESEARCH_MODEL = os.getenv("GROQ_RESEARCH_MODEL", GROQ_STRONG_MODEL)
35
  GROQ_VISION_MODEL = os.getenv("GROQ_VISION_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct")
36
  GROQ_AUDIO_MODEL = os.getenv("GROQ_AUDIO_MODEL", "whisper-large-v3-turbo")
37
 
38
  GAIA_DIR = os.getenv("GAIA_DIR", "./data/gaia")
39
  ALLOW_CODE_EXECUTION = 1
40
 
41
+ MAX_CONTEXT_CHARS = 24_000
42
+ MAX_SEARCH_CONTEXT_CHARS = 20_000
43
 
44
 
45
  def get_groq_client() -> Groq:
 
196
  b64 = base64.standard_b64encode(data).decode("utf-8")
197
  mime = _image_mime(data, ct)
198
  prompt = question or "Describe this image. Extract all visible text, numbers, symbols, and key details."
199
+ if "chess" in prompt.lower():
200
+ prompt = (
201
+ f"{prompt}\n\n"
202
+ "This is a chess task. Identify the board coordinates, side to move, relevant pieces, checks, "
203
+ "mate threats, and the best move. Return the move in standard chess notation if possible."
204
+ )
205
 
206
  try:
207
  client = get_groq_client()
 
217
  }
218
  ],
219
  temperature=0,
220
+ max_tokens=768,
221
  )
222
  return resp.choices[0].message.content.strip()
223
  except Exception as e:
 
324
  return truncate_text("\n".join(parts), MAX_CONTEXT_CHARS)
325
 
326
 
327
+ def build_spreadsheet_summary(path: Path) -> str:
328
+ parts: list[str] = []
329
+ xls = pd.ExcelFile(path)
330
+
331
+ for sheet_name in xls.sheet_names:
332
+ df = pd.read_excel(path, sheet_name=sheet_name)
333
+ if df.empty:
334
+ continue
335
+
336
+ work = df.copy()
337
+ work.columns = [str(col).strip() for col in work.columns]
338
+ numeric_cols = [
339
+ col
340
+ for col in work.columns
341
+ if pd.api.types.is_numeric_dtype(work[col])
342
+ ]
343
+ categorical_cols = [
344
+ col
345
+ for col in work.columns
346
+ if col not in numeric_cols and work[col].nunique(dropna=True) <= 40
347
+ ]
348
+
349
+ parts.append(f"Sheet: {sheet_name}")
350
+ if numeric_cols:
351
+ totals = work[numeric_cols].sum(numeric_only=True).sort_values(ascending=False)
352
+ parts.append("Numeric column totals:")
353
+ parts.append(totals.to_string())
354
+
355
+ for category_col in categorical_cols[:6]:
356
+ if not numeric_cols:
357
+ break
358
+ grouped = work.groupby(category_col, dropna=False)[numeric_cols].sum(numeric_only=True)
359
+ if grouped.empty:
360
+ continue
361
+ parts.append(f"Totals grouped by {category_col}:")
362
+ parts.append(grouped.head(40).to_csv())
363
+
364
+ if len("\n".join(parts)) > MAX_CONTEXT_CHARS:
365
+ break
366
+
367
+ return truncate_text("\n".join(parts), MAX_CONTEXT_CHARS)
368
+
369
+
370
  def read_code_context(path: Path) -> str:
371
  source = path.read_text(encoding="utf-8", errors="replace")
372
  parts = [f"Code file: {path.name}", "--- Source code ---", source]
373
  return truncate_text("\n".join(parts), MAX_CONTEXT_CHARS)
374
 
375
 
376
+ def run_python_file(path: Path, timeout_seconds: int = 45) -> str:
377
  if not ALLOW_CODE_EXECUTION:
378
  return "execution skipped"
379
  if path.suffix.lower() != ".py":
 
446
  return f"[fetch error: {type(e).__name__}: {e}]"
447
 
448
 
449
+ def likely_relevant_url(url: str) -> bool:
450
+ parsed = urlparse(url)
451
+ if parsed.scheme not in {"http", "https"}:
452
+ return False
453
+ if any(skip in parsed.netloc for skip in ["youtube.com", "youtu.be", "facebook.com", "x.com"]):
454
+ return False
455
+ return True
456
+
457
+
458
  def ddg_search(query: str, max_results: int = 5) -> list[dict[str, str]]:
459
  try:
460
  results = DDGS().text(query, max_results=max_results)
 
474
 
475
 
476
  def build_research_queries(question: str, base_query: str) -> list[str]:
477
+ queries: list[str] = []
478
  quoted_phrases = re.findall(r'["“]([^"”]{3,120})["”]', question)
479
  if quoted_phrases:
480
  queries.append(" ".join(f'"{phrase}"' for phrase in quoted_phrases[:4]))
 
494
 
495
  years = re.findall(r"\b(?:19|20)\d{2}\b", question)
496
  if years:
497
+ year_text = " ".join(years[:4])
498
+ if capitalized_terms:
499
+ queries.append(f"{' '.join(capitalized_terms[:3])} {year_text}")
500
+ queries.append(f"{base_query} {year_text}")
501
+
502
+ q_lower = question.lower()
503
+ if "wikipedia" in q_lower and capitalized_terms:
504
+ queries.append(f"site:en.wikipedia.org {' '.join(capitalized_terms[:4])}")
505
+ if "wikipedia" in q_lower:
506
+ queries.append(f"site:en.wikipedia.org {base_query}")
507
+
508
+ queries += [base_query, question]
509
 
510
  deduped: list[str] = []
511
  for query in queries:
 
515
  return deduped[:5]
516
 
517
 
518
+ def build_additional_research_queries(question: str, previous_queries: list[str]) -> list[str]:
519
+ messages = [
520
+ SystemMessage(content=(
521
+ "Create 3 concise web search queries for answering the task. "
522
+ "Prefer exact entity names, dates, source names, and required answer type. "
523
+ "Return one query per line, no numbering."
524
+ )),
525
+ HumanMessage(content=question),
526
+ ]
527
+ try:
528
+ llm = make_chat_model(GROQ_FINAL_MODEL, max_tokens=160)
529
+ raw = llm.invoke(messages).content
530
+ except Exception as e:
531
+ print(f"[query expansion warning] {type(e).__name__}: {e}")
532
+ return []
533
+
534
+ queries: list[str] = []
535
+ previous = {q.lower() for q in previous_queries}
536
+ for line in raw.splitlines():
537
+ query = clean_answer(re.sub(r"^\s*[-*\d.)]+\s*", "", line))
538
+ query = re.sub(r"\s+", " ", query).strip()
539
+ if query and query.lower() not in previous:
540
+ queries.append(query)
541
+ return queries[:3]
542
+
543
+
544
  def fetch_youtube_timedtext(video_id: str) -> str:
545
  urls = [
546
  f"https://video.google.com/timedtext?lang=en&v={video_id}",
 
565
 
566
  def build_youtube_context(question: str, video_id: str | None) -> str:
567
  queries: list[str] = []
568
+ quoted_phrases = re.findall(r'["“]([^"”]{3,120})["”]', question)
569
  if video_id:
570
  queries += [
571
  f'"{video_id}" transcript',
572
  f'"{video_id}" subtitles',
573
  f'"{video_id}"',
574
  ]
575
+ for phrase in quoted_phrases[:3]:
576
+ queries.append(f'"{video_id}" "{phrase}"')
577
+ if quoted_phrases:
578
+ queries.append(" ".join(f'"{phrase}"' for phrase in quoted_phrases[:3]))
579
 
580
  queries.append(question)
581
 
 
616
  def build_research_context(question: str, base_query: str) -> str:
617
  parts = [f"Question: {question}", f"Primary query: {base_query}"]
618
  seen_urls: set[str] = set()
619
+ queries = build_research_queries(question, base_query)
620
 
621
+ for query in queries:
622
  parts.append(f"\n=== Search query: {query} ===")
623
+ results = ddg_search(query, max_results=6)
624
  if not results:
625
  parts.append(safe_tool_run(web_search_tool, query, limit=2000))
626
  continue
627
 
628
+ fetched_count = 0
629
  for i, result in enumerate(results, 1):
630
  url = result["url"]
631
  title = result["title"]
632
  body = result["body"]
633
  parts.append(f"[{i}] {title}\nURL: {url}\nSnippet: {body}")
634
 
 
635
  if not url or url in seen_urls:
636
  continue
637
+ if not likely_relevant_url(url):
638
  continue
639
+ if fetched_count >= 2:
640
  continue
641
 
642
  seen_urls.add(url)
643
  fetched = fetch_url_text(url, limit=5000)
644
  if fetched and not fetched.startswith("[fetch error"):
645
+ fetched_count += 1
646
+ parts.append(f"Fetched text from {url}:\n{fetched}")
647
+ if len("\n".join(parts)) > MAX_SEARCH_CONTEXT_CHARS:
648
+ return truncate_text("\n".join(parts), MAX_SEARCH_CONTEXT_CHARS)
649
+
650
+ return truncate_text("\n".join(parts), MAX_SEARCH_CONTEXT_CHARS)
651
+
652
+
653
+ def extend_research_context(question: str, context: str, used_query: str) -> str:
654
+ extra_queries = build_additional_research_queries(question, [used_query])
655
+ if not extra_queries:
656
+ return context
657
+
658
+ parts = [context, "\n=== Additional focused searches ==="]
659
+ seen_urls = set(re.findall(r"URL: (https?://\S+)", context))
660
+ for query in extra_queries:
661
+ parts.append(f"\n=== Search query: {query} ===")
662
+ results = ddg_search(query, max_results=6)
663
+ if not results:
664
+ parts.append(safe_tool_run(web_search_tool, query, limit=2000))
665
+ continue
666
+
667
+ fetched_count = 0
668
+ for i, result in enumerate(results, 1):
669
+ url = result["url"]
670
+ parts.append(f"[{i}] {result['title']}\nURL: {url}\nSnippet: {result['body']}")
671
+ if not url or url in seen_urls or not likely_relevant_url(url):
672
+ continue
673
+ if fetched_count >= 2:
674
+ continue
675
+ seen_urls.add(url)
676
+ fetched = fetch_url_text(url, limit=5000)
677
+ if fetched and not fetched.startswith("[fetch error"):
678
+ fetched_count += 1
679
  parts.append(f"Fetched text from {url}:\n{fetched}")
680
  if len("\n".join(parts)) > MAX_SEARCH_CONTEXT_CHARS:
681
  return truncate_text("\n".join(parts), MAX_SEARCH_CONTEXT_CHARS)
 
825
  "grocery list",
826
  "shopping list",
827
  "given this table",
 
 
828
  "opposite of",
829
  "reverse",
830
  "what is the final numeric output",
 
864
  def __init__(self):
865
  self.answer_llm = make_chat_model(GROQ_TEXT_MODEL, max_tokens=256)
866
  self.final_llm = make_chat_model(GROQ_FINAL_MODEL, max_tokens=48)
867
+ self.strong_llm = make_chat_model(GROQ_STRONG_MODEL, max_tokens=512)
868
+ self.research_llm = make_chat_model(GROQ_RESEARCH_MODEL, max_tokens=512)
869
 
870
  self.graph = self.build_graph()
871
 
 
978
  question=question,
979
  context=context,
980
  context_label="Image analysis",
981
+ llm=self.strong_llm,
982
  )
983
  return {"context": context, "raw_answer": raw_answer}
984
 
 
1000
  path = state["local_path"]
1001
  question = state["question"]
1002
 
1003
+ context = read_spreadsheet_context(Path(path))
1004
+ summary = build_spreadsheet_summary(Path(path))
1005
+ if summary:
1006
+ context = f"{context}\n\n--- Computed spreadsheet summary ---\n{summary}"
 
 
 
 
 
 
 
1007
 
1008
  raw_answer = self.answer_from_context(
1009
  question=question,
1010
+ context=context,
1011
+ context_label="Spreadsheet data and computed summary",
1012
+ llm=self.strong_llm,
1013
  )
1014
 
1015
  return {"context": context, "raw_answer": raw_answer}
 
1036
  question=question,
1037
  context=context,
1038
  context_label="Code and execution result",
1039
+ llm=self.strong_llm,
1040
  )
1041
  return {"context": context, "raw_answer": raw_answer}
1042
 
 
1084
  llm=self.research_llm,
1085
  )
1086
 
1087
+ if is_bad_answer(raw_answer):
1088
+ context = extend_research_context(question, context, query)
1089
+ print(f"[research extended context len] {len(context)}")
1090
+ raw_answer = self.answer_from_context(
1091
+ question=question,
1092
+ context=context,
1093
+ context_label="Extended web research results",
1094
+ llm=self.strong_llm,
1095
+ )
1096
+
1097
  print(f"[research raw_answer] {repr(raw_answer[:500])}")
1098
  return {"context": context, "raw_answer": raw_answer}
1099
 
 
1110
  llm=self.research_llm,
1111
  )
1112
 
1113
+ if is_bad_answer(raw_answer):
1114
+ context = extend_research_context(question, context, question)
1115
+ raw_answer = self.answer_from_context(
1116
+ question=question,
1117
+ context=context,
1118
+ context_label="Extended YouTube/web transcript search results",
1119
+ llm=self.strong_llm,
1120
+ )
1121
+
1122
  return {
1123
  "context": context,
1124
  "raw_answer": raw_answer,