dmfelder commited on
Commit
ac4700f
·
verified ·
1 Parent(s): e95b0af

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +419 -17
app.py CHANGED
@@ -1,28 +1,430 @@
1
- from transformers import Agent
2
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
- # Hardcoded username
5
- USERNAME = "dmfelder"
 
6
 
7
- # Initialize the agent
8
- agent = Agent(
9
- "https://huggingface.co/spaces/transformers/Agent/tree/main", # example agent space
10
- remote=True,
 
 
 
 
 
 
 
 
11
  )
12
 
13
- def ask_agent(question):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  try:
15
- response = agent.run(question)
16
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  except Exception as e:
18
- return f"Error: {str(e)}"
 
 
 
 
 
 
 
19
 
 
20
  with gr.Blocks() as demo:
21
- gr.Markdown("### Simple Hugging Face Agent QA")
22
- question = gr.Textbox(label="Question")
23
- answer = gr.Textbox(label="Answer")
24
- btn = gr.Button("Ask")
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- btn.click(ask_agent, inputs=question, outputs=answer)
 
 
 
 
27
 
28
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
  import gradio as gr
3
+ import requests
4
+ import pandas as pd
5
+ import logging
6
+ import time
7
+ import json
8
+ from datetime import datetime
9
+ from typing import Dict, List, Optional, Any
10
+ from dataclasses import dataclass
11
+ import hashlib
12
+ import re
13
+ import tempfile
14
+ from PIL import Image
15
+ import soundfile as sf
16
+ import numpy as np
17
 
18
+ # Core Hugging Face Imports
19
+ from huggingface_hub import InferenceClient
20
+ from transformers import pipeline
21
 
22
+ # --- Logging Setup ---
23
+ log_file_name = f"agent_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
24
+ log_path = os.path.join(os.getcwd(), log_file_name)
25
+
26
+ print(f"[INFO] Log file will be created at: {log_path}")
27
+
28
+ logging.basicConfig(
29
+ filename=log_path,
30
+ filemode='w',
31
+ level=logging.DEBUG,
32
+ format='%(asctime)s | %(levelname)-8s | %(funcName)-15s | %(message)s',
33
+ datefmt='%Y-%m-%d %H:%M:%S'
34
  )
35
 
36
+ console_handler = logging.StreamHandler()
37
+ console_handler.setLevel(logging.INFO)
38
+ console_formatter = logging.Formatter('%(levelname)s: %(message)s')
39
+ console_handler.setFormatter(console_formatter)
40
+ logging.getLogger().addHandler(console_handler)
41
+
42
+ logging.info(f"===== Application Startup at {datetime.now().isoformat()} =====")
43
+ logging.info(f"📂 Log file configured at: {log_path}")
44
+
45
+ # --- Constants ---
46
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
47
+
48
+ # Patterns for extracting the final answer from agent output
49
+ ANSWER_PATTERNS = [
50
+ r"the answer is:\s*(.*)",
51
+ r"final_answer\((.*)\)",
52
+ r"final_answer\(\s*\"(.*?)\"\s*\)",
53
+ r"final_answer\(\s*'(.*?)'\s*\)",
54
+ r"final_answer\(\s*```(.*?)```\s*\)",
55
+ r"final_answer\(\s*`(.*?)`\s*\)",
56
+ r"final_answer\(\s*\[(.*?)\]\s*\)",
57
+ r"final_answer\(\s*\{(.*?)\}\s*\)",
58
+ r"final_answer\(\s*(.*?)\s*\)",
59
+ r"answer is\s*(.*)",
60
+ r"final answer is\s*(.*)",
61
+ r"final answer:\s*(.*)",
62
+ r"final answer of the question is:\s*(.*)",
63
+ r"the final answer is:\s*(.*)",
64
+ r"final answer\s*:\s*(.*)",
65
+ r"final_answer\s*:\s*(.*)",
66
+ r"final_answer is\s*(.*)",
67
+ r"the value is:\s*(.*)",
68
+ r"The final answer is: (.*)",
69
+ r"final answer of the question is (.*)",
70
+ r"final answer to the question is (.*)",
71
+ r"final response: (.*)",
72
+ r"Final Response: (.*)",
73
+ r"the final response is (.*)",
74
+ r"Final Response is: (.*)",
75
+ r"Final Answer:\s*(.*)",
76
+ r"Final Answer is:\s*(.*)",
77
+ r"Answer:\s*(.*)",
78
+ r"The answer is:\s*(.*)",
79
+ r"Final Answer\s*\[([^\]]+)\]",
80
+ r"The final answer is\s*\[([^\]]+)\]",
81
+ r"The answer is\s*\[([^\]]+)\]",
82
+ r"Answer\s*\[([^\]]+)\]",
83
+ r"```json\n\{\"answer\":\s*\"(.*?)\"\n\}```",
84
+ r"```json\n\{\"answer\":\s*(.*?)\n\}```",
85
+ r"\"answer\":\s*\"(.*?)\"",
86
+ r"\"answer\":\s*(.*)",
87
+ r"(\w[\w\s\.\-,\/]*)\s*$", # Broad pattern to catch simple answers at the end
88
+ ]
89
+
90
+ @dataclass
91
+ class QuestionLog:
92
+ question_num: int
93
+ question_preview: str
94
+ question_type: str
95
+ answer: str
96
+ processing_time: float
97
+ status: str
98
+
99
+ # --- SimpleAgent Class ---
100
+ class SimpleAgent:
101
+ def __init__(self, username: str, http_session: requests.Session):
102
+ self.username = username
103
+ self.http_session = http_session
104
+ self.client = None
105
+ self.pipelines = {}
106
+ logging.info("SimpleAgent initialized.")
107
+
108
+ def setup(self):
109
+ """Initialize the inference client"""
110
+ logging.info("Setting up agent...")
111
+ try:
112
+ self.client = InferenceClient()
113
+ logging.info("Agent setup complete.")
114
+ except Exception as e:
115
+ logging.exception(f"Error during agent setup: {e}")
116
+ raise
117
+
118
+ def __call__(self, question: str, question_type: str, tools_code: Optional[List[Dict]] = None) -> str:
119
+ """Main method to process a question and return an answer"""
120
+ if not self.client:
121
+ raise ValueError("Agent not initialized. Call setup() first.")
122
+
123
+ try:
124
+ # For now, we'll use a simple approach with the inference client
125
+ # In a real implementation, you'd want to use proper agent tools
126
+
127
+ # Create a prompt that encourages the model to give a direct answer
128
+ prompt = f"""Question: {question}
129
+ Question Type: {question_type}
130
+
131
+ Please provide a direct answer to this question. End your response with "Final Answer: [your answer]"
132
+
133
+ Response:"""
134
+
135
+ # Use the inference client to get a response
136
+ response = self.client.text_generation(
137
+ prompt,
138
+ max_new_tokens=512,
139
+ temperature=0.1,
140
+ do_sample=True,
141
+ return_full_text=False
142
+ )
143
+
144
+ # Extract the answer from the response
145
+ answer = self._extract_answer(response, question_type)
146
+ logging.info(f"Agent response for {question_type}: {answer}")
147
+ return answer
148
+
149
+ except Exception as e:
150
+ logging.error(f"Error in agent execution: {e}")
151
+ return "ERROR"
152
+
153
+ def _extract_answer(self, raw_answer: str, question_type: str) -> str:
154
+ """Extract the final answer from the raw response"""
155
+ logging.debug(f"Extracting answer from raw_answer: {raw_answer}")
156
+ answer = "ERROR"
157
+
158
+ for pattern in ANSWER_PATTERNS:
159
+ match = re.search(pattern, raw_answer, re.IGNORECASE | re.DOTALL)
160
+ if match:
161
+ extracted_content = match.group(1).strip()
162
+ extracted_content = extracted_content.replace("\\n", "").replace("\\", "")
163
+ if extracted_content.startswith('"') and extracted_content.endswith('"'):
164
+ extracted_content = extracted_content[1:-1]
165
+ if extracted_content.startswith("'") and extracted_content.endswith("'"):
166
+ extracted_content = extracted_content[1:-1]
167
+ answer = extracted_content
168
+ logging.debug(f"Extracted answer using pattern '{pattern}': {answer}")
169
+ break
170
+
171
+ if answer == "ERROR" and raw_answer:
172
+ # If no pattern matched, try to use the raw answer if it's short and clean
173
+ if len(raw_answer) < 200 and not any(kw in raw_answer.lower() for kw in ["thought", "tool", "action", "observation"]):
174
+ answer = raw_answer.strip()
175
+ logging.debug(f"No pattern matched, using raw answer directly: {answer}")
176
+
177
+ if not answer:
178
+ answer = "ERROR"
179
+
180
+ return answer
181
+
182
+ def cleanup(self):
183
+ """Clean up resources"""
184
+ logging.info("Cleaning up agent resources...")
185
+ self.pipelines.clear()
186
+ logging.info("SimpleAgent resources cleaned up.")
187
+
188
+ # --- Main Run and Submit Function ---
189
+ def run_and_submit_all(request: gr.Request):
190
+ """
191
+ Fetches all questions, runs the agent on them, submits all answers,
192
+ and displays the results.
193
+ """
194
+ # Get the user profile from the request
195
+ profile = request.oauth_profile if hasattr(request, 'oauth_profile') else None
196
+
197
+ if not profile:
198
+ logging.info("User not logged in.")
199
+ yield "Please Login to Hugging Face with the button above.", pd.DataFrame([])
200
+ return
201
+
202
+ username = f"{profile.username}"
203
+ logging.info(f"User logged in: {username}")
204
+
205
+ api_url = DEFAULT_API_URL
206
+
207
+ # Determine agent code URL for submission
208
+ space_id = os.getenv("SPACE_ID")
209
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" if space_id else "N/A_LOCAL_RUN"
210
+ logging.info(f"Agent code URL for submission: {agent_code}")
211
+
212
+ # Configure HTTP session with retries
213
+ from requests.adapters import HTTPAdapter
214
+ from urllib3.util.retry import Retry
215
+
216
+ retry_strategy = Retry(
217
+ total=5,
218
+ backoff_factor=1,
219
+ status_forcelist=[429, 500, 502, 503, 504],
220
+ allowed_methods=["HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS", "TRACE"]
221
+ )
222
+ adapter = HTTPAdapter(max_retries=retry_strategy)
223
+
224
+ http_session = requests.Session()
225
+ http_session.mount("https://", adapter)
226
+ http_session.mount("http://", adapter)
227
+
228
+ agent = None
229
+ results_log = []
230
+
231
  try:
232
+ # 1. Instantiate and Setup Agent
233
+ agent = SimpleAgent(username=username, http_session=http_session)
234
+ agent.setup()
235
+
236
+ # Initial yield for Gradio progress display
237
+ yield "🚀 Initializing and fetching questions...", pd.DataFrame([])
238
+
239
+ # 2. Fetch Questions
240
+ questions_url = f"{api_url}/questions"
241
+ logging.info(f"Fetching questions from: {questions_url}")
242
+ response = http_session.get(questions_url, timeout=15)
243
+ response.raise_for_status()
244
+ questions_data = response.json()
245
+
246
+ if not questions_data:
247
+ logging.warning("Fetched questions list is empty.")
248
+ yield "Fetched questions list is empty or invalid format.", pd.DataFrame([])
249
+ return
250
+
251
+ logging.info(f"Fetched {len(questions_data)} questions.")
252
+
253
+ # 3. Process each question
254
+ answers_payload = []
255
+ logging.info(f"Running agent on {len(questions_data)} questions...")
256
+
257
+ for i, item in enumerate(questions_data, 1):
258
+ task_id = item.get("task_id")
259
+ question_text = item.get("question", "")
260
+ question_type = item.get("question_type", "")
261
+ question_preview = item.get("question_preview", question_text[:50] + "...")
262
+ tools_code = item.get("tools_code") # This might be used in advanced implementations
263
+
264
+ if not task_id:
265
+ logging.warning(f"Skipping item with missing task_id: {item}")
266
+ results_log.append({
267
+ "Task ID": "MISSING",
268
+ "Question": "ERROR: Missing task_id",
269
+ "Submitted Answer": "ERROR",
270
+ "Status": "ERROR"
271
+ })
272
+ continue
273
+
274
+ start_time = time.time()
275
+ submitted_answer = "ERROR"
276
+ status = "ERROR"
277
+
278
+ try:
279
+ # Call the agent
280
+ submitted_answer = agent(question_text, question_type, tools_code)
281
+ processing_time = time.time() - start_time
282
+ status = "OK" if submitted_answer != "ERROR" else "ERROR"
283
+
284
+ logging.info(f"Q{i} [{question_type}] Answer: {submitted_answer} (Took {processing_time:.2f}s)")
285
+
286
+ # Note: The API expects 'submitted_answer' not 'answer'
287
+ answers_payload.append({
288
+ "task_id": task_id,
289
+ "submitted_answer": submitted_answer
290
+ })
291
+
292
+ results_log.append({
293
+ "Task ID": task_id,
294
+ "Question": question_preview,
295
+ "Submitted Answer": submitted_answer,
296
+ "Status": status
297
+ })
298
+
299
+ except Exception as e:
300
+ processing_time = time.time() - start_time
301
+ logging.exception(f"Error running agent on task {task_id}: {e}")
302
+ submitted_answer = f"AGENT ERROR: {str(e)}"
303
+ status = "ERROR"
304
+
305
+ answers_payload.append({
306
+ "task_id": task_id,
307
+ "submitted_answer": "ERROR"
308
+ })
309
+
310
+ results_log.append({
311
+ "Task ID": task_id,
312
+ "Question": question_preview,
313
+ "Submitted Answer": submitted_answer,
314
+ "Status": status
315
+ })
316
+
317
+ # Yield progressive updates to Gradio UI
318
+ yield (
319
+ f"Processing Q{i}/{len(questions_data)}. Last Answer: {submitted_answer[:100]}",
320
+ pd.DataFrame(results_log)
321
+ )
322
+
323
+ if not answers_payload:
324
+ logging.info("Agent did not produce any answers to submit.")
325
+ yield "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
326
+ return
327
+
328
+ # 4. Submit answers
329
+ submission_data = {
330
+ "username": username.strip(),
331
+ "agent_code": agent_code,
332
+ "answers": answers_payload
333
+ }
334
+
335
+ submit_url = f"{api_url}/submit"
336
+ logging.info(f"Submitting {len(answers_payload)} answers to: {submit_url}")
337
+
338
+ response = http_session.post(submit_url, json=submission_data, timeout=120)
339
+ response.raise_for_status()
340
+ result_data = response.json()
341
+
342
+ final_status = (
343
+ f"Submission Successful!\n"
344
+ f"User: {result_data.get('username')}\n"
345
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
346
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
347
+ f"Message: {result_data.get('message', 'No message received.')}"
348
+ )
349
+ logging.info("Submission successful.")
350
+
351
+ yield final_status, pd.DataFrame(results_log)
352
+
353
+ except requests.exceptions.HTTPError as e:
354
+ error_detail = f"Server responded with status {e.response.status_code}."
355
+ try:
356
+ error_json = e.response.json()
357
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
358
+ except requests.exceptions.JSONDecodeError:
359
+ error_detail += f" Response: {e.response.text[:500]}"
360
+ status_message = f"Submission Failed: {error_detail}"
361
+ logging.error(status_message)
362
+ yield status_message, pd.DataFrame(results_log)
363
+
364
+ except requests.exceptions.Timeout:
365
+ status_message = "Submission Failed: The request timed out."
366
+ logging.error(status_message)
367
+ yield status_message, pd.DataFrame(results_log)
368
+
369
+ except requests.exceptions.RequestException as e:
370
+ status_message = f"Submission Failed: Network error - {e}"
371
+ logging.error(status_message)
372
+ yield status_message, pd.DataFrame(results_log)
373
+
374
  except Exception as e:
375
+ status_message = f"An unexpected error occurred during run or submission: {e}"
376
+ logging.exception(status_message)
377
+ yield status_message, pd.DataFrame(results_log)
378
+
379
+ finally:
380
+ if agent:
381
+ agent.cleanup()
382
+
383
 
384
+ # --- Gradio Interface ---
385
  with gr.Blocks() as demo:
386
+ gr.Markdown("# Hugging Face Agent Certification Runner")
387
+ gr.Markdown(
388
+ """
389
+ **Instructions:**
390
+ 1. Log in to your Hugging Face account using the button below.
391
+ 2. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, and submit answers.
392
+ 3. The process may take several minutes depending on the number of questions.
393
+
394
+ **Note:** This is a simplified agent implementation. For better performance, consider implementing proper tool usage and more sophisticated reasoning.
395
+ """
396
+ )
397
+
398
+ gr.LoginButton() # Add the login button back
399
+ run_button = gr.Button("🚀 Run Evaluation & Submit All Answers")
400
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
401
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
402
 
403
+ run_button.click(
404
+ fn=run_and_submit_all,
405
+ outputs=[status_output, results_table],
406
+ show_progress=True,
407
+ )
408
 
409
+ if __name__ == "__main__":
410
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
411
+ space_host_startup = os.getenv("SPACE_HOST")
412
+ space_id_startup = os.getenv("SPACE_ID")
413
+
414
+ if space_host_startup:
415
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
416
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
417
+ else:
418
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
419
+
420
+ if space_id_startup:
421
+ print(f"✅ SPACE_ID found: {space_id_startup}")
422
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
423
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
424
+ else:
425
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
426
+
427
+ print("-"*(60 + len(" App Starting ")) + "\n")
428
+ print("Launching Gradio Interface for Agent Evaluation...")
429
+
430
+ demo.launch(debug=True, share=False)