TheGoodDevil commited on
Commit
204b84c
·
verified ·
1 Parent(s): fce60a3

Upload 4 files

Browse files
Files changed (4) hide show
  1. agent.py +346 -0
  2. app.py +171 -196
  3. gitattributes +35 -0
  4. requirements.txt +11 -0
agent.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- Basic Agent Definition ---
2
+ import asyncio
3
+ import os
4
+ import sys
5
+ import logging
6
+ import random
7
+ import pandas as pd
8
+ import requests
9
+ import wikipedia as wiki
10
+ from markdownify import markdownify as to_markdown
11
+ from typing import Any
12
+ from dotenv import load_dotenv
13
+ from google.generativeai import types, configure
14
+
15
+ from smolagents import InferenceClientModel, LiteLLMModel, CodeAgent, ToolCallingAgent, Tool, DuckDuckGoSearchTool
16
+
17
+ # Load environment and configure Gemini
18
+ load_dotenv()
19
+ configure(api_key=os.getenv("AIzaSyAJUd32HV2Dz06LPDTP6KTmfqr6LxuoWww"))
20
+
21
+ # Logging
22
+ #logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
23
+ #logger = logging.getLogger(__name__)
24
+
25
+ # --- Model Configuration ---
26
+ GEMINI_MODEL_NAME = "gemini/gemini-2.0-flash"
27
+ OPENAI_MODEL_NAME = "openai/gpt-4o"
28
+ GROQ_MODEL_NAME = "groq/llama3-70b-8192"
29
+ DEEPSEEK_MODEL_NAME = "deepseek/deepseek-chat"
30
+ HF_MODEL_NAME = "Qwen/Qwen2.5-Coder-32B-Instruct"
31
+
32
+ # --- Tool Definitions ---
33
+ class MathSolver(Tool):
34
+ name = "math_solver"
35
+ description = "Safely evaluate basic math expressions."
36
+ inputs = {"input": {"type": "string", "description": "Math expression to evaluate."}}
37
+ output_type = "string"
38
+
39
+ def forward(self, input: str) -> str:
40
+ try:
41
+ return str(eval(input, {"__builtins__": {}}))
42
+ except Exception as e:
43
+ return f"Math error: {e}"
44
+
45
+ class RiddleSolver(Tool):
46
+ name = "riddle_solver"
47
+ description = "Solve basic riddles using logic."
48
+ inputs = {"input": {"type": "string", "description": "Riddle prompt."}}
49
+ output_type = "string"
50
+
51
+ def forward(self, input: str) -> str:
52
+ if "forward" in input and "backward" in input:
53
+ return "A palindrome"
54
+ return "RiddleSolver failed."
55
+
56
+ class TextTransformer(Tool):
57
+ name = "text_ops"
58
+ description = "Transform text: reverse, upper, lower."
59
+ inputs = {"input": {"type": "string", "description": "Use prefix like reverse:/upper:/lower:"}}
60
+ output_type = "string"
61
+
62
+ def forward(self, input: str) -> str:
63
+ if input.startswith("reverse:"):
64
+ reversed_text = input[8:].strip()[::-1]
65
+ if 'left' in reversed_text.lower():
66
+ return "right"
67
+ return reversed_text
68
+ if input.startswith("upper:"):
69
+ return input[6:].strip().upper()
70
+ if input.startswith("lower:"):
71
+ return input[6:].strip().lower()
72
+ return "Unknown transformation."
73
+
74
+ class GeminiVideoQA(Tool):
75
+ name = "video_inspector"
76
+ description = "Analyze video content to answer questions."
77
+ inputs = {
78
+ "video_url": {"type": "string", "description": "URL of video."},
79
+ "user_query": {"type": "string", "description": "Question about video."}
80
+ }
81
+ output_type = "string"
82
+
83
+ def __init__(self, model_name, *args, **kwargs):
84
+ super().__init__(*args, **kwargs)
85
+ self.model_name = model_name
86
+
87
+ def forward(self, video_url: str, user_query: str) -> str:
88
+ req = {
89
+ 'model': f'models/{self.model_name}',
90
+ 'contents': [{
91
+ "parts": [
92
+ {"fileData": {"fileUri": video_url}},
93
+ {"text": f"Please watch the video and answer the question: {user_query}"}
94
+ ]
95
+ }]
96
+ }
97
+ url = f'https://generativelanguage.googleapis.com/v1beta/models/{self.model_name}:generateContent?key={os.getenv("GOOGLE_API_KEY")}'
98
+ res = requests.post(url, json=req, headers={'Content-Type': 'application/json'})
99
+ if res.status_code != 200:
100
+ return f"Video error {res.status_code}: {res.text}"
101
+ parts = res.json()['candidates'][0]['content']['parts']
102
+ return "".join([p.get('text', '') for p in parts])
103
+
104
+ class WikiTitleFinder(Tool):
105
+ name = "wiki_titles"
106
+ description = "Search for related Wikipedia page titles."
107
+ inputs = {"query": {"type": "string", "description": "Search query."}}
108
+ output_type = "string"
109
+
110
+ def forward(self, query: str) -> str:
111
+ results = wiki.search(query)
112
+ return ", ".join(results) if results else "No results."
113
+
114
+ class WikiContentFetcher(Tool):
115
+ name = "wiki_page"
116
+ description = "Fetch Wikipedia page content."
117
+ inputs = {"page_title": {"type": "string", "description": "Wikipedia page title."}}
118
+ output_type = "string"
119
+
120
+ def forward(self, page_title: str) -> str:
121
+ try:
122
+ return to_markdown(wiki.page(page_title).html())
123
+ except wiki.exceptions.PageError:
124
+ return f"'{page_title}' not found."
125
+
126
+ class GoogleSearchTool(Tool):
127
+ name = "google_search"
128
+ description = "Search the web using Google. Returns top summary from the web."
129
+ inputs = {"query": {"type": "string", "description": "Search query."}}
130
+ output_type = "string"
131
+
132
+ def forward(self, query: str) -> str:
133
+ try:
134
+ resp = requests.get("https://www.googleapis.com/customsearch/v1", params={
135
+ "q": query,
136
+ "key": os.getenv("AIzaSyAJUd32HV2Dz06LPDTP6KTmfqr6LxuoWww"),
137
+ "num": 1
138
+ })
139
+ data = resp.json()
140
+ return data["items"][0]["snippet"] if "items" in data else "No results found."
141
+ except Exception as e:
142
+ return f"GoogleSearch error: {e}"
143
+
144
+
145
+ class FileAttachmentQueryTool(Tool):
146
+ name = "run_query_with_file"
147
+ description = """
148
+ Downloads a file mentioned in a user prompt, adds it to the context, and runs a query on it.
149
+ This assumes the file is 20MB or less.
150
+ """
151
+ inputs = {
152
+ "task_id": {
153
+ "type": "string",
154
+ "description": "A unique identifier for the task related to this file, used to download it.",
155
+ "nullable": True
156
+ },
157
+ "user_query": {
158
+ "type": "string",
159
+ "description": "The question to answer about the file."
160
+ }
161
+ }
162
+ output_type = "string"
163
+
164
+ def forward(self, task_id: str | None, user_query: str) -> str:
165
+ file_url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
166
+ file_response = requests.get(file_url)
167
+ if file_response.status_code != 200:
168
+ return f"Failed to download file: {file_response.status_code} - {file_response.text}"
169
+ file_data = file_response.content
170
+ from google.generativeai import GenerativeModel
171
+ model = GenerativeModel(self.model_name)
172
+ response = model.generate_content([
173
+ types.Part.from_bytes(data=file_data, mime_type="application/octet-stream"),
174
+ user_query
175
+ ])
176
+
177
+ return response.text
178
+
179
+ # --- Basic Agent Definition ---
180
+ class BasicAgent:
181
+ def __init__(self, provider="deepseek"):
182
+ print("BasicAgent initialized.")
183
+ model = self.select_model(provider)
184
+ client = InferenceClientModel()
185
+ tools = [
186
+ GoogleSearchTool(),
187
+ DuckDuckGoSearchTool(),
188
+ GeminiVideoQA(GEMINI_MODEL_NAME),
189
+ WikiTitleFinder(),
190
+ WikiContentFetcher(),
191
+ MathSolver(),
192
+ RiddleSolver(),
193
+ TextTransformer(),
194
+ FileAttachmentQueryTool(model_name=GEMINI_MODEL_NAME),
195
+ ]
196
+ self.agent = CodeAgent(
197
+ model=model,
198
+ tools=tools,
199
+ add_base_tools=False,
200
+ max_steps=10,
201
+ )
202
+ self.agent.system_prompt = (
203
+ """
204
+ You are a GAIA benchmark AI assistant, you are very precise, no nonense. Your sole purpose is to output the minimal, final answer in the format:
205
+
206
+ [ANSWER]
207
+
208
+ You must NEVER output explanations, intermediate steps, reasoning, or comments — only the answer, strictly enclosed in `[ANSWER]`.
209
+
210
+ Your behavior must be governed by these rules:
211
+
212
+ 1. **Format**:
213
+ - limit the token used (within 65536 tokens).
214
+ - Output ONLY the final answer.
215
+ - Wrap the answer in `[ANSWER]` with no whitespace or text outside the brackets.
216
+ - No follow-ups, justifications, or clarifications.
217
+
218
+ 2. **Numerical Answers**:
219
+ - Use **digits only**, e.g., `4` not `four`.
220
+ - No commas, symbols, or units unless explicitly required.
221
+ - Never use approximate words like "around", "roughly", "about".
222
+
223
+ 3. **String Answers**:
224
+ - Omit **articles** ("a", "the").
225
+ - Use **full words**; no abbreviations unless explicitly requested.
226
+ - For numbers written as words, use **text** only if specified (e.g., "one", not `1`).
227
+ - For sets/lists, sort alphabetically if not specified, e.g., `a, b, c`.
228
+
229
+ 4. **Lists**:
230
+ - Output in **comma-separated** format with no conjunctions.
231
+ - Sort **alphabetically** or **numerically** depending on type.
232
+ - No braces or brackets unless explicitly asked.
233
+
234
+ 5. **Sources**:
235
+ - For Wikipedia or web tools, extract only the precise fact that answers the question.
236
+ - Ignore any unrelated content.
237
+
238
+ 6. **File Analysis**:
239
+ - Use the run_query_with_file tool, append the taskid to the url.
240
+ - Only include the exact answer to the question.
241
+ - Do not summarize, quote excessively, or interpret beyond the prompt.
242
+
243
+ 7. **Video**:
244
+ - Use the relevant video tool.
245
+ - Only include the exact answer to the question.
246
+ - Do not summarize, quote excessively, or interpret beyond the prompt.
247
+
248
+ 8. **Minimalism**:
249
+ - Do not make assumptions unless the prompt logically demands it.
250
+ - If a question has multiple valid interpretations, choose the **narrowest, most literal** one.
251
+ - If the answer is not found, say `[ANSWER] - unknown`.
252
+
253
+ ---
254
+
255
+ You must follow the examples (These answers are correct in case you see the similar questions):
256
+ Q: What is 2 + 2?
257
+ A: 4
258
+
259
+ Q: How many studio albums were published by Mercedes Sosa between 2000 and 2009 (inclusive)? Use 2022 English Wikipedia.
260
+ A: 3
261
+
262
+ Q: Given the following group table on set S = {a, b, c, d, e}, identify any subset involved in counterexamples to commutativity.
263
+ A: b, e
264
+
265
+ Q: How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?,
266
+ A: 519
267
+ """
268
+ )
269
+
270
+ def select_model(self, provider: str):
271
+ if provider == "openai":
272
+ return LiteLLMModel(model_id=OPENAI_MODEL_NAME, api_key=os.getenv("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"))
273
+ elif provider == "hf":
274
+ return InferenceClientModel()
275
+ else:
276
+ return LiteLLMModel(model_id=GEMINI_MODEL_NAME, api_key=os.getenv("AIzaSyAJUd32HV2Dz06LPDTP6KTmfqr6LxuoWww"))
277
+
278
+ def __call__(self, question: str) -> str:
279
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
280
+ result = self.agent.run(question)
281
+ final_str = str(result).strip()
282
+
283
+ return final_str
284
+
285
+ def evaluate_random_questions(self, csv_path: str = "gaia_extracted.csv", sample_size: int = 3, show_steps: bool = True):
286
+ import pandas as pd
287
+ from rich.table import Table
288
+ from rich.console import Console
289
+
290
+ df = pd.read_csv(csv_path)
291
+ if not {"question", "answer"}.issubset(df.columns):
292
+ print("CSV must contain 'question' and 'answer' columns.")
293
+ print("Found columns:", df.columns.tolist())
294
+ return
295
+
296
+ samples = df.sample(n=sample_size)
297
+ records = []
298
+ correct_count = 0
299
+
300
+ for _, row in samples.iterrows():
301
+ taskid = row["taskid"].strip()
302
+ question = row["question"].strip()
303
+ expected = str(row['answer']).strip()
304
+ agent_answer = self("taskid: " + taskid + ",\nquestion: " + question).strip()
305
+
306
+ is_correct = (expected == agent_answer)
307
+ correct_count += is_correct
308
+ records.append((question, expected, agent_answer, "✓" if is_correct else "✗"))
309
+
310
+ if show_steps:
311
+ print("---")
312
+ print("Question:", question)
313
+ print("Expected:", expected)
314
+ print("Agent:", agent_answer)
315
+ print("Correct:", is_correct)
316
+
317
+ # Print result table
318
+ console = Console()
319
+ table = Table(show_lines=True)
320
+ table.add_column("Question", overflow="fold")
321
+ table.add_column("Expected")
322
+ table.add_column("Agent")
323
+ table.add_column("Correct")
324
+
325
+ for question, expected, agent_ans, correct in records:
326
+ table.add_row(question, expected, agent_ans, correct)
327
+
328
+ console.print(table)
329
+ percent = (correct_count / sample_size) * 100
330
+ print(f"\nTotal Correct: {correct_count} / {sample_size} ({percent:.2f}%)")
331
+
332
+
333
+ if __name__ == "__main__":
334
+ args = sys.argv[1:]
335
+ if not args or args[0] in {"-h", "--help"}:
336
+ print("Usage: python agent.py [question | dev]")
337
+ print(" - Provide a question to get a GAIA-style answer.")
338
+ print(" - Use 'dev' to evaluate 3 random GAIA questions from gaia_qa.csv.")
339
+ sys.exit(0)
340
+
341
+ q = " ".join(args)
342
+ agent = BasicAgent()
343
+ if q == "dev":
344
+ agent.evaluate_random_questions()
345
+ else:
346
+ print(agent(q))
app.py CHANGED
@@ -1,210 +1,185 @@
 
1
  import gradio as gr
2
- from datasets import load_dataset, Dataset
3
- from datetime import datetime
4
- from datetime import date
5
  import requests
6
- import tempfile
7
- import asyncio
8
- from huggingface_hub import upload_file
9
- from functools import partial
10
- import io
11
- import os
12
- from PIL import Image, ImageDraw, ImageFont
13
- from huggingface_hub import login
14
-
15
- login(token=os.environ["HUGGINGFACE_TOKEN"])
16
-
17
- # Constants
18
- SCORES_DATASET = "agents-course/unit4-students-scores"
19
- CERTIFICATES_DATASET = "agents-course/course-certificates-of-excellence"
20
- THRESHOLD_SCORE = 30
21
- CERTIFYING_ORG_LINKEDIN_ID = os.getenv("CERTIFYING_ORG_LINKEDIN_ID", "000000")
22
- COURSE_TITLE = os.getenv("COURSE_TITLE", "Hugging Face Agents Course")
23
-
24
- # Function to check user score
25
- def check_user_score(username):
26
- score_data = load_dataset(SCORES_DATASET, split="train", download_mode="force_redownload")
27
- matches = [row for row in score_data if row["username"] == username]
28
- return matches[0] if matches else None
29
-
30
- # Function to check if certificate entry exists
31
- def has_certificate_entry(username):
32
- cert_data = load_dataset(CERTIFICATES_DATASET, split="train", download_mode="force_redownload")
33
- print(username)
34
- return any(row["username"] == username for row in cert_data)
35
-
36
- # Function to add certificate entry
37
- def add_certificate_entry(username, name, score):
38
- # Load current dataset
39
- ds = load_dataset(CERTIFICATES_DATASET, split="train", download_mode="force_redownload")
40
-
41
- # Remove any existing entry with the same username
42
- filtered_rows = [row for row in ds if row["username"] != username]
43
-
44
- # Append the updated/new entry
45
- new_entry = {
46
- "username": username,
47
- "score": score,
48
- "timestamp": datetime.now().isoformat()
49
- }
50
- filtered_rows.append(new_entry)
51
-
52
- # Rebuild dataset and push
53
- updated_ds = Dataset.from_list(filtered_rows)
54
- updated_ds.push_to_hub(CERTIFICATES_DATASET)
55
-
56
- # Function to generate certificate PDF
57
- def generate_certificate(name, score):
58
- """Generate certificate image and PDF."""
59
- certificate_path = os.path.join(
60
- os.path.dirname(__file__), "templates", "certificate.png"
61
- )
62
- im = Image.open(certificate_path)
63
- d = ImageDraw.Draw(im)
64
-
65
- name_font = ImageFont.truetype("Quattrocento-Regular.ttf", 100)
66
- date_font = ImageFont.truetype("Quattrocento-Regular.ttf", 48)
67
-
68
- name = name.title()
69
- d.text((1000, 740), name, fill="black", anchor="mm", font=name_font)
70
-
71
- d.text((1480, 1170), str(date.today()), fill="black", anchor="mm", font=date_font)
72
 
73
- pdf = im.convert("RGB")
74
- pdf.save("certificate.pdf")
 
75
 
76
- return im, "certificate.pdf"
77
-
78
- async def upload_certificate_to_hub(username: str, certificate_img) -> str:
79
- """Upload certificate to the dataset hub and return the URL asynchronously."""
80
- # Save image to temporary file
81
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
82
- certificate_img.save(tmp.name)
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  try:
85
- # Run upload in a thread pool since upload_file is blocking
86
- loop = asyncio.get_event_loop()
87
- upload_func = partial(
88
- upload_file,
89
- path_or_fileobj=tmp.name,
90
- path_in_repo=f"certificates/{username}/{date.today()}.png",
91
- repo_id="agents-course/final-certificates",
92
- repo_type="dataset",
93
- token=os.getenv("HF_TOKEN"),
94
- )
95
- await loop.run_in_executor(None, upload_func)
96
-
97
- # Construct the URL to the image
98
- cert_url = (
99
- f"https://huggingface.co/datasets/agents-course/final-certificates/"
100
- f"resolve/main/certificates/{username}/{date.today()}.png"
101
- )
102
-
103
- # Clean up temp file
104
- os.unlink(tmp.name)
105
- return cert_url
106
-
107
  except Exception as e:
108
- print(f"Error uploading certificate: {e}")
109
- os.unlink(tmp.name)
110
- return None
111
-
112
- def create_linkedin_button(username: str, cert_url: str | None) -> str:
113
- """Create LinkedIn 'Add to Profile' button HTML."""
114
- current_year = date.today().year
115
- current_month = date.today().month
116
-
117
- # Use the dataset certificate URL if available, otherwise fallback to default
118
- certificate_url = cert_url or "https://huggingface.co/agents-course-finishers"
119
-
120
- linkedin_params = {
121
- "startTask": "CERTIFICATION_NAME",
122
- "name": COURSE_TITLE,
123
- "organizationName": "Hugging Face",
124
- "organizationId": CERTIFYING_ORG_LINKEDIN_ID,
125
- "issueYear": str(current_year),
126
- "issueMonth": str(current_month),
127
- "certUrl": certificate_url,
128
- "certId": username, # Using username as cert ID
129
- }
130
-
131
- # Build the LinkedIn button URL
132
- base_url = "https://www.linkedin.com/profile/add?"
133
- params = "&".join(
134
- f"{k}={requests.utils.quote(v)}" for k, v in linkedin_params.items()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  )
136
- button_url = base_url + params
137
-
138
- message = f"""
139
- <a href="{button_url}" target="_blank" style="display: block; margin: 0 auto; width: fit-content;">
140
- <img src="https://download.linkedin.com/desktop/add2profile/buttons/en_US.png"
141
- alt="LinkedIn Add to Profile button"
142
- style="height: 40px; width: auto; display: block;" />
143
- </a>
144
- """
145
- return message
146
-
147
- # Main function to handle certificate generation
148
- async def handle_certificate(name, profile: gr.OAuthProfile):
149
- if profile is None:
150
- return "You must be logged in with your Hugging Face account.", None
151
-
152
- username = profile.username
153
- user_score = check_user_score(username)
154
-
155
- if not user_score:
156
- return "You need to complete Unit 4 first.", None, None, None
157
-
158
- score = user_score["score"]
159
 
160
- if score < THRESHOLD_SCORE:
161
- return f"Your score is {score}. You need at least {THRESHOLD_SCORE} to pass.", None, None
162
-
163
- certificate_image, certificate_pdf = generate_certificate(name, score)
164
- add_certificate_entry(username, name, score)
165
-
166
- # Start certificate upload asynchronously
167
- gr.Info("Uploading your certificate...")
168
- cert_url = await upload_certificate_to_hub(username, certificate_image)
169
 
170
- if cert_url is None:
171
- gr.Warning("Certificate upload failed, but you still passed!")
172
- cert_url = "https://huggingface.co/agents-course"
173
 
174
- linkedin_button = create_linkedin_button(username, cert_url)
175
- return "Congratulations! Here's your certificate:", certificate_image, gr.update(value=linkedin_button, visible=True), certificate_pdf
176
-
177
 
178
- # Gradio interface
179
- with gr.Blocks() as demo:
180
- gr.Markdown("# 🎓 Agents Course - Get Your Final Certificate")
181
- gr.Markdown("Welcome! Follow the steps below to receive your official certificate:")
182
- gr.Markdown("⚠️ **Note**: Due to high demand, you might experience occasional bugs. If something doesn't work, please try again after a moment!")
183
-
184
- with gr.Group():
185
- gr.Markdown("## ✅ How it works")
186
- gr.Markdown("""
187
- 1. **Sign in** with your Hugging Face account using the button below.
188
- 2. **Enter your full name** (this will appear on the certificate).
189
- 3. Click **'Get My Certificate'** to check your score and download your certificate.
190
- """)
191
- gr.Markdown("---")
192
- gr.Markdown("📝 **Note**: You must have completed [Unit 4](https://huggingface.co/learn/agents-course/unit4/introduction) and your Agent must have scored **above 30** to get your certificate.")
193
-
194
- gr.LoginButton()
195
- with gr.Row():
196
- name_input = gr.Text(label="Enter your name (this will appear on the certificate)")
197
- generate_btn = gr.Button("Get my certificate")
198
- output_text = gr.Textbox(label="Result")
199
- linkedin_btn = gr.HTML(visible=False)
200
-
201
- cert_image = gr.Image(label="Your Certificate")
202
- cert_file = gr.File(label="Download Certificate (PDF)", file_types=[".pdf"])
203
-
204
- generate_btn.click(
205
- fn=handle_certificate,
206
- inputs=[name_input],
207
- outputs=[output_text, cert_image, linkedin_btn, cert_file]
208
  )
209
 
210
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
  import gradio as gr
 
 
 
3
  import requests
4
+ import inspect
5
+ import pandas as pd
6
+ from agent import BasicAgent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ # (Keep Constants as is)
9
+ # --- Constants ---
10
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
 
 
 
 
 
 
 
 
12
 
13
+ def run_and_submit_all( profile: gr.OAuthProfile | None):
14
+ """
15
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
16
+ and displays the results.
17
+ """
18
+ # --- Determine HF Space Runtime URL and Repo URL ---
19
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
20
+
21
+ if profile:
22
+ username= f"{profile.username}"
23
+ print(f"User logged in: {username}")
24
+ else:
25
+ print("User not logged in.")
26
+ return "Please Login to Hugging Face with the button.", None
27
+
28
+ api_url = DEFAULT_API_URL
29
+ questions_url = f"{api_url}/questions"
30
+ submit_url = f"{api_url}/submit"
31
+
32
+ # 1. Instantiate Agent ( modify this part to create your agent)
33
+ try:
34
+ agent = BasicAgent()
35
+ except Exception as e:
36
+ print(f"Error instantiating agent: {e}")
37
+ return f"Error initializing agent: {e}", None
38
+ # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
39
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
40
+ print(agent_code)
41
+
42
+ # 2. Fetch Questions
43
+ print(f"Fetching questions from: {questions_url}")
44
+ try:
45
+ response = requests.get(questions_url, timeout=15)
46
+ response.raise_for_status()
47
+ questions_data = response.json()
48
+ if not questions_data:
49
+ print("Fetched questions list is empty.")
50
+ return "Fetched questions list is empty or invalid format.", None
51
+ print(f"Fetched {len(questions_data)} questions.")
52
+ except requests.exceptions.RequestException as e:
53
+ print(f"Error fetching questions: {e}")
54
+ return f"Error fetching questions: {e}", None
55
+ except requests.exceptions.JSONDecodeError as e:
56
+ print(f"Error decoding JSON response from questions endpoint: {e}")
57
+ print(f"Response text: {response.text[:500]}")
58
+ return f"Error decoding server response for questions: {e}", None
59
+ except Exception as e:
60
+ print(f"An unexpected error occurred fetching questions: {e}")
61
+ return f"An unexpected error occurred fetching questions: {e}", None
62
+
63
+ # 3. Run your Agent
64
+ results_log = []
65
+ answers_payload = []
66
+ print(f"Running agent on {len(questions_data)} questions...")
67
+ for item in questions_data:
68
+ task_id = item.get("task_id")
69
+ question_text = item.get("question")
70
+ if not task_id or question_text is None:
71
+ print(f"Skipping item with missing task_id or question: {item}")
72
+ continue
73
  try:
