raaec commited on
Commit
cbb455c
·
verified ·
1 Parent(s): 81917a3

Create agent.py

Browse files
Files changed (1) hide show
  1. agent.py +355 -0
agent.py ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from typing import Tuple, List, Dict, Any, Optional
4
+
5
+ import gradio as gr
6
+ import requests
7
+ import pandas as pd
8
+ from langchain_core.messages import HumanMessage
9
+
10
+ from agent import build_graph
11
+
12
+ # Configure logging
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format="%(asctime)s - %(levelname)s - %(message)s",
16
+ datefmt="%Y-%m-%d %H:%M:%S"
17
+ )
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # --- Constants ---
21
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
22
+ REQUEST_TIMEOUT = 60 # seconds
23
+
24
+
25
+ class BasicAgent:
26
+ """
27
+ A LangGraph-based agent that answers questions using a graph-based workflow.
28
+
29
+ This agent takes natural language questions, processes them through a
30
+ predefined graph workflow, and returns the answer.
31
+
32
+ Attributes:
33
+ graph: The LangGraph workflow that processes the questions
34
+ """
35
+
36
+ def __init__(self):
37
+ """Initialize the agent with a graph-based workflow."""
38
+ logger.info("Initializing BasicAgent")
39
+ self.graph = build_graph()
40
+
41
+ def __call__(self, question: str) -> str:
42
+ """
43
+ Process a question and return an answer.
44
+
45
+ Args:
46
+ question: The natural language question to process
47
+
48
+ Returns:
49
+ The agent's answer to the question
50
+ """
51
+ logger.info(f"Processing question (first 50 chars): {question[:50]}...")
52
+
53
+ # Wrap the question in a HumanMessage from langchain_core
54
+ messages = [HumanMessage(content=question)]
55
+
56
+ # Process through the graph
57
+ messages = self.graph.invoke({"messages": messages})
58
+
59
+ # Extract and clean the answer
60
+ answer = messages['messages'][-1].content
61
+
62
+ # Remove the "FINAL ANSWER:" prefix if present
63
+ return answer[14:] if answer.startswith("FINAL ANSWER:") else answer
64
+
65
+
66
+ def fetch_questions(api_url: str) -> List[Dict[str, Any]]:
67
+ """
68
+ Fetch questions from the evaluation server.
69
+
70
+ Args:
71
+ api_url: Base URL of the evaluation API
72
+
73
+ Returns:
74
+ List of question data dictionaries
75
+
76
+ Raises:
77
+ requests.exceptions.RequestException: If there's an error fetching questions
78
+ """
79
+ questions_url = f"{api_url}/questions"
80
+ logger.info(f"Fetching questions from: {questions_url}")
81
+
82
+ response = requests.get(questions_url, timeout=REQUEST_TIMEOUT)
83
+ response.raise_for_status()
84
+
85
+ questions_data = response.json()
86
+ if not questions_data:
87
+ raise ValueError("Fetched questions list is empty or invalid format")
88
+
89
+ logger.info(f"Successfully fetched {len(questions_data)} questions")
90
+ return questions_data
91
+
92
+
93
+ def run_agent_on_questions(
94
+ agent: BasicAgent,
95
+ questions_data: List[Dict[str, Any]]
96
+ ) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
97
+ """
98
+ Run the agent on a list of questions.
99
+
100
+ Args:
101
+ agent: The agent to run
102
+ questions_data: List of question data dictionaries
103
+
104
+ Returns:
105
+ Tuple of (answers_payload, results_log)
106
+ """
107
+ results_log = []
108
+ answers_payload = []
109
+
110
+ logger.info(f"Running agent on {len(questions_data)} questions...")
111
+
112
+ for item in questions_data:
113
+ task_id = item.get("task_id")
114
+ question_text = item.get("question")
115
+
116
+ if not task_id or question_text is None:
117
+ logger.warning(f"Skipping item with missing task_id or question: {item}")
118
+ continue
119
+
120
+ try:
121
+ submitted_answer = agent(question_text)
122
+
123
+ # Prepare answer for submission
124
+ answers_payload.append({
125
+ "task_id": task_id,
126
+ "submitted_answer": submitted_answer
127
+ })
128
+
129
+ # Log result for display
130
+ results_log.append({
131
+ "Task ID": task_id,
132
+ "Question": question_text,
133
+ "Submitted Answer": submitted_answer
134
+ })
135
+
136
+ except Exception as e:
137
+ logger.error(f"Error running agent on task {task_id}: {e}", exc_info=True)
138
+
139
+ # Log error in results
140
+ results_log.append({
141
+ "Task ID": task_id,
142
+ "Question": question_text,
143
+ "Submitted Answer": f"AGENT ERROR: {e}"
144
+ })
145
+
146
+ return answers_payload, results_log
147
+
148
+
149
+ def submit_answers(
150
+ api_url: str,
151
+ username: str,
152
+ agent_code: str,
153
+ answers_payload: List[Dict[str, Any]]
154
+ ) -> Dict[str, Any]:
155
+ """
156
+ Submit answers to the evaluation server.
157
+
158
+ Args:
159
+ api_url: Base URL of the evaluation API
160
+ username: Hugging Face username
161
+ agent_code: URL to the agent code repository
162
+ answers_payload: List of answer dictionaries
163
+
164
+ Returns:
165
+ Response data from the server
166
+
167
+ Raises:
168
+ requests.exceptions.RequestException: If there's an error during submission
169
+ """
170
+ submit_url = f"{api_url}/submit"
171
+
172
+ # Prepare submission data
173
+ submission_data = {
174
+ "username": username.strip(),
175
+ "agent_code": agent_code,
176
+ "answers": answers_payload
177
+ }
178
+
179
+ logger.info(f"Submitting {len(answers_payload)} answers to: {submit_url}")
180
+
181
+ # Submit answers
182
+ response = requests.post(submit_url, json=submission_data, timeout=REQUEST_TIMEOUT)
183
+ response.raise_for_status()
184
+
185
+ result_data = response.json()
186
+ logger.info("Submission successful")
187
+
188
+ return result_data
189
+
190
+
191
+ def run_and_submit_all(profile: Optional[gr.OAuthProfile] = None) -> Tuple[str, pd.DataFrame]:
192
+ """
193
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
194
+ and displays the results.
195
+
196
+ Args:
197
+ profile: Gradio OAuth profile containing user information
198
+
199
+ Returns:
200
+ Tuple of (status_message, results_dataframe)
201
+ """
202
+ # Check if user is logged in
203
+ if not profile:
204
+ logger.warning("User not logged in")
205
+ return "Please Login to Hugging Face with the button.", None
206
+
207
+ username = profile.username
208
+ logger.info(f"User logged in: {username}")
209
+
210
+ # Get the space ID for linking to code
211
+ space_id = os.getenv("SPACE_ID")
212
+ api_url = DEFAULT_API_URL
213
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
214
+
215
+ try:
216
+ # 1. Instantiate Agent
217
+ agent = BasicAgent()
218
+
219
+ # 2. Fetch Questions
220
+ questions_data = fetch_questions(api_url)
221
+
222
+ # 3. Run Agent on Questions
223
+ answers_payload, results_log = run_agent_on_questions(agent, questions_data)
224
+
225
+ if not answers_payload:
226
+ logger.warning("Agent did not produce any answers to submit")
227
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
228
+
229
+ # 4. Submit Answers
230
+ result_data = submit_answers(api_url, username, agent_code, answers_payload)
231
+
232
+ # 5. Format and Return Results
233
+ final_status = (
234
+ f"Submission Successful!\n"
235
+ f"User: {result_data.get('username')}\n"
236
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
237
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
238
+ f"Message: {result_data.get('message', 'No message received.')}"
239
+ )
240
+
241
+ results_df = pd.DataFrame(results_log)
242
+ return final_status, results_df
243
+
244
+ except requests.exceptions.HTTPError as e:
245
+ # Handle HTTP errors with detailed error information
246
+ error_detail = f"Server responded with status {e.response.status_code}."
247
+ try:
248
+ error_json = e.response.json()
249
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
250
+ except requests.exceptions.JSONDecodeError:
251
+ error_detail += f" Response: {e.response.text[:500]}"
252
+
253
+ status_message = f"Submission Failed: {error_detail}"
254
+ logger.error(status_message)
255
+
256
+ results_df = pd.DataFrame(results_log if 'results_log' in locals() else [])
257
+ return status_message, results_df
258
+
259
+ except requests.exceptions.Timeout:
260
+ status_message = f"Submission Failed: The request timed out after {REQUEST_TIMEOUT} seconds"
261
+ logger.error(status_message)
262
+
263
+ results_df = pd.DataFrame(results_log if 'results_log' in locals() else [])
264
+ return status_message, results_df
265
+
266
+ except Exception as e:
267
+ status_message = f"An unexpected error occurred: {str(e)}"
268
+ logger.error(status_message, exc_info=True)
269
+
270
+ results_df = pd.DataFrame(results_log if 'results_log' in locals() else [])
271
+ return status_message, results_df
272
+
273
+
274
+ def create_gradio_interface() -> gr.Blocks:
275
+ """
276
+ Create and configure the Gradio interface.
277
+
278
+ Returns:
279
+ Configured Gradio Blocks interface
280
+ """
281
+ with gr.Blocks() as demo:
282
+ gr.Markdown("# Agent Evaluation Runner")
283
+ gr.Markdown(
284
+ """
285
+ ## Instructions
286
+
287
+ 1. **Clone this space** and modify the code to define your agent's logic, tools, and dependencies
288
+ 2. **Log in to your Hugging Face account** using the button below (required for submission)
289
+ 3. **Run Evaluation** to fetch questions, run your agent, and submit answers
290
+
291
+ ## Important Notes
292
+
293
+ - The evaluation process may take several minutes to complete
294
+ - This agent framework is intentionally minimal to allow for your own improvements
295
+ - Consider implementing caching or async processing for better performance
296
+ """
297
+ )
298
+
299
+ gr.LoginButton()
300
+
301
+ run_button = gr.Button("Run Evaluation & Submit All Answers", variant="primary")
302
+
303
+ status_output = gr.Textbox(
304
+ label="Run Status / Submission Result",
305
+ lines=5,
306
+ interactive=False
307
+ )
308
+
309
+ results_table = gr.DataFrame(
310
+ label="Questions and Agent Answers",
311
+ wrap=True
312
+ )
313
+
314
+ run_button.click(
315
+ fn=run_and_submit_all,
316
+ outputs=[status_output, results_table]
317
+ )
318
+
319
+ return demo
320
+
321
+
322
+ def check_environment() -> None:
323
+ """
324
+ Check and log environment variables at startup.
325
+ """
326
+ logger.info("-" * 30 + " App Starting " + "-" * 30)
327
+
328
+ # Check for SPACE_HOST
329
+ space_host = os.getenv("SPACE_HOST")
330
+ if space_host:
331
+ logger.info(f"✅ SPACE_HOST found: {space_host}")
332
+ logger.info(f" Runtime URL should be: https://{space_host}.hf.space")
333
+ else:
334
+ logger.info("ℹ️ SPACE_HOST environment variable not found (running locally?).")
335
+
336
+ # Check for SPACE_ID
337
+ space_id = os.getenv("SPACE_ID")
338
+ if space_id:
339
+ logger.info(f"✅ SPACE_ID found: {space_id}")
340
+ logger.info(f" Repo URL: https://huggingface.co/spaces/{space_id}")
341
+ logger.info(f" Repo Tree URL: https://huggingface.co/spaces/{space_id}/tree/main")
342
+ else:
343
+ logger.info("ℹ️ SPACE_ID environment variable not found (running locally?).")
344
+
345
+ logger.info("-" * (60 + len(" App Starting ")) + "\n")
346
+
347
+
348
+ if __name__ == "__main__":
349
+ # Check environment at startup
350
+ check_environment()
351
+
352
+ # Create and launch Gradio interface
353
+ logger.info("Launching Gradio Interface for Agent Evaluation...")
354
+ demo = create_gradio_interface()
355
+ demo.launch(debug=True, share=False)