PaulJ00 commited on
Commit
6fc8489
·
verified ·
1 Parent(s): e89156b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -95
app.py CHANGED
@@ -1,6 +1,7 @@
1
  import os
2
  import re
3
  import json
 
4
  import tempfile
5
  from pathlib import Path
6
 
@@ -16,36 +17,36 @@ except ImportError:
16
  from smolagents import OpenAIModel as OpenAIServerModel
17
 
18
 
19
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
20
 
21
 
22
  @tool
23
  def get_task_file_url(task_id: str) -> str:
24
  """
25
- Returns the file URL for a GAIA task.
26
 
27
  Args:
28
- task_id: The task id.
29
 
30
  Returns:
31
- The download URL for the attached task file.
32
  """
33
- return f"{DEFAULT_API_URL}/files/{task_id}"
34
 
35
 
36
  @tool
37
  def download_file(url: str) -> str:
38
  """
39
- Downloads a file from a URL and saves it to a temporary path.
40
 
41
  Args:
42
- url: The URL of the file.
43
 
44
  Returns:
45
  The local temporary file path.
46
  """
47
  suffix = ""
48
- match = re.search(r"\.([a-zA-Z0-9]{1,6})(?:\?|$)", url)
49
  if match:
50
  suffix = "." + match.group(1)
51
 
@@ -64,38 +65,40 @@ def download_file(url: str) -> str:
64
  @tool
65
  def inspect_local_text_file(path: str, max_chars: int = 12000) -> str:
66
  """
67
- Reads a local text-like file and returns a preview.
68
 
69
  Args:
70
  path: The local file path.
71
  max_chars: Maximum number of characters to return.
72
 
73
  Returns:
74
- A text preview of the file contents.
75
  """
76
  p = Path(path)
77
  try:
78
  return p.read_text(encoding="utf-8", errors="ignore")[:max_chars]
79
  except Exception as e:
80
- return f"Could not read text file: {e}"
81
 
82
 
83
  @tool
84
  def analyze_spreadsheet(path: str, instruction: str) -> str:
85
  """
86
- Loads a spreadsheet and returns schema and preview information.
87
 
88
  Args:
89
  path: The local spreadsheet path.
90
- instruction: The analysis goal.
91
 
92
  Returns:
93
- A JSON string containing spreadsheet metadata and preview rows.
94
  """
95
  p = Path(path)
96
- if p.suffix.lower() in [".csv", ".tsv"]:
97
- sep = "\t" if p.suffix.lower() == ".tsv" else ","
98
- df = pd.read_csv(path, sep=sep)
 
 
99
  else:
100
  df = pd.read_excel(path)
101
 
@@ -104,7 +107,7 @@ def analyze_spreadsheet(path: str, instruction: str) -> str:
104
  "shape": list(df.shape),
105
  "columns": list(df.columns),
106
  "dtypes": {c: str(t) for c, t in df.dtypes.items()},
107
- "head": df.head(8).to_dict(orient="records"),
108
  "numeric_column_sums": {
109
  c: float(df[c].sum())
110
  for c in df.select_dtypes(include="number").columns
@@ -116,10 +119,10 @@ def analyze_spreadsheet(path: str, instruction: str) -> str:
116
  @tool
117
  def python_compute(code: str) -> str:
118
  """
119
- Executes short Python code for local calculations.
120
 
121
  Args:
122
- code: Python code that must assign the final output to a variable named result.
123
 
124
  Returns:
125
  The string form of result.
@@ -135,36 +138,57 @@ def python_compute(code: str) -> str:
135
  exec(code, global_vars, local_vars)
136
 
137
  if "result" not in local_vars:
138
- return "ERROR: code did not define variable `result`"
139
  return str(local_vars["result"])
140
 
141
 
142
- def clean_final_answer(text: str) -> str:
143
  if text is None:
144
  return ""
145
 
146
  answer = str(text).strip()
147
- answer = re.sub(r"^```.*?\n", "", answer, flags=re.DOTALL)
148
  answer = answer.replace("```", "").strip()
149
- answer = re.sub(r"^(FINAL ANSWER\s*:\s*)", "", answer, flags=re.IGNORECASE)
150
- answer = re.sub(r"^(Answer\s*:\s*)", "", answer, flags=re.IGNORECASE)
151
  answer = re.sub(r"[ \t]+", " ", answer).strip()
152
- return answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
 
155
  SYSTEM_PROMPT = """
156
  You are solving GAIA-style benchmark tasks.
157
 
158
  Rules:
159
- - You may use tools.
160
- - If the task includes a file, use the file tools.
161
- - If the task asks for a web fact, search and verify.
162
- - For spreadsheet/math/data tasks, compute carefully.
163
- - For exact-match scoring, your final output must be ONLY the answer and nothing else.
164
- - Do not include explanations in your final answer.
165
- - Do not include the words FINAL ANSWER.
166
- - When a question requests a format, obey that exact format.
167
- - If the answer is numeric and the prompt requests decimals, format correctly.
168
  """
169
 
170
 
@@ -172,7 +196,7 @@ class GaiaAgent:
172
  def __init__(self):
173
  api_key = os.environ.get("OPENAI_API_KEY")
174
  if not api_key:
175
- raise ValueError("Missing OPENAI_API_KEY secret.")
176
 
177
  model_id = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
178
 
@@ -180,8 +204,6 @@ class GaiaAgent:
180
  model_id=model_id,
181
  api_base="https://api.openai.com/v1",
182
  api_key=api_key,
183
- temperature=0.1,
184
- max_tokens=1200,
185
  )
186
 
187
  try:
@@ -197,7 +219,7 @@ class GaiaAgent:
197
  python_compute,
198
  ],
199
  add_base_tools=False,
200
- max_steps=10,
201
  instructions=SYSTEM_PROMPT,
202
  )
203
  except TypeError:
@@ -213,118 +235,122 @@ class GaiaAgent:
213
  python_compute,
214
  ],
215
  add_base_tools=False,
216
- max_steps=10,
217
  )
218
 
219
- def __call__(self, question: str, task_id: str) -> str:
220
- full_prompt = f"""
221
  Task ID: {task_id}
222
 
223
  Question:
224
  {question}
225
 
226
  Important:
227
- - If there is an attached file, its URL can be obtained using get_task_file_url(task_id).
228
  - Return only the final answer.
 
229
  """
230
- raw = self.agent.run(full_prompt)
231
- return clean_final_answer(raw)
232
 
 
233
 
234
- def run_and_submit_all(profile: gr.OAuthProfile | None):
235
- space_id = os.getenv("SPACE_ID")
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
- if not profile:
238
- return "Please Login to Hugging Face first.", None
239
 
240
- username = profile.username
241
- api_url = DEFAULT_API_URL
242
- questions_url = f"{api_url}/questions"
243
- submit_url = f"{api_url}/submit"
244
 
245
  try:
246
  agent = GaiaAgent()
247
  except Exception as e:
248
  return f"Error initializing agent: {e}", None
249
 
250
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
251
-
252
  try:
253
- r = requests.get(questions_url, timeout=30)
254
  r.raise_for_status()
255
- questions_data = r.json()
256
  except Exception as e:
257
  return f"Error fetching questions: {e}", None
258
 
259
- results_log = []
260
- answers_payload = []
261
 
262
- for item in questions_data:
263
- task_id = item.get("task_id")
264
- question_text = item.get("question")
265
 
266
- if not task_id or not question_text:
267
  continue
268
 
269
- try:
270
- submitted_answer = agent(question_text, task_id)
271
- except Exception as e:
272
- submitted_answer = f"ERROR: {e}"
273
 
274
- answers_payload.append(
275
  {
276
  "task_id": task_id,
277
- "submitted_answer": submitted_answer,
278
  }
279
  )
280
 
281
- results_log.append(
282
  {
283
  "Task ID": task_id,
284
- "Question": question_text,
285
- "Submitted Answer": submitted_answer,
286
  }
287
  )
288
 
289
- submission_data = {
290
- "username": username.strip(),
291
- "agent_code": agent_code,
292
- "answers": answers_payload,
293
  }
294
 
295
  try:
296
- r = requests.post(submit_url, json=submission_data, timeout=120)
297
  r.raise_for_status()
298
- result_data = r.json()
299
-
300
- final_status = (
301
- f"Submission Successful!\n"
302
- f"User: {result_data.get('username')}\n"
303
- f"Overall Score: {result_data.get('score', 'N/A')}% "
304
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
305
- f"Message: {result_data.get('message', '')}"
306
  )
307
- return final_status, pd.DataFrame(results_log)
308
-
309
  except Exception as e:
310
- return f"Submission Failed: {e}", pd.DataFrame(results_log)
311
 
312
 
313
  with gr.Blocks() as demo:
314
  gr.Markdown("# Unit 4 GAIA Agent")
315
  gr.Markdown(
316
  """
317
- 1. Add your API key as a Space Secret: `OPENAI_API_KEY`
318
- 2. Optional: set `OPENAI_MODEL`
319
- 3. Login with Hugging Face
320
- 4. Click run
321
  """
322
  )
323
 
324
  gr.LoginButton()
325
- run_button = gr.Button("Run Evaluation & Submit All Answers")
326
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=6, interactive=False)
327
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
328
 
329
  run_button.click(
330
  fn=run_and_submit_all,
 
1
  import os
2
  import re
3
  import json
4
+ import time
5
  import tempfile
6
  from pathlib import Path
7
 
 
17
  from smolagents import OpenAIModel as OpenAIServerModel
18
 
19
 
20
+ API_URL = "https://agents-course-unit4-scoring.hf.space"
21
 
22
 
23
  @tool
24
  def get_task_file_url(task_id: str) -> str:
25
  """
26
+ Build the file URL for a GAIA task.
27
 
28
  Args:
29
+ task_id: The task identifier.
30
 
31
  Returns:
32
+ The download URL for the task file.
33
  """
34
+ return f"{API_URL}/files/{task_id}"
35
 
36
 
37
  @tool
38
  def download_file(url: str) -> str:
39
  """
40
+ Download a remote file to a temporary local path.
41
 
42
  Args:
43
+ url: The remote file URL.
44
 
45
  Returns:
46
  The local temporary file path.
47
  """
48
  suffix = ""
49
+ match = re.search(r"\.([a-zA-Z0-9]{1,8})(?:\?|$)", url)
50
  if match:
51
  suffix = "." + match.group(1)
52
 
 
65
  @tool
66
  def inspect_local_text_file(path: str, max_chars: int = 12000) -> str:
67
  """
68
+ Read a local text-like file and return a preview.
69
 
70
  Args:
71
  path: The local file path.
72
  max_chars: Maximum number of characters to return.
73
 
74
  Returns:
75
+ A text preview.
76
  """
77
  p = Path(path)
78
  try:
79
  return p.read_text(encoding="utf-8", errors="ignore")[:max_chars]
80
  except Exception as e:
81
+ return f"READ_ERROR: {e}"
82
 
83
 
84
  @tool
85
  def analyze_spreadsheet(path: str, instruction: str) -> str:
86
  """
87
+ Load a spreadsheet and return a compact JSON summary.
88
 
89
  Args:
90
  path: The local spreadsheet path.
91
+ instruction: What the agent wants to determine.
92
 
93
  Returns:
94
+ A JSON summary of the sheet.
95
  """
96
  p = Path(path)
97
+
98
+ if p.suffix.lower() == ".csv":
99
+ df = pd.read_csv(path)
100
+ elif p.suffix.lower() == ".tsv":
101
+ df = pd.read_csv(path, sep="\t")
102
  else:
103
  df = pd.read_excel(path)
104
 
 
107
  "shape": list(df.shape),
108
  "columns": list(df.columns),
109
  "dtypes": {c: str(t) for c, t in df.dtypes.items()},
110
+ "head": df.head(10).to_dict(orient="records"),
111
  "numeric_column_sums": {
112
  c: float(df[c].sum())
113
  for c in df.select_dtypes(include="number").columns
 
119
  @tool
120
  def python_compute(code: str) -> str:
121
  """
122
+ Execute short Python code for local calculations.
123
 
124
  Args:
125
+ code: Python code that must assign the final value to a variable named result.
126
 
127
  Returns:
128
  The string form of result.
 
138
  exec(code, global_vars, local_vars)
139
 
140
  if "result" not in local_vars:
141
+ return "ERROR_NO_RESULT"
142
  return str(local_vars["result"])
143
 
144
 
145
+ def clean_final_answer(text) -> str:
146
  if text is None:
147
  return ""
148
 
149
  answer = str(text).strip()
 
150
  answer = answer.replace("```", "").strip()
151
+ answer = re.sub(r"^FINAL ANSWER\s*:\s*", "", answer, flags=re.IGNORECASE)
152
+ answer = re.sub(r"^Answer\s*:\s*", "", answer, flags=re.IGNORECASE)
153
  answer = re.sub(r"[ \t]+", " ", answer).strip()
154
+
155
+ if answer.startswith('"') and answer.endswith('"') and len(answer) >= 2:
156
+ answer = answer[1:-1].strip()
157
+
158
+ bad_markers = [
159
+ "incorrect api key",
160
+ "openai_api_key",
161
+ "error in generating model output",
162
+ "traceback",
163
+ "exception",
164
+ "401",
165
+ "403",
166
+ "429",
167
+ ]
168
+ lowered = answer.lower()
169
+ if any(marker in lowered for marker in bad_markers):
170
+ return ""
171
+
172
+ lines = [line.strip() for line in answer.splitlines() if line.strip()]
173
+ if len(lines) > 1:
174
+ answer = lines[-1]
175
+
176
+ return answer.strip()
177
 
178
 
179
  SYSTEM_PROMPT = """
180
  You are solving GAIA-style benchmark tasks.
181
 
182
  Rules:
183
+ - Return only the final answer, with no explanation.
184
+ - Respect the exact format requested in the question.
185
+ - If a file is needed, use get_task_file_url(task_id), then download_file(url).
186
+ - If the file is text-like, inspect it.
187
+ - If it is a spreadsheet, analyze it.
188
+ - Use web search when needed.
189
+ - Double-check dates, numbers, names, and formatting.
190
+ - Never output tool errors or debugging text as the final answer.
191
+ - If you are uncertain, reason carefully and still output one best final answer only.
192
  """
193
 
194
 
 
196
  def __init__(self):
197
  api_key = os.environ.get("OPENAI_API_KEY")
198
  if not api_key:
199
+ raise ValueError("OPENAI_API_KEY is missing")
200
 
201
  model_id = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")
202
 
 
204
  model_id=model_id,
205
  api_base="https://api.openai.com/v1",
206
  api_key=api_key,
 
 
207
  )
208
 
209
  try:
 
219
  python_compute,
220
  ],
221
  add_base_tools=False,
222
+ max_steps=12,
223
  instructions=SYSTEM_PROMPT,
224
  )
225
  except TypeError:
 
235
  python_compute,
236
  ],
237
  add_base_tools=False,
238
+ max_steps=12,
239
  )
240
 
241
+ def answer_task(self, question: str, task_id: str, retries: int = 2) -> str:
242
+ prompt = f"""
243
  Task ID: {task_id}
244
 
245
  Question:
246
  {question}
247
 
248
  Important:
 
249
  - Return only the final answer.
250
+ - If a file is required, use get_task_file_url(task_id).
251
  """
 
 
252
 
253
+ last_error = None
254
 
255
+ for attempt in range(retries + 1):
256
+ try:
257
+ raw = self.agent.run(prompt)
258
+ cleaned = clean_final_answer(raw)
259
+ if cleaned:
260
+ return cleaned
261
+ except Exception as e:
262
+ last_error = str(e)
263
+
264
+ if attempt < retries:
265
+ time.sleep(1.5)
266
+
267
+ if last_error:
268
+ print(f"Task {task_id} failed: {last_error}")
269
+ return ""
270
 
 
 
271
 
272
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
273
+ space_id = os.getenv("SPACE_ID", "")
274
+ if not profile:
275
+ return "Please log in to Hugging Face first.", None
276
 
277
  try:
278
  agent = GaiaAgent()
279
  except Exception as e:
280
  return f"Error initializing agent: {e}", None
281
 
 
 
282
  try:
283
+ r = requests.get(f"{API_URL}/questions", timeout=30)
284
  r.raise_for_status()
285
+ questions = r.json()
286
  except Exception as e:
287
  return f"Error fetching questions: {e}", None
288
 
289
+ answers = []
290
+ rows = []
291
 
292
+ for item in questions:
293
+ task_id = item.get("task_id", "")
294
+ question = item.get("question", "")
295
 
296
+ if not task_id or not question:
297
  continue
298
 
299
+ answer = agent.answer_task(question, task_id, retries=2)
 
 
 
300
 
301
+ answers.append(
302
  {
303
  "task_id": task_id,
304
+ "submitted_answer": answer,
305
  }
306
  )
307
 
308
+ rows.append(
309
  {
310
  "Task ID": task_id,
311
+ "Question": question,
312
+ "Submitted Answer": answer,
313
  }
314
  )
315
 
316
+ payload = {
317
+ "username": profile.username.strip(),
318
+ "agent_code": f"https://huggingface.co/spaces/{space_id}/tree/main" if space_id else "",
319
+ "answers": answers,
320
  }
321
 
322
  try:
323
+ r = requests.post(f"{API_URL}/submit", json=payload, timeout=180)
324
  r.raise_for_status()
325
+ result = r.json()
326
+
327
+ status = (
328
+ f"Submission successful.\n"
329
+ f"User: {result.get('username')}\n"
330
+ f"Score: {result.get('score', 'N/A')}%\n"
331
+ f"Correct: {result.get('correct_count', '?')}/{result.get('total_attempted', '?')}\n"
332
+ f"Message: {result.get('message', '')}"
333
  )
334
+ return status, pd.DataFrame(rows)
 
335
  except Exception as e:
336
+ return f"Submission failed: {e}", pd.DataFrame(rows)
337
 
338
 
339
  with gr.Blocks() as demo:
340
  gr.Markdown("# Unit 4 GAIA Agent")
341
  gr.Markdown(
342
  """
343
+ 1. Add `OPENAI_API_KEY` as a Space secret
344
+ 2. Optionally add `OPENAI_MODEL`
345
+ 3. Log in with Hugging Face
346
+ 4. Run evaluation
347
  """
348
  )
349
 
350
  gr.LoginButton()
351
+ run_button = gr.Button("Run Evaluation and Submit")
352
+ status_output = gr.Textbox(label="Status", lines=8, interactive=False)
353
+ results_table = gr.DataFrame(label="Task Results", wrap=True)
354
 
355
  run_button.click(
356
  fn=run_and_submit_all,