hf_agent_course_final_assignment/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
hf_agent_course_final_assignment/.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # enviroment files
2
+ .env
3
+ *.jpg
4
+ *.png
5
+ *.mp3
6
+ *.xlsx
7
+ f918266a-b3e0-4914-865d-4faa564f1aef.py
8
+
9
+ # pycache
10
+ __pycache__/
hf_agent_course_final_assignment/README.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Agent Course Final Assignment
3
+ emoji: 🕵🏻‍♂️
4
+ colorFrom: indigo
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 5.25.2
8
+ app_file: app.py
9
+ pinned: false
10
+ hf_oauth: true
11
+ # optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes.
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
+ ## Question analysis
18
+
19
+ We need following functionalities in our agent:
20
+ - [x] General orchestrator agent: base on the question, recognize what tools are needed to answer the question, call the tools, collect the results, and generate the final answer.
21
+ - [x] youtube video analysis
22
+ - [x] excel manipulation
23
+ - [x] web search (duckduckgo react agent)
24
+ - [x] within web search, we need also to search published papers
25
+ - [x] python exection (done, without llm, only naive code execution)
26
+ - [x] audio analysis (api calling inference client of mp3 transcript + chat model)
27
+ - [x] image analysis (api calling inference client of vlm model)
hf_agent_course_final_assignment/app.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ import inspect
5
+ import pandas as pd
6
+ from gaia_agent import GaiaAgent
7
+ from utils.fetch_files import fetch_files_gaia_validation_hub
8
+ # (Keep Constants as is)
9
+ # --- Constants ---
10
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
+
12
+ # --- Basic Agent Definition ---
13
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
14
+
15
+ def run_and_submit_all( profile: gr.OAuthProfile | None):
16
+ """
17
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
18
+ and displays the results.
19
+ """
20
+ # --- Determine HF Space Runtime URL and Repo URL ---
21
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
22
+
23
+ if profile:
24
+ username= f"{profile.username}"
25
+ print(f"User logged in: {username}")
26
+ else:
27
+ print("User not logged in.")
28
+ return "Please Login to Hugging Face with the button.", None
29
+
30
+ api_url = DEFAULT_API_URL
31
+ questions_url = f"{api_url}/questions"
32
+ submit_url = f"{api_url}/submit"
33
+
34
+ # 1. Instantiate Agent ( modify this part to create your agent)
35
+ try:
36
+ agent = GaiaAgent()
37
+ except Exception as e:
38
+ print(f"Error instantiating agent: {e}")
39
+ return f"Error initializing agent: {e}", None
40
+ # 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)
41
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
42
+ print(agent_code)
43
+
44
+ # 2. Fetch Questions
45
+ print(f"Fetching questions from: {questions_url}")
46
+ try:
47
+ response = requests.get(questions_url, timeout=15)
48
+ response.raise_for_status()
49
+ questions_data = response.json()
50
+ if not questions_data:
51
+ print("Fetched questions list is empty.")
52
+ return "Fetched questions list is empty or invalid format.", None
53
+ print(f"Fetched {len(questions_data)} questions.")
54
+ except requests.exceptions.RequestException as e:
55
+ print(f"Error fetching questions: {e}")
56
+ return f"Error fetching questions: {e}", None
57
+ except requests.exceptions.JSONDecodeError as e:
58
+ print(f"Error decoding JSON response from questions endpoint: {e}")
59
+ print(f"Response text: {response.text[:500]}")
60
+ return f"Error decoding server response for questions: {e}", None
61
+ except Exception as e:
62
+ print(f"An unexpected error occurred fetching questions: {e}")
63
+ return f"An unexpected error occurred fetching questions: {e}", None
64
+
65
+ # 3. Run your Agent
66
+ results_log = []
67
+ answers_payload = []
68
+ print(f"Running agent on {len(questions_data)} questions...")
69
+ for item in questions_data:
70
+ task_id = item.get("task_id")
71
+ question_text = item.get("question")
72
+ file_name = item.get("file_name")
73
+ if file_name != "":
74
+ file_path = fetch_files_gaia_validation_hub(file_name)
75
+ else:
76
+ file_path = ""
77
+ if not task_id or question_text is None:
78
+ print(f"Skipping item with missing task_id or question: {item}")
79
+ continue
80
+ try:
81
+ submitted_answer = agent(question_text, file_path)
82
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
83
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
84
+ except Exception as e:
85
+ print(f"Error running agent on task {task_id}: {e}")
86
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
87
+
88
+ if not answers_payload:
89
+ print("Agent did not produce any answers to submit.")
90
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
91
+
92
+ # 4. Prepare Submission
93
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
94
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
95
+ print(status_update)
96
+
97
+ # 5. Submit
98
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
99
+ try:
100
+ response = requests.post(submit_url, json=submission_data, timeout=60)
101
+ response.raise_for_status()
102
+ result_data = response.json()
103
+ final_status = (
104
+ f"Submission Successful!\n"
105
+ f"User: {result_data.get('username')}\n"
106
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
107
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
108
+ f"Message: {result_data.get('message', 'No message received.')}"
109
+ )
110
+ print("Submission successful.")
111
+ results_df = pd.DataFrame(results_log)
112
+ return final_status, results_df
113
+ except requests.exceptions.HTTPError as e:
114
+ error_detail = f"Server responded with status {e.response.status_code}."
115
+ try:
116
+ error_json = e.response.json()
117
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
118
+ except requests.exceptions.JSONDecodeError:
119
+ error_detail += f" Response: {e.response.text[:500]}"
120
+ status_message = f"Submission Failed: {error_detail}"
121
+ print(status_message)
122
+ results_df = pd.DataFrame(results_log)
123
+ return status_message, results_df
124
+ except requests.exceptions.Timeout:
125
+ status_message = "Submission Failed: The request timed out."
126
+ print(status_message)
127
+ results_df = pd.DataFrame(results_log)
128
+ return status_message, results_df
129
+ except requests.exceptions.RequestException as e:
130
+ status_message = f"Submission Failed: Network error - {e}"
131
+ print(status_message)
132
+ results_df = pd.DataFrame(results_log)
133
+ return status_message, results_df
134
+ except Exception as e:
135
+ status_message = f"An unexpected error occurred during submission: {e}"
136
+ print(status_message)
137
+ results_df = pd.DataFrame(results_log)
138
+ return status_message, results_df
139
+
140
+
141
+ # --- Build Gradio Interface using Blocks ---
142
+ with gr.Blocks() as demo:
143
+ gr.Markdown("# Basic Agent Evaluation Runner")
144
+ gr.Markdown(
145
+ """
146
+ **Instructions:**
147
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
148
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
149
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
150
+ ---
151
+ **Disclaimers:**
152
+ 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).
153
+ 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.
154
+ """
155
+ )
156
+
157
+ gr.LoginButton()
158
+
159
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
160
+
161
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
162
+ # Removed max_rows=10 from DataFrame constructor
163
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
164
+
165
+ run_button.click(
166
+ fn=run_and_submit_all,
167
+ outputs=[status_output, results_table]
168
+ )
169
+
170
+ if __name__ == "__main__":
171
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
172
+ # Check for SPACE_HOST and SPACE_ID at startup for information
173
+ space_host_startup = os.getenv("SPACE_HOST")
174
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
175
+
176
+ if space_host_startup:
177
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
178
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
179
+ else:
180
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
181
+
182
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
183
+ print(f"✅ SPACE_ID found: {space_id_startup}")
184
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
185
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
186
+ else:
187
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
188
+
189
+ print("-"*(60 + len(" App Starting ")) + "\n")
190
+
191
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
192
+ demo.launch(debug=True, share=False)
hf_agent_course_final_assignment/audio_agent.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import base64
5
+ import mimetypes
6
+ import argparse
7
+ from pathlib import Path
8
+ from typing import List, Dict, Any, Optional
9
+ from dotenv import load_dotenv
10
+ from huggingface_hub import InferenceClient
11
+
12
+ # Load environment variables (e.g., HF_TOKEN) from a .env file
13
+ load_dotenv()
14
+
15
+ # This is the system prompt you provided
16
+ AGENT_SYSTEM_PROMPT = """You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string."""
17
+
18
+
19
+ class AudioAgent:
20
+ """
21
+ Encapsulated agent for answering questions about an audio file.
22
+
23
+ This agent works in two stages:
24
+ 1. Transcribe the audio file using an ASR model.
25
+ 2. Pass the transcription and the user's question to a chat model
26
+ to get the final answer.
27
+
28
+ Usage:
29
+ # Assumes HF_TOKEN is in your environment
30
+ agent = AudioAgent()
31
+
32
+ # Ask a question with an audio file
33
+ question = "What ingredients are in this recipe?"
34
+ audio_path = "/path/to/your/recipe.mp3"
35
+ answer = agent.answer(question, audio_path)
36
+
37
+ # Or use the callable shortcut
38
+ answer = agent(question, audio_path)
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ asr_model_id: Optional[str] = None,
44
+ chat_model_id: Optional[str] = None,
45
+ provider: Optional[str] = "novita",
46
+ temperature: float = 0.2,
47
+ api_key: Optional[str] = None,
48
+ ):
49
+ """
50
+ Initializes the AudioAgent.
51
+
52
+ Args:
53
+ asr_model_id: The repo ID of the ASR (transcription) model.
54
+ chat_model_id: The repo ID of the chat model.
55
+ provider: The provider for the chat InferenceClient.
56
+ temperature: The generation temperature for the chat model.
57
+ api_key: The API key. Defaults to os.environ["HF_TOKEN"].
58
+ """
59
+ # Model for speech-to-text
60
+ self.asr_model_id = asr_model_id or "openai/whisper-large-v3"
61
+
62
+ # Model for answering the question based on the text
63
+ # Using a standard, high-performance chat model.
64
+ # You can change this to "meta-llama/Llama-4-Scout-17B-16E-Instruct"
65
+ # if you know it also performs well on text-only tasks.
66
+ self.chat_model_id = chat_model_id or "meta-llama/Llama-4-Scout-17B-16E-Instruct"
67
+
68
+ self.temperature = temperature
69
+
70
+ # Get API key from arg or environment
71
+ self.api_key = api_key or os.environ.get("HF_TOKEN")
72
+ if not self.api_key:
73
+ raise ValueError(
74
+ "API key not found. Please set the HF_TOKEN environment variable "
75
+ "or pass it as 'api_key' to the AudioAgent."
76
+ )
77
+
78
+ # 1. Client for ASR (using default HF provider)
79
+ # We pass the full model ID to use the standard HF inference API
80
+ self.asr_client = InferenceClient(
81
+ model=self.asr_model_id,
82
+ api_key=self.api_key
83
+ )
84
+
85
+ # 2. Client for Chat (using your specified provider)
86
+ # We pass the provider, and specify the model in the create call
87
+ self.chat_client = InferenceClient(
88
+ provider=provider,
89
+ api_key=self.api_key
90
+ )
91
+
92
+ # ---- Public API ----
93
+
94
+ def answer(self, question: str, audio_path: str) -> str:
95
+ """
96
+ Run the agent to answer a question about an audio file.
97
+
98
+ Args:
99
+ question: The text prompt to ask.
100
+ audio_path: The local file path to the audio file (e.g., .mp3).
101
+
102
+ Returns:
103
+ The parsed "Final Answer" string from the chat model.
104
+ """
105
+ # 1. Transcribe the audio
106
+ try:
107
+ transcription = self._transcribe(audio_path)
108
+ except FileNotFoundError:
109
+ return f"Error: Audio file not found at {audio_path}"
110
+ except Exception as e:
111
+ return f"Error during audio transcription: {e}"
112
+
113
+ # 2. Build the combined prompt for the chat model
114
+ combined_prompt = (
115
+ f"You will be given a user's question and the transcription of an audio file. "
116
+ f"Your task is to answer the question based *only* on the transcription.\n\n"
117
+ f"--- TRANSCRIPTION ---\n"
118
+ f"{transcription}\n"
119
+ f"--- END TRANSCRIPTION ---\n\n"
120
+ f"--- QUESTION ---\n"
121
+ f"{question}\n"
122
+ f"--- END QUESTION ---"
123
+ )
124
+
125
+ # 3. Build the messages payload
126
+ messages = self._build_chat_messages(combined_prompt)
127
+
128
+ # 4. Call the Chat API
129
+ try:
130
+ completion = self.chat_client.chat.completions.create(
131
+ model=self.chat_model_id,
132
+ messages=messages,
133
+ temperature=self.temperature,
134
+ )
135
+ response_text = completion.choices[0].message.content
136
+ except Exception as e:
137
+ return f"Error calling Chat API: {e}"
138
+
139
+ # 5. Parse and return the final answer
140
+ return self._parse_answer(response_text)
141
+
142
+ def __call__(self, question: str, audio_path: str) -> str:
143
+ """Callable shortcut for self.answer()."""
144
+ return self.answer(question, audio_path)
145
+
146
+ # ---- Internals ----
147
+
148
+ def _transcribe(self, audio_path: str) -> str:
149
+ """Transcribes the audio file to text using the ASR client."""
150
+ p = Path(audio_path)
151
+ if not p.exists():
152
+ raise FileNotFoundError(f"No file found at {audio_path}")
153
+
154
+ try:
155
+ # Read audio file as bytes
156
+ audio_bytes = p.read_bytes()
157
+
158
+ # Call ASR API
159
+ # This task returns a dict, e.g., {'text': '...'}
160
+ response = self.asr_client.automatic_speech_recognition(audio_bytes)
161
+
162
+ if "text" in response:
163
+ return response["text"]
164
+ else:
165
+ raise ValueError(f"ASR API response did not contain 'text': {response}")
166
+ except Exception as e:
167
+ # Re-raise as a more informative exception
168
+ raise RuntimeError(f"Error during audio transcription: {e}") from e
169
+
170
+ def _build_chat_messages(self, combined_prompt: str) -> List[Dict[str, Any]]:
171
+ """Constructs the message payload for the chat API."""
172
+ return [
173
+ {
174
+ "role": "system",
175
+ "content": AGENT_SYSTEM_PROMPT,
176
+ },
177
+ {
178
+ "role": "user",
179
+ "content": combined_prompt,
180
+ },
181
+ ]
182
+
183
+ def _parse_answer(self, response: str) -> str:
184
+ """Extracts the text after 'FINAL ANSWER: '."""
185
+ try:
186
+ # Use rpartition for robustness, splits on the *last* occurrence
187
+ _before, _marker, final_answer = response.rpartition("FINAL ANSWER: ")
188
+
189
+ if _marker:
190
+ return final_answer.strip()
191
+ else:
192
+ # If marker isn't found, return the whole response with a warning
193
+ print("Warning: Could not find 'FINAL ANSWER:' marker. Returning full response.")
194
+ return response.strip()
195
+
196
+ except Exception as e:
197
+ print(f"Error parsing response: {e}")
198
+ return response.strip()
199
+
200
+
201
+ # ---- CLI Entrypoint ----
202
+ def main():
203
+ """
204
+ Allows running the agent from the command line.
205
+
206
+ Example:
207
+ python audio_agent.py "What ingredients are listed?" "path/to/my_recipe.mp3"
208
+ """
209
+ parser = argparse.ArgumentParser(description="Class-based agent for audio questions.")
210
+ parser.add_argument("question", type=str, help="Your question (quoted).")
211
+ parser.add_argument("audio_path", type=str, help="Path to the audio file.")
212
+ args = parser.parse_args()
213
+
214
+ try:
215
+ audio_agent = AudioAgent()
216
+ answer = audio_agent.answer(args.question, args.audio_path)
217
+ print(answer)
218
+ except Exception as e:
219
+ print(f"An error occurred: {e}")
220
+
221
+ if __name__ == "__main__":
222
+ main()
hf_agent_course_final_assignment/evaluation_questions.json ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "task_id": "8e867cd7-cff9-4e6c-867a-ff5ddc2550be",
4
+ "question": "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia.",
5
+ "Level": "1",
6
+ "file_name": ""
7
+ },
8
+ {
9
+ "task_id": "a1e91b78-d3d8-4675-bb8d-62741b4b68a6",
10
+ "question": "In the video https://www.youtube.com/watch?v=L1vXCYZAYYM, what is the highest number of bird species to be on camera simultaneously?",
11
+ "Level": "1",
12
+ "file_name": ""
13
+ },
14
+ {
15
+ "task_id": "2d83110e-a098-4ebb-9987-066c06fa42d0",
16
+ "question": ".rewsna eht sa \"tfel\" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI",
17
+ "Level": "1",
18
+ "file_name": ""
19
+ },
20
+ {
21
+ "task_id": "cca530fc-4052-43b2-b130-b30968d8aa44",
22
+ "question": "Review the chess position provided in the image. It is black's turn. Provide the correct next move for black which guarantees a win. Please provide your response in algebraic notation.",
23
+ "Level": "1",
24
+ "file_name": "cca530fc-4052-43b2-b130-b30968d8aa44.png"
25
+ },
26
+ {
27
+ "task_id": "4fc2f1ae-8625-45b5-ab34-ad4433bc21f8",
28
+ "question": "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?",
29
+ "Level": "1",
30
+ "file_name": ""
31
+ },
32
+ {
33
+ "task_id": "6f37996b-2ac7-44b0-8e68-6d28256631b4",
34
+ "question": "Given this table defining * on the set S = {a, b, c, d, e}\n\n|*|a|b|c|d|e|\n|---|---|---|---|---|---|\n|a|a|b|c|b|d|\n|b|b|c|a|e|c|\n|c|c|a|b|b|a|\n|d|b|e|b|e|d|\n|e|d|b|a|d|c|\n\nprovide the subset of S involved in any possible counter-examples that prove * is not commutative. Provide your answer as a comma separated list of the elements in the set in alphabetical order.",
35
+ "Level": "1",
36
+ "file_name": ""
37
+ },
38
+ {
39
+ "task_id": "9d191bce-651d-4746-be2d-7ef8ecadb9c2",
40
+ "question": "Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec.\n\nWhat does Teal'c say in response to the question \"Isn't that hot?\"",
41
+ "Level": "1",
42
+ "file_name": ""
43
+ },
44
+ {
45
+ "task_id": "cabe07ed-9eca-40ea-8ead-410ef5e83f91",
46
+ "question": "What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?",
47
+ "Level": "1",
48
+ "file_name": ""
49
+ },
50
+ {
51
+ "task_id": "3cef3a44-215e-4aed-8e3b-b1e3f08063b7",
52
+ "question": "I'm making a grocery list for my mom, but she's a professor of botany and she's a real stickler when it comes to categorizing things. I need to add different foods to different categories on the grocery list, but if I make a mistake, she won't buy anything inserted in the wrong category. Here's the list I have so far:\n\nmilk, eggs, flour, whole bean coffee, Oreos, sweet potatoes, fresh basil, plums, green beans, rice, corn, bell pepper, whole allspice, acorns, broccoli, celery, zucchini, lettuce, peanuts\n\nI need to make headings for the fruits and vegetables. Could you please create a list of just the vegetables from my list? If you could do that, then I can figure out how to categorize the rest of the list into the appropriate categories. But remember that my mom is a real stickler, so make sure that no botanical fruits end up on the vegetable list, or she won't get them when she's at the store. Please alphabetize the list of vegetables, and place each item in a comma separated list.",
53
+ "Level": "1",
54
+ "file_name": ""
55
+ },
56
+ {
57
+ "task_id": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3",
58
+ "question": "Hi, I'm making a pie but I could use some help with my shopping list. I have everything I need for the crust, but I'm not sure about the filling. I got the recipe from my friend Aditi, but she left it as a voice memo and the speaker on my phone is buzzing so I can't quite make out what she's saying. Could you please listen to the recipe and list all of the ingredients that my friend described? I only want the ingredients for the filling, as I have everything I need to make my favorite pie crust. I've attached the recipe as Strawberry pie.mp3.\n\nIn your response, please only list the ingredients, not any measurements. So if the recipe calls for \"a pinch of salt\" or \"two cups of ripe strawberries\" the ingredients on the list would be \"salt\" and \"ripe strawberries\".\n\nPlease format your response as a comma separated list of ingredients. Also, please alphabetize the ingredients.",
59
+ "Level": "1",
60
+ "file_name": "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3.mp3"
61
+ },
62
+ {
63
+ "task_id": "305ac316-eef6-4446-960a-92d80d542f82",
64
+ "question": "Who did the actor who played Ray in the Polish-language version of Everybody Loves Raymond play in Magda M.? Give only the first name.",
65
+ "Level": "1",
66
+ "file_name": ""
67
+ },
68
+ {
69
+ "task_id": "f918266a-b3e0-4914-865d-4faa564f1aef",
70
+ "question": "What is the final numeric output from the attached Python code?",
71
+ "Level": "1",
72
+ "file_name": "f918266a-b3e0-4914-865d-4faa564f1aef.py"
73
+ },
74
+ {
75
+ "task_id": "3f57289b-8c60-48be-bd80-01f8099ca449",
76
+ "question": "How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?",
77
+ "Level": "1",
78
+ "file_name": ""
79
+ },
80
+ {
81
+ "task_id": "1f975693-876d-457b-a649-393859e79bf3",
82
+ "question": "Hi, I was out sick from my classes on Friday, so I'm trying to figure out what I need to study for my Calculus mid-term next week. My friend from class sent me an audio recording of Professor Willowbrook giving out the recommended reading for the test, but my headphones are broken :(\n\nCould you please listen to the recording for me and tell me the page numbers I'm supposed to go over? I've attached a file called Homework.mp3 that has the recording. Please provide just the page numbers as a comma-delimited list. And please provide the list in ascending order.",
83
+ "Level": "1",
84
+ "file_name": "1f975693-876d-457b-a649-393859e79bf3.mp3"
85
+ },
86
+ {
87
+ "task_id": "840bfca7-4f7b-481a-8794-c560c340185d",
88
+ "question": "On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?",
89
+ "Level": "1",
90
+ "file_name": ""
91
+ },
92
+ {
93
+ "task_id": "bda648d7-d618-4883-88f4-3466eabd860e",
94
+ "question": "Where were the Vietnamese specimens described by Kuznetzov in Nedoshivina's 2010 paper eventually deposited? Just give me the city name without abbreviations.",
95
+ "Level": "1",
96
+ "file_name": ""
97
+ },
98
+ {
99
+ "task_id": "cf106601-ab4f-4af9-b045-5295fe67b37d",
100
+ "question": "What country had the least number of athletes at the 1928 Summer Olympics? If there's a tie for a number of athletes, return the first in alphabetical order. Give the IOC country code as your answer.",
101
+ "Level": "1",
102
+ "file_name": ""
103
+ },
104
+ {
105
+ "task_id": "a0c07678-e491-4bbc-8f0b-07405144218f",
106
+ "question": "Who are the pitchers with the number before and after Taishō Tamai's number as of July 2023? Give them to me in the form Pitcher Before, Pitcher After, use their last names only, in Roman characters.",
107
+ "Level": "1",
108
+ "file_name": ""
109
+ },
110
+ {
111
+ "task_id": "7bd855d8-463d-4ed5-93ca-5fe35145f733",
112
+ "question": "The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places.",
113
+ "Level": "1",
114
+ "file_name": "7bd855d8-463d-4ed5-93ca-5fe35145f733.xlsx"
115
+ },
116
+ {
117
+ "task_id": "5a0c1adf-205e-4841-a666-7c3ef95def9d",
118
+ "question": "What is the first name of the only Malko Competition recipient from the 20th Century (after 1977) whose nationality on record is a country that no longer exists?",
119
+ "Level": "1",
120
+ "file_name": ""
121
+ }
122
+ ]
hf_agent_course_final_assignment/excel_agent.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import argparse
5
+ from pathlib import Path
6
+ from typing import List, Dict, Any, Optional
7
+ from dotenv import load_dotenv
8
+ from huggingface_hub import InferenceClient
9
+
10
+ # Import pandas for Excel/CSV handling
11
+ try:
12
+ import pandas as pd
13
+ except ImportError:
14
+ print("Error: 'pandas' and 'openpyxl' libraries are required for ExcelAgent.")
15
+ print("Please install them with: pip install pandas openpyxl")
16
+ exit(1)
17
+
18
+
19
+ # Load environment variables (e.g., HF_TOKEN) from a .env file
20
+ load_dotenv()
21
+
22
+ # This is the system prompt you provided
23
+ AGENT_SYSTEM_PROMPT = """You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string."""
24
+
25
+
26
+ class ExcelAgent:
27
+ """
28
+ Encapsulated agent for answering questions about an Excel file.
29
+
30
+ This agent works in two stages:
31
+ 1. Read the Excel file and convert its content to a text (CSV) format.
32
+ 2. Pass the user's question and the text-based data to a chat model
33
+ to get the final answer.
34
+
35
+ Usage:
36
+ # Assumes HF_TOKEN is in your environment
37
+ agent = ExcelAgent()
38
+
39
+ # Ask a question with an Excel file
40
+ question = "What are the total sales?"
41
+ excel_path = "/path/to/your/data.xlsx"
42
+ answer = agent.answer(question, excel_path)
43
+
44
+ # Or use the callable shortcut
45
+ answer = agent(question, excel_path)
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ chat_model_id: Optional[str] = None,
51
+ provider: Optional[str] = "novita",
52
+ temperature: float = 0.1,
53
+ api_key: Optional[str] = None,
54
+ ):
55
+ """
56
+ Initializes the ExcelAgent.
57
+
58
+ Args:
59
+ chat_model_id: The repo ID of the chat model.
60
+ provider: The provider for the chat InferenceClient.
61
+ temperature: The generation temperature for the chat model.
62
+ api_key: The API key. Defaults to os.environ["HF_TOKEN"].
63
+ """
64
+ # We use the same powerful model as the ImageAgent
65
+ self.chat_model_id = chat_model_id or "meta-llama/Llama-4-Scout-17B-16E-Instruct"
66
+ self.temperature = temperature
67
+
68
+ # Get API key from arg or environment
69
+ self.api_key = api_key or os.environ.get("HF_TOKEN")
70
+ if not self.api_key:
71
+ raise ValueError(
72
+ "API key not found. Please set the HF_TOKEN environment variable "
73
+ "or pass it as 'api_key' to the ExcelAgent."
74
+ )
75
+
76
+ # Client for Chat
77
+ self.chat_client = InferenceClient(
78
+ provider=provider,
79
+ api_key=self.api_key
80
+ )
81
+
82
+ # ---- Public API ----
83
+
84
+ def answer(self, question: str, excel_path: str) -> str:
85
+ """
86
+ Run the agent to answer a question about an Excel file.
87
+
88
+ Args:
89
+ question: The text prompt to ask.
90
+ excel_path: The local file path to the Excel file (.xlsx).
91
+
92
+ Returns:
93
+ The parsed "Final Answer" string from the chat model.
94
+ """
95
+ # 1. Read Excel and convert to text
96
+ try:
97
+ data_as_text = self._excel_to_text(excel_path)
98
+ except FileNotFoundError:
99
+ return f"Error: Excel file not found at {excel_path}"
100
+ except Exception as e:
101
+ return f"Error during Excel processing: {e}"
102
+
103
+ # 2. Build the combined prompt for the chat model
104
+ combined_prompt = (
105
+ f"You will be given a user's question and the content of an Excel file. "
106
+ f"The Excel data is represented in CSV format. "
107
+ f"Your task is to answer the question based *only* on the provided data.\n\n"
108
+ f"--- DATA (CSV format) ---\n"
109
+ f"{data_as_text}\n"
110
+ f"--- END DATA ---\n\n"
111
+ f"--- QUESTION ---\n"
112
+ f"{question}\n"
113
+ f"--- END QUESTION ---"
114
+ )
115
+
116
+ # 3. Build the messages payload
117
+ messages = self._build_chat_messages(combined_prompt)
118
+
119
+ # 4. Call the Chat API
120
+ try:
121
+ completion = self.chat_client.chat.completions.create(
122
+ model=self.chat_model_id,
123
+ messages=messages,
124
+ temperature=self.temperature,
125
+ )
126
+ response_text = completion.choices[0].message.content
127
+ except Exception as e:
128
+ return f"Error calling Chat API: {e}"
129
+
130
+ # 5. Parse and return the final answer
131
+ return self._parse_answer(response_text)
132
+
133
+ def __call__(self, question: str, excel_path: str) -> str:
134
+ """Callable shortcut for self.answer()."""
135
+ return self.answer(question, excel_path)
136
+
137
+ # ---- Internals ----
138
+
139
+ def _excel_to_text(self, excel_path: str) -> str:
140
+ """Reads an Excel file and converts it to a CSV string."""
141
+ p = Path(excel_path)
142
+ if not p.exists():
143
+ raise FileNotFoundError(f"No file found at {excel_path}")
144
+
145
+ try:
146
+ # Read the Excel file
147
+ # By default, reads the first sheet
148
+ df = pd.read_excel(str(p))
149
+
150
+ # Convert DataFrame to a CSV string
151
+ # index=False to avoid writing row numbers
152
+ csv_string = df.to_csv(index=False)
153
+
154
+ return csv_string
155
+ except Exception as e:
156
+ # Re-raise as a more informative exception
157
+ raise RuntimeError(f"Error reading/converting Excel file: {e}") from e
158
+
159
+ def _build_chat_messages(self, combined_prompt: str) -> List[Dict[str, Any]]:
160
+ """Constructs the message payload for the chat API."""
161
+ return [
162
+ {
163
+ "role": "system",
164
+ "content": AGENT_SYSTEM_PROMPT,
165
+ },
166
+ {
167
+ "role": "user",
168
+ "content": combined_prompt,
169
+ },
170
+ ]
171
+
172
+ def _parse_answer(self, response: str) -> str:
173
+ """Extracts the text after 'FINAL ANSWER: '."""
174
+ try:
175
+ # Use rpartition for robustness, splits on the *last* occurrence
176
+ _before, _marker, final_answer = response.rpartition("FINAL ANSWER: ")
177
+
178
+ if _marker:
179
+ return final_answer.strip()
180
+ else:
181
+ # If marker isn't found, return the whole response with a warning
182
+ print("Warning: Could not find 'FINAL ANSWER:' marker. Returning full response.")
183
+ return response.strip()
184
+
185
+ except Exception as e:
186
+ print(f"Error parsing response: {e}")
187
+ return response.strip()
188
+
189
+
190
+ # ---- CLI Entrypoint ----
191
+ def main():
192
+ """
193
+ Allows running the agent from the command line.
194
+
195
+ Example:
196
+ python excel_agent.py "What are the total sales?" "path/to/my_data.xlsx"
197
+ """
198
+ parser = argparse.ArgumentParser(description="Class-based agent for Excel questions.")
199
+ parser.add_argument("question", type=str, help="Your question (quoted).")
200
+ parser.add_argument("excel_path", type=str, help="Path to the Excel file.")
201
+ args = parser.parse_args()
202
+
203
+ try:
204
+ excel_agent = ExcelAgent()
205
+ answer = excel_agent.answer(args.question, args.excel_path)
206
+ print(answer)
207
+ except Exception as e:
208
+ print(f"An error occurred: {e}")
209
+
210
+ if __name__ == "__main__":
211
+ main()
hf_agent_course_final_assignment/gaia_agent.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # gaia_agent.py
2
+ #
3
+ # High-level GAIA orchestrator with LLM-based routing.
4
+ #
5
+ # Usage:
6
+ # from gaia_agent import GaiaAgent
7
+ # from utils.fetch_files import fetch_files_gaia_validation_hub
8
+ #
9
+ # gaia_agent = GaiaAgent()
10
+ #
11
+ # # Q without file
12
+ # ans1 = gaia_agent(
13
+ # "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia.",
14
+ # ""
15
+ # )
16
+ #
17
+ # # Q with file (image)
18
+ # img_path = fetch_files_gaia_validation_hub("cca530fc-4052-43b2-b130-b30968d8aa44.png")
19
+ # ans2 = gaia_agent(
20
+ # "Review the chess position provided in the image. It is black's turn...",
21
+ # img_path
22
+ # )
23
+ #
24
+ # # Q with YouTube link
25
+ # ans3 = gaia_agent(
26
+ # "In the video https://www.youtube.com/watch?v=L1vXCYZAYYM, what is the highest number of bird species to be on camera simultaneously?",
27
+ # ""
28
+ # )
29
+ #
30
+ # print(ans1, ans2, ans3)
31
+
32
+ from __future__ import annotations
33
+
34
+ import os
35
+ import re
36
+ import mimetypes
37
+ from typing import Optional, Dict, Any, List
38
+ from urllib.parse import urlparse, parse_qs
39
+
40
+ from dotenv import load_dotenv
41
+ from huggingface_hub import InferenceClient
42
+
43
+ from web_agent import WebAgent
44
+ from image_agent import ImageAgent
45
+ from audio_agent import AudioAgent
46
+ from python_agent import PythonAgent
47
+ from excel_agent import ExcelAgent
48
+ from youtube_agent import YouTubeSubsAgent # YouTubeSubsAgent(question, video_url)
49
+
50
+ load_dotenv()
51
+
52
+ #########################################################
53
+ # 1. Routing model prompt / helper
54
+ #########################################################
55
+
56
+ ROUTER_SYSTEM_PROMPT = """You are a routing controller for a GAIA QA system.
57
+ Your job is to look at a task and decide which specialized tool should answer it.
58
+
59
+ TOOLS:
60
+ - "web": For questions that need factual info from the internet, Wikipedia, sports stats, award numbers, historical rosters, etc. Use this if the answer relies on public info not contained in an attached file or transcript.
61
+ - "youtube": For questions that refer to a YouTube video URL and ask about what was said/heard in that video. The YouTube agent only has access to the video's subtitles.
62
+ - "image": For questions that require analyzing an image file (ex: chessboard, screenshot, diagram). File extensions like .png/.jpg/.jpeg.
63
+ - "audio": For questions that require transcribing or extracting info from an audio file (ex: .mp3, .wav, "listen to the recording and list ingredients").
64
+ - "excel": For questions that require reading a spreadsheet (.xls, .xlsx, .csv) and doing numeric reasoning or lookup in that sheet.
65
+ - "python": For questions that say "What is the final numeric output from the attached Python code?" or otherwise require running code from a .py file.
66
+ - "internal_reasoning": For pure logic, math, text manipulation, categorization, set theory, etc. No web. No external file beyond the text in the question.
67
+
68
+ RESTRICTIONS AND NOTES:
69
+ 1. If there is a YouTube link in the question (youtube.com or youtu.be) and the user is asking about what someone says or does in that video, choose "youtube".
70
+ 2. If there is an attached file_path and it's an image, choose "image".
71
+ 3. If there is an attached file_path and it's audio, choose "audio".
72
+ 4. If there is an attached file_path and it's Excel/CSV, choose "excel".
73
+ 5. If there is an attached file_path and it's Python code (.py), choose "python".
74
+ 6. Otherwise, if answering requires up-to-date or external factual knowledge (e.g. Wikipedia, sports stats, award numbers, who played in some show, etc.), choose "web".
75
+ 7. Otherwise choose "internal_reasoning".
76
+
77
+ OUTPUT FORMAT:
78
+ Return ONLY one of the following exact strings:
79
+ web
80
+ youtube
81
+ image
82
+ audio
83
+ excel
84
+ python
85
+ internal_reasoning
86
+ """
87
+
88
+ INTERNAL_REASONING_SYSTEM_PROMPT = """You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string."""
89
+
90
+ class RoutingLLM:
91
+ """
92
+ A tiny wrapper around the HF InferenceClient that decides the tool name.
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ model_id: Optional[str] = None,
98
+ provider: str = "novita",
99
+ temperature: float = 0.0,
100
+ api_key: Optional[str] = None,
101
+ ):
102
+ self.model_id = model_id or "meta-llama/Llama-4-Scout-17B-16E-Instruct"
103
+ self.temperature = temperature
104
+
105
+ self.api_key = api_key or os.environ.get("HF_TOKEN")
106
+ if not self.api_key:
107
+ raise ValueError("RoutingLLM: HF_TOKEN not found and api_key not provided.")
108
+
109
+ self.client = InferenceClient(provider=provider, api_key=self.api_key)
110
+
111
+ def route(self, question: str, file_path: Optional[str]) -> str:
112
+ """
113
+ Ask the LLM which tool to use.
114
+ We also inject lightweight hints (file extension, presence of YT link),
115
+ so the LLM has explicit signals.
116
+ """
117
+ file_path = file_path or ""
118
+ ext = os.path.splitext(file_path)[1].lower() if file_path else ""
119
+ mime, _ = mimetypes.guess_type(file_path) if file_path else (None, None)
120
+ has_youtube = "youtube.com" in question.lower() or "youtu.be" in question.lower()
121
+
122
+ user_prompt = (
123
+ "Question:\n"
124
+ f"{question}\n\n"
125
+ "file_path:\n"
126
+ f"{file_path}\n"
127
+ f"(extension='{ext}', mime='{mime}', has_youtube={has_youtube})\n\n"
128
+ "Which tool should handle this? Remember: output ONLY one of:\n"
129
+ "web | youtube | image | audio | excel | python | internal_reasoning"
130
+ )
131
+
132
+ messages = [
133
+ {"role": "system", "content": ROUTER_SYSTEM_PROMPT},
134
+ {"role": "user", "content": user_prompt},
135
+ ]
136
+
137
+ completion = self.client.chat.completions.create(
138
+ model=self.model_id,
139
+ messages=messages,
140
+ temperature=self.temperature,
141
+ )
142
+
143
+ raw = completion.choices[0].message.content.strip().lower()
144
+
145
+ # clean it to a known label
146
+ return self._normalize_route_label(raw)
147
+
148
+ def _normalize_route_label(self, raw: str) -> str:
149
+ """
150
+ Normalize whatever the model said into one of the 7 valid labels,
151
+ with a safe fallback.
152
+ """
153
+ allowed = {
154
+ "web",
155
+ "youtube",
156
+ "image",
157
+ "audio",
158
+ "excel",
159
+ "python",
160
+ "internal_reasoning",
161
+ }
162
+
163
+ # grab the first token-like word that matches any allowed label
164
+ for word in re.findall(r"[a-z_]+", raw):
165
+ if word in allowed:
166
+ return word
167
+
168
+ # fallback: default to "web" because GAIA often needs retrieval
169
+ return "web"
170
+
171
+
172
+ #########################################################
173
+ # 2. GaiaAgent Orchestrator
174
+ #########################################################
175
+
176
+ class GaiaAgent:
177
+ """
178
+ High-level GAIA orchestrator with an LLM router.
179
+
180
+ Init:
181
+ gaia_agent = GaiaAgent()
182
+
183
+ Call:
184
+ ans = gaia_agent(question, file_path)
185
+
186
+ where:
187
+ - question: str
188
+ - file_path: str or "" (path to local file if provided for that task)
189
+ """
190
+
191
+ def __init__(
192
+ self,
193
+ chat_model_id: Optional[str] = None,
194
+ router_model_id: Optional[str] = None,
195
+ provider: str = "novita",
196
+ temperature: float = 0.2,
197
+ router_temperature: float = 0.0,
198
+ api_key: Optional[str] = None,
199
+ ):
200
+ self.chat_model_id = chat_model_id or "meta-llama/Llama-4-Scout-17B-16E-Instruct"
201
+ self.temperature = temperature
202
+
203
+ # shared key/client
204
+ self.api_key = api_key or os.environ.get("HF_TOKEN")
205
+ if not self.api_key:
206
+ raise ValueError("GaiaAgent: HF_TOKEN not found and api_key not provided.")
207
+
208
+ # core reasoning client (for internal_reasoning branch)
209
+ self.reasoning_client = InferenceClient(
210
+ provider=provider,
211
+ api_key=self.api_key,
212
+ )
213
+
214
+ # router (LLM-based intent classifier)
215
+ self.router = RoutingLLM(
216
+ model_id=router_model_id or self.chat_model_id,
217
+ provider=provider,
218
+ temperature=router_temperature,
219
+ api_key=self.api_key,
220
+ )
221
+
222
+ # instantiate sub-agents once
223
+ self.web_agent = WebAgent() # expects question
224
+ self.youtube_agent = YouTubeSubsAgent(
225
+ api_key=self.api_key,
226
+ ) # expects (question, video_url)
227
+ self.image_agent = ImageAgent(
228
+ api_key=self.api_key,
229
+ ) # expects (question, file_path)
230
+ self.audio_agent = AudioAgent(
231
+ api_key=self.api_key,
232
+ ) # expects (question, file_path)
233
+ self.python_agent = PythonAgent() # expects (file_path)
234
+ self.excel_agent = ExcelAgent(
235
+ api_key=self.api_key,
236
+ ) # expects (question, file_path)
237
+
238
+ def __call__(self, question: str, file_path: Optional[str] = "") -> str:
239
+ return self.answer(question, file_path)
240
+
241
+ def answer(self, question: str, file_path: Optional[str] = "") -> str:
242
+ """
243
+ 1. Ask router LLM which tool to use.
244
+ 2. Dispatch to that tool.
245
+ 3. Return the tool's final answer.
246
+ """
247
+ route = self.router.route(question, file_path)
248
+
249
+ if route == "youtube":
250
+ print("[GaiaAgent] Routing to YouTube agent.")
251
+ video_url = self._extract_youtube_url(question)
252
+ return self.youtube_agent(question, video_url if video_url else "")
253
+
254
+ if route == "image":
255
+ print("[GaiaAgent] Routing to Image agent.")
256
+ return self.image_agent(question, file_path)
257
+
258
+ if route == "audio":
259
+ print("[GaiaAgent] Routing to Audio agent.")
260
+ return self.audio_agent(question, file_path)
261
+
262
+ if route == "excel":
263
+ print("[GaiaAgent] Routing to Excel agent.")
264
+ return self.excel_agent(question, file_path)
265
+
266
+ if route == "python":
267
+ print("[GaiaAgent] Routing to Python agent.")
268
+ return self._run_python_agent(file_path)
269
+
270
+ if route == "web":
271
+ print("[GaiaAgent] Routing to Web agent.")
272
+ return self.web_agent(question)
273
+
274
+ # internal_reasoning or fallback
275
+ print("[GaiaAgent] Routing to Internal Reasoning.")
276
+ return self._internal_reasoning(question)
277
+
278
+ #########################################################
279
+ # internal helpers
280
+ #########################################################
281
+
282
+ def _extract_youtube_url(self, text: str) -> Optional[str]:
283
+ """
284
+ Pull first YouTube URL from question.
285
+ """
286
+ m = re.search(r"(https?://[^\s]+youtu[^\s]+)", text)
287
+ if m:
288
+ return m.group(1).strip().rstrip(').,]')
289
+ return None
290
+
291
+ def _run_python_agent(self, file_path: str) -> str:
292
+ """
293
+ PythonAgent currently is called like python_agent_instance(file_path)
294
+ and returns output as string.
295
+ """
296
+ return self.python_agent(file_path)
297
+
298
+ def _internal_reasoning(self, question: str) -> str:
299
+ """
300
+ Use the same GAIA-style 'FINAL ANSWER:' contract,
301
+ but do pure reasoning, no web.
302
+ """
303
+ reasoning_prompt = (
304
+ "You will be given a question that can be answered using reasoning, "
305
+ "math, logic, text manipulation, categorization, or other internal thinking. "
306
+ "Do not use any outside knowledge beyond what a normal educated human would know; "
307
+ "do not fabricate web data.\n\n"
308
+ "Question:\n"
309
+ f"{question}\n\n"
310
+ "Remember to follow the FINAL ANSWER format exactly."
311
+ )
312
+
313
+ messages = [
314
+ {"role": "system", "content": INTERNAL_REASONING_SYSTEM_PROMPT},
315
+ {"role": "user", "content": reasoning_prompt},
316
+ ]
317
+
318
+ completion = self.reasoning_client.chat.completions.create(
319
+ model=self.chat_model_id,
320
+ messages=messages,
321
+ temperature=self.temperature,
322
+ )
323
+
324
+ raw = completion.choices[0].message.content
325
+ return self._parse_final_answer(raw)
326
+
327
+ def _parse_final_answer(self, response: str) -> str:
328
+ """
329
+ Extract text after 'FINAL ANSWER:' to stay consistent with other agents.
330
+ """
331
+ _before, _marker, final_answer = response.rpartition("FINAL ANSWER: ")
332
+ if _marker:
333
+ return final_answer.strip()
334
+ return response.strip()
hf_agent_course_final_assignment/image_agent.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import base64
5
+ import mimetypes
6
+ import argparse
7
+ from pathlib import Path
8
+ from typing import List, Dict, Any, Optional
9
+ from dotenv import load_dotenv
10
+ from huggingface_hub import InferenceClient
11
+
12
+ # Load environment variables (e.g., HF_TOKEN) from a .env file
13
+ load_dotenv()
14
+
15
+ # This is the system prompt you provided
16
+ AGENT_SYSTEM_PROMPT = """You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string."""
17
+
18
+
19
+ class ImageAgent:
20
+ """
21
+ Encapsulated agent for answering questions about an image.
22
+
23
+ Usage:
24
+ # Assumes HF_TOKEN is in your environment
25
+ agent = ImageAgent()
26
+
27
+ # Ask a question with an image
28
+ question = "What is in this image?"
29
+ image_path = "/path/to/your/image.png"
30
+ answer = agent.answer(question, image_path)
31
+
32
+ # Or use the callable shortcut
33
+ answer = agent(question, image_path)
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ model_id: Optional[str] = None,
39
+ provider: Optional[str] = "novita",
40
+ temperature: float = 0.2,
41
+ api_key: Optional[str] = None,
42
+ ):
43
+ """
44
+ Initializes the ImageAgent.
45
+
46
+ Args:
47
+ model_id: The repository ID of the model to use.
48
+ provider: The provider for the InferenceClient.
49
+ temperature: The generation temperature.
50
+ api_key: The API key. Defaults to os.environ["HF_TOKEN"].
51
+ """
52
+ self.model_id = model_id or "meta-llama/Llama-4-Scout-17B-16E-Instruct"
53
+ self.temperature = temperature
54
+
55
+ # Get API key from arg or environment, raising an error if missing
56
+ self.api_key = api_key or os.environ.get("HF_TOKEN")
57
+ if not self.api_key:
58
+ raise ValueError(
59
+ "API key not found. Please set the HF_TOKEN environment variable "
60
+ "or pass it as 'api_key' to the ImageAgent."
61
+ )
62
+
63
+ # Initialize the client
64
+ self.client = InferenceClient(
65
+ provider=provider,
66
+ api_key=self.api_key,
67
+ )
68
+
69
+ # ---- Public API ----
70
+
71
+ def answer(self, question: str, image_path: str) -> str:
72
+ """
73
+ Run the agent to answer a question about an image.
74
+
75
+ Args:
76
+ question: The text prompt to ask the model.
77
+ image_path: The local file path to the image.
78
+
79
+ Returns:
80
+ The parsed "Final Answer" string from the model.
81
+ """
82
+ # 1. Convert image to data URL
83
+ try:
84
+ image_data_url = self._to_data_url(image_path)
85
+ except FileNotFoundError:
86
+ return f"Error: Image file not found at {image_path}"
87
+ except Exception as e:
88
+ return f"Error processing image: {e}"
89
+
90
+ # 2. Build the messages payload
91
+ messages = self._build_messages(question, image_data_url)
92
+
93
+ # 3. Call the API
94
+ try:
95
+ completion = self.client.chat.completions.create(
96
+ model=self.model_id,
97
+ messages=messages,
98
+ temperature=self.temperature,
99
+ )
100
+ response_text = completion.choices[0].message.content
101
+ except Exception as e:
102
+ return f"Error calling API: {e}"
103
+
104
+ # 4. Parse and return the final answer
105
+ return self._parse_answer(response_text)
106
+
107
+ def __call__(self, question: str, image_path: str) -> str:
108
+ """Callable shortcut for self.answer()."""
109
+ return self.answer(question, image_path)
110
+
111
+ # ---- Internals ----
112
+
113
+ def _to_data_url(self, path: str) -> str:
114
+ """Converts a local file path to a base64 data URL."""
115
+ p = Path(path)
116
+ if not p.exists():
117
+ raise FileNotFoundError(f"No file found at {path}")
118
+
119
+ mime = mimetypes.guess_type(str(p))[0] or "image/png"
120
+ b64 = base64.b64encode(p.read_bytes()).decode("utf-8")
121
+ return f"data:{mime};base64,{b64}"
122
+
123
+ def _build_messages(self, question: str, image_data_url: str) -> List[Dict[str, Any]]:
124
+ """Constructs the message payload for the API."""
125
+ return [
126
+ {
127
+ "role": "system",
128
+ "content": [
129
+ {"type": "text", "text": AGENT_SYSTEM_PROMPT},
130
+ ],
131
+ },
132
+ {
133
+ "role": "user",
134
+ "content": [
135
+ {"type": "text", "text": question},
136
+ {
137
+ "type": "image_url",
138
+ "image_url": {"url": image_data_url},
139
+ },
140
+ ],
141
+ },
142
+ ]
143
+
144
+ def _parse_answer(self, response: str) -> str:
145
+ """Extracts the text after 'FINAL ANSWER: '."""
146
+ try:
147
+ # Use rpartition for robustness, splits on the *last* occurrence
148
+ _before, _marker, final_answer = response.rpartition("FINAL ANSWER: ")
149
+
150
+ if _marker:
151
+ return final_answer.strip()
152
+ else:
153
+ # If marker isn't found, return the whole response with a warning
154
+ print("Warning: Could not find 'FINAL ANSWER:' marker. Returning full response.")
155
+ return response.strip()
156
+
157
+ except Exception as e:
158
+ print(f"Error parsing response: {e}")
159
+ return response.strip()
160
+
161
+
162
+ # ---- CLI Entrypoint ----
163
+ def main():
164
+ """
165
+ Allows running the agent from the command line.
166
+
167
+ Example:
168
+ python image_agent.py "What move is this?" "path/to/my_image.png"
169
+ """
170
+ parser = argparse.ArgumentParser(description="Class-based agent for image questions.")
171
+ parser.add_argument("question", type=str, help="Your question (quoted).")
172
+ parser.add_argument("image_path", type=str, help="Path to the image file.")
173
+ args = parser.parse_args()
174
+
175
+ try:
176
+ image_agent = ImageAgent()
177
+ answer = image_agent.answer(args.question, args.image_path)
178
+ print(answer)
179
+ except Exception as e:
180
+ print(f"An error occurred: {e}")
181
+
182
+ if __name__ == "__main__":
183
+ main()
hf_agent_course_final_assignment/packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ stockfish
hf_agent_course_final_assignment/python_agent.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import base64
5
+ import mimetypes
6
+ import argparse
7
+ from pathlib import Path
8
+ from typing import List, Dict, Any, Optional
9
+ from dotenv import load_dotenv
10
+ from huggingface_hub import InferenceClient
11
+ import io
12
+ from contextlib import redirect_stdout
13
+
14
+ load_dotenv()
15
+
16
+ class PythonAgent:
17
+ def __init__(self):
18
+ # print("Python code execution agent initialized.")
19
+ pass
20
+
21
+ def __call__(self, file_path: str) -> str:
22
+ return self.execute_python_file(file_path)
23
+
24
+
25
+ def execute_python_file(self, file_path: str) -> str:
26
+ import io
27
+ from contextlib import redirect_stdout
28
+
29
+ with open(file_path, "r") as f_in:
30
+ code_to_exec = f_in.read()
31
+
32
+ # important: real module-like globals with __name__="__main__"
33
+ sandbox_globals = {
34
+ "__name__": "__main__",
35
+ "__file__": file_path,
36
+ }
37
+
38
+ buf = io.StringIO()
39
+ with redirect_stdout(buf):
40
+ try:
41
+ exec(code_to_exec, sandbox_globals)
42
+ except Exception as e:
43
+ print(f"An error occurred: {e}")
44
+
45
+ raw_response = buf.getvalue()
46
+ # the response is the last line
47
+ response = raw_response.strip().splitlines()[-1]
48
+ return response
49
+
50
+ # path_python_file = r"downloaded\f918266a-b3e0-4914-865d-4faa564f1aef.py"
51
+ # response = execute_python_file(path_python_file)
52
+ # print(response)
hf_agent_course_final_assignment/requirements.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.44.0
2
+ requests>=2.32.0
3
+ pandas>=2.2.2
4
+
5
+ # LangChain stack
6
+ langchain==0.3.27
7
+ langchain-core==0.3.79
8
+ langchain-community==0.3.31
9
+ langchain-huggingface>=0.1.0
10
+ huggingface_hub==0.35.3
11
+ transformers==4.57.1
12
+ pytorch
13
+
14
+ pillow==11.3.0
15
+ ddgs==9.6.1
16
+ beautifulsoup4>=4.12.3
17
+ duckduckgo-search>=6.2.5
18
+ wikipedia>=1.4.0
19
+ youtube-transcript-api==1.2.3
20
+ faster-whisper>=1.0.3
21
+ openpyxl>=3.1.5
22
+ python-dotenv==1.1.1
23
+ bs4>=0.0.2
24
+ lxml>=5.3.0
25
+
26
+ openpyxl==3.1.5
27
+
28
+
29
+ # accelerate
hf_agent_course_final_assignment/test_agents.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from web_agent import WebAgent
2
+ from utils.fetch_files import fetch_files_gaia_validation_hub
3
+ from image_agent import ImageAgent
4
+ from audio_agent import AudioAgent
5
+ from python_agent import PythonAgent
6
+ from excel_agent import ExcelAgent
7
+ from youtube_agent import YouTubeSubsAgent
8
+ from gaia_agent import GaiaAgent
9
+
10
+ # ## testing the web agent alone
11
+ # test_question_web = "What is the surname of the equine veterinarian mentioned in 1.E Exercises from the chemistry materials licensed by Marisa Alviar-Agnew & Henry Agnew under the CK-12 license in LibreText's Introductory Chemistry materials as compiled 08/21/2023?"
12
+ # webagent_instance = WebAgent()
13
+ # webagent_answer = webagent_instance(test_question_web)
14
+
15
+ ## testing the image agent
16
+ # test_question_image = "Review the chess position provided in the image. It is black's turn. Provide the correct next move for black which guarantees a win. Please provide your response in algebraic notation."
17
+ # file_name = "cca530fc-4052-43b2-b130-b30968d8aa44.png"
18
+ # img_path = fetch_files_gaia_validation_hub(file_name)
19
+ # print(img_path)
20
+ # print("------- Image Agent ------------------")
21
+ # imageagent_instance = ImageAgent()
22
+ # imageagent_answer = imageagent_instance(test_question_image, img_path)
23
+ # print(imageagent_answer)
24
+
25
+ ## testing the audio agent
26
+ # test_question_audio = "Hi, I'm making a pie but I could use some help with my shopping list. I have everything I need for the crust, but I'm not sure about the filling. I got the recipe from my friend Aditi, but she left it as a voice memo and the speaker on my phone is buzzing so I can't quite make out what she's saying. Could you please listen to the recipe and list all of the ingredients that my friend described? I only want the ingredients for the filling, as I have everything I need to make my favorite pie crust. I've attached the recipe as Strawberry pie.mp3.\n\nIn your response, please only list the ingredients, not any measurements. So if the recipe calls for \"a pinch of salt\" or \"two cups of ripe strawberries\" the ingredients on the list would be \"salt\" and \"ripe strawberries\".\n\nPlease format your response as a comma separated list of ingredients. Also, please alphabetize the ingredients."
27
+ # file_name = "99c9cc74-fdc8-46c6-8f8d-3ce2d3bfeea3.mp3"
28
+ # video_path = fetch_files_gaia_validation_hub(file_name)
29
+ # print(video_path)
30
+
31
+ # audioagent_instance = AudioAgent()
32
+ # audioagent_answer = audioagent_instance(test_question_audio, video_path)
33
+ # print(audioagent_answer)
34
+
35
+
36
+ ## Testing the python code execution agent
37
+ # test_question_python = "What is the final numeric output from the attached Python code?"
38
+ # file_name = "f918266a-b3e0-4914-865d-4faa564f1aef.py"
39
+ # script_path = fetch_files_gaia_validation_hub(file_name)
40
+ # print(script_path)
41
+ # pythonagent_instance = PythonAgent()
42
+ # pythonagent_answer = pythonagent_instance(script_path)
43
+ # print(pythonagent_answer)
44
+
45
+ ## Testing the excel agent
46
+ # test_question_excel = "The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places."
47
+ # file_name = "7bd855d8-463d-4ed5-93ca-5fe35145f733.xlsx"
48
+ # excel_path = fetch_files_gaia_validation_hub(file_name)
49
+ # print(excel_path)
50
+ # excelagent_instance = ExcelAgent()
51
+ # excelagent_answer = excelagent_instance(test_question_excel, excel_path)
52
+ # print(excelagent_answer)
53
+
54
+ ## Testing youtube agent
55
+ # youtube_agent = YouTubeSubsAgent()
56
+ # ans = youtube_agent(
57
+ # "What does Teal'c say in response to the question \"Isn't that hot?\"",
58
+ # "https://www.youtube.com/watch?v=1htKBjuUWec"
59
+ # )
60
+ # print(ans)
61
+
62
+
63
+ gaia_agent = GaiaAgent()
64
+
65
+ # q = "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia."
66
+ # ans = gaia_agent(q, "")
67
+
68
+ # q = "Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec. What does Teal'c say in response to the question \"Isn't that hot?\""
69
+ # ans = gaia_agent(q, "")
70
+
71
+ # file_img = fetch_files_gaia_validation_hub("cca530fc-4052-43b2-b130-b30968d8aa44.png")
72
+ # q = "Review the chess position provided in the image. It is black's turn. Provide the correct next move for black which guarantees a win. Please provide your response in algebraic notation."
73
+ # ans = gaia_agent(q, file_img)
74
+
75
+ # file_audio = fetch_files_gaia_validation_hub("1f975693-876d-457b-a649-393859e79bf3.mp3")
76
+ # q = "Hi, I was out sick from my classes on Friday, so I'm trying to figure out what I need to study for my Calculus mid-term next week. My friend from class sent me an audio recording of Professor Willowbrook giving out the recommended reading for the test, but my headphones are broken :(\n\nCould you please listen to the recording for me and tell me the page numbers I'm supposed to go over? I've attached a file called Homework.mp3 that has the recording. Please provide just the page numbers as a comma-delimited list. And please provide the list in ascending order."
77
+ # ans = gaia_agent(q, file_audio)
78
+
79
+ # file_xlsx = fetch_files_gaia_validation_hub("7bd855d8-463d-4ed5-93ca-5fe35145f733.xlsx")
80
+ # q = "The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places."
81
+ # ans = gaia_agent(q, file_xlsx)
82
+
83
+ # file_py = fetch_files_gaia_validation_hub("f918266a-b3e0-4914-865d-4faa564f1aef.py")
84
+ # q = "What is the final numeric output from the attached Python code?"
85
+ # ans = gaia_agent(q, file_py)
86
+
87
+ q = ".rewsna eht sa \"tfel\" drow eht fo etisoppo eht etirw ,ecnetnes siht dnatsrednu uoy fI"
88
+ ans = gaia_agent(q, "")
89
+
90
+ print(ans)
91
+
92
+ # from huggingface_hub import whoami
93
+ # whoami()
hf_agent_course_final_assignment/utils/fetch_files.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ Function to fetch files from the GAIA (validation set) in huggingface repo. """
2
+ import os, requests, huggingface_hub
3
+ from huggingface_hub import hf_hub_download
4
+
5
+ def fetch_files_gaia_validation_hub(filename: str):
6
+ """
7
+ Fetch a file from the GAIA validation set hosted on Hugging Face Hub.
8
+ Args:
9
+ filename (str): The name of the file to fetch (e.g., 'question_1.json').
10
+ Returns:
11
+ str: Local path to the downloaded file.
12
+ """
13
+ if not filename:
14
+ raise ValueError("Please provide a valid filename.")
15
+
16
+ repo_id = "gaia-benchmark/GAIA"
17
+ repo_type = "dataset"
18
+ path_in_repo = f"2023/validation/{filename}"
19
+
20
+ # If you need a private token: set HF_TOKEN env var or pass token=...
21
+ downloaded_path = hf_hub_download(
22
+ repo_id=repo_id,
23
+ repo_type=repo_type,
24
+ filename=path_in_repo,
25
+ # token=os.getenv("HF_TOKEN"), # uncomment if needed
26
+ force_download=False
27
+ )
28
+
29
+ # Copy into ./downloaded to keep a consistent local layout
30
+ os.makedirs("downloaded", exist_ok=True)
31
+ local_path = os.path.join("downloaded", filename)
32
+ if os.path.abspath(downloaded_path) != os.path.abspath(local_path):
33
+ # Lightweight copy
34
+ with open(downloaded_path, "rb") as src, open(local_path, "wb") as dst:
35
+ dst.write(src.read())
36
+
37
+ return local_path
hf_agent_course_final_assignment/web_agent.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # web_agent.py
2
+ # Minimal web-searching LangChain agent for GAIA-style questions.
3
+ # - Tools: DuckDuckGo search + HTTP GET
4
+ # - Model: defaults to a lightweight reasoning-ready open model (Hugging Face), but you can
5
+ # swap to any chat model supported by LangChain. If you don't set HF token, we'll
6
+ # fall back to a simple reasoning prompt with the same interface.
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import os
12
+ from typing import List, Optional, Dict, Any
13
+
14
+ # LangChain core (v0.2+ / v0.3)
15
+ from langchain.agents import AgentExecutor, create_react_agent
16
+ from langchain_core.prompts import PromptTemplate
17
+ from langchain_core.runnables import RunnableConfig
18
+ from langchain_community.utilities import RequestsWrapper
19
+ from langchain.tools import Tool
20
+
21
+ # Chat model: Hugging Face Endpoint (no key required to run, but highly recommended)
22
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
23
+
24
+ # Community tools
25
+ from langchain_community.tools import DuckDuckGoSearchRun, RequestsGetTool
26
+
27
+ # ---- Prompt (self-contained: no hub dependency) ----
28
+ REACT_PROMPT = """You are a meticulous research assistant for GAIA-style fact questions.
29
+
30
+ You have access to these tools:
31
+ {tools}
32
+
33
+ Use this format:
34
+
35
+ Question: the input question to answer
36
+ Thought: think about what to search or read next
37
+ Action: one of [{tool_names}]
38
+ Action Input: the input to the action
39
+ Observation: the result of the action
40
+ ... (repeat Thought/Action/Observation as needed)
41
+ Thought: I can now answer.
42
+ Final Answer: <the shortest exact answer>
43
+
44
+ Follow the rules:
45
+ - Prefer precise quotes from primary/official sources when possible.
46
+ - Keep the final answer SHORT (a few words if possible)
47
+ - If you need multiple pages, open them sequentially.
48
+ - Never repeat the exact same Action Input twice. If an Action returns a login, auth, or error page, change strategy (use search with site: filters, try a different public page, or a cached/mirrored source).
49
+ - If a URL contains "login", "@app/auth", or "returnto=", do NOT open it; search for a public page instead.
50
+
51
+ Question: {input}
52
+ {agent_scratchpad}"""
53
+
54
+ # Final Answer: <the shortest exact answer> — <single best source URL>
55
+ # - Keep the final answer SHORT (a few words if possible) plus one URL.
56
+ class WebAgent:
57
+ """Encapsulated GAIA-style web agent.
58
+
59
+ Usage:
60
+ web_agent = WebAgent()
61
+ result = web_agent("Who is the CEO of ...?")
62
+ # or
63
+ result = web_agent.answer("...")
64
+
65
+ You can also run as a script:
66
+ python web_agent.py "Your question here"
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ hf_repo_id: Optional[str] = None,
72
+ max_new_tokens: int = 2048,
73
+ do_sample: bool = False,
74
+ repetition_penalty: float = 1.05,
75
+ max_iterations: int = 20,
76
+ verbose: bool = True,
77
+ user_agent: str = "Mozilla/5.0 (compatible; GAIA/1.0; +https://example.org/bot)",
78
+ ):
79
+ self.hf_repo_id = hf_repo_id or os.environ.get("HF_REPO_ID", "Qwen/Qwen2.5-7B-Instruct")
80
+ self.max_new_tokens = max_new_tokens
81
+ self.do_sample = do_sample
82
+ self.repetition_penalty = repetition_penalty
83
+ self.max_iterations = max_iterations
84
+ self.verbose = verbose
85
+ self.user_agent = user_agent
86
+
87
+ # Built lazily so each call gets a fresh per-run "seen URL" set for the guarded GET.
88
+ self._executor: Optional[AgentExecutor] = None
89
+ self._tools: Optional[List[Tool]] = None
90
+
91
+ # ---- Public API ----
92
+ def answer(self, question: str) -> str:
93
+ """Run the agent and return the model's 'Final Answer' string."""
94
+ executor = self._build_agent() # build fresh each time to reset guarded state
95
+ result: Dict[str, Any] = executor.invoke({"input": question})
96
+ return result["output"]
97
+
98
+ def __call__(self, question: str) -> str:
99
+ return self.answer(question)
100
+
101
+ # ---- Internals ----
102
+ def _build_model(self) -> ChatHuggingFace:
103
+ llm = HuggingFaceEndpoint(
104
+ repo_id=self.hf_repo_id,
105
+ task="text-generation",
106
+ max_new_tokens=self.max_new_tokens,
107
+ do_sample=self.do_sample,
108
+ repetition_penalty=self.repetition_penalty,
109
+ )
110
+ return ChatHuggingFace(llm=llm)
111
+
112
+ def _build_tools(self) -> List[Tool]:
113
+ wrapper = RequestsWrapper(headers={"User-Agent": self.user_agent})
114
+ raw_get = RequestsGetTool(
115
+ name="Requests Get (raw)",
116
+ requests_wrapper=wrapper,
117
+ allow_dangerous_requests=True,
118
+ )
119
+
120
+ seen: set[str] = set()
121
+
122
+ def guarded_get(url: str) -> str:
123
+ low = url.lower()
124
+ if any(x in low for x in ["@app/auth", "login?returnto=", "/login"]):
125
+ return ("BLOCKED: This looks like a login/auth wall. "
126
+ "Use search to find a public or cached page instead.")
127
+ if url in seen:
128
+ return (f"BLOCKED: Already fetched {url}. "
129
+ "Choose a different URL or try a different query.")
130
+ seen.add(url)
131
+ return raw_get.run(url)
132
+
133
+ return [
134
+ DuckDuckGoSearchRun(name="DuckDuckGo Search"),
135
+ Tool.from_function(
136
+ name="Requests Get",
137
+ func=guarded_get,
138
+ description="HTTP GET for public pages; prevents repeats and login walls.",
139
+ ),
140
+ ]
141
+
142
+ def _build_agent(self) -> AgentExecutor:
143
+ model = self._build_model()
144
+ tools = self._build_tools()
145
+ prompt = PromptTemplate.from_template(REACT_PROMPT)
146
+
147
+ agent = create_react_agent(model, tools, prompt)
148
+ executor = AgentExecutor(
149
+ agent=agent,
150
+ tools=tools,
151
+ max_iterations=self.max_iterations,
152
+ handle_parsing_errors=True,
153
+ verbose=self.verbose,
154
+ )
155
+ return executor
156
+
157
+
158
+ # def main():
159
+ # parser = argparse.ArgumentParser(description="Simple LangChain web agent for GAIA-style questions.")
160
+ # parser.add_argument("question", type=str, help="Your question (quoted).")
161
+ # args = parser.parse_args()
162
+
163
+ # print(answer(args.question))
164
+
165
+
166
+ # if __name__ == "__main__":
167
+ # main()
168
+
169
+ # ---- CLI Entrypoint ----
170
+ def main():
171
+ parser = argparse.ArgumentParser(description="Class-based LangChain web agent for GAIA-style questions.")
172
+ parser.add_argument("question", type=str, help="Your question (quoted).")
173
+ args = parser.parse_args()
174
+
175
+ web_agent = WebAgent()
176
+ print(web_agent.answer(args.question))
177
+
178
+
179
+ if __name__ == "__main__":
180
+ main()
hf_agent_course_final_assignment/youtube_agent.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # youtube_subs_agent.py
2
+ # GAIA-style YouTube subtitle-based agent using youtube-transcript-api.
3
+ #
4
+ # Usage:
5
+ # pip install youtube-transcript-api python-dotenv huggingface_hub
6
+ #
7
+ # Example:
8
+ # from youtube_subs_agent import YouTubeSubsAgent
9
+ #
10
+ # agent = YouTubeSubsAgent()
11
+ # answer = agent(
12
+ # "What does Teal'c say in response to the question 'Isn't that hot?'",
13
+ # "https://www.youtube.com/watch?v=1htKBjuUWec"
14
+ # )
15
+ # print(answer)
16
+
17
+ from __future__ import annotations
18
+
19
+ import os
20
+ from typing import Optional, List, Dict, Any
21
+ from urllib.parse import urlparse, parse_qs
22
+
23
+ from dotenv import load_dotenv
24
+ from youtube_transcript_api import (
25
+ YouTubeTranscriptApi,
26
+ TranscriptsDisabled,
27
+ NoTranscriptFound,
28
+ VideoUnavailable,
29
+ )
30
+ from huggingface_hub import InferenceClient
31
+
32
+ load_dotenv()
33
+
34
+ AGENT_SYSTEM_PROMPT = """You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string."""
35
+
36
+
37
+ class YouTubeSubsAgent:
38
+ """
39
+ GAIA-style agent for answering questions about YouTube videos using subtitles.
40
+
41
+ Usage:
42
+ agent = YouTubeSubsAgent()
43
+ answer = agent(
44
+ "What does Teal'c say in response to the question 'Isn't that hot?'",
45
+ "https://www.youtube.com/watch?v=1htKBjuUWec"
46
+ )
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ chat_model_id: Optional[str] = None,
52
+ provider: str = "novita",
53
+ temperature: float = 0.2,
54
+ api_key: Optional[str] = None,
55
+ transcript_char_limit: int = 12000,
56
+ languages: Optional[List[str]] = None,
57
+ ):
58
+ """
59
+ languages: priority list for transcript languages.
60
+ default = English-first fallback.
61
+ """
62
+ self.chat_model_id = chat_model_id or "meta-llama/Llama-4-Scout-17B-16E-Instruct"
63
+ self.temperature = temperature
64
+ self.transcript_char_limit = transcript_char_limit
65
+ self.languages = languages or ["en", "en-US", "en-GB"]
66
+
67
+ self.api_key = api_key or os.environ.get("HF_TOKEN")
68
+ if not self.api_key:
69
+ raise ValueError(
70
+ "API key not found. Please set the HF_TOKEN environment variable or pass api_key=."
71
+ )
72
+
73
+ self.client = InferenceClient(provider=provider, api_key=self.api_key)
74
+
75
+ # ---- Public API ----
76
+
77
+ def answer(self, question: str, video_url: str) -> str:
78
+ """Main call method: question + YouTube URL."""
79
+ transcript_text = self._fetch_transcript_text(video_url)
80
+ prompt = self._build_reasoning_prompt(question, transcript_text)
81
+ response = self._run_chat(prompt)
82
+ return self._parse_final_answer(response)
83
+
84
+ def __call__(self, question: str, video_url: str) -> str:
85
+ """Shortcut for answer()."""
86
+ return self.answer(question, video_url)
87
+
88
+ # ---- Internals ----
89
+
90
+ def _extract_video_id(self, url: str) -> str:
91
+ """
92
+ Extracts the video ID from common YouTube URL formats.
93
+ Falls back to returning the input if it looks like a bare ID already.
94
+ """
95
+ parsed = urlparse(url)
96
+
97
+ # Short link: https://youtu.be/VIDEOID
98
+ if parsed.hostname in ("youtu.be",):
99
+ # path is like "/VIDEOID"
100
+ return parsed.path.strip("/")
101
+
102
+ # Standard watch link: https://www.youtube.com/watch?v=VIDEOID
103
+ if parsed.hostname in ("www.youtube.com", "youtube.com", "m.youtube.com"):
104
+ qs = parse_qs(parsed.query)
105
+ if "v" in qs:
106
+ return qs["v"][0]
107
+
108
+ # Fallback: assume caller already passed just the ID
109
+ return url
110
+
111
+ def _fetch_transcript_text(self, video_url: str) -> str:
112
+ """
113
+ Fetch transcript with youtube-transcript-api, format it as
114
+ MM:SS text
115
+ one line per snippet, and truncate if needed.
116
+ """
117
+ video_id = self._extract_video_id(video_url)
118
+
119
+ try:
120
+ fetched = YouTubeTranscriptApi().fetch(
121
+ video_id,
122
+ languages=self.languages,
123
+ )
124
+ except (TranscriptsDisabled, NoTranscriptFound, VideoUnavailable) as e:
125
+ print(f"Warning: no transcript available for this video ({e}).")
126
+ return ""
127
+
128
+ # IMPORTANT FIX:
129
+ # 'fetched' is a FetchedTranscript, which is iterable, but each element
130
+ # is a FetchedTranscriptSnippet (attributes, not dicts).
131
+ # Easiest: convert to raw dicts first.
132
+ raw_snippets = fetched.to_raw_data()
133
+
134
+ def format_ts(seconds_float: float) -> str:
135
+ seconds_int = int(seconds_float)
136
+ mm = seconds_int // 60
137
+ ss = seconds_int % 60
138
+ return f"{mm:02d}:{ss:02d}"
139
+
140
+ lines: List[str] = []
141
+ for snip in raw_snippets:
142
+ start_ts = format_ts(snip["start"])
143
+ text = snip["text"]
144
+ # Collapse internal newlines that YouTube sometimes puts in a single snippet
145
+ text = text.replace("\n", " ").strip()
146
+ if text:
147
+ lines.append(f"{start_ts} {text}")
148
+
149
+ transcript_text = "\n".join(lines)
150
+
151
+ if len(transcript_text) > self.transcript_char_limit:
152
+ transcript_text = transcript_text[: self.transcript_char_limit] + "\n[truncated]"
153
+
154
+ return transcript_text
155
+
156
+ def _build_reasoning_prompt(self, question: str, transcript_text: str) -> str:
157
+ """
158
+ Create the reasoning prompt for the LLM.
159
+ """
160
+ return (
161
+ "You will be given a question about a YouTube video's dialogue and its transcript.\n"
162
+ "Answer ONLY using what is said in the transcript. If the user asks for an exact quote, "
163
+ "repeat the line(s) exactly as spoken.\n\n"
164
+ "----- QUESTION -----\n"
165
+ f"{question}\n"
166
+ "----- END QUESTION -----\n\n"
167
+ "----- TRANSCRIPT -----\n"
168
+ f"{transcript_text if transcript_text else '[no transcript available]'}\n"
169
+ "----- END TRANSCRIPT -----\n"
170
+ )
171
+
172
+ def _run_chat(self, prompt: str) -> str:
173
+ """
174
+ Send the reasoning prompt to the chat model.
175
+ """
176
+ messages: List[Dict[str, Any]] = [
177
+ {"role": "system", "content": AGENT_SYSTEM_PROMPT},
178
+ {"role": "user", "content": prompt},
179
+ ]
180
+
181
+ completion = self.client.chat.completions.create(
182
+ model=self.chat_model_id,
183
+ messages=messages,
184
+ temperature=self.temperature,
185
+ )
186
+
187
+ return completion.choices[0].message.content
188
+
189
+ def _parse_final_answer(self, response: str) -> str:
190
+ """
191
+ Extract 'FINAL ANSWER: ...' just like in your other agents.
192
+ """
193
+ _before, _marker, final_answer = response.rpartition("FINAL ANSWER: ")
194
+ if _marker:
195
+ return final_answer.strip()
196
+ else:
197
+ return response.strip()
198
+
199
+ # ---- CLI Entrypoint ----
200
+ def main():
201
+ import argparse
202
+ parser = argparse.ArgumentParser(description="GAIA-style YouTube subtitles agent (youtube-transcript-api).")
203
+ parser.add_argument("question", type=str, help='Your question (quoted).')
204
+ parser.add_argument("video_url", type=str, help="YouTube video URL.")
205
+ args = parser.parse_args()
206
+
207
+ try:
208
+ agent = YouTubeSubsAgent()
209
+ answer = agent(args.question, args.video_url)
210
+ print(answer)
211
+ except Exception as e:
212
+ print(f"An error occurred: {e}")
213
+
214
+
215
+ if __name__ == "__main__":
216
+ main()