Denys Kanunnikov commited on
Commit
8e5831a
·
1 Parent(s): 81917a3
Files changed (4) hide show
  1. README.md +36 -1
  2. agent.py +9 -0
  3. api.py +43 -0
  4. app.py +23 -104
README.md CHANGED
@@ -12,4 +12,39 @@ hf_oauth: true
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
16
+
17
+ # Modular Agent Evaluation Template
18
+
19
+ This project provides a modular, production-ready template for evaluating agents using the Hugging Face Unit 4 Evaluation API.
20
+
21
+ ## Architecture
22
+
23
+ - `app.py`: Gradio UI and orchestration
24
+ - `agent.py`: Agent logic (extend `BaseAgent`)
25
+ - `api.py`: API interaction and error handling
26
+ - `requirements.txt`: Dependencies
27
+
28
+ ## Setup
29
+
30
+ ```bash
31
+ pip install -r requirements.txt
32
+ python app.py
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ 1. Log in with your Hugging Face account in the UI.
38
+ 2. Click "Run Evaluation & Submit All Answers" to evaluate and submit.
39
+ 3. Extend `agent.py` to implement your own agent logic.
40
+
41
+ ## Extending the Agent
42
+
43
+ - Edit `agent.py` and subclass `BaseAgent`.
44
+ - Implement the `__call__` method to generate answers for questions.
45
+
46
+ ## Troubleshooting
47
+
48
+ - Ensure all dependencies are installed.
49
+ - Check API availability and network connection.
50
+ - Review error messages in the UI for guidance.
agent.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ class BaseAgent:
2
+ """Base class for all agents. Override __call__ to implement agent logic."""
3
+ def __call__(self, question: str) -> str:
4
+ raise NotImplementedError("Agent must implement __call__ method.")
5
+
6
+ class ExampleAgent(BaseAgent):
7
+ """A simple agent that returns a fixed answer for demonstration purposes."""
8
+ def __call__(self, question: str) -> str:
9
+ return "42"
api.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ class APIClient:
4
+ """Handles all API interactions with error handling."""
5
+ def __init__(self, base_url):
6
+ self.base_url = base_url.rstrip('/')
7
+
8
+ def get_questions(self):
9
+ """Fetch the full list of evaluation questions."""
10
+ try:
11
+ resp = requests.get(f"{self.base_url}/questions", timeout=10)
12
+ resp.raise_for_status()
13
+ return resp.json()
14
+ except Exception as e:
15
+ raise RuntimeError(f"Failed to fetch questions: {e}")
16
+
17
+ def get_random_question(self):
18
+ """Fetch a single random question."""
19
+ try:
20
+ resp = requests.get(f"{self.base_url}/random-question", timeout=10)
21
+ resp.raise_for_status()
22
+ return resp.json()
23
+ except Exception as e:
24
+ raise RuntimeError(f"Failed to fetch random question: {e}")
25
+
26
+ def get_file(self, task_id):
27
+ """Download a specific file associated with a given task ID."""
28
+ try:
29
+ resp = requests.get(f"{self.base_url}/files/{task_id}", timeout=20)
30
+ resp.raise_for_status()
31
+ return resp.content
32
+ except Exception as e:
33
+ raise RuntimeError(f"Failed to fetch file for task {task_id}: {e}")
34
+
35
+ def submit(self, username, agent_code, answers):
36
+ """Submit agent answers and return the result."""
37
+ data = {"username": username, "agent_code": agent_code, "answers": answers}
38
+ try:
39
+ resp = requests.post(f"{self.base_url}/submit", json=data, timeout=60)
40
+ resp.raise_for_status()
41
+ return resp.json()
42
+ except Exception as e:
43
+ raise RuntimeError(f"Failed to submit answers: {e}")
app.py CHANGED
@@ -1,107 +1,57 @@
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
-
30
  if profile:
31
- username= f"{profile.username}"
32
- print(f"User logged in: {username}")
33
  else:
34
- print("User not logged in.")
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
47
- # 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)
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:
87
- print(f"Error running agent on task {task_id}: {e}")
88
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
89
 
90
  if not answers_payload:
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}'..."
97
- print(status_update)
98
-
99
- # 5. Submit
100
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
101
  try:
102
- response = requests.post(submit_url, json=submission_data, timeout=60)
103
- response.raise_for_status()
104
- result_data = response.json()
105
  final_status = (
106
  f"Submission Successful!\n"
107
  f"User: {result_data.get('username')}\n"
@@ -109,40 +59,15 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
109
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
110
  f"Message: {result_data.get('message', 'No message received.')}"
111
  )
112
- print("Submission successful.")
113
  results_df = pd.DataFrame(results_log)
114
  return final_status, results_df
115
- except requests.exceptions.HTTPError as e:
116
- error_detail = f"Server responded with status {e.response.status_code}."
117
- try:
118
- error_json = e.response.json()
119
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
120
- except requests.exceptions.JSONDecodeError:
121
- error_detail += f" Response: {e.response.text[:500]}"
122
- status_message = f"Submission Failed: {error_detail}"
123
- print(status_message)
124
- results_df = pd.DataFrame(results_log)
125
- return status_message, results_df
126
- except requests.exceptions.Timeout:
127
- status_message = "Submission Failed: The request timed out."
128
- print(status_message)
129
- results_df = pd.DataFrame(results_log)
130
- return status_message, results_df
131
- except requests.exceptions.RequestException as e:
132
- status_message = f"Submission Failed: Network error - {e}"
133
- print(status_message)
134
- results_df = pd.DataFrame(results_log)
135
- return status_message, results_df
136
  except Exception as e:
137
- status_message = f"An unexpected error occurred during submission: {e}"
138
- print(status_message)
139
  results_df = pd.DataFrame(results_log)
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
  """
148
  **Instructions:**
@@ -150,22 +75,16 @@ with gr.Blocks() as demo:
150
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
151
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
152
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
153
-
154
  ---
155
  **Disclaimers:**
156
- 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).
157
- 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.
158
  """
159
  )
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
167
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
168
-
169
  run_button.click(
170
  fn=run_and_submit_all,
171
  outputs=[status_output, results_table]
@@ -192,5 +111,5 @@ if __name__ == "__main__":
192
 
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 pandas as pd
4
+ from agent import ExampleAgent
5
+ from api import APIClient
6
 
7
  # (Keep Constants as is)
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
11
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
 
 
 
 
 
 
 
 
 
 
 
12
  """
13
+ Fetches all questions, runs the ExampleAgent on them, submits all answers,
14
+ and displays the results. Uses modular agent and API logic.
15
  """
16
+ space_id = os.getenv("SPACE_ID")
 
 
17
  if profile:
18
+ username = f"{profile.username}"
 
19
  else:
 
20
  return "Please Login to Hugging Face with the button.", None
21
 
22
+ api = APIClient(DEFAULT_API_URL)
23
+ agent = ExampleAgent()
 
 
 
 
 
 
 
 
 
24
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
 
25
 
26
+ # Fetch questions
 
27
  try:
28
+ questions_data = api.get_questions()
 
 
29
  if not questions_data:
30
+ return "Fetched questions list is empty or invalid format.", None
 
 
 
 
 
 
 
 
 
31
  except Exception as e:
32
+ return f"Error fetching questions: {e}", None
 
33
 
34
+ # Run agent
35
  results_log = []
36
  answers_payload = []
 
37
  for item in questions_data:
38
  task_id = item.get("task_id")
39
  question_text = item.get("question")
40
  if not task_id or question_text is None:
 
41
  continue
42
  try:
43
  submitted_answer = agent(question_text)
44
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
45
  results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
46
  except Exception as e:
47
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
 
48
 
49
  if not answers_payload:
 
50
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
51
 
52
+ # Submit answers
 
 
 
 
 
 
53
  try:
54
+ result_data = api.submit(username.strip(), agent_code, answers_payload)
 
 
55
  final_status = (
56
  f"Submission Successful!\n"
57
  f"User: {result_data.get('username')}\n"
 
59
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
60
  f"Message: {result_data.get('message', 'No message received.')}"
61
  )
 
62
  results_df = pd.DataFrame(results_log)
63
  return final_status, results_df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  except Exception as e:
 
 
65
  results_df = pd.DataFrame(results_log)
66
+ return f"Submission Failed: {e}", results_df
67
 
68
+ # --- Gradio UI ---
 
69
  with gr.Blocks() as demo:
70
+ gr.Markdown("# Modular Agent Evaluation Runner")
71
  gr.Markdown(
72
  """
73
  **Instructions:**
 
75
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
76
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
77
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
 
78
  ---
79
  **Disclaimers:**
80
+ 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).
81
+ This space provides a modular setup for robust, maintainable solutions.
82
  """
83
  )
 
84
  gr.LoginButton()
 
85
  run_button = gr.Button("Run Evaluation & Submit All Answers")
 
86
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
87
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
 
88
  run_button.click(
89
  fn=run_and_submit_all,
90
  outputs=[status_output, results_table]
 
111
 
112
  print("-"*(60 + len(" App Starting ")) + "\n")
113
 
114
+ print("Launching Gradio Interface for Modular Agent Evaluation...")
115
  demo.launch(debug=True, share=False)