czrrr commited on
Commit
4e424f4
·
verified ·
1 Parent(s): 513b67b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -38
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import re
 
3
  from io import BytesIO
4
  from pathlib import Path
5
  from zipfile import ZipFile
@@ -7,7 +8,6 @@ from zipfile import ZipFile
7
  import gradio as gr
8
  import pandas as pd
9
  import requests
10
- from litellm import completion
11
  from smolagents import (
12
  CodeAgent,
13
  DuckDuckGoSearchTool,
@@ -22,7 +22,9 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
22
  RESULT_COLUMNS = ["Task ID", "Question", "Submitted Answer"]
23
  HTTP_TIMEOUT = 45
24
  MAX_EXTRACTED_CHARS = 35_000
25
- MODEL_ID = "huggingface/Qwen/Qwen2.5-Coder-32B-Instruct"
 
 
26
 
27
 
28
  def clean_filename(value: str) -> str:
@@ -101,12 +103,35 @@ def extract_attachment_text(data: bytes, filename: str) -> str:
101
  BytesIO(data), sep=separator, encoding_errors="replace"
102
  )
103
  text = dataframe.to_csv(index=False)
104
- elif suffix in {".txt", ".md", ".json", ".html", ".htm", ".xml"}:
 
 
 
 
 
 
 
 
105
  text = data.decode("utf-8", errors="replace")
106
  if suffix in {".html", ".htm"}:
107
  from bs4 import BeautifulSoup
108
 
109
  text = BeautifulSoup(text, "html.parser").get_text("\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  elif suffix == ".zip":
111
  with ZipFile(BytesIO(data)) as archive:
112
  text = "Files inside ZIP:\n" + "\n".join(archive.namelist())
@@ -167,6 +192,122 @@ class InspectGaiaAttachmentTool(Tool):
167
  return f"Could not inspect attachment for task {task_id}: {exc}"
168
 
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  class BasicAgent:
171
  def __init__(self):
172
  print("Inicializando o agente GAIA...")
@@ -194,6 +335,8 @@ class BasicAgent:
194
  language="en",
195
  ),
196
  InspectGaiaAttachmentTool(),
 
 
197
  ],
198
  model=self.model,
199
  max_steps=10,
@@ -220,6 +363,12 @@ Research carefully before answering and cross-check uncertain facts.
220
  Use web_search to find sources and visit_webpage to read a result in detail.
221
  If the task mentions an attached file, call inspect_gaia_attachment with the
222
  task_id given in the task.
 
 
 
 
 
 
223
  Only call tools that are explicitly available. Never invent a function such as
224
  visit_webpage if it is not listed, and never use subprocess or shell commands.
225
  Do not repeat nearly identical searches. If one approach fails, change source
@@ -295,43 +444,34 @@ If a number is requested, return only that number.
295
  return text.replace("FINAL ANSWER", "").strip()
296
 
297
  def format_exact_answer(self, question: str, raw_answer: str) -> str:
298
- """Usa uma chamada curta para converter o resultado em exact match."""
299
- raw_answer = self.deterministic_answer_cleanup(raw_answer)
300
- formatter_prompt = f"""
301
- You are a strict answer formatter for an exact-match benchmark.
302
-
303
- Original question:
304
- {question}
305
-
306
- Candidate answer:
307
- {raw_answer}
308
-
309
- Extract only the final value requested by the original question.
310
- Do not solve the question again and do not explain anything.
311
- Do not include labels such as "answer" or "final answer".
312
- Do not use Markdown, citations, full sentences, or quotation marks unless the
313
- question explicitly requires them.
314
- Preserve the requested ordering, spelling, capitalization, units, plural form,
315
- and separators. If a comma-separated list is requested, output only that list.
316
- Your entire response must be the exact answer and nothing else.
317
- """.strip()
318
-
319
- try:
320
- response = completion(
321
- model=MODEL_ID,
322
- api_key=self.hf_token,
323
- messages=[{"role": "user", "content": formatter_prompt}],
324
- temperature=0,
325
- max_tokens=180,
326
  )
327
- formatted = response.choices[0].message.content
328
- cleaned = self.deterministic_answer_cleanup(formatted)
329
- if cleaned:
330
- return cleaned
331
- except Exception as exc:
332
- print(f"Exact-answer formatter failed; using cleaned result: {exc}")
333
 
334
- return raw_answer
335
 
336
 
337
  def empty_results() -> pd.DataFrame:
 
1
  import os
2
  import re
3
+ import base64
4
  from io import BytesIO
