Spaces:
Sleeping
Sleeping
Raj commited on
Upload 2 files
Browse files
agent.py
CHANGED
|
@@ -18,29 +18,50 @@ from tools import (
|
|
| 18 |
youtube_transcript,
|
| 19 |
)
|
| 20 |
|
| 21 |
-
|
|
|
|
|
|
|
| 22 |
DEFAULT_PROVIDER = "auto"
|
| 23 |
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
"""
|
| 45 |
|
| 46 |
|
|
@@ -57,7 +78,7 @@ class GaiaAgent:
|
|
| 57 |
model_id=model_id or os.getenv("AGENT_MODEL_ID", DEFAULT_MODEL_ID),
|
| 58 |
provider=provider or os.getenv("AGENT_PROVIDER", DEFAULT_PROVIDER),
|
| 59 |
token=token,
|
| 60 |
-
max_tokens=
|
| 61 |
)
|
| 62 |
self.tools = [
|
| 63 |
web_search,
|
|
@@ -72,22 +93,42 @@ class GaiaAgent:
|
|
| 72 |
|
| 73 |
def _build_agent(self) -> CodeAgent:
|
| 74 |
# Fresh agent per question β keeps memory clean between tasks.
|
| 75 |
-
|
| 76 |
tools=self.tools,
|
| 77 |
model=self.model,
|
| 78 |
-
max_steps=
|
| 79 |
additional_authorized_imports=[
|
| 80 |
"pandas", "numpy", "json", "re", "math", "statistics",
|
| 81 |
"itertools", "collections", "datetime", "csv", "io",
|
| 82 |
-
"pathlib", "string", "base64",
|
| 83 |
],
|
| 84 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
def __call__(self, question: str, task_id: Optional[str] = None) -> str:
|
| 87 |
agent = self._build_agent()
|
| 88 |
prompt = SYSTEM_PROMPT
|
| 89 |
if task_id:
|
| 90 |
prompt += f"\nThe current task_id is: {task_id}\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
prompt += f"\nQuestion:\n{question}\n"
|
| 92 |
try:
|
| 93 |
raw = agent.run(prompt)
|
|
@@ -97,16 +138,30 @@ class GaiaAgent:
|
|
| 97 |
|
| 98 |
@staticmethod
|
| 99 |
def _normalize(text: str) -> str:
|
| 100 |
-
"""
|
| 101 |
if not text:
|
| 102 |
return text
|
| 103 |
-
t = text.strip()
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
t = t[1:-1].strip()
|
| 109 |
-
|
| 110 |
-
if
|
|
|
|
| 111 |
t = t.rstrip(".")
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
youtube_transcript,
|
| 19 |
)
|
| 20 |
|
| 21 |
+
# Qwen2.5-72B is a much stronger tool-using / code-writing model than Llama-3.3
|
| 22 |
+
# for GAIA. Override via AGENT_MODEL_ID env var if you want to test others.
|
| 23 |
+
DEFAULT_MODEL_ID = "Qwen/Qwen2.5-72B-Instruct"
|
| 24 |
DEFAULT_PROVIDER = "auto"
|
| 25 |
|
| 26 |
+
# Canonical GAIA system prompt β the exact answer-format spec the leaderboard
|
| 27 |
+
# uses. Deviating from this format is the #1 cause of "right reasoning, wrong
|
| 28 |
+
# score".
|
| 29 |
+
SYSTEM_PROMPT = """You are a general AI assistant solving questions from the GAIA benchmark.
|
| 30 |
+
|
| 31 |
+
I will ask you a question. Think step by step using the tools available. When you
|
| 32 |
+
are confident, finish your answer with the following template:
|
| 33 |
+
|
| 34 |
+
FINAL ANSWER: [YOUR FINAL ANSWER]
|
| 35 |
+
|
| 36 |
+
YOUR FINAL ANSWER must obey these rules exactly:
|
| 37 |
+
- It is a number, OR as few words as possible, OR a comma separated list of
|
| 38 |
+
numbers and/or strings.
|
| 39 |
+
- If a number: digits only. No commas inside numbers. No units ($, %, kg, etc.)
|
| 40 |
+
unless the question explicitly asks for them.
|
| 41 |
+
- If a string: no articles (a, an, the), no abbreviations (write "Saint Louis"
|
| 42 |
+
not "St. Louis", "New York City" not "NYC"), and digits in plain text unless
|
| 43 |
+
the question asks for numerals.
|
| 44 |
+
- If a comma separated list: items separated by ", " (comma + single space),
|
| 45 |
+
in the order requested, each item following the number/string rules above.
|
| 46 |
+
- Do NOT include explanations, units, quotes, or trailing punctuation in the
|
| 47 |
+
FINAL ANSWER line itself.
|
| 48 |
+
|
| 49 |
+
Tool playbook:
|
| 50 |
+
- If the question mentions an attached file ("the attached", "this Excel /
|
| 51 |
+
CSV / audio / image / Python file", "the file"), call
|
| 52 |
+
`download_task_file(task_id)` FIRST to get a local path.
|
| 53 |
+
- Spreadsheets / CSV: `read_table(path)` then pandas in Python.
|
| 54 |
+
- Audio: `transcribe_audio(path)`.
|
| 55 |
+
- Image: `analyze_image(path, question)` β phrase the question precisely.
|
| 56 |
+
- YouTube link: `youtube_transcript(url)`.
|
| 57 |
+
- Open web: `web_search(query)` then `read_webpage(url)` on the best hit.
|
| 58 |
+
- Encyclopedic facts: `wikipedia_search(topic)`.
|
| 59 |
+
- Use Python freely for math, parsing, sorting, set operations, etc.
|
| 60 |
+
|
| 61 |
+
Self-check before answering:
|
| 62 |
+
- Did you answer the literal question asked? (Not a related one.)
|
| 63 |
+
- Does the format match the rules above?
|
| 64 |
+
- If unsure, do one more verification search.
|
| 65 |
"""
|
| 66 |
|
| 67 |
|
|
|
|
| 78 |
model_id=model_id or os.getenv("AGENT_MODEL_ID", DEFAULT_MODEL_ID),
|
| 79 |
provider=provider or os.getenv("AGENT_PROVIDER", DEFAULT_PROVIDER),
|
| 80 |
token=token,
|
| 81 |
+
max_tokens=4096,
|
| 82 |
)
|
| 83 |
self.tools = [
|
| 84 |
web_search,
|
|
|
|
| 93 |
|
| 94 |
def _build_agent(self) -> CodeAgent:
|
| 95 |
# Fresh agent per question β keeps memory clean between tasks.
|
| 96 |
+
kwargs = dict(
|
| 97 |
tools=self.tools,
|
| 98 |
model=self.model,
|
| 99 |
+
max_steps=15,
|
| 100 |
additional_authorized_imports=[
|
| 101 |
"pandas", "numpy", "json", "re", "math", "statistics",
|
| 102 |
"itertools", "collections", "datetime", "csv", "io",
|
| 103 |
+
"pathlib", "string", "base64", "urllib", "unicodedata",
|
| 104 |
],
|
| 105 |
)
|
| 106 |
+
# planning_interval was added in smolagents 1.x β guard for older installs.
|
| 107 |
+
try:
|
| 108 |
+
return CodeAgent(planning_interval=4, **kwargs)
|
| 109 |
+
except TypeError:
|
| 110 |
+
return CodeAgent(**kwargs)
|
| 111 |
|
| 112 |
def __call__(self, question: str, task_id: Optional[str] = None) -> str:
|
| 113 |
agent = self._build_agent()
|
| 114 |
prompt = SYSTEM_PROMPT
|
| 115 |
if task_id:
|
| 116 |
prompt += f"\nThe current task_id is: {task_id}\n"
|
| 117 |
+
# Eager-download the attachment so the agent never misses it.
|
| 118 |
+
try:
|
| 119 |
+
path = download_task_file(task_id)
|
| 120 |
+
if path and not path.startswith("NO_FILE") and not path.startswith(
|
| 121 |
+
("Download error", "Download failed")
|
| 122 |
+
):
|
| 123 |
+
prompt += (
|
| 124 |
+
f"An attached file for this task has already been "
|
| 125 |
+
f"downloaded to: {path}\n"
|
| 126 |
+
f"Use the right tool on this path (read_table for "
|
| 127 |
+
f"spreadsheets/CSV, transcribe_audio for audio, "
|
| 128 |
+
f"analyze_image for images, or open it with Python).\n"
|
| 129 |
+
)
|
| 130 |
+
except Exception as e:
|
| 131 |
+
print(f" pre-download skipped: {e}")
|
| 132 |
prompt += f"\nQuestion:\n{question}\n"
|
| 133 |
try:
|
| 134 |
raw = agent.run(prompt)
|
|
|
|
| 138 |
|
| 139 |
@staticmethod
|
| 140 |
def _normalize(text: str) -> str:
|
| 141 |
+
"""Strip every common LLM cruft pattern so exact-match has a chance."""
|
| 142 |
if not text:
|
| 143 |
return text
|
| 144 |
+
t = str(text).strip()
|
| 145 |
+
|
| 146 |
+
# 1) If the model emitted "FINAL ANSWER: X" anywhere, keep only X.
|
| 147 |
+
m = re.search(r"final answer\s*[:\-]\s*(.+)", t, flags=re.IGNORECASE | re.DOTALL)
|
| 148 |
+
if m:
|
| 149 |
+
t = m.group(1).strip()
|
| 150 |
+
# Trim at first newline / closing markdown.
|
| 151 |
+
t = t.split("\n")[0].strip()
|
| 152 |
+
|
| 153 |
+
# 2) Strip wrapping markdown bold/italic/code.
|
| 154 |
+
t = re.sub(r"^[*_`]+|[*_`]+$", "", t).strip()
|
| 155 |
+
|
| 156 |
+
# 3) Strip wrapping quotes.
|
| 157 |
+
if len(t) >= 2 and t[0] == t[-1] and t[0] in "\"'`ββββ":
|
| 158 |
t = t[1:-1].strip()
|
| 159 |
+
|
| 160 |
+
# 4) Drop a single trailing period if the answer is short (not a sentence).
|
| 161 |
+
if t.count(" ") < 6:
|
| 162 |
t = t.rstrip(".")
|
| 163 |
+
|
| 164 |
+
# 5) Collapse internal whitespace.
|
| 165 |
+
t = re.sub(r"[ \t]+", " ", t).strip()
|
| 166 |
+
|
| 167 |
+
return t
|
tools.py
CHANGED
|
@@ -27,7 +27,7 @@ USER_AGENT = (
|
|
| 27 |
# Web search
|
| 28 |
# ---------------------------------------------------------------------------
|
| 29 |
@tool
|
| 30 |
-
def web_search(query: str, num_results: int =
|
| 31 |
"""Search the web with Serper (Google results) and return the top hits.
|
| 32 |
|
| 33 |
Args:
|
|
@@ -92,7 +92,7 @@ def web_search(query: str, num_results: int = 8) -> str:
|
|
| 92 |
# Web page reader
|
| 93 |
# ---------------------------------------------------------------------------
|
| 94 |
@tool
|
| 95 |
-
def read_webpage(url: str, max_chars: int =
|
| 96 |
"""Fetch a URL and return its main text content as Markdown.
|
| 97 |
|
| 98 |
Args:
|
|
|
|
| 27 |
# Web search
|
| 28 |
# ---------------------------------------------------------------------------
|
| 29 |
@tool
|
| 30 |
+
def web_search(query: str, num_results: int = 10) -> str:
|
| 31 |
"""Search the web with Serper (Google results) and return the top hits.
|
| 32 |
|
| 33 |
Args:
|
|
|
|
| 92 |
# Web page reader
|
| 93 |
# ---------------------------------------------------------------------------
|
| 94 |
@tool
|
| 95 |
+
def read_webpage(url: str, max_chars: int = 15000) -> str:
|
| 96 |
"""Fetch a URL and return its main text content as Markdown.
|
| 97 |
|
| 98 |
Args:
|