volker commited on
Commit
a9491cd
·
1 Parent(s): 81917a3

Update agent.

Browse files
Files changed (6) hide show
  1. .gitignore +16 -0
  2. ac_tools.py +38 -0
  3. app.py +130 -40
  4. basic_agent.py +115 -0
  5. data/.gitkeep +0 -0
  6. requirements.txt +8 -2
.gitignore ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Jupyter Notebook
7
+ .ipynb_checkpoints
8
+
9
+ # Environments
10
+ .env
11
+ .venv
12
+ env/
13
+ venv/
14
+
15
+ !data/.gitkeep
16
+ data/*
ac_tools.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from smolagents import Tool
2
+ import time
3
+ import random
4
+ from duckduckgo_search import DDGS
5
+
6
+
7
+ class DuckDuckGoSearchToolWH(Tool):
8
+ name = "web_search"
9
+ description = """Performs a DuckDuckGo web search based on your query (think a Google search) then returns the top search results."""
10
+ inputs = {"query": {"type": "string", "description": "The search query to perform."}}
11
+ output_type = "string"
12
+
13
+ def __init__(self, max_results=10, **kwargs):
14
+ super().__init__()
15
+ self.max_results = max_results
16
+ self.USER_AGENTS = [
17
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
18
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0",
19
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
20
+ "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
21
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
22
+ ]
23
+ self.kwargs = kwargs
24
+ self.kwargs.pop('headers', None)
25
+
26
+
27
+ def forward(self, query: str) -> str:
28
+ headers = {"User-Agent": random.choice(self.USER_AGENTS)}
29
+ self.ddgs = DDGS(headers=headers, **self.kwargs)
30
+ time.sleep(2.0)
31
+ results = self.ddgs.text(query, max_results=self.max_results)
32
+ if not results:
33
+ raise Exception("No results found! Try a less restrictive/shorter query.")
34
+ postprocessed_results = [
35
+ f"[{result['title']}]({result['href']})\n{result['body']}"
36
+ for result in results
37
+ ]
38
+ return "## Search Results\n\n" + "\n\n".join(postprocessed_results)
app.py CHANGED
@@ -1,29 +1,48 @@
1
  import os
2
  import gradio as gr
3
  import requests
4
- import inspect
5
  import pandas as pd
6
 
 
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
11
- # --- Basic Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
- class BasicAgent:
14
- def __init__(self):
15
- print("BasicAgent initialized.")
16
- def __call__(self, question: str) -> str:
17
- print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
21
-
22
- def run_and_submit_all( profile: gr.OAuthProfile | None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  """
24
  Fetches all questions, runs the BasicAgent on them, submits all answers,
25
  and displays the results.
26
  """
 
27
  # --- Determine HF Space Runtime URL and Repo URL ---
28
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
29
 
@@ -35,12 +54,11 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
35
  return "Please Login to Hugging Face with the button.", None
36
 
37
  api_url = DEFAULT_API_URL
38
- questions_url = f"{api_url}/questions"
39
  submit_url = f"{api_url}/submit"
40
 
41
  # 1. Instantiate Agent ( modify this part to create your agent)
42
  try:
43
- agent = BasicAgent()
44
  except Exception as e:
45
  print(f"Error instantiating agent: {e}")
46
  return f"Error initializing agent: {e}", None
@@ -48,39 +66,23 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
48
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
49
  print(agent_code)
50
 
51
- # 2. Fetch Questions
52
- print(f"Fetching questions from: {questions_url}")
53
- try:
54
- response = requests.get(questions_url, timeout=15)
55
- response.raise_for_status()
56
- questions_data = response.json()
57
- if not questions_data:
58
- print("Fetched questions list is empty.")
59
- return "Fetched questions list is empty or invalid format.", None
60
- print(f"Fetched {len(questions_data)} questions.")
61
- except requests.exceptions.RequestException as e:
62
- print(f"Error fetching questions: {e}")
63
- return f"Error fetching questions: {e}", None
64
- except requests.exceptions.JSONDecodeError as e:
65
- print(f"Error decoding JSON response from questions endpoint: {e}")
66
- print(f"Response text: {response.text[:500]}")
67
- return f"Error decoding server response for questions: {e}", None
68
- except Exception as e:
69
- print(f"An unexpected error occurred fetching questions: {e}")
70
- return f"An unexpected error occurred fetching questions: {e}", None
71
 
72
  # 3. Run your Agent
73
  results_log = []
74
  answers_payload = []
75
  print(f"Running agent on {len(questions_data)} questions...")
76
- for item in questions_data:
 
77
  task_id = item.get("task_id")
78
  question_text = item.get("question")
79
  if not task_id or question_text is None:
80
  print(f"Skipping item with missing task_id or question: {item}")
81
  continue
82
  try:
83
- submitted_answer = agent(question_text)
84
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
86
  except Exception as e:
@@ -91,6 +93,11 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
91
  print("Agent did not produce any answers to submit.")
92
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
93
 
 
 
 
 
 
94
  # 4. Prepare Submission
95
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
96
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
@@ -140,8 +147,35 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
140
  return status_message, results_df
141
 
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  # --- Build Gradio Interface using Blocks ---
144
- with gr.Blocks() as demo:
145
  gr.Markdown("# Basic Agent Evaluation Runner")
146
  gr.Markdown(
147
  """
@@ -160,7 +194,59 @@ with gr.Blocks() as demo:
160
 
161
  gr.LoginButton()
162
 
163
- run_button = gr.Button("Run Evaluation & Submit All Answers")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
 
165
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
166
  # Removed max_rows=10 from DataFrame constructor
@@ -168,9 +254,12 @@ with gr.Blocks() as demo:
168
 
169
  run_button.click(
170
  fn=run_and_submit_all,
 
171
  outputs=[status_output, results_table]
172
  )
173
 
 
 
174
  if __name__ == "__main__":
175
  print("\n" + "-"*30 + " App Starting " + "-"*30)
176
  # Check for SPACE_HOST and SPACE_ID at startup for information
@@ -193,4 +282,5 @@ if __name__ == "__main__":
193
  print("-"*(60 + len(" App Starting ")) + "\n")
194
 
195
  print("Launching Gradio Interface for Basic Agent Evaluation...")
196
- demo.launch(debug=True, share=False)
 
 
1
  import os
2
  import gradio as gr
3
  import requests
 
4
  import pandas as pd
5
 
6
+ from basic_agent import init_agent
7
+
8
  # (Keep Constants as is)
9
  # --- Constants ---
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
 
12
+
13
+ def fetch_questions():
14
+ api_url = DEFAULT_API_URL
15
+ questions_url = f"{api_url}/questions"
16
+
17
+ print(f"Fetching questions from: {questions_url}")
18
+ try:
19
+ response = requests.get(questions_url, timeout=15)
20
+ response.raise_for_status()
21
+ questions_data = response.json()
22
+
23
+ if not questions_data:
24
+ print("Fetched questions list is empty.")
25
+ return "Fetched questions list is empty or invalid format.", None
26
+ print(f"Fetched {len(questions_data)} questions.")
27
+ except requests.exceptions.RequestException as e:
28
+ print(f"Error fetching questions: {e}")
29
+ return f"Error fetching questions: {e}", None
30
+ except requests.exceptions.JSONDecodeError as e:
31
+ print(f"Error decoding JSON response from questions endpoint: {e}")
32
+ print(f"Response text: {response.text[:500]}")
33
+ return f"Error decoding server response for questions: {e}", None
34
+ except Exception as e:
35
+ print(f"An unexpected error occurred fetching questions: {e}")
36
+ return f"An unexpected error occurred fetching questions: {e}", None
37
+ return None, questions_data
38
+
39
+
40
+ def run_and_submit_all(submit: bool, max_questions: int | None, profile: gr.OAuthProfile | None):
41
  """
42
  Fetches all questions, runs the BasicAgent on them, submits all answers,
43
  and displays the results.
44
  """
45
+ message, questions_data = fetch_questions()
46
  # --- Determine HF Space Runtime URL and Repo URL ---
47
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
48
 
 
54
  return "Please Login to Hugging Face with the button.", None
55
 
56
  api_url = DEFAULT_API_URL
 
57
  submit_url = f"{api_url}/submit"
58
 
59
  # 1. Instantiate Agent ( modify this part to create your agent)
60
  try:
61
+ agent = init_agent()
62
  except Exception as e:
63
  print(f"Error instantiating agent: {e}")
64
  return f"Error initializing agent: {e}", None
 
66
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
67
  print(agent_code)
68
 
69
+
70
+ if max_questions > 0:
71
+ questions_data = questions_data[:max_questions] # Limit number of questions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  # 3. Run your Agent
74
  results_log = []
75
  answers_payload = []
76
  print(f"Running agent on {len(questions_data)} questions...")
77
+ for iid, item in enumerate(questions_data):
78
+ print(f"Running agent on question {iid}")
79
  task_id = item.get("task_id")
80
  question_text = item.get("question")
81
  if not task_id or question_text is None:
82
  print(f"Skipping item with missing task_id or question: {item}")
83
  continue
84
  try:
85
+ submitted_answer = agent(item)
86
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
87
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
88
  except Exception as e:
 
93
  print("Agent did not produce any answers to submit.")
94
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
95
 
96
+ if not submit:
97
+ status_update = f"Processed {len(answers_payload)} questions. Submission skipped (submit checkbox unchecked)."
98
+ print(status_update)
99
+ return status_update, pd.DataFrame(results_log)
100
+
101
  # 4. Prepare Submission
102
  submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
103
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
 
147
  return status_message, results_df
148
 
149
 
150
+ def fetch_and_run_single(selected_id, questions_data, profile: gr.OAuthProfile | None):
151
+ if profile:
152
+ print(f"User logged in: {profile.username}")
153
+ else:
154
+ print("User not logged in.")
155
+ return "Please Login to Hugging Face with the button.", None, None
156
+
157
+ try:
158
+ index = int(selected_id)
159
+ question_item = questions_data[index]
160
+ task_id = question_item.get("task_id")
161
+ question_text = question_item.get("question")
162
+ if not task_id or question_text is None:
163
+ return "Invalid question format received.", None, None
164
+ except Exception as e:
165
+ return f"Error selecting question: {e}", None, None
166
+
167
+ agent = init_agent()
168
+ generated_answer = agent(question_item)
169
+ result_df = pd.DataFrame([{
170
+ "Task ID": task_id,
171
+ "Question": question_text,
172
+ "Generated Answer": generated_answer
173
+ }])
174
+ return "Fetched and ran agent on selected question.", result_df, question_item
175
+
176
+
177
  # --- Build Gradio Interface using Blocks ---
178
+ with (gr.Blocks() as demo):
179
  gr.Markdown("# Basic Agent Evaluation Runner")
180
  gr.Markdown(
181
  """
 
194
 
195
  gr.LoginButton()
196
 
197
+ questions_data_state = gr.State()
198
+ question_ids_state = gr.State()
199
+
200
+
201
+ def show_questions():
202
+ message, questions_data = fetch_questions()
203
+ if not questions_data:
204
+ return pd.DataFrame([{'error': message}]), gr.update(choices=[]), questions_data
205
+ questions_data.sort(key=lambda item: item['task_id'])
206
+ for i in range(len(questions_data)):
207
+ questions_data[i]['ID'] = i
208
+
209
+ df = pd.DataFrame(questions_data)
210
+ return df, gr.update(choices=list(range(len(df)))), questions_data
211
+
212
+ q_button = gr.Button("Fetch all questions")
213
+ q_table = gr.DataFrame(label="All Questions", wrap=True)
214
+
215
+
216
+ question_id_dropdown = gr.Dropdown(label="Select Question ID to Run", choices=[])
217
+ questions_data_state = gr.State()
218
+
219
+ q_button.click(
220
+ fn=show_questions,
221
+ inputs=[],
222
+ outputs=[q_table, question_id_dropdown, questions_data_state]
223
+ )
224
+
225
+
226
+ # NEW BUTTON for single question run
227
+ gr.Markdown("## Single test run")
228
+ gr.Markdown("---")
229
+ single_run_button = gr.Button("Run Single Question")
230
+ single_question_json = gr.JSON(label="Raw Question JSON")
231
+ single_status = gr.Textbox(label="Single Question Status", lines=2, interactive=False)
232
+ single_result_table = gr.DataFrame(label="Single Question and Answer", wrap=True)
233
+
234
+ single_run_button.click(
235
+ fn=fetch_and_run_single,
236
+ inputs=[question_id_dropdown, questions_data_state],
237
+ outputs=[single_status, single_result_table, single_question_json]
238
+ )
239
+
240
+ # All questions for submission run
241
+ gr.Markdown("## Run and Submit")
242
+ submit_checkbox = gr.Checkbox(label="Submit Results?", value=False)
243
+ max_questions_input = gr.Dropdown(
244
+ label="Maximum Number of Questions to Process",
245
+ choices=[0, 1, 2, 5, 10, 20], # 0 to 20
246
+ value=0
247
+ )
248
+
249
+ run_button = gr.Button("Run Evaluation & Maybe Submit Answers")
250
 
251
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
252
  # Removed max_rows=10 from DataFrame constructor
 
254
 
255
  run_button.click(
256
  fn=run_and_submit_all,
257
+ inputs=[submit_checkbox, max_questions_input],
258
  outputs=[status_output, results_table]
259
  )
260
 
261
+
262
+
263
  if __name__ == "__main__":
264
  print("\n" + "-"*30 + " App Starting " + "-"*30)
265
  # Check for SPACE_HOST and SPACE_ID at startup for information
 
282
  print("-"*(60 + len(" App Starting ")) + "\n")
283
 
284
  print("Launching Gradio Interface for Basic Agent Evaluation...")
285
+ demo.launch(debug=True, share=False)
286
+
basic_agent.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from smolagents import Tool, CodeAgent, HfApiModel, OpenAIServerModel
3
+ import dotenv
4
+ from ac_tools import DuckDuckGoSearchToolWH
5
+ import requests
6
+ import os
7
+ from PIL import Image
8
+ from transformers import pipeline
9
+
10
+
11
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
12
+
13
+
14
+ def init_agent():
15
+ dotenv.load_dotenv()
16
+ model = OpenAIServerModel(model_id="gpt-4o")
17
+ agent = BasicSmolAgent(model=model)
18
+ return agent
19
+
20
+
21
+ def download_file(task_id: str, filename: str) -> str:
22
+ """
23
+ Downloads a file associated with the given task_id and saves it to the specified filename.
24
+
25
+ Args:
26
+ task_id (str): The task identifier used to fetch the file.
27
+ filename (str): The desired filename to save the file as.
28
+
29
+ Returns:
30
+ str: The absolute path to the saved file.
31
+ """
32
+ api_url = DEFAULT_API_URL
33
+ file_url = f"{api_url}/files/{task_id}"
34
+ folder = 'data'
35
+ print(f"📡 Fetching file from: {file_url}")
36
+ try:
37
+ response = requests.get(file_url, timeout=15)
38
+ response.raise_for_status()
39
+
40
+ # Save binary content to the given filename
41
+ fpath = os.path.join(folder, filename)
42
+ with open(fpath, "wb") as f:
43
+ f.write(response.content)
44
+
45
+ abs_path = os.path.abspath(fpath)
46
+ print(f"✅ File saved as: {abs_path}")
47
+ return abs_path
48
+
49
+ except requests.exceptions.RequestException as e:
50
+ error_msg = f"❌ Failed to download file for task {task_id}: {e}"
51
+ print(error_msg)
52
+ raise RuntimeError(error_msg)
53
+
54
+
55
+ class BasicAgent:
56
+ def __init__(self):
57
+ print("BasicAgent initialized.")
58
+ def __call__(self, question_item: dict) -> str:
59
+ task_id = question_item.get("task_id")
60
+ question_text = question_item.get("question")
61
+ file_name = question_item.get("file_name")
62
+ print(f"Agent received question (first 50 chars): {question_text[:50]}...")
63
+ fixed_answer = "This is a default answer."
64
+ print(f"Agent returning fixed answer: {fixed_answer}")
65
+ return fixed_answer
66
+
67
+
68
+ class BasicSmolAgent:
69
+ def __init__(self, model=None):
70
+ print("BasicSmolAgent initialized.")
71
+ if not model:
72
+ model = HfApiModel()
73
+ search_tool = DuckDuckGoSearchToolWH()
74
+ self.agent = CodeAgent(tools=[search_tool], model=model)
75
+ self.prompt = ("You are a general AI assistant. I will ask you a question."
76
+ " Return only your FINAL ANSWER."
77
+ " YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings."
78
+ " If you are asked for a number, don't use comma to write your number neither use units"
79
+ " such as $ or percent sign unless specified otherwise. If you are asked for a string,"
80
+ " don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise."
81
+ " If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string."
82
+ " make sure you don’t include the text “FINAL ANSWER” in your submission, just reply with the answer and nothing else."
83
+ " The question is the following: {}")
84
+ # Load the Whisper pipeline
85
+ self.mp3_pipe = pipeline("automatic-speech-recognition", model="openai/whisper-base")
86
+
87
+ def __call__(self, question_item: dict) -> str:
88
+ task_id = question_item.get("task_id")
89
+ question_text = question_item.get("question")
90
+ file_name = question_item.get("file_name")
91
+
92
+ print(f"Agent received question (first 50 chars): {question_text[:50]}...")
93
+ prompted_question = self.prompt.format(question_text)
94
+
95
+ images = []
96
+ if file_name:
97
+ fpath = download_file(task_id, file_name)
98
+ if fpath.endswith('.png'):
99
+ image = Image.open(fpath).convert("RGB")
100
+ images.append(image)
101
+ if fpath.endswith('xlsx') or fpath.endswith('.py'):
102
+ data = open(fpath, "rb").read().decode("utf-8", errors="ignore")
103
+ prompted_question += f"\nThere is textual data included with the question, it is from a file {fpath} and is: ```{data}```"
104
+ if fpath.endswith('.mp3'):
105
+ try:
106
+ result = self.mp3_pipe(fpath)
107
+ text = result["text"]
108
+ prompted_question += f"\nThere is textual data included with the question, it is from a file {fpath} and is: ```{text}```"
109
+ except Exception as e:
110
+ print("Exception occurred during mp3 transcription: ", e)
111
+
112
+ result = self.agent.run(prompted_question, images=images)
113
+ print(f"Agent returning answer: {result}")
114
+ return result
115
+
data/.gitkeep ADDED
File without changes
requirements.txt CHANGED
@@ -1,2 +1,8 @@
1
- gradio
2
- requests
 
 
 
 
 
 
 
1
+ gradio[oauth]
2
+ requests
3
+ openai
4
+ python-dotenv
5
+ pandas
6
+ duckduckgo_search
7
+ smolagents
8
+ transformers