5
  from pathlib import Path
6
  from zipfile import ZipFile
 
8
  import gradio as gr
9
  import pandas as pd
10
  import requests
 
11
  from smolagents import (
12
  CodeAgent,
13
  DuckDuckGoSearchTool,
 
22
  RESULT_COLUMNS = ["Task ID", "Question", "Submitted Answer"]
23
  HTTP_TIMEOUT = 45
24
  MAX_EXTRACTED_CHARS = 35_000
25
+ MODEL_ID = os.getenv(
26
+ "GAIA_MODEL_ID", "huggingface/openai/gpt-oss-120b"
27
+ )
28
 
29
 
30
  def clean_filename(value: str) -> str:
 
103
  BytesIO(data), sep=separator, encoding_errors="replace"
104
  )
105
  text = dataframe.to_csv(index=False)
106
+ elif suffix in {
107
+ ".txt",
108
+ ".md",
109
+ ".json",
110
+ ".html",
111
+ ".htm",
112
+ ".xml",
113
+ ".py",
114
+ }:
115
  text = data.decode("utf-8", errors="replace")
116
  if suffix in {".html", ".htm"}:
117
  from bs4 import BeautifulSoup
118
 
119
  text = BeautifulSoup(text, "html.parser").get_text("\n")
120
+ elif suffix in {".mp3", ".wav", ".flac", ".m4a", ".ogg"}:
121
+ from huggingface_hub import InferenceClient
122
+
123
+ token = os.getenv("HF_TOKEN")
124
+ if not token:
125
+ text = "Audio transcription failed: HF_TOKEN is not configured."
126
+ else:
127
+ client = InferenceClient(api_key=token, provider="auto")
128
+ asr_model = os.getenv(
129
+ "GAIA_ASR_MODEL", "openai/whisper-large-v3"
130
+ )
131
+ transcript = client.automatic_speech_recognition(
132
+ data, model=asr_model
133
+ )
134
+ text = f"Audio transcript:\n{transcript.text}"
135
  elif suffix == ".zip":
136
  with ZipFile(BytesIO(data)) as archive:
137
  text = "Files inside ZIP:\n" + "\n".join(archive.namelist())
 
192
  return f"Could not inspect attachment for task {task_id}: {exc}"
193
 
194
 
195
+ class YouTubeTranscriptTool(Tool):
196
+ name = "youtube_transcript"
197
+ description = (
198
+ "Retrieves the spoken transcript or subtitles of a YouTube video. "
199
+ "Use it for questions asking what a person says in a linked video. "
200
+ "It cannot determine purely visual events."
201
+ )
202
+ inputs = {
203
+ "url": {
204
+ "type": "string",
205
+ "description": "Full YouTube URL or the 11-character video ID.",
206
+ }
207
+ }
208
+ output_type = "string"
209
+
210
+ def forward(self, url: str) -> str:
211
+ from youtube_transcript_api import YouTubeTranscriptApi
212
+
213
+ value = str(url or "").strip()
214
+ match = re.search(
215
+ r"(?:v=|youtu\.be/|shorts/)([A-Za-z0-9_-]{11})", value
216
+ )
217
+ video_id = match.group(1) if match else value
218
+ if not re.fullmatch(r"[A-Za-z0-9_-]{11}", video_id):
219
+ return "Could not identify a valid YouTube video ID."
220
+
221
+ try:
222
+ api = YouTubeTranscriptApi()
223
+ transcript = api.fetch(video_id)
224
+ lines = []
225
+ for snippet in transcript:
226
+ text = getattr(snippet, "text", None)
227
+ if text is None and isinstance(snippet, dict):
228
+ text = snippet.get("text")
229
+ if text:
230
+ lines.append(str(text))
231
+ result = " ".join(lines).strip()
232
+ return result or "The video has no available transcript."
233
+ except Exception as exc:
234
+ return f"Could not retrieve YouTube transcript: {exc}"
235
+
236
+
237
+ class AnalyzeGaiaImageTool(Tool):
238
+ name = "analyze_gaia_image"
239
+ description = (
240
+ "Downloads the official image for a GAIA task and analyzes it with a "
241
+ "vision model. Use this for questions whose answer depends on image "
242
+ "pixels, diagrams, chess positions, or visual details."
243
+ )
244
+ inputs = {
245
+ "task_id": {
246
+ "type": "string",
247
+ "description": "The exact GAIA task_id associated with the image.",
248
+ },
249
+ "question": {
250
+ "type": "string",
251
+ "description": "The complete question the image must answer.",
252
+ },
253
+ }
254
+ output_type = "string"
255
+
256
+ def forward(self, task_id: str, question: str) -> str:
257
+ from openai import OpenAI
258
+
259
+ token = os.getenv("HF_TOKEN")
260
+ if not token:
261
+ return "Image analysis failed: HF_TOKEN is not configured."
262
+
263
+ try:
264
+ response = requests.get(
265
+ f"{DEFAULT_API_URL}/files/{str(task_id).strip()}",
266
+ timeout=HTTP_TIMEOUT,
267
+ )
268
+ response.raise_for_status()
269
+ mime = response.headers.get("content-type", "image/png").split(";")[0]
270
+ encoded = base64.b64encode(response.content).decode("ascii")
271
+
272
+ client = OpenAI(
273
+ base_url="https://router.huggingface.co/v1",
274
+ api_key=token,
275
+ )
276
+ vision_model = os.getenv(
277
+ "GAIA_VISION_MODEL",
278
+ "Qwen/Qwen3-VL-235B-A22B-Instruct:cheapest",
279
+ )
280
+ result = client.chat.completions.create(
281
+ model=vision_model,
282
+ messages=[
283
+ {
284
+ "role": "user",
285
+ "content": [
286
+ {
287
+ "type": "text",
288
+ "text": (
289
+ "Analyze the supplied image carefully and "
290
+ "answer this task. Explain visual evidence "
291
+ f"briefly so another agent can verify it:\n{question}"
292
+ ),
293
+ },
294
+ {
295
+ "type": "image_url",
296
+ "image_url": {
297
+ "url": f"data:{mime};base64,{encoded}"
298
+ },
299
+ },
300
+ ],
301
+ }
302
+ ],
303
+ temperature=0,
304
+ max_tokens=600,
305
+ )
306
+ return str(result.choices[0].message.content).strip()
307
+ except Exception as exc:
308
+ return f"Could not analyze the GAIA image: {exc}"
309
+
310
+
311
  class BasicAgent:
312
  def __init__(self):
313
  print("Inicializando o agente GAIA...")
 
335
  language="en",
336
  ),
337
  InspectGaiaAttachmentTool(),
338
+ YouTubeTranscriptTool(),
339
+ AnalyzeGaiaImageTool(),
340
  ],
341
  model=self.model,
342
  max_steps=10,
 
363
  Use web_search to find sources and visit_webpage to read a result in detail.
364
  If the task mentions an attached file, call inspect_gaia_attachment with the
365
  task_id given in the task.
366
+ If the task asks what someone says in a YouTube video, call youtube_transcript
367
+ with the exact video URL before searching the web.
368
+ If the task depends on an attached image, call analyze_gaia_image with the
369
+ task_id and complete question. Do not try to infer image contents from metadata.
370
+ Prefer primary or official sources. When search snippets conflict, open the
371
+ source and verify the relevant passage instead of guessing.
372
  Only call tools that are explicitly available. Never invent a function such as
373
  visit_webpage if it is not listed, and never use subprocess or shell commands.
374
  Do not repeat nearly identical searches. If one approach fails, change source
 
444
  return text.replace("FINAL ANSWER", "").strip()
445
 
446
  def format_exact_answer(self, question: str, raw_answer: str) -> str:
447
+ """Limpa o resultado mecanicamente, sem pedir a outro modelo para alterá-lo."""
448
+ del question
449
+ cleaned = self.deterministic_answer_cleanup(raw_answer)
450
+ lines = [line.strip() for line in cleaned.splitlines() if line.strip()]
451
+
452
+ # Quando o agente ainda inclui uma explicação e deixa uma resposta curta
453
+ # isolada na última linha, conserva somente essa última linha.
454
+ if len(lines) > 1:
455
+ last_line = lines[-1]
456
+ reasoning_cues = (
457
+ "because",
458
+ "therefore",
459
+ "research",
460
+ "source",
461
+ "conclude",
462
+ "analysis",
463
+ "porque",
464
+ "portanto",
465
+ "pesquisa",
466
+ "conclu",
 
 
 
 
 
 
 
 
467
  )
468
+ preceding = " ".join(lines[:-1]).lower()
469
+ if len(last_line) <= 250 and any(
470
+ cue in preceding for cue in reasoning_cues
471
+ ):
472
+ cleaned = last_line
 
473
 
474
+ return self.deterministic_answer_cleanup(cleaned)
475
 
476
 
477
  def empty_results() -> pd.DataFrame: