Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
import re
|
| 3 |
import base64
|
|
|
|
| 4 |
from io import BytesIO
|
| 5 |
from pathlib import Path
|
| 6 |
from zipfile import ZipFile
|
|
@@ -369,6 +370,12 @@ class BasicAgent:
|
|
| 369 |
gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 370 |
configured_model = os.getenv("GAIA_MODEL_ID")
|
| 371 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
model_id = configured_model or DEFAULT_HF_MODEL
|
| 373 |
|
| 374 |
if model_id.startswith("gemini/"):
|
|
@@ -391,21 +398,44 @@ class BasicAgent:
|
|
| 391 |
max_tokens=2_000,
|
| 392 |
)
|
| 393 |
self.hf_token = hf_token
|
|
|
|
| 394 |
self.model_id = model_id
|
| 395 |
print(f"Modelo principal selecionado: {model_id}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
agent_tools = [
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
user_agent="GAIA-Course-Agent/1.0 (educational project)",
|
| 401 |
-
language="en",
|
| 402 |
-
),
|
| 403 |
InspectGaiaAttachmentTool(),
|
| 404 |
YouTubeTranscriptTool(),
|
| 405 |
AnalyzeGaiaImageTool(),
|
|
|
|
| 406 |
]
|
| 407 |
-
if gemini_api_key:
|
| 408 |
-
agent_tools.append(ConsultGeminiTool())
|
| 409 |
|
| 410 |
self.agent = CodeAgent(
|
| 411 |
tools=agent_tools,
|
|
@@ -430,28 +460,38 @@ class BasicAgent:
|
|
| 430 |
|
| 431 |
exact_match_prompt = """
|
| 432 |
You are an expert AI assistant solving tasks from the GAIA benchmark.
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
"""
|
| 456 |
self.agent.prompt_templates["system_prompt"] = (
|
| 457 |
exact_match_prompt.strip()
|
|
@@ -484,7 +524,12 @@ If a number is requested, return only that number.
|
|
| 484 |
"(GEMINI_API_KEY para Gemini ou HF_TOKEN para Hugging Face)."
|
| 485 |
) from exc
|
| 486 |
raise
|
| 487 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
|
| 489 |
@staticmethod
|
| 490 |
def deterministic_answer_cleanup(answer: str) -> str:
|
|
@@ -548,6 +593,90 @@ If a number is requested, return only that number.
|
|
| 548 |
|
| 549 |
return self.deterministic_answer_cleanup(cleaned)
|
| 550 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 551 |
|
| 552 |
def empty_results() -> pd.DataFrame:
|
| 553 |
return pd.DataFrame(columns=RESULT_COLUMNS)
|
|
|
|
| 1 |
import os
|
| 2 |
import re
|
| 3 |
import base64
|
| 4 |
+
import json
|
| 5 |
from io import BytesIO
|
| 6 |
from pathlib import Path
|
| 7 |
from zipfile import ZipFile
|
|
|
|
| 370 |
gemini_api_key = os.getenv("GEMINI_API_KEY")
|
| 371 |
configured_model = os.getenv("GAIA_MODEL_ID")
|
| 372 |
|
| 373 |
+
if not gemini_api_key:
|
| 374 |
+
raise RuntimeError(
|
| 375 |
+
"O secret GEMINI_API_KEY é obrigatório porque todas as "
|
| 376 |
+
"respostas passam pela revisão final do Gemini."
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
model_id = configured_model or DEFAULT_HF_MODEL
|
| 380 |
|
| 381 |
if model_id.startswith("gemini/"):
|
|
|
|
| 398 |
max_tokens=2_000,
|
| 399 |
)
|
| 400 |
self.hf_token = hf_token
|
| 401 |
+
self.gemini_api_key = gemini_api_key
|
| 402 |
self.model_id = model_id
|
| 403 |
print(f"Modelo principal selecionado: {model_id}")
|
| 404 |
+
|
| 405 |
+
web_search_tool = DuckDuckGoSearchTool(
|
| 406 |
+
max_results=8, rate_limit=1.0
|
| 407 |
+
)
|
| 408 |
+
web_search_tool.description = (
|
| 409 |
+
"Searches the public web and returns result titles, URLs, and short "
|
| 410 |
+
"snippets. Use it to discover candidate sources. It does NOT open "
|
| 411 |
+
"or read the full pages; call visit_webpage on a returned URL."
|
| 412 |
+
)
|
| 413 |
+
visit_page_tool = VisitWebpageTool(max_output_length=30_000)
|
| 414 |
+
visit_page_tool.description = (
|
| 415 |
+
"Opens one exact HTTP/HTTPS URL and returns the readable page "
|
| 416 |
+
"content. Use it after web_search when facts depend on the page "
|
| 417 |
+
"itself, a table, article text, or linked source."
|
| 418 |
+
)
|
| 419 |
+
wikipedia_tool = WikipediaSearchTool(
|
| 420 |
+
user_agent="GAIA-Course-Agent/1.0 (educational project)",
|
| 421 |
+
language="en",
|
| 422 |
+
)
|
| 423 |
+
wikipedia_tool.description = (
|
| 424 |
+
"Searches English Wikipedia content directly. Use it for questions "
|
| 425 |
+
"that explicitly mention Wikipedia or for encyclopedic facts. "
|
| 426 |
+
"For version-specific or nomination details, verify the exact page "
|
| 427 |
+
"or archive with visit_webpage."
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
agent_tools = [
|
| 431 |
+
web_search_tool,
|
| 432 |
+
visit_page_tool,
|
| 433 |
+
wikipedia_tool,
|
|
|
|
|
|
|
|
|
|
| 434 |
InspectGaiaAttachmentTool(),
|
| 435 |
YouTubeTranscriptTool(),
|
| 436 |
AnalyzeGaiaImageTool(),
|
| 437 |
+
ConsultGeminiTool(),
|
| 438 |
]
|
|
|
|
|
|
|
| 439 |
|
| 440 |
self.agent = CodeAgent(
|
| 441 |
tools=agent_tools,
|
|
|
|
| 460 |
|
| 461 |
exact_match_prompt = """
|
| 462 |
You are an expert AI assistant solving tasks from the GAIA benchmark.
|
| 463 |
+
|
| 464 |
+
TOOL ROUTING POLICY:
|
| 465 |
+
1. web_search discovers URLs and snippets. It does not read full pages.
|
| 466 |
+
2. visit_webpage opens and reads one exact URL. Use it after web_search to
|
| 467 |
+
verify articles, tables, archives, papers, and linked primary sources.
|
| 468 |
+
3. wikipedia_search searches English Wikipedia. Use it when Wikipedia is
|
| 469 |
+
explicitly mentioned or for encyclopedic facts. For revision-specific,
|
| 470 |
+
nomination, archive, or table details, verify the exact page.
|
| 471 |
+
4. inspect_gaia_attachment reads the official file for the supplied task_id.
|
| 472 |
+
Call it first for attached PDFs, spreadsheets, documents, audio, or code.
|
| 473 |
+
5. analyze_gaia_image reads image pixels. Use it for diagrams, screenshots,
|
| 474 |
+
chess positions, or questions that visually depend on an attached image.
|
| 475 |
+
6. youtube_transcript retrieves spoken subtitles. Use it when asked what a
|
| 476 |
+
person said. It cannot answer purely visual video questions.
|
| 477 |
+
7. consult_gemini gives a second opinion. Use it after gathering evidence when
|
| 478 |
+
sources conflict or the candidate answer is uncertain. Include the complete
|
| 479 |
+
question, evidence, and candidate answer.
|
| 480 |
+
|
| 481 |
+
Research carefully, prefer primary or official sources, and cross-check
|
| 482 |
+
uncertain facts. A search snippet alone is insufficient when the source page
|
| 483 |
+
can be opened. Never invent a tool, use subprocess, or use shell commands.
|
| 484 |
+
Do not repeat nearly identical searches; change the source or method.
|
| 485 |
+
|
| 486 |
+
FINAL RESPONSE POLICY:
|
| 487 |
+
Call final_answer with only the requested value. Never include reasoning,
|
| 488 |
+
explanations, labels, Markdown, citations, or the words "FINAL ANSWER".
|
| 489 |
+
- Quantity/count: return only the number, unless units or currency are requested.
|
| 490 |
+
- Person: return only the requested name component.
|
| 491 |
+
- City/country/code: return only that value.
|
| 492 |
+
- List: return only items with the requested separator and ordering.
|
| 493 |
+
- Chess move: return only algebraic notation.
|
| 494 |
+
- Quote: return only the requested spoken words.
|
| 495 |
"""
|
| 496 |
self.agent.prompt_templates["system_prompt"] = (
|
| 497 |
exact_match_prompt.strip()
|
|
|
|
| 524 |
"(GEMINI_API_KEY para Gemini ou HF_TOKEN para Hugging Face)."
|
| 525 |
) from exc
|
| 526 |
raise
|
| 527 |
+
candidate = self.format_exact_answer(question, str(result))
|
| 528 |
+
return self.review_answer_with_gemini(
|
| 529 |
+
question=question,
|
| 530 |
+
candidate=candidate,
|
| 531 |
+
task_id=task_id,
|
| 532 |
+
)
|
| 533 |
|
| 534 |
@staticmethod
|
| 535 |
def deterministic_answer_cleanup(answer: str) -> str:
|
|
|
|
| 593 |
|
| 594 |
return self.deterministic_answer_cleanup(cleaned)
|
| 595 |
|
| 596 |
+
def review_answer_with_gemini(
|
| 597 |
+
self, question: str, candidate: str, task_id: str | None = None
|
| 598 |
+
) -> str:
|
| 599 |
+
"""Revisa obrigatoriamente conteúdo e formato antes de salvar a resposta."""
|
| 600 |
+
reviewer_model = os.getenv(
|
| 601 |
+
"GAIA_GEMINI_REVIEW_MODEL", DEFAULT_GEMINI_MODEL
|
| 602 |
+
)
|
| 603 |
+
review_prompt = f"""
|
| 604 |
+
You are the mandatory final reviewer for a GAIA exact-match answer.
|
| 605 |
+
|
| 606 |
+
Task ID: {task_id or "test"}
|
| 607 |
+
Question:
|
| 608 |
+
{question}
|
| 609 |
+
|
| 610 |
+
Candidate answer:
|
| 611 |
+
{candidate}
|
| 612 |
+
|
| 613 |
+
Review the candidate for both likely correctness and exact requested format.
|
| 614 |
+
Preserve it unless there is a clear factual, logical, ordering, spelling, or
|
| 615 |
+
formatting error. Never add explanations to final_answer.
|
| 616 |
+
|
| 617 |
+
Formatting rules:
|
| 618 |
+
- If asked "how many", for a count, or for a numeric output: final_answer must
|
| 619 |
+
contain only the number, unless the question explicitly requests currency,
|
| 620 |
+
units, decimals, or another representation.
|
| 621 |
+
- If asked for a first name, surname, city, country, or IOC code: return only
|
| 622 |
+
that requested value.
|
| 623 |
+
- If asked for a list: return only the list, with the exact requested separator,
|
| 624 |
+
ordering, capitalization, and plurality.
|
| 625 |
+
- If asked for a quote: return only the requested spoken words.
|
| 626 |
+
- If asked for a chess move: return only algebraic notation.
|
| 627 |
+
- Never include labels, Markdown, citations, rationale, or "FINAL ANSWER".
|
| 628 |
+
|
| 629 |
+
Return one JSON object and nothing else:
|
| 630 |
+
{{
|
| 631 |
+
"final_answer": "exact value to submit",
|
| 632 |
+
"changed": true,
|
| 633 |
+
"review_note": "brief reason, maximum 20 words"
|
| 634 |
+
}}
|
| 635 |
+
""".strip()
|
| 636 |
+
|
| 637 |
+
last_error = None
|
| 638 |
+
for use_json_mode in (True, False):
|
| 639 |
+
try:
|
| 640 |
+
kwargs = {
|
| 641 |
+
"model": reviewer_model,
|
| 642 |
+
"api_key": self.gemini_api_key,
|
| 643 |
+
"messages": [{"role": "user", "content": review_prompt}],
|
| 644 |
+
"temperature": 0,
|
| 645 |
+
"max_tokens": 350,
|
| 646 |
+
}
|
| 647 |
+
if use_json_mode:
|
| 648 |
+
kwargs["response_format"] = {"type": "json_object"}
|
| 649 |
+
response = completion(**kwargs)
|
| 650 |
+
content = str(response.choices[0].message.content).strip()
|
| 651 |
+
content = re.sub(
|
| 652 |
+
r"^```(?:json)?\s*|\s*```$", "", content, flags=re.I
|
| 653 |
+
)
|
| 654 |
+
start = content.find("{")
|
| 655 |
+
end = content.rfind("}")
|
| 656 |
+
if start >= 0 and end > start:
|
| 657 |
+
content = content[start : end + 1]
|
| 658 |
+
payload = json.loads(content)
|
| 659 |
+
final_answer = self.deterministic_answer_cleanup(
|
| 660 |
+
str(payload.get("final_answer", ""))
|
| 661 |
+
)
|
| 662 |
+
if not final_answer:
|
| 663 |
+
raise ValueError("Gemini returned an empty final_answer.")
|
| 664 |
+
|
| 665 |
+
changed = final_answer != candidate
|
| 666 |
+
note = str(payload.get("review_note", "")).strip()
|
| 667 |
+
print(f"Candidate answer: {candidate}")
|
| 668 |
+
print(f"Gemini reviewed answer: {final_answer}")
|
| 669 |
+
print(f"Gemini changed answer: {changed}")
|
| 670 |
+
print(f"Gemini review note: {note}")
|
| 671 |
+
return final_answer
|
| 672 |
+
except Exception as exc:
|
| 673 |
+
last_error = exc
|
| 674 |
+
|
| 675 |
+
raise RuntimeError(
|
| 676 |
+
"A revisão obrigatória do Gemini falhou; a resposta não foi salva. "
|
| 677 |
+
f"Detalhe: {last_error}"
|
| 678 |
+
)
|
| 679 |
+
|
| 680 |
|
| 681 |
def empty_results() -> pd.DataFrame:
|
| 682 |
return pd.DataFrame(columns=RESULT_COLUMNS)
|