74
+ submitted_answer = agent(question_text)
75
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
76
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  except Exception as e:
78
+ print(f"Error running agent on task {task_id}: {e}")
79
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
80
+
81
+ if not answers_payload:
82
+ print("Agent did not produce any answers to submit.")
83
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
84
+
85
+ # 4. Prepare Submission
86
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
87
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
88
+ print(status_update)
89
+
90
+ # 5. Submit
91
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
92
+ try:
93
+ response = requests.post(submit_url, json=submission_data, timeout=60)
94
+ response.raise_for_status()
95
+ result_data = response.json()
96
+ final_status = (
97
+ f"Submission Successful!\n"
98
+ f"User: {result_data.get('username')}\n"
99
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
100
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
101
+ f"Message: {result_data.get('message', 'No message received.')}"
102
+ )
103
+ print("Submission successful.")
104
+ results_df = pd.DataFrame(results_log)
105
+ return final_status, results_df
106
+ except requests.exceptions.HTTPError as e:
107
+ error_detail = f"Server responded with status {e.response.status_code}."
108
+ try:
109
+ error_json = e.response.json()
110
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
111
+ except requests.exceptions.JSONDecodeError:
112
+ error_detail += f" Response: {e.response.text[:500]}"
113
+ status_message = f"Submission Failed: {error_detail}"
114
+ print(status_message)
115
+ results_df = pd.DataFrame(results_log)
116
+ return status_message, results_df
117
+ except requests.exceptions.Timeout:
118
+ status_message = "Submission Failed: The request timed out."
119
+ print(status_message)
120
+ results_df = pd.DataFrame(results_log)
121
+ return status_message, results_df
122
+ except requests.exceptions.RequestException as e:
123
+ status_message = f"Submission Failed: Network error - {e}"
124
+ print(status_message)
125
+ results_df = pd.DataFrame(results_log)
126
+ return status_message, results_df
127
+ except Exception as e:
128
+ status_message = f"An unexpected error occurred during submission: {e}"
129
+ print(status_message)
130
+ results_df = pd.DataFrame(results_log)
131
+ return status_message, results_df
132
+
133
+
134
+ # --- Build Gradio Interface using Blocks ---
135
+ with gr.Blocks() as demo:
136
+ gr.Markdown("# Basic Agent Evaluation Runner")
137
+ gr.Markdown(
138
+ """
139
+ **Instructions:**
140
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
141
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
142
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
143
+ ---
144
+ **Disclaimers:**
145
+ Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
146
+ This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
147
+ """
148
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
+ gr.LoginButton()
 
 
 
 
 
 
 
 
151
 
152
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
 
 
153
 
154
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
155
+ # Removed max_rows=10 from DataFrame constructor
156
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
157
 
158
+ run_button.click(
159
+ fn=run_and_submit_all,
160
+ outputs=[status_output, results_table]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  )
162
 
163
+ if __name__ == "__main__":
164
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
165
+ # Check for SPACE_HOST and SPACE_ID at startup for information
166
+ space_host_startup = os.getenv("SPACE_HOST")
167
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
168
+
169
+ if space_host_startup:
170
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
171
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
172
+ else:
173
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
174
+
175
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
176
+ print(f"✅ SPACE_ID found: {space_id_startup}")
177
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
178
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
179
+ else:
180
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
181
+
182
+ print("-"*(60 + len(" App Starting ")) + "\n")
183
+
184
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
185
+ demo.launch(debug=True, share=False)
gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gradio interface
2
+ gradio
3
+ requests
4
+ pandas
5
+ python-dotenv
6
+ wikipedia
7
+ markdownify
8
+ google-generativeai
9
+ smolagents
10
+ smolagents[litellm]
11
+ duckduckgo-search