Gabe commited on
Commit
1e6b798
·
1 Parent(s): 9e3bc51

better score

Browse files
Files changed (2) hide show
  1. app.py +124 -312
  2. requirements.txt +2 -1
app.py CHANGED
@@ -7,6 +7,7 @@ import io
7
  import contextlib
8
  from typing import TypedDict, Annotated
9
  import torch
 
10
 
11
  # --- Multimodal & Web Tool Imports ---
12
  from transformers import pipeline
@@ -16,7 +17,7 @@ from bs4 import BeautifulSoup
16
 
17
  # --- LangChain & LangGraph Imports ---
18
  from langgraph.graph.message import add_messages
19
- from langchain_core.messages import AnyMessage, HumanMessage, AIMessage, ToolMessage
20
  from langgraph.prebuilt import ToolNode
21
  from langgraph.graph import START, StateGraph
22
  from langgraph.prebuilt import tools_condition
@@ -27,13 +28,13 @@ from langchain_core.tools import tool
27
 
28
  # (Keep Constants as is)
29
  # --- Constants ---
30
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
31
 
32
  # --- Initialize ASR Pipeline (for Audio Tool) ---
33
  # Load the model once when the app starts for efficiency
34
  try:
35
  asr_pipeline = pipeline(
36
- "automatic-speech-recognition",
37
  model="openai/whisper-base",
38
  torch_dtype=torch.float16, # Use float16 for faster inference
39
  device_map="auto" # Use GPU if available
@@ -47,7 +48,7 @@ except Exception as e:
47
 
48
  @tool
49
  def search_tool(query: str) -> str:
50
- """Calls DuckDuckGo search and returns the results."""
51
  print(f"--- Calling Search Tool with query: {query} ---")
52
  try:
53
  search = DuckDuckGoSearchRun()
@@ -59,61 +60,78 @@ def search_tool(query: str) -> str:
59
  def code_interpreter(code: str) -> str:
60
  """
61
  Executes a string of Python code and returns its stdout, stderr, and any error.
62
- Use this for calculations, data manipulation, or any other Python operation.
63
- The code runs in a sandboxed environment.
64
- Note: 'pandas' and 'openpyxl' are available.
65
  """
66
  print(f"--- Calling Code Interpreter with code:\n{code}\n---")
67
  output_stream = io.StringIO()
68
  error_stream = io.StringIO()
69
-
70
  try:
71
  # Use contextlib to redirect stdout and stderr
72
  with contextlib.redirect_stdout(output_stream), contextlib.redirect_stderr(error_stream):
73
  # Execute the code. Provide 'pd' (pandas) in the globals
74
  exec(code, {"pd": pd}, {})
75
-
76
  stdout = output_stream.getvalue()
77
  stderr = error_stream.getvalue()
78
-
79
  if stderr:
80
  return f"Error: {stderr}\nStdout: {stdout}"
81
- return f"Success:\n{stdout}"
82
-
 
 
83
  except Exception as e:
84
  # Capture any exception during exec
85
  return f"Execution failed with error: {str(e)}"
86
 
87
  @tool
88
  def read_file(path: str) -> str:
89
- """Reads the content of a file at the specified path."""
90
  print(f"--- Calling Read File Tool at path: {path} ---")
91
  try:
92
- with open(path, 'r', encoding='utf-8') as f:
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  return f.read()
94
  except Exception as e:
95
  return f"Error reading file {path}: {str(e)}"
96
 
97
  @tool
98
  def write_file(path: str, content: str) -> str:
99
- """Writes the given content to a file at the specified path."""
100
  print(f"--- Calling Write File Tool at path: {path} ---")
101
  try:
102
  # Ensure the directory exists
103
- os.makedirs(os.path.dirname(path), exist_ok=True)
104
-
105
- with open(path, 'w', encoding='utf-8') as f:
 
106
  f.write(content)
107
- return f"Successfully wrote to file {path}."
108
  except Exception as e:
109
  return f"Error writing to file {path}: {str(e)}"
110
 
111
  @tool
112
  def list_directory(path: str = ".") -> str:
113
- """Lists the contents of a directory at the specified path."""
114
  print(f"--- Calling List Directory Tool at path: {path} ---")
115
  try:
116
- files = os.listdir(path)
 
117
  return "\n".join(files) if files else "Directory is empty."
118
  except Exception as e:
119
  return f"Error listing directory {path}: {str(e)}"
@@ -121,21 +139,28 @@ def list_directory(path: str = ".") -> str:
121
  @tool
122
  def audio_transcription_tool(file_path: str) -> str:
123
  """
124
- Transcribes an audio file (like .mp3 or .wav) and returns the text.
 
125
  """
126
  print(f"--- Calling Audio Transcription Tool at path: {file_path} ---")
127
  if not asr_pipeline:
128
  return "Error: Audio transcription pipeline is not available."
129
  try:
130
- if not os.path.exists(file_path):
131
- # GAIA questions might provide relative paths, so we check
132
- if os.path.exists(os.path.basename(file_path)):
133
- file_path = os.path.basename(file_path)
134
- else:
135
- return f"Error: File not found at {file_path}"
136
-
 
 
 
 
 
 
137
  # The pipeline handles file loading
138
- transcription = asr_pipeline(file_path)
139
  print("--- Transcription Complete ---")
140
  return transcription["text"]
141
  except Exception as e:
@@ -144,47 +169,72 @@ def audio_transcription_tool(file_path: str) -> str:
144
  @tool
145
  def get_youtube_transcript(video_url: str) -> str:
146
  """
147
- Fetches the transcript for a given YouTube video URL.
148
  """
149
  print(f"--- Calling YouTube Transcript Tool for URL: {video_url} ---")
150
  try:
151
- # Extract video ID from URL
152
- video_id = video_url.split("v=")[1].split("&")[0]
 
 
 
 
 
 
 
 
153
  transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
154
-
155
  # Combine all transcript parts into one string
156
  full_transcript = " ".join([item["text"] for item in transcript_list])
157
  print("--- Transcript Fetched ---")
158
- return full_transcript
 
159
  except Exception as e:
160
  return f"Error fetching YouTube transcript: {str(e)}"
161
 
162
  @tool
163
  def scrape_web_page(url: str) -> str:
164
  """
165
- Fetches the full text content of a given web page URL.
 
166
  """
167
  print(f"--- Calling Web Scraper Tool for URL: {url} ---")
168
  try:
169
- response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=10)
170
- response.raise_for_status() # Raise an error for bad responses
171
-
 
 
 
 
 
172
  soup = BeautifulSoup(response.text, 'html.parser')
173
-
174
- # Remove script/style tags
175
- for script in soup(["script", "style", "nav", "footer", "aside"]):
176
- script.extract()
177
-
178
- text = soup.get_text()
179
-
180
- # Clean up whitespace
 
 
 
 
 
181
  lines = (line.strip() for line in text.splitlines())
182
  chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
183
  text = '\n'.join(chunk for chunk in chunks if chunk)
 
184
  print("--- Web Page Scraped ---")
185
- return text[:8000] # Return first 8000 chars to avoid overload
 
 
 
 
186
  except Exception as e:
187
- return f"Error scraping web page: {str(e)}"
188
 
189
  # --- End of Tool Definitions ---
190
 
@@ -197,12 +247,11 @@ class AgentState(TypedDict):
197
  # --- Basic Agent Definition ---
198
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
199
  class BasicAgent:
200
-
201
  def __init__(self):
202
  print("BasicAgent (LangGraph) initialized.")
203
-
204
  # 1. Get API Token from Space Secrets
205
- # Go to your Space's Settings -> Secrets and add HUGGINGFACEHUB_API_TOKEN
206
  HUGGINGFACEHUB_API_TOKEN = os.getenv("HUGGINGFACEHUB_API_TOKEN")
207
  if not HUGGINGFACEHUB_API_TOKEN:
208
  raise ValueError("HUGGINGFACEHUB_API_TOKEN secret is not set! Please add it to your Space secrets.")
@@ -218,264 +267,27 @@ class BasicAgent:
218
  get_youtube_transcript,
219
  scrape_web_page
220
  ]
221
-
222
- # 3. Initialize the LLM
223
- # We wrap HuggingFaceEndpoint in ChatHuggingFace for LangChain compatibility
224
- llm = HuggingFaceEndpoint(
225
- repo_id="HuggingFaceH4/zephyr-7b-beta", # A good, fast model for tool use
226
- # repo_id="Qwen/Qwen2.5-Coder-32B-Instruct", # Your chosen model
227
- huggingfacehub_api_token=HUGGINGFACEHUB_API_TOKEN,
228
- max_new_tokens=1500,
229
- temperature=0.1,
230
- )
231
- chat_llm = ChatHuggingFace(llm=llm)
232
-
233
- # 4. Bind tools to the LLM
234
- self.llm_with_tools = chat_llm.bind_tools(self.tools)
235
-
236
- # 5. Define the Agent Node
237
- def agent_node(state: AgentState):
238
- print("--- Running Agent Node ---")
239
- ai_message = self.llm_with_tools.invoke(state["messages"])
240
- print(f"AI Message: {ai_message.pretty_repr()}")
241
- return {"messages": [ai_message]}
242
-
243
- # 6. Define the Tool Node
244
- tool_node = ToolNode(self.tools)
245
-
246
- # 7. Create the Graph
247
- graph_builder = StateGraph(AgentState)
248
-
249
- # Add the nodes
250
- graph_builder.add_node("agent", agent_node)
251
- graph_builder.add_node("tools", tool_node)
252
-
253
- # Define the edges
254
- graph_builder.add_edge(START, "agent")
255
-
256
- # Add the conditional edge
257
- graph_builder.add_conditional_edges(
258
- "agent",
259
- tools_condition,
260
- {
261
- "tools": "tools",
262
- "__end__": "__end__",
263
- },
264
- )
265
- graph_builder.add_edge("tools", "agent")
266
-
267
- # 8. Compile the graph and store it
268
- self.graph = graph_builder.compile()
269
- print("Graph compiled successfully with all tools.")
270
-
271
- def __call__(self, question: str) -> str:
272
- print(f"Agent received question (first 50 chars): {question[:50]}...")
273
-
274
- # Prepare the input for the graph
275
- graph_input = {"messages": [HumanMessage(content=question)]}
276
-
277
- final_answer = ""
278
-
279
- # Stream the graph's execution
280
- try:
281
- # We use stream_mode="values" to get the full state at each step
282
- for event in self.graph.stream(graph_input, stream_mode="values"):
283
- last_message = event["messages"][-1]
284
-
285
- # Update the final answer with the latest AI message
286
- if isinstance(last_message, AIMessage):
287
- if last_message.content:
288
- print(f"AI: {last_message.content[:200]}...")
289
- final_answer = last_message.content
290
- elif isinstance(last_message, ToolMessage):
291
- print(f"Tool Result: {last_message.content[:200]}...")
292
-
293
- print(f"Agent returning final answer: {final_answer}")
294
- return final_answer
295
-
296
- except Exception as e:
297
- print(f"Error running agent graph: {e}")
298
- return f"AGENT ERROR: {e}"
299
-
300
-
301
- # --- (Original Template Code Starts Here) ---
302
-
303
- def run_and_submit_all( profile: gr.OAuthProfile | None):
304
- """
305
- Fetches all questions, runs the BasicAgent on them, submits all answers,
306
- and displays the results.
307
- """
308
- # --- Determine HF Space Runtime URL and Repo URL ---
309
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
310
- if profile:
311
- username= f"{profile.username}"
312
- print(f"User logged in: {username}")
313
- else:
314
- print("User not logged in.")
315
- return "Please Login to Hugging Face with the button.", None
316
-
317
- api_url = DEFAULT_API_URL
318
- questions_url = f"{api_url}/questions"
319
- submit_url = f"{api_url}/submit"
320
-
321
- # 1. Instantiate Agent ( modify this part to create your agent)
322
- print("Initializing agent...")
323
- try:
324
- agent = BasicAgent()
325
- except Exception as e:
326
- print(f"Error instantiating agent: {e}")
327
- return f"Error initializing agent: {e}", None
328
- print("Agent initialized successfully.")
329
-
330
- # 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)
331
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
332
- print(f"Agent code URL: {agent_code}")
333
-
334
- # 2. Fetch Questions
335
- print(f"Fetching questions from: {questions_url}")
336
- try:
337
- response = requests.get(questions_url, timeout=15)
338
- response.raise_for_status()
339
- questions_data = response.json()
340
- if not questions_data:
341
- print("Fetched questions list is empty.")
342
- return "Fetched questions list is empty or invalid format.", None
343
- print(f"Fetched {len(questions_data)} questions.")
344
- except requests.exceptions.RequestException as e:
345
- print(f"Error fetching questions: {e}")
346
- return f"Error fetching questions: {e}", None
347
- except requests.exceptions.JSONDecodeError as e:
348
- print(f"Error decoding JSON response from questions endpoint: {e}")
349
- print(f"Response text: {response.text[:500]}")
350
- return f"Error decoding server response for questions: {e}", None
351
- except Exception as e:
352
- print(f"An unexpected error occurred fetching questions: {e}")
353
- return f"An unexpected error occurred fetching questions: {e}", None
354
-
355
- # 3. Run your Agent
356
- results_log = []
357
- answers_payload = []
358
- print(f"Running agent on {len(questions_data)} questions...")
359
-
360
- # Set a limit for testing. Remove '[:question_limit]' for the full submission.
361
- # question_limit = 10
362
-
363
- for item in questions_data: # [:question_limit]: # Using limit here
364
- task_id = item.get("task_id")
365
- question_text = item.get("question")
366
- if not task_id or question_text is None:
367
- print(f"Skipping item with missing task_id or question: {item}")
368
- continue
369
-
370
- print(f"\n--- Running Task {task_id} ---")
371
- try:
372
- submitted_answer = agent(question_text)
373
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
374
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
375
- print(f"--- Task {task_id} Complete ---")
376
- except Exception as e:
377
- print(f"Error running agent on task {task_id}: {e}")
378
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
379
-
380
- if not answers_payload:
381
- print("Agent did not produce any answers to submit.")
382
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
383
-
384
- # 4. Prepare Submission
385
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
386
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
387
- print(status_update)
388
-
389
- # 5. Submit
390
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
391
- try:
392
- response = requests.post(submit_url, json=submission_data, timeout=60)
393
- response.raise_for_status()
394
- result_data = response.json()
395
- final_status = (
396
- f"Submission Successful!\n"
397
- f"User: {result_data.get('username')}\n"
398
- f"Overall Score: {result_data.get('score', 'N/A')}% "
399
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
400
- f"Message: {result_data.get('message', 'No message received.')}"
401
- )
402
- print("Submission successful.")
403
- results_df = pd.DataFrame(results_log)
404
- return final_status, results_df
405
- except requests.exceptions.HTTPError as e:
406
- error_detail = f"Server responded with status {e.response.status_code}."
407
- try:
408
- error_json = e.response.json()
409
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
410
- except requests.exceptions.JSONDecodeError:
411
- error_detail += f" Response: {e.response.text[:500]}"
412
- status_message = f"Submission Failed: {error_detail}"
413
- print(status_message)
414
- results_df = pd.DataFrame(results_log)
415
- return status_message, results_df
416
- except requests.exceptions.Timeout:
417
- status_message = "Submission Failed: The request timed out."
418
- print(status_message)
419
- results_df = pd.DataFrame(results_log)
420
- return status_message, results_df
421
- except requests.exceptions.RequestException as e:
422
- status_message = f"Submission Failed: Network error - {e}"
423
- print(status_message)
424
- results_df = pd.DataFrame(results_log)
425
- return status_message, results_df
426
- except Exception as e:
427
- status_message = f"An unexpected error occurred during submission: {e}"
428
- print(status_message)
429
- results_df = pd.DataFrame(results_log)
430
- return status_message, results_df
431
-
432
- # --- Build Gradio Interface using Blocks ---
433
- with gr.Blocks() as demo:
434
- gr.Markdown("# Basic Agent Evaluation Runner")
435
- gr.Markdown(
436
- """
437
- **Instructions:**
438
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
439
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
440
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
441
- ---
442
- **Disclaimers:**
443
- 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).
444
- 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.
445
- """
446
- )
447
- gr.LoginButton()
448
- run_button = gr.Button("Run Evaluation & Submit All Answers")
449
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
450
- # Removed max_rows=10 from DataFrame constructor
451
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
452
-
453
- run_button.click(
454
- fn=run_and_submit_all,
455
- outputs=[status_output, results_table]
456
- )
457
-
458
- if __name__ == "__main__":
459
- print("\n" + "-"*30 + " App Starting " + "-"*30)
460
-
461
- # Check for SPACE_HOST and SPACE_ID at startup for information
462
- space_host_startup = os.getenv("SPACE_HOST")
463
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
464
-
465
- if space_host_startup:
466
- print(f"✅ SPACE_HOST found: {space_host_startup}")
467
- print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
468
- else:
469
- print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
470
-
471
- if space_id_startup: # Print repo URLs if SPACE_ID is found
472
- print(f"✅ SPACE_ID found: {space_id_startup}")
473
- print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
474
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
475
- else:
476
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
477
-
478
- print("-"*(60 + len(" App Starting ")) + "\n")
479
- print("Launching Gradio Interface for Basic Agent Evaluation...")
480
- demo.launch(debug=True, share=False)
481
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import contextlib
8
  from typing import TypedDict, Annotated
9
  import torch
10
+ import json # For robust tool call parsing/generation if needed
11
 
12
  # --- Multimodal & Web Tool Imports ---
13
  from transformers import pipeline
 
17
 
18
  # --- LangChain & LangGraph Imports ---
19
  from langgraph.graph.message import add_messages
20
+ from langchain_core.messages import AnyMessage, HumanMessage, AIMessage, ToolMessage, SystemMessage
21
  from langgraph.prebuilt import ToolNode
22
  from langgraph.graph import START, StateGraph
23
  from langgraph.prebuilt import tools_condition
 
28
 
29
  # (Keep Constants as is)
30
  # --- Constants ---
31
+ DEFAULT_API_URL = "[https://agents-course-unit4-scoring.hf.space](https://agents-course-unit4-scoring.hf.space)"
32
 
33
  # --- Initialize ASR Pipeline (for Audio Tool) ---
34
  # Load the model once when the app starts for efficiency
35
  try:
36
  asr_pipeline = pipeline(
37
+ "automatic-speech-recognition",
38
  model="openai/whisper-base",
39
  torch_dtype=torch.float16, # Use float16 for faster inference
40
  device_map="auto" # Use GPU if available
 
48
 
49
  @tool
50
  def search_tool(query: str) -> str:
51
+ """Calls DuckDuckGo search and returns the results. Use this for recent information or general web searches."""
52
  print(f"--- Calling Search Tool with query: {query} ---")
53
  try:
54
  search = DuckDuckGoSearchRun()
 
60
  def code_interpreter(code: str) -> str:
61
  """
62
  Executes a string of Python code and returns its stdout, stderr, and any error.
63
+ Use this for calculations, data manipulation (including pandas on dataframes read from files), list operations, string manipulations, or any other Python operation.
64
+ The code runs in a sandboxed environment. 'pandas' (as pd) and 'openpyxl' are available.
65
+ Ensure the code is complete and executable. If printing, use print().
66
  """
67
  print(f"--- Calling Code Interpreter with code:\n{code}\n---")
68
  output_stream = io.StringIO()
69
  error_stream = io.StringIO()
70
+
71
  try:
72
  # Use contextlib to redirect stdout and stderr
73
  with contextlib.redirect_stdout(output_stream), contextlib.redirect_stderr(error_stream):
74
  # Execute the code. Provide 'pd' (pandas) in the globals
75
  exec(code, {"pd": pd}, {})
76
+
77
  stdout = output_stream.getvalue()
78
  stderr = error_stream.getvalue()
79
+
80
  if stderr:
81
  return f"Error: {stderr}\nStdout: {stdout}"
82
+ if stdout:
83
+ return f"Success:\n{stdout}"
84
+ return "Success: Code executed without error and produced no stdout."
85
+
86
  except Exception as e:
87
  # Capture any exception during exec
88
  return f"Execution failed with error: {str(e)}"
89
 
90
  @tool
91
  def read_file(path: str) -> str:
92
+ """Reads the content of a file at the specified path. Use this to examine files provided in the question."""
93
  print(f"--- Calling Read File Tool at path: {path} ---")
94
  try:
95
+ # Try finding the file relative to the app directory first
96
+ script_dir = os.path.dirname(__file__)
97
+ full_path = os.path.join(script_dir, path)
98
+ if not os.path.exists(full_path):
99
+ # If not found, try the direct path (might be absolute or relative to cwd)
100
+ full_path = path
101
+ if not os.path.exists(full_path):
102
+ # Try basename for GAIA questions providing just the filename
103
+ if os.path.exists(os.path.basename(path)):
104
+ full_path = os.path.basename(path)
105
+ else:
106
+ return f"Error: File not found at '{path}', '{os.path.join(script_dir, path)}', or '{os.path.basename(path)}'"
107
+
108
+ with open(full_path, 'r', encoding='utf-8') as f:
109
  return f.read()
110
  except Exception as e:
111
  return f"Error reading file {path}: {str(e)}"
112
 
113
  @tool
114
  def write_file(path: str, content: str) -> str:
115
+ """Writes the given content to a file at the specified path. Creates directories if they don't exist."""
116
  print(f"--- Calling Write File Tool at path: {path} ---")
117
  try:
118
  # Ensure the directory exists
119
+ full_path = os.path.join(os.path.dirname(__file__), path) # Write relative to script dir
120
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
121
+
122
+ with open(full_path, 'w', encoding='utf-8') as f:
123
  f.write(content)
124
+ return f"Successfully wrote to file {path} (relative to app)."
125
  except Exception as e:
126
  return f"Error writing to file {path}: {str(e)}"
127
 
128
  @tool
129
  def list_directory(path: str = ".") -> str:
130
+ """Lists the contents (files and directories) of a directory at the specified path relative to the app."""
131
  print(f"--- Calling List Directory Tool at path: {path} ---")
132
  try:
133
+ full_path = os.path.join(os.path.dirname(__file__), path) # List relative to script dir
134
+ files = os.listdir(full_path)
135
  return "\n".join(files) if files else "Directory is empty."
136
  except Exception as e:
137
  return f"Error listing directory {path}: {str(e)}"
 
139
  @tool
140
  def audio_transcription_tool(file_path: str) -> str:
141
  """
142
+ Transcribes an audio file (like .mp3 or .wav) using Whisper and returns the text content.
143
+ Use this for questions involving audio file analysis.
144
  """
145
  print(f"--- Calling Audio Transcription Tool at path: {file_path} ---")
146
  if not asr_pipeline:
147
  return "Error: Audio transcription pipeline is not available."
148
  try:
149
+ # Try finding the file relative to the app directory first
150
+ script_dir = os.path.dirname(__file__)
151
+ full_path = os.path.join(script_dir, file_path)
152
+ if not os.path.exists(full_path):
153
+ # If not found, try the direct path
154
+ full_path = file_path
155
+ if not os.path.exists(full_path):
156
+ # Try basename for GAIA questions
157
+ if os.path.exists(os.path.basename(file_path)):
158
+ full_path = os.path.basename(file_path)
159
+ else:
160
+ return f"Error: Audio file not found at '{file_path}', '{os.path.join(script_dir, file_path)}', or '{os.path.basename(file_path)}'"
161
+
162
  # The pipeline handles file loading
163
+ transcription = asr_pipeline(full_path)
164
  print("--- Transcription Complete ---")
165
  return transcription["text"]
166
  except Exception as e:
 
169
  @tool
170
  def get_youtube_transcript(video_url: str) -> str:
171
  """
172
+ Fetches the transcript for a given YouTube video URL. Use this for questions about YouTube video content.
173
  """
174
  print(f"--- Calling YouTube Transcript Tool for URL: {video_url} ---")
175
  try:
176
+ # Extract video ID from URL more robustly
177
+ video_id = None
178
+ if "watch?v=" in video_url:
179
+ video_id = video_url.split("v=")[1].split("&")[0]
180
+ elif "youtu.be/" in video_url:
181
+ video_id = video_url.split("youtu.be/")[1].split("?")[0]
182
+
183
+ if not video_id:
184
+ return f"Error: Could not extract video ID from URL: {video_url}"
185
+
186
  transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
187
+
188
  # Combine all transcript parts into one string
189
  full_transcript = " ".join([item["text"] for item in transcript_list])
190
  print("--- Transcript Fetched ---")
191
+ # Return a limited amount to avoid overwhelming the context
192
+ return full_transcript[:8000]
193
  except Exception as e:
194
  return f"Error fetching YouTube transcript: {str(e)}"
195
 
196
  @tool
197
  def scrape_web_page(url: str) -> str:
198
  """
199
+ Fetches the primary text content of a given web page URL, removing navigation, footer, scripts, and styles.
200
+ Use this when you need the full content of a webpage found via search.
201
  """
202
  print(f"--- Calling Web Scraper Tool for URL: {url} ---")
203
  try:
204
+ headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
205
+ response = requests.get(url, headers=headers, timeout=15) # Increased timeout
206
+ response.raise_for_status() # Raise an error for bad responses (4xx or 5xx)
207
+
208
+ # Check content type to avoid parsing non-HTML
209
+ if 'html' not in response.headers.get('Content-Type', '').lower():
210
+ return f"Error: URL {url} did not return HTML content."
211
+
212
  soup = BeautifulSoup(response.text, 'html.parser')
213
+
214
+ # Remove common non-content tags
215
+ for tag in soup(["script", "style", "nav", "footer", "aside", "header", "form"]):
216
+ tag.extract()
217
+
218
+ # Attempt to find the main content area (heuristics, may not always work)
219
+ main_content = soup.find('main') or soup.find('article') or soup.find('div', role='main') or soup.body
220
+ if not main_content:
221
+ main_content = soup # Fallback to the whole soup if no main area found
222
+
223
+ text = main_content.get_text(separator='\n', strip=True)
224
+
225
+ # Clean up excessive whitespace
226
  lines = (line.strip() for line in text.splitlines())
227
  chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
228
  text = '\n'.join(chunk for chunk in chunks if chunk)
229
+
230
  print("--- Web Page Scraped ---")
231
+ # Limit context size
232
+ return text[:8000]
233
+
234
+ except requests.exceptions.RequestException as e:
235
+ return f"Error fetching web page {url}: {str(e)}"
236
  except Exception as e:
237
+ return f"Error scraping web page {url}: {str(e)}"
238
 
239
  # --- End of Tool Definitions ---
240
 
 
247
  # --- Basic Agent Definition ---
248
  # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
249
  class BasicAgent:
250
+
251
  def __init__(self):
252
  print("BasicAgent (LangGraph) initialized.")
253
+
254
  # 1. Get API Token from Space Secrets
 
255
  HUGGINGFACEHUB_API_TOKEN = os.getenv("HUGGINGFACEHUB_API_TOKEN")
256
  if not HUGGINGFACEHUB_API_TOKEN:
257
  raise ValueError("HUGGINGFACEHUB_API_TOKEN secret is not set! Please add it to your Space secrets.")
 
267
  get_youtube_transcript,
268
  scrape_web_page
269
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
 
271
+ # 3. Define the Improved System Prompt
272
+ tool_descriptions = "\n".join([f"- {tool.name}: {tool.description}" for tool in self.tools])
273
+ self.system_prompt = f"""You are a highly intelligent and meticulous AI assistant built to answer questions from the GAIA benchmark.
274
+ Your primary goal is to provide **only the concise, factual, and direct answer** to the user's question, exactly matching the format required by the benchmark (e.g., a name, a number, a specific string format, a comma-separated list).
275
+
276
+ **CRITICAL INSTRUCTIONS:**
277
+ * **DO NOT** include conversational filler (e.g., "Sure, I can help...", "The answer is...", "Here is the information...").
278
+ * **DO NOT** explain your reasoning or the steps you took unless the question *explicitly* asks for it.
279
+ * **DO NOT** repeat the question in your final answer.
280
+ * **FINAL ANSWER FORMAT:** Your final response must contain *only* the answer itself.
281
+
282
+ You have access to the following tools to gather information and perform actions:
283
+ {tool_descriptions}
284
+
285
+ **TOOL USAGE PROTOCOL:**
286
+ * To use a tool, you MUST respond ONLY with a single JSON object formatted exactly like this:
287
+ ```json
288
+ {{
289
+ "tool": "tool_name",
290
+ "tool_input": {{ "arg_name1": "value1", "arg_name2": "value2", ... }}
291
+ }}
292
+ ```
293
+ """
requirements.txt CHANGED
@@ -12,4 +12,5 @@ torchaudio
12
  librosa
13
  youtube-transcript-api
14
  beautifulsoup4
15
- openpyxl
 
 
12
  librosa
13
  youtube-transcript-api
14
  beautifulsoup4
15
+ openpyxl
16
+ accelerate