rnrahate007 commited on
Commit
27dd1e5
Β·
verified Β·
1 Parent(s): 81462b4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -139
app.py CHANGED
@@ -7,221 +7,248 @@ import pandas as pd
7
  from google import genai
8
  from google.genai import types
9
 
10
- # --- Constants ---
11
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
12
 
13
 
 
14
  def clean_answer(text: str) -> str:
 
 
15
  text = text.strip()
16
- text = re.sub(r"^```[a-zA-Z]*\n?", "", text)
17
- text = re.sub(r"```$", "", text)
 
 
18
  for prefix in [
19
- "Final Answer:", "Answer:", "FINAL ANSWER:", "ANSWER:",
20
- "The answer is:", "The final answer is:",
21
  ]:
22
- if text.lower().startswith(prefix.lower()):
23
  text = text[len(prefix):].strip()
24
- text = " ".join(text.split())
25
- return text[:200]
26
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- class BasicAgent:
29
- SYSTEM = """You are an expert AI assistant solving GAIA benchmark questions.
30
 
31
- RULES:
32
- 1. Think step-by-step before answering.
33
- 2. Use Google Search for any factual, date, or real-world questions.
34
- 3. Use code execution for arithmetic, unit conversions, or data processing.
35
- 4. Return ONLY the final answer β€” no explanation, no preamble.
36
- 5. Numbers: no commas, correct decimals (42 not 42.0 if whole).
37
- 6. Lists: comma-separated, alphabetical unless told otherwise.
38
- 7. Yes/no questions: answer exactly "yes" or "no" (lowercase).
39
- 8. Be as concise as possible while being complete and correct.
40
- """
41
 
42
- VERIFY = """Question: {question}
43
 
44
- Proposed answer: {answer}
 
45
 
46
- Review:
47
- - Is it factually correct?
48
- - Is it in the exact requested format?
49
- - Is it as concise as possible?
50
 
51
- If correct, repeat it unchanged.
52
- If wrong or mis-formatted, return ONLY the corrected answer.
 
 
 
 
 
53
 
54
- Return ONLY the final answer β€” nothing else."""
 
55
 
56
  def __init__(self):
57
  api_key = os.getenv("GEMINI_API_KEY")
58
  if not api_key:
59
- raise EnvironmentError("GEMINI_API_KEY environment variable is not set.")
60
 
61
- self.client = genai.Client(api_key=api_key)
62
- self.model_id = "gemini-2.5-flash-preview-04-17"
63
 
64
- self.tools = [
65
- types.Tool(google_search=types.GoogleSearch()),
66
- types.Tool(code_execution=types.ToolCodeExecution()),
67
- ]
 
 
68
 
69
- self.main_config = types.GenerateContentConfig(
 
70
  system_instruction=self.SYSTEM,
71
- tools=self.tools,
 
72
  )
73
 
74
- self.verify_config = types.GenerateContentConfig(
75
- system_instruction="You are a precise answer validator. Return ONLY the final answer.",
 
 
 
 
 
76
  )
77
 
78
- print("βœ… BasicAgent (gemini-2.5-flash + Search + Code) initialised.")
79
 
 
80
  def __call__(self, question: str) -> str:
81
  try:
82
  return self._run(question)
83
  except Exception as exc:
84
- print(f"[AGENT ERROR] {exc}\n{traceback.format_exc()}")
85
  return "N/A"
86
 
87
  def _run(self, question: str) -> str:
88
- resp1 = self.client.models.generate_content(
89
- model=self.model_id,
90
- contents=question,
91
- config=self.main_config,
92
- )
93
- ans1 = clean_answer(self._extract_text(resp1))
94
- print(f" [Pass 1] {ans1!r}")
95
 
96
- if not ans1 or ans1 == "N/A":
97
- return "N/A"
 
 
 
 
98
 
99
- verify_prompt = self.VERIFY.format(question=question, answer=ans1)
100
- resp2 = self.client.models.generate_content(
101
- model=self.model_id,
102
- contents=verify_prompt,
103
- config=self.verify_config,
104
  )
105
- ans2 = clean_answer(self._extract_text(resp2))
106
- print(f" [Pass 2] {ans2!r}")
107
-
108
- return ans2 if ans2 else ans1
109
-
110
- @staticmethod
111
- def _extract_text(response) -> str:
112
- try:
113
- if response.text:
114
- return response.text.strip()
115
- except Exception:
116
- pass
117
- texts = []
118
- for candidate in response.candidates:
119
- for part in candidate.content.parts:
120
- if hasattr(part, "text") and part.text:
121
- texts.append(part.text.strip())
122
- return " ".join(texts).strip() or "N/A"
123
-
124
-
 
 
 
 
 
 
 
 
 
 
125
  def run_and_submit_all(profile: gr.OAuthProfile | None):
126
- space_id = os.getenv("SPACE_ID")
127
-
128
  if not profile:
129
  return "Please log in to Hugging Face first.", None
130
 
131
- username = profile.username
132
- print(f"Logged in as: {username}")
133
-
134
- questions_url = f"{DEFAULT_API_URL}/questions"
135
- submit_url = f"{DEFAULT_API_URL}/submit"
136
 
137
  try:
138
  agent = BasicAgent()
139
  except Exception as e:
140
  return f"Error initialising agent: {e}", None
141
 
142
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" if space_id else "unknown"
143
-
144
- print(f"Fetching questions from {questions_url} …")
145
  try:
146
- resp = requests.get(questions_url, timeout=15)
147
- resp.raise_for_status()
148
- questions_data = resp.json()
149
- if not questions_data:
150
- return "Question list is empty.", None
151
  print(f"Fetched {len(questions_data)} questions.")
152
  except Exception as e:
153
  return f"Error fetching questions: {e}", None
154
 
155
- results_log = []
156
- answers_payload = []
157
 
158
  for item in questions_data:
159
- task_id = item.get("task_id")
160
- question_text = item.get("question")
161
- if not task_id or question_text is None:
162
  continue
163
 
164
- print(f"\n[{task_id}] {question_text[:120]}")
 
165
  try:
166
- answer = agent(question_text)
167
  except Exception as e:
168
- answer = f"AGENT ERROR: {e}"
 
169
 
170
- print(f" β†’ {answer!r}")
171
  answers_payload.append({"task_id": task_id, "submitted_answer": answer})
172
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": answer})
 
 
 
 
173
 
174
  if not answers_payload:
175
- return "Agent produced no answers.", pd.DataFrame(results_log)
176
-
177
- submission_data = {
178
- "username": username.strip(),
179
- "agent_code": agent_code,
180
- "answers": answers_payload,
181
- }
182
- print(f"\nSubmitting {len(answers_payload)} answers …")
183
  try:
184
- resp = requests.post(submit_url, json=submission_data, timeout=120)
185
- resp.raise_for_status()
186
- result = resp.json()
187
- final_status = (
 
 
 
 
188
  f"βœ… Submission successful!\n"
189
- f"User: {result.get('username')}\n"
190
- f"Score: {result.get('score', 'N/A')}% "
191
- f"({result.get('correct_count', '?')}/{result.get('total_attempted', '?')} correct)\n"
192
- f"Msg: {result.get('message', '')}"
193
  )
194
  except requests.exceptions.HTTPError as e:
195
- final_status = f"❌ HTTP {e.response.status_code}: {e.response.text[:500]}"
196
  except Exception as e:
197
- final_status = f"❌ Submission error: {e}"
198
 
199
- print(final_status)
200
- return final_status, pd.DataFrame(results_log)
201
 
202
 
 
203
  with gr.Blocks() as demo:
204
- gr.Markdown("# GAIA Agent Evaluation Runner")
205
  gr.Markdown(
206
- """
207
- **How to use:**
208
- 1. Set `GEMINI_API_KEY` in your Space secrets.
209
- 2. Log in with the button below.
210
- 3. Click **Run Evaluation** β€” the agent answers all questions and submits automatically.
211
- """
212
  )
213
  gr.LoginButton()
214
- run_btn = gr.Button("β–Ά Run Evaluation & Submit All Answers", variant="primary")
215
- status_out = gr.Textbox(label="Status / Result", lines=6, interactive=False)
216
- results_tbl = gr.DataFrame(label="Questions & Agent Answers", wrap=True)
217
-
218
- run_btn.click(fn=run_and_submit_all, outputs=[status_out, results_tbl])
219
 
220
 
221
  if __name__ == "__main__":
222
- print("\n" + "─" * 50)
223
- for var in ("SPACE_HOST", "SPACE_ID", "GEMINI_API_KEY"):
224
- val = os.getenv(var)
225
- print(f"{'βœ…' if val else '⚠️ '} {var}: {'set' if val else 'NOT SET'}")
226
- print("─" * 50 + "\n")
 
 
227
  demo.launch(debug=True, share=False)
 
7
  from google import genai
8
  from google.genai import types
9
 
 
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
 
12
 
13
+ # ── Answer cleaner ────────────────────────────────────────────────────
14
  def clean_answer(text: str) -> str:
15
+ if not text:
16
+ return "N/A"
17
  text = text.strip()
18
+ # Strip code fences
19
+ text = re.sub(r"^```[a-zA-Z]*\s*", "", text)
20
+ text = re.sub(r"\s*```$", "", text)
21
+ # Strip label prefixes
22
  for prefix in [
23
+ "final answer:", "answer:", "the answer is:",
24
+ "the final answer is:", "result:",
25
  ]:
