Kackle commited on
Commit
58c17c0
·
verified ·
1 Parent(s): 74560a3

losing my mind

Browse files
Files changed (1) hide show
  1. app.py +402 -243
app.py CHANGED
@@ -1,272 +1,431 @@
1
  import os
2
- import google.generativeai as genai
3
- from dotenv import load_dotenv
4
- from excel_parser import ExcelParser
5
- import re
6
- import time
7
  import asyncio
 
 
 
 
 
 
 
 
 
 
8
 
9
  load_dotenv()
 
 
 
 
10
 
11
- class GeminiAgent:
 
 
 
 
 
 
 
 
12
  def __init__(self):
13
- print("GeminiAgent initialized.")
14
-
15
- # Get Google API key from environment variables
16
- api_key = os.getenv('GOOGLE_API_KEY')
17
- genai.configure(api_key=api_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- self.model = genai.GenerativeModel('gemini-2.0-flash-exp')
20
- self.last_request_time = 0
21
- self.min_request_interval = 6.0 # 6 seconds between requests (10 per minute limit)
 
 
 
 
 
 
 
 
 
22
 
23
- # Initialize parsers
24
- self.excel_parser = ExcelParser()
 
 
 
 
 
25
 
26
  async def __call__(self, question: str) -> str:
27
- print(f"GeminiAgent received question (first 50 chars): {question}...")
28
-
29
- try:
30
- # Check if question involves video analysis
31
- if 'youtube.com' in question or 'video' in question.lower():
32
- return await self._handle_video_question(question)
33
-
34
- # Check if question involves Excel files
35
- if '.xlsx' in question or '.xls' in question or 'excel' in question.lower():
36
- return await self._handle_excel_question(question)
37
-
38
- # Regular text-based question
39
- return await self._handle_text_question(question)
40
-
41
- except Exception as e:
42
- print(f"Error processing question: {e}")
43
- return "Unable to process request."
44
-
45
- async def _handle_video_question(self, question: str) -> str:
46
- """Handle questions that require video analysis"""
47
- # Extract YouTube URL
48
- youtube_url = re.search(r'https://www\.youtube\.com/watch\?v=[\w-]+', question)
49
- if not youtube_url:
50
- return "No valid YouTube URL found in question."
51
 
52
- url = youtube_url.group()
 
 
53
 
54
- # Extract video ID for reference
55
- video_id = re.search(r'v=([\w-]+)', url).group(1)
 
 
 
 
56
 
57
- # Extract video information from the question to provide relevant answers
58
- # without hardcoding specific IDs
 
 
 
 
 
 
 
 
59
 
60
- # Enhanced video prompt for better accuracy
61
- video_prompt = f"""You need to answer this question about YouTube video {url}:
62
-
63
- {question}
 
 
 
 
 
64
 
65
- Provide only the direct answer. If it's a quote, give just the quoted text. If it's a number, give just the number. If it's about bird species count, analyze carefully and give the exact count. If it's about dialogue, provide the exact words spoken."""
66
-
67
- try:
68
- await self._rate_limit()
69
- response = self.model.generate_content(
70
- video_prompt,
71
- generation_config=genai.types.GenerationConfig(
72
- max_output_tokens=50,
73
- temperature=0.0
74
- )
75
- )
76
- answer = response.text.strip()
77
-
78
- # Clean up video responses to be more concise
79
- if len(answer) > 100:
80
- # Extract key information
81
- if '"' in answer:
82
- # Extract quoted text
83
- quotes = re.findall(r'"([^"]+)"', answer)
84
- if quotes:
85
- return quotes[0]
86
- # Extract numbers if it's a counting question
87
- if 'how many' in question.lower() or 'number' in question.lower():
88
- numbers = re.findall(r'\b\d+\b', answer)
89
- if numbers:
90
- return numbers[0]
91
- # Take first sentence
92
- sentences = answer.split('. ')
93
- answer = sentences[0]
94
-
95
- return answer
96
-
97
- except Exception as e:
98
- print(f"Video analysis failed: {str(e)}")
99
- # Generate answer based on question content
100
- return await self._generate_video_answer_from_question(question, video_id)
101
-
102
- async def _handle_excel_question(self, question: str) -> str:
103
- """Handle questions that require Excel file analysis"""
104
- # Extract file path from question if present
105
- file_patterns = [r'([A-Za-z]:\\[^\s]+\.xlsx?)', r'([^\s]+\.xlsx?)']
106
- file_path = None
107
 
108
- for pattern in file_patterns:
109
- match = re.search(pattern, question)
110
- if match:
111
- file_path = match.group(1)
112
- break
113
 
114
- # If we have a file path, try to process it
115
- if file_path:
116
  try:
117
- if 'sales' in question.lower() and 'food' in question.lower():
118
- results = self.excel_parser.analyze_sales_data(file_path)
119
- return results.get('total_food_sales', 'No sales data found')
120
- else:
121
- df = self.excel_parser.read_excel_file(file_path)
122
- return f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns."
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  except Exception as e:
124
- print(f"Excel analysis failed: {str(e)}")
125
- # Fall through to Nova Pro search
 
 
 
 
 
 
 
 
 
126
 
127
- # Use Nova Pro to search for information about the Excel file
128
- excel_prompt = f"""I need to analyze an Excel file mentioned in this question, but I don't have direct access to it.
129
- Based on your knowledge, provide the most accurate answer possible:
130
-
131
- {question}
132
-
133
- If you don't have specific information about this Excel file, provide a reasonable estimate based on similar data."""
134
 
135
- try:
136
- await self._rate_limit()
137
- response = self.model.generate_content(
138
- excel_prompt,
139
- generation_config=genai.types.GenerationConfig(
140
- max_output_tokens=150,
141
- temperature=0.0
142
- )
143
- )
144
- answer = response.text.strip()
145
 
146
- # Check if the answer contains a dollar amount
147
- dollar_match = re.search(r'\$[\d,]+\.\d{2}', answer)
148
- if dollar_match:
149
- return dollar_match.group(0)
150
- else:
151
- return answer
152
-
153
- except Exception as e:
154
- print(f"Gemini search failed: {str(e)}")
155
- return "Unable to analyze Excel data. Please provide the file directly."
156
 
157
- async def _handle_text_question(self, question: str) -> str:
158
- """Handle regular text-based questions"""
159
- prompt = ""
160
- # Handle attached file questions with enhanced prompts
161
- if 'attached' in question.lower():
162
- if 'python code' in question.lower():
163
- prompt = f"""This question refers to attached Python code. Based on typical code execution patterns, provide the most likely numeric output:\n\n{question}\n\nAnswer:"""
164
- elif '.mp3' in question.lower():
165
- prompt = f"""This question refers to an attached audio file. Provide the most likely answer based on the context:\n\n{question}\n\nAnswer:"""
166
- else:
167
- prompt = f"""This question refers to an attached file. Provide the most likely answer:\n\n{question}\n\nAnswer:"""
168
- # Handle chess position question
169
- elif 'chess position' in question.lower() and 'image' in question.lower():
170
- prompt = f"""This is a chess question with an attached image. Provide the best chess move in algebraic notation:\n\n{question}\n\nAnswer:"""
171
- # Handle list extraction and formatting
172
- elif (
173
- 'alphabetize' in question.lower() or
174
- 'comma separated' in question.lower() or
175
- 'list' in question.lower() or
176
- 'ingredients' in question.lower() or
177
- 'page numbers' in question.lower() or
178
- 'vegetables' in question.lower()
179
- ):
180
- # Add domain definition for botanical vegetables
181
- if 'vegetable' in question.lower() and ('botany' in question.lower() or 'botanical' in question.lower()):
182
- definition = ("In botany, a vegetable is any edible part of a plant that is not a fruit or seed. "
183
- "Fruits contain seeds and develop from the ovary of a flower. Use this definition.")
184
- prompt = f"{definition}\n\n{question}\n\nList only the requested items, alphabetized, comma separated, and do not include any explanations or extra words."
185
- else:
186
- prompt = f"{question}\n\nList only the requested items, alphabetized, comma separated, and do not include any explanations or extra words."
187
- # Create enhanced prompt based on question type
188
- elif 'how many' in question.lower() or 'what is the' in question.lower():
189
- prompt = f"""Provide only the exact answer to this question. No explanations, just the specific number, name, or fact requested:\n\n{question}\n\nAnswer:"""
190
- elif 'who' in question.lower():
191
- prompt = f"""Provide only the name requested. No explanations or additional context:\n\n{question}\n\nAnswer:"""
192
- elif 'where' in question.lower():
193
- prompt = f"""Provide only the location requested. No explanations:\n\n{question}\n\nAnswer:"""
194
  else:
195
- prompt = f"""Answer this question with only the essential information requested:\n\n{question}\n\nAnswer:"""
196
-
197
- # Use the constructed prompt for all cases
198
- await self._rate_limit()
199
- response = self.model.generate_content(
200
- prompt,
201
- generation_config=genai.types.GenerationConfig(
202
- max_output_tokens=100,
203
- temperature=0.0
204
- )
205
- )
206
- answer = response.text.strip()
207
-
208
- # Extract the core answer
209
- if ':' in answer:
210
- answer = answer.split(':')[-1].strip()
211
-
212
- # Remove common prefixes
213
- prefixes = ['The answer is', 'Based on', 'According to']
214
- for prefix in prefixes:
215
- if answer.lower().startswith(prefix.lower()):
216
- answer = answer[len(prefix):].strip()
217
- if answer.startswith(','):
218
- answer = answer[1:].strip()
219
-
220
- # Limit length
221
- if len(answer) > 200:
222
- sentences = answer.split('. ')
223
- answer = sentences[0] + '.'
224
-
225
- # If the question expects a single value, extract it
226
- if any(kw in question.lower() for kw in ["how many", "what is the", "who", "where", "give only", "provide only"]):
227
- # Extract the first number, word, or phrase (tweak regex as needed)
228
- match = re.search(r'^[A-Za-z0-9 ,+-]+', answer)
229
- if match:
230
- answer = match.group(0).strip()
231
-
232
- return answer
 
 
 
 
 
 
 
 
233
 
234
- async def _generate_video_answer_from_question(self, question: str, video_id: str) -> str:
235
- """Generate an answer for a video question based on the question content"""
236
- # Create a prompt that asks Nova Pro to analyze the question and generate a likely answer
237
- prompt = f"""Based on this question about YouTube video ID {video_id},
238
- what would be the most likely accurate answer? The question is:
239
-
240
- {question}
241
-
242
- Provide only the direct answer without explanation."""
243
 
244
- try:
245
- await self._rate_limit()
246
- response = self.model.generate_content(
247
- prompt,
248
- generation_config=genai.types.GenerationConfig(
249
- max_output_tokens=100,
250
- temperature=0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  )
252
- )
253
- answer = response.text.strip()
254
-
255
- # Clean up the answer to make it concise
256
- if len(answer) > 100:
257
- sentences = answer.split('. ')
258
- answer = sentences[0]
259
-
260
- return answer
261
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  except Exception as e:
263
- print(f"Failed to generate video answer: {str(e)}")
264
- return "Video analysis unavailable."
265
 
266
- async def _rate_limit(self):
267
- """Ensure minimum time between API requests"""
268
- current_time = time.time()
269
- time_since_last = current_time - self.last_request_time
270
- if time_since_last < self.min_request_interval:
271
- await asyncio.sleep(self.min_request_interval - time_since_last)
272
- self.last_request_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import gradio as gr
3
+ import requests
4
+ import inspect
5
+ import pandas as pd
 
6
  import asyncio
7
+ import aiohttp
8
+ import time
9
+ import random
10
+ import json
11
+ import boto3
12
+ from smolagents import FinalAnswerTool, Tool, tool, OpenAIServerModel, DuckDuckGoSearchTool, CodeAgent, VisitWebpageTool
13
+ from nova_agent import NovaProAgent
14
+ from gemini_agent import GeminiAgent
15
+
16
+ from dotenv import load_dotenv
17
 
18
  load_dotenv()
19
+ # (Keep Constants as is)
20
+ # --- Constants ---
21
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
22
+
23
 
24
+ OPENAI_TOKEN = os.getenv("OPENAI_API_KEY")
25
+
26
+ # --- Custom Tools ---
27
+ class KnowledgeBaseTool(Tool):
28
+ name = "knowledge_base"
29
+ description = "Access structured knowledge for common topics"
30
+ inputs = {"topic": {"type": "string", "description": "The topic to look up"}}
31
+ output_type = "string"
32
+
33
  def __init__(self):
34
+ super().__init__()
35
+ self.is_initialized = True
36
+ # Common knowledge base
37
+ self.knowledge = {
38
+ "olympics": "Olympic Games data: Countries, athletes, years, sports",
39
+ "countries": "Country codes: ISO, IOC, FIFA codes and country information",
40
+ "sports": "Sports history, rules, famous athletes and events",
41
+ "science": "Scientific facts, formulas, discoveries, and researchers",
42
+ "history": "Historical events, dates, people, and places",
43
+ "geography": "Countries, capitals, populations, and geographical features"
44
+ }
45
+
46
+ def forward(self, topic: str) -> str:
47
+ topic_lower = topic.lower()
48
+ for key, info in self.knowledge.items():
49
+ if key in topic_lower:
50
+ return f"Knowledge base: {info}. Use this context to answer questions about {topic}."
51
+ return f"No specific knowledge base entry for '{topic}'. Use general reasoning."
52
+
53
+ class WikipediaSearchTool(Tool):
54
+ name = "wikipedia_search"
55
+ description = "Search Wikipedia for information"
56
+ inputs = {"query": {"type": "string", "description": "The search query for Wikipedia"}}
57
+ output_type = "string"
58
+
59
+ def __init__(self):
60
+ super().__init__()
61
+ self.is_initialized = True
62
 
63
+ def forward(self, query: str) -> str:
64
+ """Search Wikipedia with simple fallback."""
65
+ try:
66
+ import requests
67
+ wiki_url = "https://en.wikipedia.org/api/rest_v1/page/summary/" + query.replace(" ", "_")
68
+ response = requests.get(wiki_url, timeout=2)
69
+ if response.status_code == 200:
70
+ data = response.json()
71
+ if 'extract' in data and data['extract']:
72
+ return f"Wikipedia: {data['extract'][:500]}" # Limit length
73
+ except Exception as e:
74
+ print(f"Wikipedia search failed: {e}")
75
 
76
+ return f"Wikipedia search unavailable for '{query}'. Use your knowledge to answer."
77
+
78
+ # --- Basic Agent Definition ---
79
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
80
+ class SlpMultiAgent:
81
+ def __init__(self):
82
+ print("BasicAgent initialized.")
83
 
84
  async def __call__(self, question: str) -> str:
85
+ print(f"Agent received question (first 50 chars): {question}...")
86
+ fixed_answer = "This is a default answer."
87
+ print(f"Agent returning fixed answer: {fixed_answer}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
+ # Truncate question to avoid exceeding model context length
90
+ MAX_QUESTION_LENGTH = 1000
91
+ short_question = question # [:MAX_QUESTION_LENGTH]
92
 
93
+ # Use cheaper, faster model
94
+ model = OpenAIServerModel(
95
+ model_id="gpt-3.5-turbo",
96
+ temperature=0.0, # Deterministic for consistency
97
+ max_tokens=400 # Reduced tokens for cost efficiency
98
+ )
99
 
100
+ # Create only essential agents with reduced complexity
101
+ research_agent = CodeAgent(
102
+ tools=[KnowledgeBaseTool()], # Remove Wikipedia to avoid timeouts
103
+ model=model,
104
+ additional_authorized_imports=["re", "datetime"],
105
+ max_steps=2, # Reduced steps for cost
106
+ name="ResearchAgent",
107
+ verbosity_level=0,
108
+ description="Quick factual research and knowledge lookup."
109
+ )
110
 
111
+ solver_agent = CodeAgent(
112
+ tools=[],
113
+ model=model,
114
+ additional_authorized_imports=["math", "re", "collections", "itertools"],
115
+ max_steps=2, # Reduced steps
116
+ name="SolverAgent",
117
+ verbosity_level=0,
118
+ description="Problem solving, calculations, and logical reasoning."
119
+ )
120
 
121
+ manager_agent = CodeAgent(
122
+ model=OpenAIServerModel(
123
+ model_id="gpt-3.5-turbo",
124
+ temperature=0.0,
125
+ max_tokens=500
126
+ ),
127
+ tools=[KnowledgeBaseTool()], # Remove Wikipedia to avoid timeouts
128
+ managed_agents=[research_agent, solver_agent], # Only 2 agents
129
+ name="ManagerAgent",
130
+ description="Efficient manager for quick problem solving.",
131
+ additional_authorized_imports=["re", "math"],
132
+ planning_interval=1, # Faster planning
133
+ verbosity_level=0, # Reduce verbosity
134
+ max_steps=3, # Further reduced steps to avoid timeouts
135
+ final_answer_checks=[check_reasoning]
136
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ # Create a task for the agent run with retry mechanism for rate limits
139
+ max_retries = 3
140
+ result = None
 
 
141
 
142
+ for attempt in range(max_retries):
 
143
  try:
144
+ loop = asyncio.get_event_loop()
145
+ result = await loop.run_in_executor(
146
+ None,
147
+ lambda: manager_agent.run(f"""
148
+ Question: {short_question}
149
+
150
+ You have knowledge_base() tool and two agents:
151
+ - ResearchAgent: For factual questions
152
+ - SolverAgent: For calculations and logic
153
+
154
+ IMPORTANT: Always end with exactly this format:
155
+ <code>
156
+ final_answer("your direct answer")
157
+ </code>
158
+
159
+ Be concise and direct.
160
+ """)
161
+ )
162
+ break # Success, exit retry loop
163
  except Exception as e:
164
+ print(f"Attempt {attempt+1}/{max_retries} failed: {e}")
165
+ if "rate limit" in str(e).lower() and attempt < max_retries - 1:
166
+ # Add jitter to avoid synchronized retries
167
+ wait_time = (attempt + 1) * 10 + random.uniform(0, 5)
168
+ print(f"Rate limit hit. Waiting {wait_time:.2f} seconds before retry...")
169
+ await asyncio.sleep(wait_time)
170
+ elif attempt < max_retries - 1:
171
+ await asyncio.sleep(5) # Wait before general retry
172
+ else:
173
+ print(f"All attempts failed. Returning default answer.")
174
+ return "I apologize, but I'm currently experiencing technical difficulties. Please try again later."
175
 
176
+ # If we couldn't get a result after all retries
177
+ if result is None:
178
+ return "I apologize, but I'm currently experiencing technical difficulties. Please try again later."
 
 
 
 
179
 
180
+
181
+ # Extract clean answer from result
182
+ if result and isinstance(result, str):
183
+ # Look for final_answer pattern
184
+ import re
185
+ final_answer_match = re.search(r'final_answer\(["\']([^"\']*)["\'\)]', result) # Fixed regex
186
+ if final_answer_match:
187
+ clean_answer = final_answer_match.group(1)
188
+ return clean_answer
 
189
 
190
+ # If no final_answer found, try to extract the last meaningful line
191
+ lines = result.strip().split('\n')
192
+ for line in reversed(lines):
193
+ line = line.strip()
194
+ if line and not line.startswith('#') and not line.startswith('###') and len(line) < 200:
195
+ return line
196
+
197
+ # Return the result from the agent
198
+ return result if result else "Unable to determine answer."
 
199
 
200
+ def check_reasoning(final_answer, agent_memory):
201
+ # Skip expensive validation to save costs
202
+ return True
203
+
204
+
205
+ async def run_and_submit_all(profile):
206
+ """
207
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
208
+ and displays the results asynchronously.
209
+ """
210
+ # --- Determine HF Space Runtime URL and Repo URL ---
211
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
212
+
213
+ # Handle different profile types
214
+ if profile:
215
+ if hasattr(profile, 'username'):
216
+ # It's an OAuthProfile object
217
+ username = profile.username
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  else:
219
+ # It's a string or other type
220
+ username = str(profile)
221
+ print(f"User logged in: {username}")
222
+ else:
223
+ print("User not logged in.")
224
+ return "Please Login to Hugging Face with the button.", None
225
+
226
+ api_url = DEFAULT_API_URL
227
+ questions_url = f"{api_url}/questions"
228
+ submit_url = f"{api_url}/submit"
229
+
230
+ # 1. Instantiate Agent ( modify this part to create your agent)
231
+ try:
232
+ agent = GeminiAgent()
233
+ except Exception as e:
234
+ print(f"Error instantiating agent: {e}")
235
+ return f"Error initializing agent: {e}", None
236
+ # 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)
237
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
238
+ print(agent_code)
239
+
240
+ # 2. Fetch Questions
241
+ print(f"Fetching questions from: {questions_url}")
242
+ try:
243
+ async with aiohttp.ClientSession() as session:
244
+ async with session.get(questions_url, timeout=15) as response:
245
+ response.raise_for_status()
246
+ questions_data = await response.json()
247
+ if not questions_data:
248
+ print("Fetched questions list is empty.")
249
+ return "Fetched questions list is empty or invalid format.", None
250
+ print(f"Fetched {len(questions_data)} questions.")
251
+ except aiohttp.ClientError as e:
252
+ print(f"Error fetching questions: {e}")
253
+ return f"Error fetching questions: {e}", None
254
+ except ValueError as e: # JSON decode error
255
+ print(f"Error decoding JSON response from questions endpoint: {e}")
256
+ return f"Error decoding server response for questions: {e}", None
257
+ except Exception as e:
258
+ print(f"An unexpected error occurred fetching questions: {e}")
259
+ return f"An unexpected error occurred fetching questions: {e}", None
260
+
261
+ # 3. Run your Agent
262
+ results_log = []
263
+ answers_payload = []
264
+ print(f"Running agent on {len(questions_data)} questions...")
265
 
266
+ # Process questions one at a time to avoid rate limits
267
+ semaphore = asyncio.Semaphore(1) # Process 1 question at a time
268
+
269
+ async def process_question(item):
270
+ task_id = item.get("task_id")
271
+ question_text = item.get("question")
272
+ if not task_id or question_text is None:
273
+ print(f"Skipping item with missing task_id or question: {item}")
274
+ return None
275
 
276
+ async with semaphore:
277
+ max_retries = 3
278
+ for attempt in range(max_retries):
279
+ try:
280
+ print(f"Processing task {task_id}, attempt {attempt+1}/{max_retries}")
281
+ submitted_answer = await agent(question_text)
282
+ return {"task_id": task_id, "submitted_answer": submitted_answer,
283
+ "log": {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}}
284
+ except Exception as e:
285
+ print(f"Error running agent on task {task_id}, attempt {attempt+1}: {e}")
286
+ if "rate limit" in str(e).lower() and attempt < max_retries - 1:
287
+ # Exponential backoff with jitter
288
+ wait_time = (2 ** attempt) * 5 + random.uniform(0, 3)
289
+ print(f"Rate limit hit. Waiting {wait_time:.2f} seconds before retry...")
290
+ await asyncio.sleep(wait_time)
291
+ elif attempt < max_retries - 1:
292
+ await asyncio.sleep(5) # Reduced wait time
293
+ else:
294
+ # All retries failed, return default answer
295
+ default_answer = "This is a default answer."
296
+ return {"task_id": task_id, "submitted_answer": default_answer,
297
+ "log": {"Task ID": task_id, "Question": question_text, "Submitted Answer": default_answer}}
298
+
299
+ # Create tasks for all questions
300
+ tasks = [process_question(item) for item in questions_data]
301
+ results = await asyncio.gather(*tasks)
302
+
303
+ # Process results
304
+ for result in results:
305
+ if result is not None:
306
+ answers_payload.append({"task_id": result["task_id"], "submitted_answer": result["submitted_answer"]})
307
+ results_log.append(result["log"])
308
+
309
+ if not answers_payload:
310
+ print("Agent did not produce any answers to submit.")
311
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
312
+
313
+ # 4. Prepare Submission
314
+ submission_data = {"username": str(username).strip(), "agent_code": agent_code, "answers": answers_payload}
315
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
316
+ print(status_update)
317
+
318
+ # 5. Submit
319
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
320
+ try:
321
+ async with aiohttp.ClientSession() as session:
322
+ async with session.post(submit_url, json=submission_data, timeout=60) as response:
323
+ response.raise_for_status()
324
+ result_data = await response.json()
325
+ final_status = (
326
+ f"Submission Successful!\n"
327
+ f"User: {result_data.get('username')}\n"
328
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
329
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
330
+ f"Message: {result_data.get('message', 'No message received.')}"
331
  )
332
+ print("Submission successful.")
333
+ results_df = pd.DataFrame(results_log)
334
+ return final_status, results_df
335
+ except aiohttp.ClientResponseError as e:
336
+ error_detail = f"Server responded with status {e.status}."
337
+ try:
338
+ error_text = await e.response.text()
339
+ try:
340
+ error_json = await e.response.json()
341
+ error_detail += f" Detail: {error_json.get('detail', error_text)}"
342
+ except ValueError:
343
+ error_detail += f" Response: {error_text[:500]}"
344
+ except:
345
+ pass
346
+ status_message = f"Submission Failed: {error_detail}"
347
+ print(status_message)
348
+ results_df = pd.DataFrame(results_log)
349
+ return status_message, results_df
350
+ except asyncio.TimeoutError:
351
+ status_message = "Submission Failed: The request timed out."
352
+ print(status_message)
353
+ results_df = pd.DataFrame(results_log)
354
+ return status_message, results_df
355
+ except aiohttp.ClientError as e:
356
+ status_message = f"Submission Failed: Network error - {e}"
357
+ print(status_message)
358
+ results_df = pd.DataFrame(results_log)
359
+ return status_message, results_df
360
+ except Exception as e:
361
+ status_message = f"An unexpected error occurred during submission: {e}"
362
+ print(status_message)
363
+ results_df = pd.DataFrame(results_log)
364
+ return status_message, results_df
365
+
366
+
367
+ # --- Build Gradio Interface using Blocks ---
368
+ with gr.Blocks() as demo:
369
+ gr.Markdown("# Basic Agent Evaluation Runner")
370
+ gr.Markdown(
371
+ """
372
+ **Instructions:**
373
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
374
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
375
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
376
+ ---
377
+ **Disclaimers:**
378
+ 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).
379
+ 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.
380
+ """
381
+ )
382
+
383
+ login_button = gr.LoginButton()
384
+
385
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
386
+
387
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
388
+ # Removed max_rows=10 from DataFrame constructor
389
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
390
+
391
+ def sync_wrapper(profile):
392
+ # This wrapper ensures we have access to the profile
393
+ if not profile:
394
+ print("No profile available in sync_wrapper")
395
+ return "Please Login to Hugging Face with the button.", None
396
+ print(f"Profile type in wrapper: {type(profile)}")
397
+ try:
398
+ return asyncio.run(run_and_submit_all(profile))
399
  except Exception as e:
400
+ print(f"Error in sync_wrapper: {e}")
401
+ return f"Error processing request: {e}", None
402
 
403
+ run_button.click(
404
+ fn=sync_wrapper,
405
+ inputs=login_button,
406
+ outputs=[status_output, results_table]
407
+ )
408
+
409
+ if __name__ == "__main__":
410
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
411
+ # Check for SPACE_HOST and SPACE_ID at startup for information
412
+ space_host_startup = os.getenv("SPACE_HOST")
413
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
414
+
415
+ if space_host_startup:
416
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
417
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
418
+ else:
419
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
420
+
421
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
422
+ print(f"✅ SPACE_ID found: {space_id_startup}")
423
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
424
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
425
+ else:
426
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
427
+
428
+ print("-"*(60 + len(" App Starting ")) + "\n")
429
+
430
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
431
+ demo.launch(debug=True, share=False)