victor-johnson commited on
Commit
80fa192
·
verified ·
1 Parent(s): 97b958d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -107
app.py CHANGED
@@ -1,208 +1,209 @@
 
 
1
  import requests
 
2
  import pandas as pd
3
  import re
4
-
5
  import textwrap
6
  from transformers import AutoTokenizer, AutoModelForCausalLM
7
  import torch
8
 
9
  # --- Constants ---
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
- DOCS_URL = f"{DEFAULT_API_URL}/docs" # or perhaps /openapi.json if exposed
12
-
13
 
 
14
  class BasicAgent:
15
  """
16
  Loads and runs a small LLM *locally* inside the Hugging Face Space
17
- instead of calling the Hugging Face Inference API (which might be blocked).
18
  """
19
  def __init__(self):
20
-
21
  model_id = "HuggingFaceTB/SmolLM2-1.7B-Instruct"
22
  print(f"🚀 Loading model locally: {model_id}")
23
 
24
-
25
  self.tokenizer = AutoTokenizer.from_pretrained(model_id)
26
  self.model = AutoModelForCausalLM.from_pretrained(
27
  model_id,
 
 
 
28
  print("✅ Local model ready.")
29
 
30
  def _clean(self, raw: str) -> str:
31
-
32
  txt = raw.strip()
33
  lines = [l.strip() for l in txt.splitlines() if l.strip()]
34
  if lines:
 
 
 
 
 
35
 
36
  def __call__(self, question: str) -> str:
37
  print(f"🧠 Agent received question: {question[:120]}...")
38
-
39
-
40
  prompt = textwrap.dedent(f"""
41
  You must answer the question with a single, concise value
42
  (number, word, date, or short phrase) and nothing else.
 
43
  Question: {question}
44
  Final answer:
45
  """).strip()
46
-
47
  inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
48
-
49
-
50
  with torch.no_grad():
51
  outputs = self.model.generate(
52
  **inputs,
 
 
53
  do_sample=True,
54
  pad_token_id=self.tokenizer.eos_token_id,
55
  )
56
-
57
  generated_ids = outputs[0][inputs["input_ids"].shape[1]:]
58
  raw_answer = self.tokenizer.decode(generated_ids, skip_special_tokens=True)
59
  clean_ans = self._clean(raw_answer)
60
  print(f"💡 Agent raw: '{raw_answer[:80]}' → clean: '{clean_ans}'")
61
  return clean_ans
62
 
63
- def fetch_questions_from_docs() -> list[dict]:
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
 
 
 
74
  """
75
- Try to fetch question & expected answer pairs from the API docs / spec.
76
- Returns a list of dicts: {"question": ..., "expected": ...} if available.
77
  """
78
- # Try to fetch OpenAPI spec
79
- try:
80
- resp = requests.get(f"{DEFAULT_API_URL}/openapi.json", timeout=30)
81
- resp.raise_for_status()
82
- spec = resp.json()
83
- except Exception as e:
84
- print(f"⚠️ Failed to fetch OpenAPI spec: {e}")
85
- return []
86
-
87
- # Example: maybe there's a component schema "QuestionAnswer" or an endpoint returning the list
88
- questions = []
89
- # This part depends heavily on how the spec is structured.
90
- # For instance, spec["components"]["schemas"]["QuestionAnswer"]["example"] might exist.
91
- comp = spec.get("components", {}).get("schemas", {})
92
- qa_schema = comp.get("QuestionAnswer")
93
- if qa_schema:
94
- example = qa_schema.get("example") or qa_schema.get("examples")
95
- if example:
96
- # If example is a list or single object
97
- if isinstance(example, list):
98
- for ex in example:
99
- questions.append({"question": ex.get("question", ""), "expected": ex.get("expected", "")})
100
- elif isinstance(example, dict):
101
- # maybe contains multiple
102
- # Or maybe there's a property which is list
103
- if "questions" in example and isinstance(example["questions"], list):
104
- for ex in example["questions"]:
105
- questions.append({"question": ex.get("question", ""), "expected": ex.get("expected", "")})
106
- else:
107
- questions.append({"question": example.get("question", ""), "expected": example.get("expected", "")})
108
- # Fallback: look for paths that look like "/questions" or similar
109
- for path, methods in spec.get("paths", {}).items():
110
- if "questions" in path.lower():
111
- get_op = methods.get("get")
112
- if get_op and "responses" in get_op:
113
- # attempt to fetch via the actual endpoint
114
- try:
115
- resp2 = requests.get(DEFAULT_API_URL + path, timeout=30)
116
- resp2.raise_for_status()
117
- data = resp2.json()
118
- # assume list of {question, expected}
119
- if isinstance(data, list):
120
- for q in data:
121
- questions.append({"question": q.get("question",""), "expected": q.get("expected","")})
122
- except Exception as e:
123
- print(f"⚠️ Failed to fetch questions from path {path}: {e}")
124
- return questions
125
-
126
- def submit_answers(answers: list, token: str) -> dict:
127
  try:
128
  space_host = os.getenv("SPACE_HOST", "")
129
  space_id = os.getenv("SPACE_ID", "")
130
-
131
  payload = {
132
  "answers": answers,
133
  "space_host": space_host,
134
  "space_id": space_id,
135
  }
136
-
137
  headers = {"Authorization": f"Bearer {token}"}
138
  resp = requests.post(
139
  f"{DEFAULT_API_URL}/submit",
 
 
 
 
 
 
140
  except Exception as e:
141
  return {"success": False, "message": str(e)}
142
 
143
-
144
- def run_and_submit_all(token: str):
145
- if not token:
146
- return "❌ You must provide a valid Hugging Face token.", pd.DataFrame()
147
 
148
- questions = fetch_questions_from_docs()
149
- if not questions:
150
- # fallback to some default or error
151
- return "❌ Could not fetch questions from docs/spec.", pd.DataFrame()
 
 
 
 
 
 
 
 
 
152
 
153
- agent = None
 
 
 
 
 
 
154
  try:
155
  agent = BasicAgent()
156
  except Exception as e:
157
  return f"❌ Error instantiating agent: {e}", pd.DataFrame()
158
 
159
-
160
  results = []
161
- for qa in questions:
162
- q = qa.get("question", "")
163
- expected = qa.get("expected", "")
164
  try:
165
  answer = agent(q)
166
  except Exception as e:
167
  answer = f"[Error: {e}]"
168
  results.append({"question": q, "answer": answer, "expected": expected})
169
 
170
-
171
  df = pd.DataFrame(results)
172
-
173
-
174
  answers_list = [r["answer"] for r in results]
175
  submission_result = submit_answers(answers_list, token)
176
 
 
 
 
 
177
  msg = submission_result.get("message", "Unknown error")
178
  return f"❌ Submission failed: {msg}", df
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
-
182
  if __name__ == "__main__":
183
- print("\n" + "-" * 30 + " CLI Mode " + "-" * 30 + "\n")
184
-
185
  space_host = os.getenv("SPACE_HOST")
186
  space_id = os.getenv("SPACE_ID")
187
-
188
  if space_host:
189
  print(f"✅ SPACE_HOST found: {space_host}")
190
  print(f" Runtime URL should be: https://{space_host}.hf.space")
191
  else:
192
- print("ℹ️ SPACE_HOST not found.")
193
-
194
  if space_id:
195
  print(f"✅ SPACE_ID found: {space_id}")
196
  print(f" Repo URL: https://huggingface.co/spaces/{space_id}")
197
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id}/tree/main")
198
  else:
199
- print("ℹ️ SPACE_ID not found.")
200
-
201
- print("-" * (60 + len(" CLI Mode ")) + "\n")
202
 
203
- token = os.getenv("HF_TOKEN") or input("🔑 Enter your Hugging Face token: ").strip()
204
- status, df = run_and_submit_all(token)
205
-
206
- print("\n" + "=" * 80)
207
- print(status)
208
- print("=" * 80)
 
1
+ import os
2
+ import gradio as gr
3
  import requests
4
+ import inspect
5
  import pandas as pd
6
  import re
7
+ import json
8
  import textwrap
9
  from transformers import AutoTokenizer, AutoModelForCausalLM
10
  import torch
11
 
12
  # --- Constants ---
13
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
 
 
14
 
15
+ # --- Enhanced BasicAgent (local model version) ---
16
  class BasicAgent:
17
  """
18
  Loads and runs a small LLM *locally* inside the Hugging Face Space
19
+ instead of calling the Hugging Face Inference API (which is blocked).
20
  """
21
  def __init__(self):
22
+ # ✅ Small model to fit free Spaces — change to another instruct model if needed
23
  model_id = "HuggingFaceTB/SmolLM2-1.7B-Instruct"
24
  print(f"🚀 Loading model locally: {model_id}")
25
 
26
+ # Load tokenizer and model
27
  self.tokenizer = AutoTokenizer.from_pretrained(model_id)
28
  self.model = AutoModelForCausalLM.from_pretrained(
29
  model_id,
30
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
31
+ device_map="auto",
32
+ )
33
  print("✅ Local model ready.")
34
 
35
  def _clean(self, raw: str) -> str:
36
+ """Post-process the model output to a short, exact value."""
37
  txt = raw.strip()
38
  lines = [l.strip() for l in txt.splitlines() if l.strip()]
39
  if lines:
40
+ txt = lines[-1]
41
+ txt = re.sub(r"^(final answer|answer|prediction)\s*[:\-]\s*", "", txt, flags=re.I)
42
+ txt = txt.strip("`'\" \t\n\r")
43
+ txt = re.sub(r"[ \t]*[.;,:-]+$", "", txt)
44
+ return txt[:200]
45
 
46
  def __call__(self, question: str) -> str:
47
  print(f"🧠 Agent received question: {question[:120]}...")
48
+
49
+ # Simple concise prompt
50
  prompt = textwrap.dedent(f"""
51
  You must answer the question with a single, concise value
52
  (number, word, date, or short phrase) and nothing else.
53
+
54
  Question: {question}
55
  Final answer:
56
  """).strip()
57
+
58
  inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
59
+
60
+ # Generate
61
  with torch.no_grad():
62
  outputs = self.model.generate(
63
  **inputs,
64
+ max_new_tokens=50,
65
+ temperature=0.7,
66
  do_sample=True,
67
  pad_token_id=self.tokenizer.eos_token_id,
68
  )
69
+
70
  generated_ids = outputs[0][inputs["input_ids"].shape[1]:]
71
  raw_answer = self.tokenizer.decode(generated_ids, skip_special_tokens=True)
72
  clean_ans = self._clean(raw_answer)
73
  print(f"💡 Agent raw: '{raw_answer[:80]}' → clean: '{clean_ans}'")
74
  return clean_ans
75
 
76
+ # --- Test Questions & Expected Answers ---
77
+ QUESTIONS_AND_ANSWERS = [
78
+ {"question": "What is the capital of France?", "expected": "Paris"},
79
+ {"question": "What is 5 + 7?", "expected": "12"},
80
+ {"question": "In what year did World War II end?", "expected": "1945"},
81
+ {"question": "What is the largest planet in our solar system?", "expected": "Jupiter"},
82
+ {"question": "Who wrote 'Romeo and Juliet'?", "expected": "Shakespeare"},
83
+ ]
 
 
84
 
85
+ # --- Submission Function ---
86
+ def submit_answers(answers: list, token: str) -> dict:
87
  """
88
+ Submit answers to the scoring API.
 
89
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  try:
91
  space_host = os.getenv("SPACE_HOST", "")
92
  space_id = os.getenv("SPACE_ID", "")
93
+
94
  payload = {
95
  "answers": answers,
96
  "space_host": space_host,
97
  "space_id": space_id,
98
  }
99
+
100
  headers = {"Authorization": f"Bearer {token}"}
101
  resp = requests.post(
102
  f"{DEFAULT_API_URL}/submit",
103
+ json=payload,
104
+ headers=headers,
105
+ timeout=60,
106
+ )
107
+ resp.raise_for_status()
108
+ return resp.json()
109
  except Exception as e:
110
  return {"success": False, "message": str(e)}
111
 
112
+ # --- Main Run Function ---
113
+ def run_and_submit_all(profile: gr.OAuthProfile | None = None):
114
+ if not profile:
115
+ return "❌ You must log in first.", pd.DataFrame()
116
 
117
+ # Try to get the token using multiple approaches
118
+ token = None
119
+ # Try direct attributes
120
+ if hasattr(profile, '_oauth_token'):
121
+ token = profile._oauth_token
122
+ elif hasattr(profile, 'oauth_token'):
123
+ token_obj = profile.oauth_token
124
+ if hasattr(token_obj, 'access_token'):
125
+ token = token_obj.access_token
126
+ elif isinstance(token_obj, dict) and 'access_token' in token_obj:
127
+ token = token_obj['access_token']
128
+ else:
129
+ token = token_obj
130
 
131
+ if not token:
132
+ # Debug: print what attributes the profile actually has
133
+ attrs = [attr for attr in dir(profile) if not attr.startswith('__')]
134
+ print(f"🔍 Profile attributes: {attrs}")
135
+ return f"❌ No token found. Profile type: {type(profile).__name__}. Available attributes: {', '.join(attrs[:10])}", pd.DataFrame()
136
+
137
+ # Instantiate the agent
138
  try:
139
  agent = BasicAgent()
140
  except Exception as e:
141
  return f"❌ Error instantiating agent: {e}", pd.DataFrame()
142
 
143
+ # Collect answers
144
  results = []
145
+ for qa in QUESTIONS_AND_ANSWERS:
146
+ q = qa["question"]
147
+ expected = qa["expected"]
148
  try:
149
  answer = agent(q)
150
  except Exception as e:
151
  answer = f"[Error: {e}]"
152
  results.append({"question": q, "answer": answer, "expected": expected})
153
 
154
+ # Build DataFrame
155
  df = pd.DataFrame(results)
156
+
157
+ # Submit
158
  answers_list = [r["answer"] for r in results]
159
  submission_result = submit_answers(answers_list, token)
160
 
161
+ if submission_result.get("success"):
162
+ msg = submission_result.get("message", "✅ Submission successful")
163
+ return f"✅ {msg}", df
164
+ else:
165
  msg = submission_result.get("message", "Unknown error")
166
  return f"❌ Submission failed: {msg}", df
167
 
168
+ # --- Gradio Interface ---
169
+ with gr.Blocks() as demo:
170
+ gr.Markdown("# Basic Agent Evaluation Runner")
171
+ gr.Markdown(
172
+ """
173
+ **Instructions:**
174
+ 1. Log in to your Hugging Face account.
175
+ 2. Click 'Run Evaluation & Submit All Answers'.
176
+ ---
177
+ The agent now runs *locally* inside the Space instead of using the API.
178
+ """
179
+ )
180
+ login_button = gr.LoginButton()
181
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
182
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
183
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
184
+
185
+ run_button.click(fn=run_and_submit_all, inputs=[login_button], outputs=[status_output, results_table])
186
 
187
+ # --- Launch ---
188
  if __name__ == "__main__":
189
+ print("\n" + "-" * 30 + " App Starting " + "-" * 30)
190
+
191
  space_host = os.getenv("SPACE_HOST")
192
  space_id = os.getenv("SPACE_ID")
193
+
194
  if space_host:
195
  print(f"✅ SPACE_HOST found: {space_host}")
196
  print(f" Runtime URL should be: https://{space_host}.hf.space")
197
  else:
198
+ print("ℹ️ SPACE_HOST not found (running locally?).")
199
+
200
  if space_id:
201
  print(f"✅ SPACE_ID found: {space_id}")
202
  print(f" Repo URL: https://huggingface.co/spaces/{space_id}")
203
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id}/tree/main")
204
  else:
205
+ print("ℹ️ SPACE_ID not found (running locally?).")
 
 
206
 
207
+ print("-" * (60 + len(" App Starting ")) + "\n")
208
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
209
+ demo.launch(debug=True, share=False)