Raj commited on
Commit
91f0524
Β·
verified Β·
1 Parent(s): 058e8a6

Upload 2 files

Browse files
Files changed (2) hide show
  1. agent.py +89 -34
  2. tools.py +2 -2
agent.py CHANGED
@@ -18,29 +18,50 @@ from tools import (
18
  youtube_transcript,
19
  )
20
 
21
- DEFAULT_MODEL_ID = "meta-llama/Llama-3.3-70B-Instruct"
 
 
22
  DEFAULT_PROVIDER = "auto"
23
 
24
- SYSTEM_PROMPT = """You are a careful research agent solving GAIA benchmark questions.
25
-
26
- Hard rules for the FINAL ANSWER:
27
- - Answers are graded by EXACT string match after light normalization. Be precise.
28
- - Output ONLY the answer β€” no prefixes, no units unless the question asks, no
29
- trailing punctuation, no quotes.
30
- - Numbers: digits only unless the question wants words. No commas, no $, no %.
31
- - Strings: lowercase only if the question wants lowercase; otherwise match the
32
- natural casing of the entity. Do not add articles (a, an, the).
33
- - Lists: comma + single space between items, in the order requested.
34
-
35
- Workflow:
36
- 1. If the question references "the attached file", "this Excel/CSV/audio/image",
37
- etc., call `download_task_file(task_id)` FIRST. The task_id is given to you.
38
- 2. Pick the right tool: `read_table` for spreadsheets, `transcribe_audio` for
39
- audio, `analyze_image` for images, `read_webpage` after `web_search` for the
40
- open web, `wikipedia_search` for encyclopedic facts, `youtube_transcript`
41
- for YouTube. Use Python freely for math, parsing, and logic.
42
- 3. Cross-check facts when an answer feels uncertain. Don't fabricate.
43
- 4. End with `final_answer("<the exact answer>")`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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=2048,
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
- return CodeAgent(
76
  tools=self.tools,
77
  model=self.model,
78
- max_steps=8,
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
- """Clean common LLM cruft so the grader's exact match has a chance."""
101
  if not text:
102
  return text
103
- t = text.strip()
104
- # Strip "FINAL ANSWER:" prefix if the model left it in.
105
- t = re.sub(r"^\s*final answer\s*[:\-]\s*", "", t, flags=re.IGNORECASE)
106
- # Strip wrapping quotes / backticks.
107
- if len(t) >= 2 and t[0] == t[-1] and t[0] in "\"'`":
 
 
 
 
 
 
 
 
 
108
  t = t[1:-1].strip()
109
- # Drop trailing sentence-ending punctuation unless the answer is a sentence.
110
- if not re.search(r"\s", t) or t.count(" ") < 6:
 
111
  t = t.rstrip(".")
112
- return t.strip()
 
 
 
 
 
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 = 8) -> str:
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 = 8000) -> str:
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: