srijanjoshi commited on
Commit
73cf8af
·
verified ·
1 Parent(s): f5e41e0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -18
app.py CHANGED
@@ -14,8 +14,10 @@ from typing import List, Dict, TypedDict, Annotated
14
  from tenacity import retry, stop_after_attempt, wait_exponential
15
  import io
16
  import sys
 
 
17
 
18
- # Set recursion limit for Python and LangGraph
19
  sys.setrecursionlimit(1500)
20
 
21
  # --- Setup Logging ---
@@ -46,6 +48,23 @@ class RateLimitedDuckDuckGoSearch(DuckDuckGoSearchRun):
46
  logging.warning(f"All retries failed for query '{query}'. Falling back to reasoning.")
47
  return "Web search failed; using reasoning."
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  # --- Answer Formatting ---
50
  def format_answer(answer: str, question: str) -> str:
51
  """Post-process answers to match GAIA's exact-match requirements."""
@@ -95,7 +114,7 @@ class AgentState(TypedDict):
95
  messages: Annotated[List, add_messages]
96
  question: str
97
  task_id: str
98
- file_data: pd.DataFrame | None
99
  search_result: str
100
  answer: str
101
  attempts: int
@@ -117,7 +136,7 @@ class GaiaAgent:
117
  Analyze the question to determine the required action:
118
 
119
  - List tasks (keywords: "list," "comma-separated," "categorize"): Action: reason, Input: none
120
- - File tasks (keywords: "attached," "Excel," "spreadsheet", ".mp3"): Action: fetch_file, Input: none
121
  - Search tasks (keywords: "who," "when," "historical," "competition", "wikipedia"): Action: search, Input: specific query
122
  - Number tasks (keywords: "how many," "count"): Action: search, Input: specific query
123
  - Fallback: Action: reason, Input: none
@@ -146,11 +165,11 @@ class GaiaAgent:
146
  Steps:
147
  1. Analyze question format (list, USD, name, number).
148
  2. Use data:
149
- - File: Analyze DataFrame (e.g., sum sales where 'Category' != 'Drink').
150
  - Search: Extract names, numbers, codes.
151
  3. Reason:
152
  - Lists: Filter, alphabetize.
153
- - Files: Sum columns, handle missing data.
154
  - Search: Parse for key info.
155
  4. Format:
156
  - Lists: 'item1, item2' (alphabetized).
@@ -162,7 +181,8 @@ class GaiaAgent:
162
  Specific instructions:
163
  - Mercedes Sosa studio albums (2000-2009): Misa Criolla (2000), Acústico (2002), Corazón Libre (2005), Cantora 1 (2009), Cantora 2 (2009). Answer: 5
164
  - Vegetable lists: Only non-botanical fruits (broccoli, celery, fresh basil, green beans, lettuce, sweet potatoes).
165
- - .mp3 files: Return 'ERROR: Audio transcription not supported'.
 
166
 
167
  Examples:
168
  - Q: List of vegetables...
@@ -183,17 +203,17 @@ class GaiaAgent:
183
  Validate the answer:
184
 
185
  - Check format (list, USD, name, number).
186
- - Verify relevance.
187
  - Return 'valid' or 'invalid: [reason]'.
188
  - Numbers: Ensure non-negative for counts.
189
- - Allow reasonable answers even if not perfect to avoid excessive retries.
190
 
191
  Question: {question}
192
  Answer: {answer}
193
  """)
194
  self.graph = self._build_graph()
195
 
196
- async def fetch_file(self, task_id: str) -> pd.DataFrame | None:
197
  @retry(stop=stop_after_attempt(6), wait=wait_exponential(multiplier=1, min=2, max=5))
198
  def _fetch():
199
  file_url = f"{self.api_url}/files/{task_id}"
@@ -203,8 +223,14 @@ class GaiaAgent:
203
  content = io.BytesIO(response.content)
204
  if file_url.endswith('.xlsx') or file_url.endswith('.xls'):
205
  df = pd.read_excel(content)
206
- logging.info(f"File fetched. Shape: {df.shape}, Columns: {list(df.columns)}")
207
  return df
 
 
 
 
 
 
208
  logging.warning(f"Unsupported file type for task {task_id}")
209
  return None
210
 
@@ -216,7 +242,7 @@ class GaiaAgent:
216
 
217
  def _build_graph(self):
218
  workflow = StateGraph(AgentState)
219
- # Set higher recursion limit for LangGraph
220
  workflow.config = {"recursion_limit": 50}
221
 
222
  def classify(state: AgentState) -> AgentState:
@@ -233,7 +259,7 @@ class GaiaAgent:
233
  task_id=state["task_id"],
234
  attempts=state["attempts"]
235
  ))
236
- response_content = str(response.content)
237
  action = response_content.split("Action: ")[1].split("\n")[0].strip() if "Action: " in response_content else "reason"
238
  action_input = response_content.split("Input: ")[1].strip() if "Input: " in response_content else "none"
239
  except Exception as e:
@@ -247,7 +273,7 @@ class GaiaAgent:
247
 
248
  async def fetch_file_node(state: AgentState) -> AgentState:
249
  logging.info(f"Fetching file for task {state['task_id']}")
250
- if state["file_data"] is None and state["attempts"] < 3:
251
  try:
252
  state["file_data"] = await asyncio.wait_for(self.fetch_file(state["task_id"]), timeout=30)
253
  state["messages"].append(f"Fetched file: {state['file_data'] is not None}")
@@ -260,7 +286,7 @@ class GaiaAgent:
260
 
261
  async def search_node(state: AgentState) -> AgentState:
262
  logging.info(f"Searching for task {state['task_id']}")
263
- if state["attempts"] < 3:
264
  action_input = state["action_input"]
265
  if action_input != "none":
266
  try:
@@ -284,7 +310,14 @@ class GaiaAgent:
284
 
285
  def generate_answer(state: AgentState) -> AgentState:
286
  logging.info(f"Generating answer for task {state['task_id']}")
287
- file_data_str = state["file_data"].to_dict() if state["file_data"] is not None else "none"
 
 
 
 
 
 
 
288
  try:
289
  response = self.llm.invoke(self.answer_prompt.format(
290
  question=state["question"],
@@ -311,9 +344,8 @@ class GaiaAgent:
311
  logging.error(f"Critique error: {str(e)}")
312
  critique = "valid" # Avoid looping on critique failure
313
  state["messages"].append(f"Critique: {critique}")
314
- # Stricter exit condition to prevent infinite loops
315
  state["attempts"] += 1
316
- if "invalid" in critique.lower() and state["attempts"] < 2:
317
  state["action"] = "reason"
318
  state["action_input"] = "none"
319
  else:
@@ -327,7 +359,7 @@ class GaiaAgent:
327
  workflow.add_node("critique", critique_answer)
328
 
329
  def route_action(state: AgentState) -> str:
330
- if state["attempts"] >= 3 or state["action"] is None:
331
  return END
332
  return state["action"] if state["action"] in ["fetch_file", "search", "reason"] else "generate_answer"
333
 
 
14
  from tenacity import retry, stop_after_attempt, wait_exponential
15
  import io
16
  import sys
17
+ import speech_recognition as sr
18
+ import tempfile
19
 
20
+ # Set recursion limit for Python
21
  sys.setrecursionlimit(1500)
22
 
23
  # --- Setup Logging ---
 
48
  logging.warning(f"All retries failed for query '{query}'. Falling back to reasoning.")
49
  return "Web search failed; using reasoning."
50
 
51
+ # --- Audio Transcription ---
52
+ def transcribe_audio(file_content: bytes) -> str:
53
+ try:
54
+ recognizer = sr.Recognizer()
55
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_file:
56
+ temp_file.write(file_content)
57
+ temp_file_path = temp_file.name
58
+ with sr.AudioFile(temp_file_path) as source:
59
+ audio = recognizer.record(source)
60
+ text = recognizer.recognize_google(audio)
61
+ os.unlink(temp_file_path)
62
+ logging.info(f"Transcribed audio: {text[:200]}...")
63
+ return text
64
+ except Exception as e:
65
+ logging.error(f"Audio transcription failed: {str(e)}")
66
+ return "ERROR: Audio transcription failed"
67
+
68
  # --- Answer Formatting ---
69
  def format_answer(answer: str, question: str) -> str:
70
  """Post-process answers to match GAIA's exact-match requirements."""
 
114
  messages: Annotated[List, add_messages]
115
  question: str
116
  task_id: str
117
+ file_data: pd.DataFrame | bytes | None
118
  search_result: str
119
  answer: str
120
  attempts: int
 
136
  Analyze the question to determine the required action:
137
 
138
  - List tasks (keywords: "list," "comma-separated," "categorize"): Action: reason, Input: none
139
+ - File tasks (keywords: "attached," "Excel," "spreadsheet", ".mp3", "image"): Action: fetch_file, Input: none
140
  - Search tasks (keywords: "who," "when," "historical," "competition", "wikipedia"): Action: search, Input: specific query
141
  - Number tasks (keywords: "how many," "count"): Action: search, Input: specific query
142
  - Fallback: Action: reason, Input: none
 
165
  Steps:
166
  1. Analyze question format (list, USD, name, number).
167
  2. Use data:
168
+ - File: Analyze DataFrame, audio transcript, or image description.
169
  - Search: Extract names, numbers, codes.
170
  3. Reason:
171
  - Lists: Filter, alphabetize.
172
+ - Files: Sum columns, extract text, or describe visuals.
173
  - Search: Parse for key info.
174
  4. Format:
175
  - Lists: 'item1, item2' (alphabetized).
 
181
  Specific instructions:
182
  - Mercedes Sosa studio albums (2000-2009): Misa Criolla (2000), Acústico (2002), Corazón Libre (2005), Cantora 1 (2009), Cantora 2 (2009). Answer: 5
183
  - Vegetable lists: Only non-botanical fruits (broccoli, celery, fresh basil, green beans, lettuce, sweet potatoes).
184
+ - .mp3 files: Transcribe audio and extract relevant info.
185
+ - Image-based (e.g., chess): Describe image or return error if no image provided.
186
 
187
  Examples:
188
  - Q: List of vegetables...
 
203
  Validate the answer:
204
 
205
  - Check format (list, USD, name, number).
206
+ - Verify relevance to question.
207
  - Return 'valid' or 'invalid: [reason]'.
208
  - Numbers: Ensure non-negative for counts.
209
+ - Allow reasonable answers to avoid excessive retries.
210
 
211
  Question: {question}
212
  Answer: {answer}
213
  """)
214
  self.graph = self._build_graph()
215
 
216
+ async def fetch_file(self, task_id: str) -> pd.DataFrame | bytes | None:
217
  @retry(stop=stop_after_attempt(6), wait=wait_exponential(multiplier=1, min=2, max=5))
218
  def _fetch():
219
  file_url = f"{self.api_url}/files/{task_id}"
 
223
  content = io.BytesIO(response.content)
224
  if file_url.endswith('.xlsx') or file_url.endswith('.xls'):
225
  df = pd.read_excel(content)
226
+ logging.info(f"Excel file fetched. Shape: {df.shape}, Columns: {list(df.columns)}")
227
  return df
228
+ elif file_url.endswith('.mp3'):
229
+ logging.info("Audio file fetched.")
230
+ return response.content
231
+ elif file_url.endswith('.png') or file_url.endswith('.jpg'):
232
+ logging.info("Image file fetched.")
233
+ return response.content
234
  logging.warning(f"Unsupported file type for task {task_id}")
235
  return None
236
 
 
242
 
243
  def _build_graph(self):
244
  workflow = StateGraph(AgentState)
245
+ # Explicitly set recursion limit
246
  workflow.config = {"recursion_limit": 50}
247
 
248
  def classify(state: AgentState) -> AgentState:
 
259
  task_id=state["task_id"],
260
  attempts=state["attempts"]
261
  ))
262
+ response_content = str(response.content).strip()
263
  action = response_content.split("Action: ")[1].split("\n")[0].strip() if "Action: " in response_content else "reason"
264
  action_input = response_content.split("Input: ")[1].strip() if "Input: " in response_content else "none"
265
  except Exception as e:
 
273
 
274
  async def fetch_file_node(state: AgentState) -> AgentState:
275
  logging.info(f"Fetching file for task {state['task_id']}")
276
+ if state["file_data"] is None and state["attempts"] < 2:
277
  try:
278
  state["file_data"] = await asyncio.wait_for(self.fetch_file(state["task_id"]), timeout=30)
279
  state["messages"].append(f"Fetched file: {state['file_data'] is not None}")
 
286
 
287
  async def search_node(state: AgentState) -> AgentState:
288
  logging.info(f"Searching for task {state['task_id']}")
289
+ if state["attempts"] < 2:
290
  action_input = state["action_input"]
291
  if action_input != "none":
292
  try:
 
310
 
311
  def generate_answer(state: AgentState) -> AgentState:
312
  logging.info(f"Generating answer for task {state['task_id']}")
313
+ file_data_str = "none"
314
+ if isinstance(state["file_data"], pd.DataFrame):
315
+ file_data_str = state["file_data"].to_dict()
316
+ elif isinstance(state["file_data"], bytes):
317
+ if "mp3" in state["question"].lower():
318
+ file_data_str = transcribe_audio(state["file_data"])
319
+ else:
320
+ file_data_str = "Image data; cannot process directly."
321
  try:
322
  response = self.llm.invoke(self.answer_prompt.format(
323
  question=state["question"],
 
344
  logging.error(f"Critique error: {str(e)}")
345
  critique = "valid" # Avoid looping on critique failure
346
  state["messages"].append(f"Critique: {critique}")
 
347
  state["attempts"] += 1
348
+ if "invalid" in critique.lower() and state["attempts"] < 1: # Reduced retries to 1
349
  state["action"] = "reason"
350
  state["action_input"] = "none"
351
  else:
 
359
  workflow.add_node("critique", critique_answer)
360
 
361
  def route_action(state: AgentState) -> str:
362
+ if state["attempts"] >= 2 or state["action"] is None:
363
  return END
364
  return state["action"] if state["action"] in ["fetch_file", "search", "reason"] else "generate_answer"
365