MonkMiner commited on
Commit
97a7e44
·
verified ·
1 Parent(s): b4e0f53

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -85
app.py CHANGED
@@ -1,6 +1,4 @@
1
  import os
2
- import math
3
- import datetime
4
  import requests
5
  import pandas as pd
6
  import gradio as gr
@@ -8,61 +6,73 @@ import torch
8
 
9
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
10
  from smolagents import CodeAgent, DuckDuckGoSearchTool
11
- from smolagents.tools import Tool
12
 
13
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
14
- MODEL_ID = "google/flan-t5-xl"
15
 
16
- # DuckDuckGo-based Wikipedia-style tool
17
- class WikipediaSearchTool(DuckDuckGoSearchTool, Tool):
 
 
 
 
 
18
  name = "wikipedia_search"
19
  description = "Search Wikipedia-style information using DuckDuckGo backend."
20
 
21
- # Local Flan-T5 model wrapper
22
  class LocalFlanT5Model:
23
- def __init__(self, model_id=MODEL_ID):
 
 
 
 
 
24
  self.tokenizer = AutoTokenizer.from_pretrained(model_id)
25
- self.model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
26
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
27
- self.model.to(self.device)
28
 
29
- def _sanitize_prompt(self, raw_prompt):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  if isinstance(raw_prompt, str):
31
  return raw_prompt.strip()
32
 
33
- if isinstance(raw_prompt, dict):
34
- for key in ["prompt", "text", "question", "input"]:
35
- value = raw_prompt.get(key)
36
- if isinstance(value, str):
37
- return value.strip()
38
- elif isinstance(value, list):
39
- return " ".join([str(v) for v in value if isinstance(v, str)])
40
-
41
  if isinstance(raw_prompt, list):
42
  parts = []
43
  for item in raw_prompt:
44
  if isinstance(item, dict):
45
- content = item.get("content")
46
- if isinstance(content, str):
47
- parts.append(content)
48
  elif isinstance(item, str):
49
  parts.append(item)
50
- else:
51
- try:
52
- content = getattr(item, "content", None)
53
- if isinstance(content, str):
54
- parts.append(content)
55
- except Exception:
56
- continue
57
- return " ".join(parts).strip()
58
-
59
- # Fallback
60
- try:
61
- return str(raw_prompt).strip()
62
- except Exception:
63
- raise ValueError(f"Prompt must be a string, got {type(raw_prompt)}: {raw_prompt}")
64
 
65
- def generate(self, prompt, **kwargs) -> str:
 
66
  prompt_str = self._sanitize_prompt(prompt)
67
  if not prompt_str:
68
  raise ValueError("Prompt must be a non-empty string.")
@@ -71,54 +81,75 @@ class LocalFlanT5Model:
71
  prompt_str,
72
  return_tensors="pt",
73
  truncation=True,
74
- max_length=self.tokenizer.model_max_length
75
  ).to(self.device)
76
 
77
- outputs = self.model.generate(**inputs, max_new_tokens=500)
78
- return self.tokenizer.decode(outputs[0], skip_special_tokens=True)
 
 
 
 
79
 
80
  def __call__(self, prompt):
81
  return self.generate(prompt)
82
 
83
- # Wrapper for agent execution
84
  class BasicAgent:
85
  def __init__(self):
86
  model = LocalFlanT5Model()
87
- tools = [
88
- DuckDuckGoSearchTool(),
89
- WikipediaSearchTool(),
90
- ]
91
  self.agent = CodeAgent(
92
  tools=tools,
93
  model=model,
94
- additional_authorized_imports=["math", "datetime"]
95
  )
96
 
97
  def __call__(self, question: str) -> str:
98
- # FIX: Provide input as a list of message dicts
99
- return self.agent.run([{"role": "user", "content": question}]).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- # Core function to run evaluation
102
  def run_and_submit_all(profile: gr.OAuthProfile | None) -> tuple[str, pd.DataFrame]:
103
  if not profile:
104
  return "Please log in with your Hugging Face account.", pd.DataFrame()
105
 
106
- space_id = os.getenv("SPACE_ID", "your-username/your-space")
107
- if space_id == "your-username/your-space":
108
- return "Error: Please set the SPACE_ID environment variable.", pd.DataFrame()
 
 
 
 
109
 
110
  username = profile.username
111
- agent_code_url = f"https://huggingface.co/spaces/{space_id}/tree/main"
112
 
113
  try:
114
  agent = BasicAgent()
115
  except Exception as e:
116
  return f"Error initializing agent: {e}", pd.DataFrame()
117
 
 
118
  try:
119
- response = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15)
120
- response.raise_for_status()
121
- questions = response.json()
122
  if not questions:
123
  return "No questions received from the API.", pd.DataFrame()
124
  except Exception as e:
@@ -132,23 +163,17 @@ def run_and_submit_all(profile: gr.OAuthProfile | None) -> tuple[str, pd.DataFra
132
  question = item.get("question")
133
  if not task_id or not question:
134
  continue
 
135
  try:
136
  submitted_answer = agent(question)
137
- answers_payload.append({
138
- "task_id": task_id,
139
- "submitted_answer": submitted_answer
140
- })
141
- results_log.append({
142
- "Task ID": task_id,
143
- "Question": question,
144
- "Submitted Answer": submitted_answer
145
- })
146
  except Exception as e:
147
- results_log.append({
148
- "Task ID": task_id,
149
- "Question": question,
150
- "Submitted Answer": f"AGENT ERROR: {e}"
151
- })
152
 
153
  if not answers_payload:
154
  return "Agent failed to generate any answers.", pd.DataFrame(results_log)
@@ -156,15 +181,16 @@ def run_and_submit_all(profile: gr.OAuthProfile | None) -> tuple[str, pd.DataFra
156
  submission_data = {
157
  "username": username,
158
  "agent_code": agent_code_url,
159
- "answers": answers_payload
160
  }
161
 
 
162
  try:
163
- res = requests.post(f"{DEFAULT_API_URL}/submit", json=submission_data, timeout=60)
164
  res.raise_for_status()
165
  data = res.json()
166
  summary = (
167
- f"✅ Submission Successful!\n"
168
  f"User: {data.get('username')}\n"
169
  f"Score: {data.get('score', 'N/A')}% "
170
  f"({data.get('correct_count', '?')}/{data.get('total_attempted', '?')})\n"
@@ -174,24 +200,21 @@ def run_and_submit_all(profile: gr.OAuthProfile | None) -> tuple[str, pd.DataFra
174
  except Exception as e:
175
  return f"Submission Failed: {e}", pd.DataFrame(results_log)
176
 
177
- # Gradio UI definition
178
  with gr.Blocks() as demo:
179
- gr.Markdown("# 🤖 Agent Evaluation Runner")
180
  gr.Markdown(
181
- "1. Clone this space and edit `app.py` to customize your agent logic.\n"
182
- "2. Log in with your Hugging Face account.\n"
183
- "3. Press the button to evaluate and submit all answers."
184
  )
185
 
186
  gr.LoginButton()
187
  run_button = gr.Button("Run Evaluation & Submit All Answers")
188
- status_output = gr.Textbox(label="Submission Status", lines=5, interactive=False)
189
  results_table = gr.DataFrame(label="Agent Responses")
190
 
191
- run_button.click(
192
- fn=run_and_submit_all,
193
- outputs=[status_output, results_table]
194
- )
195
 
196
  if __name__ == "__main__":
197
- demo.launch(debug=True, share=False)
 
1
  import os
 
 
2
  import requests
3
  import pandas as pd
4
  import gradio as gr
 
6
 
7
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
8
  from smolagents import CodeAgent, DuckDuckGoSearchTool
 
9
 
10
+ DEFAULT_API_URL = os.getenv("DEFAULT_API_URL", "https://agents-course-unit4-scoring.hf.space")
 
11
 
12
+ # IMPORTANT: xl will likely OOM on CPU/free Spaces.
13
+ # Override via Space variables if you have a beefier GPU Space.
14
+ DEFAULT_MODEL_ID = os.getenv("MODEL_ID", "google/flan-t5-base")
15
+
16
+
17
+ # DuckDuckGo-based Wikipedia-style tool (just rename + description)
18
+ class WikipediaSearchTool(DuckDuckGoSearchTool):
19
  name = "wikipedia_search"
20
  description = "Search Wikipedia-style information using DuckDuckGo backend."
21
 
22
+
23
  class LocalFlanT5Model:
24
+ """
25
+ Minimal wrapper compatible with smolagents' model interface:
26
+ model(prompt) -> str
27
+ """
28
+ def __init__(self, model_id: str = DEFAULT_MODEL_ID):
29
+ self.model_id = model_id
30
  self.tokenizer = AutoTokenizer.from_pretrained(model_id)
31
+
32
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
 
33
 
34
+ # Be conservative: CPU uses fp32, CUDA uses fp16 if possible.
35
+ if self.device == "cuda":
36
+ self.model = AutoModelForSeq2SeqLM.from_pretrained(
37
+ model_id,
38
+ torch_dtype=torch.float16,
39
+ low_cpu_mem_usage=True,
40
+ ).to(self.device)
41
+ else:
42
+ self.model = AutoModelForSeq2SeqLM.from_pretrained(
43
+ model_id,
44
+ torch_dtype=torch.float32,
45
+ low_cpu_mem_usage=True,
46
+ ).to(self.device)
47
+
48
+ self.model.eval()
49
+
50
+ def _sanitize_prompt(self, raw_prompt) -> str:
51
+ # smolagents may pass: str OR [{"role": "...", "content": "..."}]
52
  if isinstance(raw_prompt, str):
53
  return raw_prompt.strip()
54
 
 
 
 
 
 
 
 
 
55
  if isinstance(raw_prompt, list):
56
  parts = []
57
  for item in raw_prompt:
58
  if isinstance(item, dict):
59
+ c = item.get("content")
60
+ if isinstance(c, str):
61
+ parts.append(c)
62
  elif isinstance(item, str):
63
  parts.append(item)
64
+ return "\n".join(parts).strip()
65
+
66
+ if isinstance(raw_prompt, dict):
67
+ for key in ("prompt", "text", "question", "input", "content"):
68
+ v = raw_prompt.get(key)
69
+ if isinstance(v, str) and v.strip():
70
+ return v.strip()
71
+
72
+ return str(raw_prompt).strip()
 
 
 
 
 
73
 
74
+ @torch.inference_mode()
75
+ def generate(self, prompt, max_new_tokens: int = 256) -> str:
76
  prompt_str = self._sanitize_prompt(prompt)
77
  if not prompt_str:
78
  raise ValueError("Prompt must be a non-empty string.")
 
81
  prompt_str,
82
  return_tensors="pt",
83
  truncation=True,
84
+ max_length=min(self.tokenizer.model_max_length, 512),
85
  ).to(self.device)
86
 
87
+ outputs = self.model.generate(
88
+ **inputs,
89
+ max_new_tokens=max_new_tokens,
90
+ do_sample=False,
91
+ )
92
+ return self.tokenizer.decode(outputs[0], skip_special_tokens=True).strip()
93
 
94
  def __call__(self, prompt):
95
  return self.generate(prompt)
96
 
97
+
98
  class BasicAgent:
99
  def __init__(self):
100
  model = LocalFlanT5Model()
101
+ tools = [DuckDuckGoSearchTool(), WikipediaSearchTool()]
 
 
 
102
  self.agent = CodeAgent(
103
  tools=tools,
104
  model=model,
105
+ additional_authorized_imports=["math", "datetime"],
106
  )
107
 
108
  def __call__(self, question: str) -> str:
109
+ # smolagents versions differ:
110
+ # - some expect agent.run("question")
111
+ # - some accept messages list
112
+ try:
113
+ out = self.agent.run(question)
114
+ except TypeError:
115
+ out = self.agent.run([{"role": "user", "content": question}])
116
+ return str(out).strip()
117
+
118
+
119
+ def _get_space_repo_id() -> str:
120
+ # HF Spaces commonly exposes one of these
121
+ return (
122
+ os.getenv("SPACE_REPO_NAME") # often "username/space"
123
+ or os.getenv("SPACE_ID")
124
+ or ""
125
+ ).strip()
126
+
127
 
 
128
  def run_and_submit_all(profile: gr.OAuthProfile | None) -> tuple[str, pd.DataFrame]:
129
  if not profile:
130
  return "Please log in with your Hugging Face account.", pd.DataFrame()
131
 
132
+ space_id = _get_space_repo_id()
133
+ if not space_id or "/" not in space_id:
134
+ return (
135
+ "Error: Could not detect Space repo id. "
136
+ "Make sure you're running inside a HF Space (and not locally).",
137
+ pd.DataFrame(),
138
+ )
139
 
140
  username = profile.username
141
+ agent_code_url = f"https://huggingface.co/spaces/{space_id}/blob/main/app.py"
142
 
143
  try:
144
  agent = BasicAgent()
145
  except Exception as e:
146
  return f"Error initializing agent: {e}", pd.DataFrame()
147
 
148
+ # Fetch questions
149
  try:
150
+ resp = requests.get(f"{DEFAULT_API_URL}/questions", timeout=30)
151
+ resp.raise_for_status()
152
+ questions = resp.json()
153
  if not questions:
154
  return "No questions received from the API.", pd.DataFrame()
155
  except Exception as e:
 
163
  question = item.get("question")
164
  if not task_id or not question:
165
  continue
166
+
167
  try:
168
  submitted_answer = agent(question)
169
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
170
+ results_log.append(
171
+ {"Task ID": task_id, "Question": question, "Submitted Answer": submitted_answer}
172
+ )
 
 
 
 
 
173
  except Exception as e:
174
+ results_log.append(
175
+ {"Task ID": task_id, "Question": question, "Submitted Answer": f"AGENT ERROR: {e}"}
176
+ )
 
 
177
 
178
  if not answers_payload:
179
  return "Agent failed to generate any answers.", pd.DataFrame(results_log)
 
181
  submission_data = {
182
  "username": username,
183
  "agent_code": agent_code_url,
184
+ "answers": answers_payload,
185
  }
186
 
187
+ # Submit
188
  try:
189
+ res = requests.post(f"{DEFAULT_API_URL}/submit", json=submission_data, timeout=120)
190
  res.raise_for_status()
191
  data = res.json()
192
  summary = (
193
+ f" Submission Successful!\n"
194
  f"User: {data.get('username')}\n"
195
  f"Score: {data.get('score', 'N/A')}% "
196
  f"({data.get('correct_count', '?')}/{data.get('total_attempted', '?')})\n"
 
200
  except Exception as e:
201
  return f"Submission Failed: {e}", pd.DataFrame(results_log)
202
 
203
+
204
  with gr.Blocks() as demo:
205
+ gr.Markdown("# Agent Evaluation Runner")
206
  gr.Markdown(
207
+ "1) Clone this Space and edit `app.py`.\n"
208
+ "2) Log in with your HF account.\n"
209
+ "3) Click the button to evaluate and submit."
210
  )
211
 
212
  gr.LoginButton()
213
  run_button = gr.Button("Run Evaluation & Submit All Answers")
214
+ status_output = gr.Textbox(label="Submission Status", lines=6, interactive=False)
215
  results_table = gr.DataFrame(label="Agent Responses")
216
 
217
+ run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
 
 
 
218
 
219
  if __name__ == "__main__":
220
+ demo.launch()