keynes42 commited on
Commit
60ec0e8
·
verified ·
1 Parent(s): 921d17a

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +351 -0
  2. requirements.txt +14 -0
app.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys
2
+ from enum import Enum
3
+ import gradio as gr
4
+ import requests
5
+ import inspect
6
+ import subprocess
7
+ import dateparser
8
+ from bs4 import BeautifulSoup
9
+ import regex
10
+ import pandas as pd
11
+ import torch
12
+ from functools import lru_cache
13
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
14
+ from smolagents import CodeAgent, WebSearchTool, WikipediaSearchTool, VisitWebpageTool, PythonInterpreterTool
15
+ import smolagents.tools as _tools
16
+ from smolagents.models import ChatMessage
17
+ # from huggingface_hub import InferenceClient, hf_hub_download
18
+
19
+ subprocess.run(["playwright", "install"], check=True)
20
+
21
+ print(dir(_tools))
22
+
23
+ # (Keep Constants as is)
24
+ # --- Constants ---
25
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
26
+
27
+ # class LocalLLM:
28
+ # def __init__(self, pipe):
29
+ # self.pipe = pipe
30
+
31
+ # def generate(self, prompt, **kwargs):
32
+ # unsupported_keys = ["stop_sequences"] # Remove keys not accepted by HF pipelines
33
+ # cleaned_kwargs = {k: v for k, v in kwargs.items() if k not in unsupported_keys}
34
+ # # print(f"🧪 kwargs cleaned: {cleaned_kwargs.keys()}")
35
+ # try:
36
+ # outputs = self.pipe(prompt, **cleaned_kwargs)
37
+ # # print(f"🧪 Raw output from pipe: {outputs}")
38
+ # if isinstance(outputs, list) and isinstance(outputs[0], dict):
39
+ # out = outputs[0]["generated_text"]
40
+ # elif isinstance(outputs, list):
41
+ # out = outputs[0] # fallback if it's just a list of strings
42
+ # else:
43
+ # out = str(outputs)
44
+ # print("🧪 Final object to return:", type(out), out[:100])
45
+ # return {'role': 'assistant', 'content': [{'type':'text', 'text': out}]}
46
+ # except Exception as e:
47
+ # print(f"❌ Error in LocalLLM.generate(): {e}")
48
+ # raise
49
+
50
+ def check_token_access():
51
+ token = os.environ.get("HF_TOKEN", "")
52
+ if not token:
53
+ print("❌ No token found")
54
+ return
55
+ headers = {"Authorization": f"Bearer {token}"}
56
+ url = "https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct/resolve/main/config.json"
57
+ try:
58
+ r = requests.get(url, headers=headers, timeout=10)
59
+ print(f"🔍 Token test response: {r.status_code}")
60
+ if r.status_code == 200:
61
+ print("✅ Token access confirmed for gated model.")
62
+ elif r.status_code == 403:
63
+ print("❌ 403 Forbidden: Token does not have access.")
64
+ else:
65
+ print("⚠️ Unexpected status:", r.status_code)
66
+ except Exception as e:
67
+ print("❌ Token check failed:", e)
68
+
69
+ class CachedWebSearchTool(WebSearchTool):
70
+ @lru_cache(maxsize=128)
71
+ def run(self, query: str):
72
+ # identical queries return instantly
73
+ return super().run(query)
74
+
75
+ class CachedWikiTool(WikipediaSearchTool):
76
+ @lru_cache(maxsize=128)
77
+ def run(self, page: str):
78
+ return super().run(page)
79
+
80
+ # --- Basic Agent Definition ---
81
+ # ----- THIS IS WHERE YOU CAN BUILD WHAT YOU WANT ------
82
+ class BasicAgent:
83
+ def __init__(self, model_id="meta-llama/Llama-3.1-8B-Instruct", hf_token=""):
84
+ print("BasicAgent initialized.")
85
+ print("ENV-HF_TOKEN-LEN", len(hf_token), file=sys.stderr)
86
+ check_token_access()
87
+
88
+ # Local test
89
+ # client = InferenceClient(
90
+ # model="meta-llama/Llama-3.1-8B-Instruct",
91
+ # token=os.environ["HF_TOKEN"]
92
+ # )
93
+ # print(client.text_generation("Hello, my name is", max_new_tokens=20))
94
+
95
+ # Initialize the model
96
+ # model = HfApiModel(model_id="meta-llama/Llama-3.1-8B-Instruct",
97
+ # # format="text-generation",
98
+ # token=os.environ["HF_TOKEN"],
99
+ # max_tokens=2048,
100
+ # temperature=0.0
101
+ # )
102
+
103
+ # Initialize the tools other than the base tools
104
+ # See list of base tools in https://github.com/huggingface/smolagents/blob/main/src/smolagents/default_tools.py
105
+
106
+ # Download the model weights and build the pipeline
107
+ tok = AutoTokenizer.from_pretrained(model_id, token=hf_token)
108
+ mod = AutoModelForCausalLM.from_pretrained(
109
+ model_id,
110
+ torch_dtype=torch.float16,
111
+ device_map="auto", # auto-distributes to GPU
112
+ token=hf_token
113
+ )
114
+ self.pipe = pipeline(
115
+ "text-generation",
116
+ model=mod,
117
+ tokenizer=tok,
118
+ max_new_tokens=512,
119
+ return_full_text=False, # <— only get the completion, not the prompt + completion
120
+ # temperature=1.0,
121
+ )
122
+ # Introduce tools
123
+ wiki_tool = CachedWikiTool()
124
+ search_tool = CachedWebSearchTool()
125
+ python_tool = PythonInterpreterTool()
126
+ html_parse_tool = VisitWebpageTool()
127
+ # Initialize the agent
128
+ self.agent = CodeAgent(model=self,
129
+ tools=[wiki_tool, search_tool, python_tool, html_parse_tool],
130
+ add_base_tools=True,
131
+ additional_authorized_imports=["dateparser", "bs4", "regex"])
132
+
133
+ def _serialize_messages(self, messages):
134
+ prompt = []
135
+ for m in messages:
136
+ r = m["role"]
137
+ role = r.value if isinstance(r, Enum) and hasattr(r, "value") else r # "system" / "user" / "assistant"
138
+ text = "".join([c['text'] for c in m['content']])
139
+ prompt.append(f"{role}: {text}")
140
+ return "\n".join(prompt)
141
+
142
+ def generate(self, question: str, stop_sequences=None, **kwargs) -> str:
143
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
144
+ # 1. Build the HF kwargs
145
+ allowed = {"max_new_tokens", "temperature", "top_k", "top_p"}
146
+ gen_kwargs = {k: v for k, v in kwargs.items() if k in allowed}
147
+
148
+ # 2. Serialize the message and get the response
149
+ prompt_str = (
150
+ self._serialize_messages(question)
151
+ if isinstance(question, list)
152
+ else question
153
+ )
154
+ outputs = self.pipe(prompt_str, **gen_kwargs)
155
+ response = outputs[0]["generated_text"]
156
+ # response = self.agent.run(question)
157
+
158
+ # 3. Optionally map SmolAgents’ stop_sequences → HF pipeline’s 'stop'
159
+ if stop_sequences:
160
+ # find the earliest occurrence of any stop token
161
+ cuts = [response.find(s) for s in stop_sequences if response.find(s) != -1]
162
+ if cuts:
163
+ response = response[: min(cuts)]
164
+
165
+ print(f"Agent returning its generated answer: {response}")
166
+
167
+ # wrap back into a chat message dict
168
+ return ChatMessage(role="assistant", content=response)
169
+ # return {
170
+ # "role": 'assistant',
171
+ # "content": [{"type": "text", "text": response}],
172
+ # }
173
+
174
+ __call__ = generate
175
+
176
+ def run_and_submit_all( profile: gr.OAuthProfile | None):
177
+ """
178
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
179
+ and displays the results.
180
+ """
181
+ # --- Determine HF Space Runtime URL and Repo URL ---
182
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
183
+ hf_token = os.getenv("HF_TOKEN")
184
+
185
+ if profile:
186
+ username= f"{profile.username}"
187
+ print(f"User logged in: {username}")
188
+ else:
189
+ print("User not logged in.")
190
+ return "Please Login to Hugging Face with the button.", None
191
+
192
+ api_url = DEFAULT_API_URL
193
+ questions_url = f"{api_url}/questions"
194
+ submit_url = f"{api_url}/submit"
195
+
196
+ # 1. Instantiate Agent ( modify this part to create your agent)
197
+ try:
198
+ agent = BasicAgent(hf_token=hf_token).agent
199
+ except Exception as e:
200
+ print(f"Error instantiating agent: {e}")
201
+ return f"Error initializing agent: {e}", None
202
+ # 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)
203
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
204
+ print(agent_code)
205
+
206
+ # 2. Fetch Questions
207
+ print(f"Fetching questions from: {questions_url}")
208
+ try:
209
+ response = requests.get(questions_url, timeout=15)
210
+ response.raise_for_status()
211
+ questions_data = response.json()
212
+ if not questions_data:
213
+ print("Fetched questions list is empty.")
214
+ return "Fetched questions list is empty or invalid format.", None
215
+ print(f"Fetched {len(questions_data)} questions.")
216
+ except requests.exceptions.RequestException as e:
217
+ print(f"Error fetching questions: {e}")
218
+ return f"Error fetching questions: {e}", None
219
+ except requests.exceptions.JSONDecodeError as e:
220
+ print(f"Error decoding JSON response from questions endpoint: {e}")
221
+ print(f"Response text: {response.text[:500]}")
222
+ return f"Error decoding server response for questions: {e}", None
223
+ except Exception as e:
224
+ print(f"An unexpected error occurred fetching questions: {e}")
225
+ return f"An unexpected error occurred fetching questions: {e}", None
226
+
227
+ questions_data = questions_data[:5]
228
+
229
+ # 3. Run your Agent
230
+ results_log = []
231
+ answers_payload = []
232
+ print(f"Running agent on {len(questions_data)} questions...")
233
+ for item in questions_data:
234
+ task_id = item.get("task_id")
235
+ question_text = item.get("question")
236
+ if not task_id or question_text is None:
237
+ print(f"Skipping item with missing task_id or question: {item}")
238
+ continue
239
+ try:
240
+ submitted_answer = agent(question_text)
241
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
242
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
243
+ except Exception as e:
244
+ print(f"Error running agent on task {task_id}: {e}")
245
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
246
+
247
+ if not answers_payload:
248
+ print("Agent did not produce any answers to submit.")
249
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
250
+
251
+ # 4. Prepare Submission
252
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
253
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
254
+ print(status_update)
255
+
256
+ # 5. Submit
257
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
258
+ try:
259
+ response = requests.post(submit_url, json=submission_data, timeout=60)
260
+ response.raise_for_status()
261
+ result_data = response.json()
262
+ final_status = (
263
+ f"Submission Successful!\n"
264
+ f"User: {result_data.get('username')}\n"
265
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
266
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
267
+ f"Message: {result_data.get('message', 'No message received.')}"
268
+ )
269
+ print("Submission successful.")
270
+ results_df = pd.DataFrame(results_log)
271
+ return final_status, results_df
272
+ except requests.exceptions.HTTPError as e:
273
+ error_detail = f"Server responded with status {e.response.status_code}."
274
+ try:
275
+ error_json = e.response.json()
276
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
277
+ except requests.exceptions.JSONDecodeError:
278
+ error_detail += f" Response: {e.response.text[:500]}"
279
+ status_message = f"Submission Failed: {error_detail}"
280
+ print(status_message)
281
+ results_df = pd.DataFrame(results_log)
282
+ return status_message, results_df
283
+ except requests.exceptions.Timeout:
284
+ status_message = "Submission Failed: The request timed out."
285
+ print(status_message)
286
+ results_df = pd.DataFrame(results_log)
287
+ return status_message, results_df
288
+ except requests.exceptions.RequestException as e:
289
+ status_message = f"Submission Failed: Network error - {e}"
290
+ print(status_message)
291
+ results_df = pd.DataFrame(results_log)
292
+ return status_message, results_df
293
+ except Exception as e:
294
+ status_message = f"An unexpected error occurred during submission: {e}"
295
+ print(status_message)
296
+ results_df = pd.DataFrame(results_log)
297
+ return status_message, results_df
298
+
299
+
300
+ # --- Build Gradio Interface using Blocks ---
301
+ with gr.Blocks() as demo:
302
+ gr.Markdown("# Basic Agent Evaluation Runner")
303
+ gr.Markdown(
304
+ """
305
+ **Instructions:**
306
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
307
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
308
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
309
+ ---
310
+ **Disclaimers:**
311
+ 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).
312
+ 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.
313
+ """
314
+ )
315
+
316
+ gr.LoginButton()
317
+
318
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
319
+
320
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
321
+ # Removed max_rows=10 from DataFrame constructor
322
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
323
+
324
+ run_button.click(
325
+ fn=run_and_submit_all,
326
+ outputs=[status_output, results_table]
327
+ )
328
+
329
+ if __name__ == "__main__":
330
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
331
+ # Check for SPACE_HOST and SPACE_ID at startup for information
332
+ space_host_startup = os.getenv("SPACE_HOST")
333
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
334
+
335
+ if space_host_startup:
336
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
337
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
338
+ else:
339
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
340
+
341
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
342
+ print(f"✅ SPACE_ID found: {space_id_startup}")
343
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
344
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
345
+ else:
346
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
347
+
348
+ print("-"*(60 + len(" App Starting ")) + "\n")
349
+
350
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
351
+ demo.launch(debug=True, share=False)
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ requests
3
+ smolagents[toolkit] @ git+https://github.com/huggingface/smolagents.git@v1.16.1
4
+ torch
5
+ transformers>=4.38.0
6
+ duckduckgo-search
7
+ wikipedia-api
8
+ dateparser
9
+ playwright
10
+ accelerate>=0.24.0
11
+ peft
12
+ bitsandbytes
13
+ beautifulsoup4
14
+ regex