26
+ if text.lower().startswith(prefix):
27
  text = text[len(prefix):].strip()
28
+ break
29
+ return " ".join(text.split())[:200]
30
+
31
+
32
+ def extract_text(response) -> str:
33
+ """
34
+ Pull the final answer text out of a GenerateContentResponse.
35
+ When google_search is used, Gemini emits:
36
+ [search_tool_use part] β†’ [search_tool_result part] β†’ [text part]
37
+ We want the LAST text part.
38
+ """
39
+ text_parts = []
40
+ try:
41
+ for candidate in response.candidates:
42
+ for part in candidate.content.parts:
43
+ if hasattr(part, "text") and part.text and part.text.strip():
44
+ text_parts.append(part.text.strip())
45
+ except Exception as e:
46
+ print(f" [extract_text error] {e}")
47
 
48
+ if text_parts:
49
+ return text_parts[-1] # last text part = final answer after search
50
 
51
+ # Last resort: .text property
52
+ try:
53
+ return response.text.strip()
54
+ except Exception:
55
+ return ""
 
 
 
 
 
56
 
 
57
 
58
+ # ── Agent ─────────────────────────────────────────────────────────────
59
+ class BasicAgent:
60
 
61
+ SYSTEM = """You are an expert assistant solving GAIA benchmark questions.
 
 
 
62
 
63
+ STRICT OUTPUT FORMAT:
64
+ - Return ONLY the final answer. No explanation. No preamble. No punctuation tail.
65
+ - Numbers: digits only, no commas (e.g. 1234567 not 1,234,567). Omit trailing .0.
66
+ - Lists: comma-separated, in the order the question requests (default: alphabetical).
67
+ - Yes/no questions: exactly "yes" or "no" (lowercase).
68
+ - Dates: use the format the question implies (e.g. "January 5, 1990" or "1990-01-05").
69
+ - Names: full name unless the question asks for first/last only.
70
 
71
+ Use Google Search whenever you need facts, dates, counts, or external data.
72
+ Think carefully step by step, then output ONLY the answer."""
73
 
74
  def __init__(self):
75
  api_key = os.getenv("GEMINI_API_KEY")
76
  if not api_key:
77
+ raise EnvironmentError("GEMINI_API_KEY not set.")
78
 
79
+ self.client = genai.Client(api_key=api_key)
80
+ self.model_id = "gemini-2.0-flash" # stable + has search grounding
81
 
82
+ # Search-enabled config (separate from code-exec to avoid conflicts)
83
+ self.search_config = types.GenerateContentConfig(
84
+ system_instruction=self.SYSTEM,
85
+ tools=[types.Tool(google_search=types.GoogleSearch())],
86
+ temperature=0, # deterministic
87
+ )
88
 
89
+ # Code-execution config (for maths / data questions)
90
+ self.code_config = types.GenerateContentConfig(
91
  system_instruction=self.SYSTEM,
92
+ tools=[types.Tool(code_execution=types.ToolCodeExecution())],
93
+ temperature=0,
94
  )
95
 
96
+ # Plain config for the verify pass
97
+ self.plain_config = types.GenerateContentConfig(
98
+ system_instruction=(
99
+ "You are a strict answer formatter. "
100
+ "Return ONLY the final answer, no explanation."
101
+ ),
102
+ temperature=0,
103
  )
104
 
105
+ print(f"βœ… BasicAgent ready (model={self.model_id})")
106
 
107
+ # ------------------------------------------------------------------
108
  def __call__(self, question: str) -> str:
109
  try:
110
  return self._run(question)
111
  except Exception as exc:
112
+ print(f" [FATAL] {exc}\n{traceback.format_exc()}")
113
  return "N/A"
114
 
115
  def _run(self, question: str) -> str:
116
+ q_lower = question.lower()
 
 
 
 
 
 
117
 
118
+ # Route to code-exec for pure maths / counting questions
119
+ needs_code = any(kw in q_lower for kw in [
120
+ "calculate", "compute", "how many", "sum of", "average",
121
+ "percentage", "multiply", "divide", "convert",
122
+ ])
123
+ config = self.code_config if needs_code else self.search_config
124
 
125
+ resp = self.client.models.generate_content(
126
+ model=self.model_id, contents=question, config=config,
 
 
 
127
  )
128
+ raw = extract_text(resp)
129
+ ans = clean_answer(raw)
130
+ print(f" [raw] {raw[:120]!r}")
131
+ print(f" [ans] {ans!r}")
132
+
133
+ if not ans or ans == "N/A":
134
+ # Fallback: try plain (no tools)
135
+ resp2 = self.client.models.generate_content(
136
+ model=self.model_id, contents=question, config=self.plain_config,
137
+ )
138
+ ans = clean_answer(extract_text(resp2))
139
+ print(f" [fallback] {ans!r}")
140
+
141
+ return ans or "N/A"
142
+
143
+
144
+ # ── Debug helper – fetch & show first N answers without submitting ────
145
+ def debug_first_n(n: int = 3):
146
+ """Call this from __main__ to see raw vs cleaned answers."""
147
+ agent = BasicAgent()
148
+ resp = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15)
149
+ questions = resp.json()[:n]
150
+ for item in questions:
151
+ print("\n" + "="*60)
152
+ print(f"Q: {item['question']}")
153
+ ans = agent(item["question"])
154
+ print(f"FINAL SUBMITTED: {ans!r}")
155
+
156
+
157
+ # ── Gradio runner ─────────────────────────────────────────────────────
158
  def run_and_submit_all(profile: gr.OAuthProfile | None):
 
 
159
  if not profile:
160
  return "Please log in to Hugging Face first.", None
161
 
162
+ username = profile.username
163
+ space_id = os.getenv("SPACE_ID", "")
164
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
 
 
165
 
166
  try:
167
  agent = BasicAgent()
168
  except Exception as e:
169
  return f"Error initialising agent: {e}", None
170
 
171
+ # Fetch questions
 
 
172
  try:
173
+ r = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15)
174
+ r.raise_for_status()
175
+ questions_data = r.json()
 
 
176
  print(f"Fetched {len(questions_data)} questions.")
177
  except Exception as e:
178
  return f"Error fetching questions: {e}", None
179
 
180
+ results_log, answers_payload = [], []
 
181
 
182
  for item in questions_data:
183
+ task_id = item.get("task_id")
184
+ q_text = item.get("question")
185
+ if not task_id or q_text is None:
186
  continue
187
 
188
+ print(f"\n── {task_id} ──")
189
+ print(f"Q: {q_text[:150]}")
190
  try:
191
+ answer = agent(q_text)
192
  except Exception as e:
193
+ answer = "N/A"
194
+ print(f" ERROR: {e}")
195
 
 
196
  answers_payload.append({"task_id": task_id, "submitted_answer": answer})
197
+ results_log.append({
198
+ "Task ID": task_id,
199
+ "Question": q_text[:120],
200
+ "Submitted Answer": answer,
201
+ })
202
 
203
  if not answers_payload:
204
+ return "No answers produced.", pd.DataFrame(results_log)
205
+
206
+ # Submit
 
 
 
 
 
207
  try:
208
+ r = requests.post(
209
+ f"{DEFAULT_API_URL}/submit",
210
+ json={"username": username, "agent_code": agent_code, "answers": answers_payload},
211
+ timeout=120,
212
+ )
213
+ r.raise_for_status()
214
+ res = r.json()
215
+ status = (
216
  f"βœ… Submission successful!\n"
217
+ f"User: {res.get('username')}\n"
218
+ f"Score: {res.get('score', 'N/A')}% "
219
+ f"({res.get('correct_count', '?')}/{res.get('total_attempted', '?')} correct)\n"
220
+ f"Msg: {res.get('message', '')}"
221
  )
222
  except requests.exceptions.HTTPError as e:
223
+ status = f"❌ HTTP {e.response.status_code}: {e.response.text[:300]}"
224
  except Exception as e:
225
+ status = f"❌ Error: {e}"
226
 
227
+ print(status)
228
+ return status, pd.DataFrame(results_log)
229
 
230
 
231
+ # ── UI ────────────────────────────────────────────────────────────────
232
  with gr.Blocks() as demo:
233
+ gr.Markdown("# GAIA Agent β€” Gemini 2.0 Flash + Search")
234
  gr.Markdown(
235
+ "1. Set `GEMINI_API_KEY` in Space secrets.\n"
236
+ "2. Log in below.\n"
237
+ "3. Click **Run Evaluation**."
 
 
 
238
  )
239
  gr.LoginButton()
240
+ btn = gr.Button("β–Ά Run Evaluation & Submit All Answers", variant="primary")
241
+ status = gr.Textbox(label="Status", lines=6, interactive=False)
242
+ table = gr.DataFrame(label="Answers", wrap=True)
243
+ btn.click(fn=run_and_submit_all, outputs=[status, table])
 
244
 
245
 
246
  if __name__ == "__main__":
247
+ print("\n── Env check ──")
248
+ for v in ("SPACE_HOST", "SPACE_ID", "GEMINI_API_KEY"):
249
+ print(f" {v}: {'SET' if os.getenv(v) else 'MISSING'}")
250
+
251
+ # Uncomment to preview first 3 answers before submitting:
252
+ # debug_first_n(3)
253
+
254
  demo.launch(debug=True, share=False)