Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- agent.py +195 -7
- app.py +13 -6
- requirements.txt +2 -1
agent.py
CHANGED
|
@@ -4,9 +4,12 @@
|
|
| 4 |
|
| 5 |
import os
|
| 6 |
import re
|
| 7 |
-
import
|
|
|
|
| 8 |
import tempfile
|
| 9 |
import subprocess
|
|
|
|
|
|
|
| 10 |
|
| 11 |
from dotenv import load_dotenv
|
| 12 |
|
|
@@ -18,10 +21,16 @@ from langchain_core.tools import tool
|
|
| 18 |
from langchain_core.messages import SystemMessage
|
| 19 |
|
| 20 |
from langchain_community.document_loaders import WikipediaLoader
|
| 21 |
-
from langchain_community.tools import
|
| 22 |
|
| 23 |
import requests
|
| 24 |
from bs4 import BeautifulSoup
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
load_dotenv()
|
| 27 |
|
|
@@ -51,9 +60,12 @@ def wiki_search(query: str) -> str:
|
|
| 51 |
|
| 52 |
@tool
|
| 53 |
def web_search(query: str) -> str:
|
| 54 |
-
"""Search the web
|
| 55 |
try:
|
| 56 |
-
result =
|
|
|
|
|
|
|
|
|
|
| 57 |
return result if result else "No results found. Try a different query."
|
| 58 |
except Exception as e:
|
| 59 |
return f"Web search unavailable: {type(e).__name__}. Try wiki_search instead."
|
|
@@ -74,15 +86,180 @@ def fetch_page(url: str) -> str:
|
|
| 74 |
return f"Could not fetch page: {type(e).__name__}."
|
| 75 |
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
@tool
|
| 78 |
def run_python(code: str) -> str:
|
| 79 |
"""Execute Python code and return stdout. Use for calculations, counting, data processing."""
|
|
|
|
| 80 |
try:
|
| 81 |
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
| 82 |
f.write(code)
|
| 83 |
fname = f.name
|
| 84 |
p = subprocess.run(
|
| 85 |
-
[
|
| 86 |
capture_output=True,
|
| 87 |
text=True,
|
| 88 |
timeout=20
|
|
@@ -98,6 +275,12 @@ def run_python(code: str) -> str:
|
|
| 98 |
return "Script timed out after 20 seconds."
|
| 99 |
except Exception as e:
|
| 100 |
return f"Execution failed: {type(e).__name__}."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
|
| 103 |
@tool
|
|
@@ -106,7 +289,7 @@ def reverse_text(text: str) -> str:
|
|
| 106 |
return text[::-1]
|
| 107 |
|
| 108 |
|
| 109 |
-
TOOLS = [wiki_search, web_search, fetch_page, run_python, reverse_text]
|
| 110 |
|
| 111 |
# ==========================================================
|
| 112 |
# MODELS — primary + ordered fallback chain
|
|
@@ -117,6 +300,9 @@ def _llm(name: str) -> ChatGroq:
|
|
| 117 |
model=name,
|
| 118 |
api_key=os.getenv("GROQ_API_KEY"),
|
| 119 |
temperature=0,
|
|
|
|
|
|
|
|
|
|
| 120 |
)
|
| 121 |
|
| 122 |
|
|
@@ -141,9 +327,11 @@ Produce the exact correct answer — nothing more, nothing less.
|
|
| 141 |
- Use wiki_search for historical facts, biographies, science, geography.
|
| 142 |
- Use web_search for recent events, specific articles, prices, or anything time-sensitive.
|
| 143 |
- Use fetch_page when a URL is provided or a search result points to a relevant page.
|
|
|
|
|
|
|
| 144 |
- Use run_python for any arithmetic, counting, sorting, or data transformation.
|
| 145 |
- Use reverse_text only when asked to reverse a string.
|
| 146 |
-
-
|
| 147 |
|
| 148 |
## Answer format rules
|
| 149 |
1. Output the raw value only — no explanation, no preamble.
|
|
|
|
| 4 |
|
| 5 |
import os
|
| 6 |
import re
|
| 7 |
+
import sys
|
| 8 |
+
import json
|
| 9 |
import tempfile
|
| 10 |
import subprocess
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from urllib.parse import urlparse
|
| 13 |
|
| 14 |
from dotenv import load_dotenv
|
| 15 |
|
|
|
|
| 21 |
from langchain_core.messages import SystemMessage
|
| 22 |
|
| 23 |
from langchain_community.document_loaders import WikipediaLoader
|
| 24 |
+
from langchain_community.tools import DuckDuckGoSearchResults
|
| 25 |
|
| 26 |
import requests
|
| 27 |
from bs4 import BeautifulSoup
|
| 28 |
+
import pandas as pd
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
from youtube_transcript_api import YouTubeTranscriptApi
|
| 32 |
+
except Exception:
|
| 33 |
+
YouTubeTranscriptApi = None
|
| 34 |
|
| 35 |
load_dotenv()
|
| 36 |
|
|
|
|
| 60 |
|
| 61 |
@tool
|
| 62 |
def web_search(query: str) -> str:
|
| 63 |
+
"""Search the web and return compact title/url/snippet results."""
|
| 64 |
try:
|
| 65 |
+
result = DuckDuckGoSearchResults(
|
| 66 |
+
max_results=5,
|
| 67 |
+
output_format="list",
|
| 68 |
+
).run(query)
|
| 69 |
return result if result else "No results found. Try a different query."
|
| 70 |
except Exception as e:
|
| 71 |
return f"Web search unavailable: {type(e).__name__}. Try wiki_search instead."
|
|
|
|
| 86 |
return f"Could not fetch page: {type(e).__name__}."
|
| 87 |
|
| 88 |
|
| 89 |
+
def _youtube_video_id(text: str) -> str | None:
|
| 90 |
+
patterns = [
|
| 91 |
+
r"(?:v=|youtu\.be/|shorts/|embed/)([A-Za-z0-9_-]{11})",
|
| 92 |
+
r"^([A-Za-z0-9_-]{11})$",
|
| 93 |
+
]
|
| 94 |
+
for pattern in patterns:
|
| 95 |
+
match = re.search(pattern, text)
|
| 96 |
+
if match:
|
| 97 |
+
return match.group(1)
|
| 98 |
+
return None
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
@tool
|
| 102 |
+
def youtube_transcript(video_url_or_id: str) -> str:
|
| 103 |
+
"""Get available YouTube captions/transcript for a video URL or video id."""
|
| 104 |
+
if YouTubeTranscriptApi is None:
|
| 105 |
+
return "YouTube transcript library is unavailable."
|
| 106 |
+
video_id = _youtube_video_id(video_url_or_id)
|
| 107 |
+
if not video_id:
|
| 108 |
+
return "Could not identify a YouTube video id."
|
| 109 |
+
try:
|
| 110 |
+
rows = YouTubeTranscriptApi.get_transcript(video_id, languages=["en", "en-US", "en-GB"])
|
| 111 |
+
except Exception:
|
| 112 |
+
try:
|
| 113 |
+
rows = YouTubeTranscriptApi.get_transcript(video_id)
|
| 114 |
+
except Exception as e:
|
| 115 |
+
return f"Transcript unavailable: {type(e).__name__}. Use web_search for quotes or descriptions."
|
| 116 |
+
lines = []
|
| 117 |
+
for row in rows[:220]:
|
| 118 |
+
start = int(float(row.get("start", 0)))
|
| 119 |
+
text = " ".join(str(row.get("text", "")).split())
|
| 120 |
+
if text:
|
| 121 |
+
lines.append(f"{start}s: {text}")
|
| 122 |
+
return "\n".join(lines)[:7000] if lines else "Transcript is empty."
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def _download_to_temp(source: str) -> tuple[Path | None, str]:
|
| 126 |
+
if re.fullmatch(r"[0-9a-fA-F-]{36}", source.strip()):
|
| 127 |
+
source = f"https://agents-course-unit4-scoring.hf.space/files/{source.strip()}"
|
| 128 |
+
if not source.startswith(("http://", "https://")):
|
| 129 |
+
return None, "Input must be a URL or benchmark task_id."
|
| 130 |
+
|
| 131 |
+
try:
|
| 132 |
+
r = requests.get(source, timeout=25, headers={"User-Agent": "Mozilla/5.0"})
|
| 133 |
+
if r.status_code == 404:
|
| 134 |
+
return None, "No attached file found for this task."
|
| 135 |
+
r.raise_for_status()
|
| 136 |
+
except Exception as e:
|
| 137 |
+
return None, f"Download failed: {type(e).__name__}."
|
| 138 |
+
|
| 139 |
+
parsed = urlparse(source)
|
| 140 |
+
suffix = Path(parsed.path).suffix
|
| 141 |
+
content_type = r.headers.get("content-type", "").lower()
|
| 142 |
+
if not suffix:
|
| 143 |
+
if "spreadsheet" in content_type or "excel" in content_type:
|
| 144 |
+
suffix = ".xlsx"
|
| 145 |
+
elif "csv" in content_type:
|
| 146 |
+
suffix = ".csv"
|
| 147 |
+
elif "pdf" in content_type:
|
| 148 |
+
suffix = ".pdf"
|
| 149 |
+
elif "python" in content_type or "text" in content_type:
|
| 150 |
+
suffix = ".txt"
|
| 151 |
+
elif "image" in content_type:
|
| 152 |
+
suffix = "." + content_type.split("/")[-1].split(";")[0]
|
| 153 |
+
else:
|
| 154 |
+
cd = r.headers.get("content-disposition", "")
|
| 155 |
+
match = re.search(r'filename="?([^";]+)', cd)
|
| 156 |
+
suffix = Path(match.group(1)).suffix if match else ".bin"
|
| 157 |
+
|
| 158 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as f:
|
| 159 |
+
f.write(r.content)
|
| 160 |
+
return Path(f.name), f"Downloaded {len(r.content)} bytes as {suffix or '.bin'}."
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def _read_text_file(path: Path) -> str:
|
| 164 |
+
for enc in ("utf-8", "latin-1"):
|
| 165 |
+
try:
|
| 166 |
+
return path.read_text(encoding=enc)[:9000]
|
| 167 |
+
except Exception:
|
| 168 |
+
continue
|
| 169 |
+
return "Could not decode text file."
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
@tool
|
| 173 |
+
def inspect_file(source: str) -> str:
|
| 174 |
+
"""Download and inspect an attached benchmark file or URL. Input can be a task_id or URL."""
|
| 175 |
+
path, status = _download_to_temp(source)
|
| 176 |
+
if path is None:
|
| 177 |
+
return status
|
| 178 |
+
|
| 179 |
+
try:
|
| 180 |
+
suffix = path.suffix.lower()
|
| 181 |
+
|
| 182 |
+
if suffix in {".py", ".txt", ".md", ".json", ".html", ".csv"}:
|
| 183 |
+
if suffix == ".csv":
|
| 184 |
+
df = pd.read_csv(path)
|
| 185 |
+
return _summarize_dataframe(df, "csv")
|
| 186 |
+
text = _read_text_file(path)
|
| 187 |
+
if suffix == ".py":
|
| 188 |
+
run = subprocess.run(
|
| 189 |
+
[sys.executable, str(path)],
|
| 190 |
+
capture_output=True,
|
| 191 |
+
text=True,
|
| 192 |
+
timeout=20,
|
| 193 |
+
)
|
| 194 |
+
stdout = run.stdout.strip()
|
| 195 |
+
stderr = run.stderr.strip()
|
| 196 |
+
return f"{status}\nPython stdout:\n{stdout[:3000] or '(empty)'}\nPython stderr:\n{stderr[:1000] or '(empty)'}\n\nCode preview:\n{text[:4000]}"
|
| 197 |
+
return f"{status}\n{text}"
|
| 198 |
+
|
| 199 |
+
if suffix in {".xlsx", ".xls"}:
|
| 200 |
+
xl = pd.ExcelFile(path)
|
| 201 |
+
parts = [status, f"Workbook sheets: {', '.join(xl.sheet_names)}"]
|
| 202 |
+
for sheet in xl.sheet_names[:4]:
|
| 203 |
+
df = xl.parse(sheet)
|
| 204 |
+
parts.append(_summarize_dataframe(df, sheet))
|
| 205 |
+
return "\n\n".join(parts)[:9000]
|
| 206 |
+
|
| 207 |
+
if suffix == ".pdf":
|
| 208 |
+
try:
|
| 209 |
+
from pypdf import PdfReader
|
| 210 |
+
reader = PdfReader(str(path))
|
| 211 |
+
text = "\n".join((p.extract_text() or "") for p in reader.pages[:8])
|
| 212 |
+
return f"{status}\nPDF pages: {len(reader.pages)}\n{text[:8500] or 'No extractable text.'}"
|
| 213 |
+
except Exception as e:
|
| 214 |
+
return f"{status}\nPDF extraction unavailable: {type(e).__name__}."
|
| 215 |
+
|
| 216 |
+
if suffix in {".png", ".jpg", ".jpeg", ".webp", ".gif"}:
|
| 217 |
+
return f"{status}\nImage file detected. If the question requires visual reasoning, use any visible description in the question and web_search; this runtime has no vision model."
|
| 218 |
+
|
| 219 |
+
if suffix in {".mp3", ".wav", ".m4a", ".ogg", ".flac"}:
|
| 220 |
+
return f"{status}\nAudio file detected. This runtime has no local speech-to-text model; use web_search if the recording is from public material."
|
| 221 |
+
|
| 222 |
+
return f"{status}\nUnsupported file type: {suffix or 'unknown'}."
|
| 223 |
+
except Exception as e:
|
| 224 |
+
return f"{status}\nInspection failed: {type(e).__name__}: {e}"
|
| 225 |
+
finally:
|
| 226 |
+
try:
|
| 227 |
+
path.unlink(missing_ok=True)
|
| 228 |
+
except Exception:
|
| 229 |
+
pass
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _summarize_dataframe(df: pd.DataFrame, name: str) -> str:
|
| 233 |
+
rows, cols = df.shape
|
| 234 |
+
df = df.dropna(how="all")
|
| 235 |
+
preview = df.head(12).to_string(index=False)
|
| 236 |
+
numeric = df.select_dtypes(include="number")
|
| 237 |
+
sums = numeric.sum(numeric_only=True).to_dict()
|
| 238 |
+
sums_text = json.dumps({str(k): round(float(v), 4) for k, v in sums.items()}, ensure_ascii=True)
|
| 239 |
+
cols_text = ", ".join(map(str, df.columns))
|
| 240 |
+
food_hint = ""
|
| 241 |
+
lower_cols = {str(c).lower(): c for c in df.columns}
|
| 242 |
+
category_col = next((c for key, c in lower_cols.items() if any(x in key for x in ["category", "type", "item type"])), None)
|
| 243 |
+
sales_col = next((c for key, c in lower_cols.items() if any(x in key for x in ["sales", "revenue", "amount", "total"])), None)
|
| 244 |
+
if category_col is not None and sales_col is not None:
|
| 245 |
+
try:
|
| 246 |
+
grouped = df.groupby(category_col)[sales_col].sum(numeric_only=True).to_dict()
|
| 247 |
+
food_hint = "\nGrouped sums: " + json.dumps({str(k): round(float(v), 2) for k, v in grouped.items()}, ensure_ascii=True)
|
| 248 |
+
except Exception:
|
| 249 |
+
pass
|
| 250 |
+
return f"Sheet/table {name}: {rows} rows x {cols} cols\nColumns: {cols_text}\nNumeric sums: {sums_text}{food_hint}\nPreview:\n{preview}"
|
| 251 |
+
|
| 252 |
+
|
| 253 |
@tool
|
| 254 |
def run_python(code: str) -> str:
|
| 255 |
"""Execute Python code and return stdout. Use for calculations, counting, data processing."""
|
| 256 |
+
fname = None
|
| 257 |
try:
|
| 258 |
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
| 259 |
f.write(code)
|
| 260 |
fname = f.name
|
| 261 |
p = subprocess.run(
|
| 262 |
+
[sys.executable, fname],
|
| 263 |
capture_output=True,
|
| 264 |
text=True,
|
| 265 |
timeout=20
|
|
|
|
| 275 |
return "Script timed out after 20 seconds."
|
| 276 |
except Exception as e:
|
| 277 |
return f"Execution failed: {type(e).__name__}."
|
| 278 |
+
finally:
|
| 279 |
+
if fname:
|
| 280 |
+
try:
|
| 281 |
+
Path(fname).unlink(missing_ok=True)
|
| 282 |
+
except Exception:
|
| 283 |
+
pass
|
| 284 |
|
| 285 |
|
| 286 |
@tool
|
|
|
|
| 289 |
return text[::-1]
|
| 290 |
|
| 291 |
|
| 292 |
+
TOOLS = [wiki_search, web_search, fetch_page, youtube_transcript, inspect_file, run_python, reverse_text]
|
| 293 |
|
| 294 |
# ==========================================================
|
| 295 |
# MODELS — primary + ordered fallback chain
|
|
|
|
| 300 |
model=name,
|
| 301 |
api_key=os.getenv("GROQ_API_KEY"),
|
| 302 |
temperature=0,
|
| 303 |
+
max_tokens=384,
|
| 304 |
+
timeout=45,
|
| 305 |
+
max_retries=1,
|
| 306 |
)
|
| 307 |
|
| 308 |
|
|
|
|
| 327 |
- Use wiki_search for historical facts, biographies, science, geography.
|
| 328 |
- Use web_search for recent events, specific articles, prices, or anything time-sensitive.
|
| 329 |
- Use fetch_page when a URL is provided or a search result points to a relevant page.
|
| 330 |
+
- Use youtube_transcript first for YouTube questions.
|
| 331 |
+
- Use inspect_file whenever the question says attached file, attached image, spreadsheet, audio, Python code, or provides a task file URL.
|
| 332 |
- Use run_python for any arithmetic, counting, sorting, or data transformation.
|
| 333 |
- Use reverse_text only when asked to reverse a string.
|
| 334 |
+
- Prefer tools over guessing. Use compact searches. Stop as soon as you have a confident answer.
|
| 335 |
|
| 336 |
## Answer format rules
|
| 337 |
1. Output the raw value only — no explanation, no preamble.
|
app.py
CHANGED
|
@@ -29,7 +29,7 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
|
| 29 |
|
| 30 |
# Single values — no per-difficulty branching
|
| 31 |
TIMEOUT_SECONDS = 120
|
| 32 |
-
RECURSION_LIMIT =
|
| 33 |
PAUSE_SECONDS = 1.5
|
| 34 |
|
| 35 |
# ==========================================================
|
|
@@ -78,9 +78,16 @@ class BenchmarkAgent:
|
|
| 78 |
self.graph = build_graph()
|
| 79 |
log("Agent ready.")
|
| 80 |
|
| 81 |
-
def __call__(self, question: str) -> tuple[str, list, dict]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
result = self.graph.invoke(
|
| 83 |
-
{"messages": [HumanMessage(content=
|
| 84 |
{"recursion_limit": RECURSION_LIMIT},
|
| 85 |
)
|
| 86 |
answer = extract_final_answer(result) or "N/A"
|
|
@@ -164,7 +171,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
|
|
| 164 |
|
| 165 |
try:
|
| 166 |
def solve():
|
| 167 |
-
return agent(question)
|
| 168 |
|
| 169 |
result, did_timeout = run_with_timeout(solve, TIMEOUT_SECONDS)
|
| 170 |
elapsed = round(time.time() - start, 1)
|
|
@@ -260,8 +267,8 @@ with gr.Blocks() as demo:
|
|
| 260 |
gr.Markdown(
|
| 261 |
"""
|
| 262 |
**Model**: `qwen/qwen3-32b` (primary) → `llama-3.3-70b-versatile` → `llama-3.1-8b-instant`
|
| 263 |
-
**
|
| 264 |
-
**Timeout**:
|
| 265 |
"""
|
| 266 |
)
|
| 267 |
|
|
|
|
| 29 |
|
| 30 |
# Single values — no per-difficulty branching
|
| 31 |
TIMEOUT_SECONDS = 120
|
| 32 |
+
RECURSION_LIMIT = 20
|
| 33 |
PAUSE_SECONDS = 1.5
|
| 34 |
|
| 35 |
# ==========================================================
|
|
|
|
| 78 |
self.graph = build_graph()
|
| 79 |
log("Agent ready.")
|
| 80 |
|
| 81 |
+
def __call__(self, question: str, task_id: str = "") -> tuple[str, list, dict]:
|
| 82 |
+
enriched_question = question
|
| 83 |
+
if task_id:
|
| 84 |
+
enriched_question = (
|
| 85 |
+
f"Task ID: {task_id}\n"
|
| 86 |
+
f"Attached file URL, if any: {DEFAULT_API_URL}/files/{task_id}\n\n"
|
| 87 |
+
f"Question: {question}"
|
| 88 |
+
)
|
| 89 |
result = self.graph.invoke(
|
| 90 |
+
{"messages": [HumanMessage(content=enriched_question)]},
|
| 91 |
{"recursion_limit": RECURSION_LIMIT},
|
| 92 |
)
|
| 93 |
answer = extract_final_answer(result) or "N/A"
|
|
|
|
| 171 |
|
| 172 |
try:
|
| 173 |
def solve():
|
| 174 |
+
return agent(question, task_id)
|
| 175 |
|
| 176 |
result, did_timeout = run_with_timeout(solve, TIMEOUT_SECONDS)
|
| 177 |
elapsed = round(time.time() - start, 1)
|
|
|
|
| 267 |
gr.Markdown(
|
| 268 |
"""
|
| 269 |
**Model**: `qwen/qwen3-32b` (primary) → `llama-3.3-70b-versatile` → `llama-3.1-8b-instant`
|
| 270 |
+
**Tools**: structured search, page fetch, YouTube transcripts, task-file inspection, Python execution
|
| 271 |
+
**Timeout**: 120 s per question · **Recursion limit**: 20
|
| 272 |
"""
|
| 273 |
)
|
| 274 |
|
requirements.txt
CHANGED
|
@@ -10,6 +10,7 @@ groq
|
|
| 10 |
# Data / file processing
|
| 11 |
pandas
|
| 12 |
openpyxl # required by pandas for .xlsx read/write
|
|
|
|
| 13 |
|
| 14 |
# Web tools
|
| 15 |
requests
|
|
@@ -26,4 +27,4 @@ youtube-transcript-api
|
|
| 26 |
gradio
|
| 27 |
|
| 28 |
# Env vars
|
| 29 |
-
python-dotenv
|
|
|
|
| 10 |
# Data / file processing
|
| 11 |
pandas
|
| 12 |
openpyxl # required by pandas for .xlsx read/write
|
| 13 |
+
pypdf # lightweight PDF text extraction
|
| 14 |
|
| 15 |
# Web tools
|
| 16 |
requests
|
|
|
|
| 27 |
gradio
|
| 28 |
|
| 29 |
# Env vars
|
| 30 |
+
python-dotenv
|