Vindemia commited on
Commit
c089d04
·
1 Parent(s): e188ef9

init commit

Browse files
Files changed (11) hide show
  1. .gitignore +3 -0
  2. README.md +8 -6
  3. agent.py +51 -0
  4. app.py +331 -0
  5. assignment_api.py +122 -0
  6. requirements.txt +149 -0
  7. sys_prompt.txt +7 -0
  8. tools.py +235 -0
  9. utils/final_answer.py +6 -0
  10. utils/handle_file.py +76 -0
  11. utils/read_file.py +8 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .python-version
2
+ .env
3
+ .task_files
README.md CHANGED
@@ -1,13 +1,15 @@
1
  ---
2
- title: Learn Agent Unit4
3
- emoji: 🐢
4
- colorFrom: blue
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 5.29.0
8
  app_file: app.py
9
  pinned: false
10
- short_description: Submission of final assignment of agent course
 
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Template 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
agent.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import TypedDict, Annotated
3
+ from dotenv import load_dotenv
4
+ from langgraph.graph.message import add_messages
5
+ from langchain_core.messages import AnyMessage
6
+ from langgraph.prebuilt import ToolNode
7
+ from langgraph.graph import START, StateGraph
8
+ from langgraph.prebuilt import tools_condition
9
+ from langchain_anthropic import ChatAnthropic
10
+
11
+ from utils.read_file import read_txt_file
12
+ from tools import available_tools
13
+
14
+ SYS_PROMPT_PATH = "./sys_prompt.txt"
15
+
16
+ load_dotenv()
17
+ api_key = os.getenv("ANTHROPIC_API_KEY")
18
+
19
+ llm = ChatAnthropic(
20
+ model="claude-3-7-sonnet-20250219",
21
+ temperature=0.5,
22
+ api_key=api_key,
23
+ # max_tokens=5000,
24
+ # thinking={"type": "enabled", "budget_tokens": 2000}
25
+ )
26
+ tools = available_tools
27
+ llm_with_tools = llm.bind_tools(tools)
28
+
29
+ class AgentState(TypedDict):
30
+ messages: Annotated[list[AnyMessage], add_messages]
31
+
32
+ def assistant(state: AgentState):
33
+ return {
34
+ "messages": [llm_with_tools.invoke(state["messages"])],
35
+ }
36
+
37
+ builder = StateGraph(AgentState)
38
+
39
+ builder.add_node("assistant", assistant)
40
+ builder.add_node("tools", ToolNode(tools))
41
+
42
+ builder.add_edge(START, "assistant")
43
+ builder.add_conditional_edges(
44
+ "assistant",
45
+ tools_condition
46
+ )
47
+ builder.add_edge("tools","assistant")
48
+
49
+ my_agent = builder.compile()
50
+
51
+ SYSTEM_PROMPT = read_txt_file(SYS_PROMPT_PATH)
app.py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ import inspect
5
+ import base64
6
+ import pandas as pd
7
+ from langchain_core.messages import HumanMessage, SystemMessage
8
+
9
+ from utils.final_answer import extract_final_answer
10
+ from utils.handle_file import handle_attachment
11
+ from agent import my_agent, SYSTEM_PROMPT
12
+ from assignment_api import get_all_questions, get_one_random_question, submit
13
+
14
+
15
+
16
+
17
+ # --- Basic Agent Definition ---
18
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
19
+ # class BasicAgent:
20
+ # def __init__(self):
21
+ # print("BasicAgent initialized.")
22
+ # def __call__(self, question: str) -> str:
23
+ # print(f"Agent received question (first 50 chars): {question[:50]}...")
24
+ # fixed_answer = "This is a default answer."
25
+ # print(f"Agent returning fixed answer: {fixed_answer}")
26
+ # return fixed_answer
27
+
28
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
29
+ """
30
+ Fetches all questions, runs the agent on them, submits all answers,
31
+ and displays the results. Handles attachments if present.
32
+ """
33
+ # --- Determine HF Space Runtime URL and Repo URL ---
34
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
35
+ if profile:
36
+ username= f"{profile.username}"
37
+ print(f"User logged in: {username}")
38
+ else:
39
+ print("User not logged in.")
40
+ return "Please Login to Hugging Face with the button.", None
41
+
42
+ # 1. Instantiate Agent (modify this part to create your agent)
43
+ try:
44
+ agent = my_agent
45
+ except Exception as e:
46
+ print(f"Error instantiating agent: {e}")
47
+ return f"Error initializing agent: {e}", None
48
+
49
+ # In the case of an app running as a hugging Face space, this link points toward your codebase (useful for others so please keep it public)
50
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
51
+ print(agent_code)
52
+
53
+ # 2. Fetch Questions
54
+ questions_data = get_all_questions()
55
+
56
+ # 3. Run your Agent
57
+ results_log = []
58
+ answers_payload = []
59
+ print(f"Running agent on {len(questions_data)} questions...")
60
+
61
+ for item in questions_data:
62
+ task_id = item.get("task_id")
63
+ question_text = item.get("question")
64
+
65
+ if not task_id or question_text is None:
66
+ print(f"Skipping item with missing task_id or question: {item}")
67
+ continue
68
+
69
+ # 2.2 Handle attachment if present
70
+ attachment_info = None
71
+ if "file_name" in item and item["file_name"]:
72
+ file_name = item.get("file_name")
73
+ attachment_info = handle_attachment(task_id, file_name)
74
+ print(f"Attachment handling result: {attachment_info['status']}")
75
+
76
+ try:
77
+ # Prepare messages based on attachment handling
78
+ messages = [
79
+ SystemMessage(content=SYSTEM_PROMPT),
80
+ SystemMessage(content=f"Current task id: {task_id}")
81
+ ]
82
+
83
+ # If we have an attachment that Claude can process directly
84
+ if attachment_info and attachment_info["status"] == "success" and attachment_info["handling"] == "direct":
85
+ # Encode content for direct inclusion
86
+ encoded_content = base64.b64encode(attachment_info["raw_content"]).decode('utf-8')
87
+ content_type = attachment_info["content_type"]
88
+
89
+ # Create multimodal message
90
+ if content_type.startswith('image/'):
91
+ multimodal_content = [
92
+ {"type": "text", "text": question_text},
93
+ {
94
+ "type": "image",
95
+ "source": {
96
+ "type": "base64",
97
+ "media_type": content_type,
98
+ "data": encoded_content
99
+ }
100
+ }
101
+ ]
102
+ elif content_type == "application/pdf" or "spreadsheet" in content_type or "excel" in content_type or "csv" in content_type:
103
+ multimodal_content = [
104
+ {"type": "text", "text": question_text},
105
+ {
106
+ "type": "file",
107
+ "source": {
108
+ "type": "base64",
109
+ "media_type": content_type,
110
+ "data": encoded_content
111
+ },
112
+ "name": attachment_info["file_name"]
113
+ }
114
+ ]
115
+
116
+ messages.append(HumanMessage(content=multimodal_content))
117
+
118
+ # If we have an attachment that needs tool processing
119
+ elif attachment_info and attachment_info["status"] == "success" and attachment_info["handling"] == "tool":
120
+ # Add info about the file to the question
121
+ file_info = (
122
+ f"{question_text}\n\n"
123
+ f"Note: This task has an attached file that can be accessed at: {attachment_info['file_path']}\n"
124
+ f"File type: {attachment_info['content_type']}"
125
+ )
126
+ messages.append(HumanMessage(content=file_info))
127
+
128
+ # If no attachment or error with attachment
129
+ else:
130
+ messages.append(HumanMessage(content=question_text))
131
+
132
+ # Invoke the agent with the prepared messages
133
+ agent_answer = agent.invoke({"messages": messages})
134
+ submitted_answer = extract_final_answer(agent_answer['messages'][-1].content)
135
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
136
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
137
+
138
+ except Exception as e:
139
+ print(f"Error running agent on task {task_id}: {e}")
140
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
141
+
142
+ if not answers_payload:
143
+ print("Agent did not produce any answers to submit.")
144
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
145
+
146
+ # 4. Prepare Submission
147
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
148
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
149
+ print(status_update)
150
+
151
+ # 5. Submit
152
+ return submit(submission_data, results_log)
153
+
154
+ def run_and_submit_one( profile: gr.OAuthProfile | None):
155
+ # --- Determine HF Space Runtime URL and Repo URL ---
156
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
157
+
158
+ if profile:
159
+ username= f"{profile.username}"
160
+ print(f"User logged in: {username}")
161
+ else:
162
+ print("User not logged in.")
163
+ return "Please Login to Hugging Face with the button.", None
164
+
165
+ # 1. Instantiate Agent ( modify this part to create your agent)
166
+ try:
167
+ agent = my_agent
168
+ except Exception as e:
169
+ print(f"Error instantiating agent: {e}")
170
+ return f"Error initializing agent: {e}", None
171
+ # 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)
172
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
173
+ print(agent_code)
174
+
175
+ # 2. Fetch Questions
176
+ questions_data = get_one_random_question()
177
+ print("questions_data:", questions_data)
178
+
179
+ # 2.2 Handle attachment if present
180
+ attachment_info = None
181
+ if "file_name" in questions_data and questions_data["file_name"]:
182
+ task_id = questions_data.get("task_id")
183
+ file_name = questions_data.get("file_name")
184
+ attachment_info = handle_attachment(task_id, file_name)
185
+ print(f"Attachment handling result: {attachment_info['status']}")
186
+
187
+ # 3. Run your Agent
188
+ results_log = []
189
+ answers_payload = []
190
+ print(f"Running agent on {len(questions_data)} questions...")
191
+
192
+ task_id = questions_data.get("task_id")
193
+ question_text = questions_data.get("question")
194
+
195
+ if not task_id or question_text is None:
196
+ print(f"Skipping item with missing task_id or question")
197
+ try:
198
+ # Prepare messages based on attachment handling
199
+ messages = [
200
+ SystemMessage(content=SYSTEM_PROMPT),
201
+ SystemMessage(content=f"Current task id: {task_id}")
202
+ ]
203
+
204
+ # If we have an attachment that Claude can process directly
205
+ if attachment_info and attachment_info["status"] == "success" and attachment_info["handling"] == "direct":
206
+ # Encode content for direct inclusion
207
+ encoded_content = base64.b64encode(attachment_info["raw_content"]).decode('utf-8')
208
+ content_type = attachment_info["content_type"]
209
+
210
+ # Create multimodal message
211
+ if content_type.startswith('image/'):
212
+ multimodal_content = [
213
+ {"type": "text", "text": question_text},
214
+ {
215
+ "type": "image",
216
+ "source": {
217
+ "type": "base64",
218
+ "media_type": content_type,
219
+ "data": encoded_content
220
+ }
221
+ }
222
+ ]
223
+ elif content_type == "application/pdf" or "spreadsheet" in content_type or "excel" in content_type or "csv" in content_type:
224
+ multimodal_content = [
225
+ {"type": "text", "text": question_text},
226
+ {
227
+ "type": "file",
228
+ "source": {
229
+ "type": "base64",
230
+ "media_type": content_type,
231
+ "data": encoded_content
232
+ },
233
+ "name": attachment_info["file_name"]
234
+ }
235
+ ]
236
+
237
+ messages.append(HumanMessage(content=multimodal_content))
238
+
239
+ # If we have an attachment that needs tool processing
240
+ elif attachment_info and attachment_info["status"] == "success" and attachment_info["handling"] == "tool":
241
+ # Add info about the file to the question
242
+ file_info = (
243
+ f"{question_text}\n\n"
244
+ f"Note: This task has an attached file that can be accessed at: {attachment_info['file_path']}\n"
245
+ f"File type: {attachment_info['content_type']}"
246
+ )
247
+ messages.append(HumanMessage(content=file_info))
248
+
249
+ # If no attachment or error with attachment
250
+ else:
251
+ messages.append(HumanMessage(content=question_text))
252
+
253
+ # Invoke the agent with the prepared messages
254
+ agent_answer = agent.invoke({"messages": messages})
255
+ submitted_answer = extract_final_answer(agent_answer['messages'][-1].content)
256
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
257
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
258
+ except Exception as e:
259
+ print(f"Error running agent on task {task_id}: {e}")
260
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
261
+
262
+ if not answers_payload:
263
+ print("Agent did not produce any answers to submit.")
264
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
265
+
266
+ # 4. Prepare Submission
267
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
268
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
269
+ print(status_update)
270
+
271
+ return submit(submission_data, results_log)
272
+
273
+ # --- Build Gradio Interface using Blocks ---
274
+ with gr.Blocks() as demo:
275
+ gr.Markdown("# Basic Agent Evaluation Runner")
276
+ gr.Markdown(
277
+ """
278
+ **Instructions:**
279
+
280
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
281
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
282
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
283
+
284
+ ---
285
+ **Disclaimers:**
286
+ 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).
287
+ 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.
288
+ """
289
+ )
290
+
291
+ gr.LoginButton()
292
+
293
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
294
+ run_one_button = gr.Button("Run one question and submit")
295
+
296
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
297
+ # Removed max_rows=10 from DataFrame constructor
298
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
299
+
300
+ run_button.click(
301
+ fn=run_and_submit_all,
302
+ outputs=[status_output, results_table]
303
+ )
304
+ run_one_button.click(
305
+ fn=run_and_submit_one,
306
+ outputs=[status_output, results_table]
307
+ )
308
+
309
+ if __name__ == "__main__":
310
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
311
+ # Check for SPACE_HOST and SPACE_ID at startup for information
312
+ space_host_startup = os.getenv("SPACE_HOST")
313
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
314
+
315
+ if space_host_startup:
316
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
317
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
318
+ else:
319
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
320
+
321
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
322
+ print(f"✅ SPACE_ID found: {space_id_startup}")
323
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
324
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
325
+ else:
326
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
327
+
328
+ print("-"*(60 + len(" App Starting ")) + "\n")
329
+
330
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
331
+ demo.launch(debug=True, share=False)
assignment_api.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any
2
+ import pandas as pd
3
+ import requests
4
+ import mimetypes
5
+
6
+ # --- Constants ---
7
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
8
+
9
+ def get_files_by_task_id(id: str):
10
+ files_url = f"{DEFAULT_API_URL}/files/{id}"
11
+ print(f"Fetching files for task {id} from: {files_url}")
12
+ try:
13
+ response = requests.get(files_url, timeout=30)
14
+ response.raise_for_status()
15
+
16
+ # Return the raw response and content type
17
+ return {
18
+ "content": response.content,
19
+ "content_type": response.headers.get('Content-Type', ''),
20
+ "status": "success"
21
+ }
22
+
23
+ except requests.exceptions.RequestException as e:
24
+ error_message = f"Error fetching file: {e}"
25
+ print(error_message)
26
+ return {
27
+ "status": "error",
28
+ "error": error_message
29
+ }
30
+
31
+ def get_one_random_question():
32
+ questions_url = f"{DEFAULT_API_URL}/random-question"
33
+ print(f"Fetching questions from: {questions_url}")
34
+ try:
35
+ response = requests.get(questions_url, timeout=15)
36
+ response.raise_for_status()
37
+ questions_data = response.json()
38
+ if not questions_data:
39
+ print("Fetched questions list is empty.")
40
+ return "Fetched questions list is empty or invalid format.", None
41
+ print(f"Fetched {len(questions_data)} questions.")
42
+ return questions_data
43
+ except requests.exceptions.RequestException as e:
44
+ print(f"Error fetching questions: {e}")
45
+ return f"Error fetching questions: {e}", None
46
+ except requests.exceptions.JSONDecodeError as e:
47
+ print(f"Error decoding JSON response from questions endpoint: {e}")
48
+ print(f"Response text: {response.text[:500]}")
49
+ return f"Error decoding server response for questions: {e}", None
50
+ except Exception as e:
51
+ print(f"An unexpected error occurred fetching questions: {e}")
52
+ return f"An unexpected error occurred fetching questions: {e}", None
53
+
54
+ # 2. Fetch Questions
55
+ def get_all_questions():
56
+ questions_url = f"{DEFAULT_API_URL}/questions"
57
+ print(f"Fetching questions from: {questions_url}")
58
+ try:
59
+ response = requests.get(questions_url, timeout=15)
60
+ response.raise_for_status()
61
+ questions_data = response.json()
62
+ if not questions_data:
63
+ print("Fetched questions list is empty.")
64
+ return "Fetched questions list is empty or invalid format.", None
65
+ print(f"Fetched {len(questions_data)} questions.")
66
+ return questions_data
67
+ except requests.exceptions.RequestException as e:
68
+ print(f"Error fetching questions: {e}")
69
+ return f"Error fetching questions: {e}", None
70
+ except requests.exceptions.JSONDecodeError as e:
71
+ print(f"Error decoding JSON response from questions endpoint: {e}")
72
+ print(f"Response text: {response.text[:500]}")
73
+ return f"Error decoding server response for questions: {e}", None
74
+ except Exception as e:
75
+ print(f"An unexpected error occurred fetching questions: {e}")
76
+ return f"An unexpected error occurred fetching questions: {e}", None
77
+
78
+ # 5. Submit
79
+ def submit(submission_data:dict[str, Any], results_log:list):
80
+ submit_url = f"{DEFAULT_API_URL}/submit"
81
+ print(f"Submitting {len(submission_data["answers"])} answers to: {submit_url}")
82
+ try:
83
+ print(f"Submitting:", submission_data)
84
+ response = requests.post(submit_url, json=submission_data, timeout=60)
85
+ response.raise_for_status()
86
+ result_data = response.json()
87
+ final_status = (
88
+ f"Submission Successful!\n"
89
+ f"User: {result_data.get('username')}\n"
90
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
91
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
92
+ f"Message: {result_data.get('message', 'No message received.')}"
93
+ )
94
+ print("Submission successful.")
95
+ results_df = pd.DataFrame(results_log)
96
+ return final_status, results_df
97
+ except requests.exceptions.HTTPError as e:
98
+ error_detail = f"Server responded with status {e.response.status_code}."
99
+ try:
100
+ error_json = e.response.json()
101
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
102
+ except requests.exceptions.JSONDecodeError:
103
+ error_detail += f" Response: {e.response.text[:500]}"
104
+ status_message = f"Submission Failed: {error_detail}"
105
+ print(status_message)
106
+ results_df = pd.DataFrame(results_log)
107
+ return status_message, results_df
108
+ except requests.exceptions.Timeout:
109
+ status_message = "Submission Failed: The request timed out."
110
+ print(status_message)
111
+ results_df = pd.DataFrame(results_log)
112
+ return status_message, results_df
113
+ except requests.exceptions.RequestException as e:
114
+ status_message = f"Submission Failed: Network error - {e}"
115
+ print(status_message)
116
+ results_df = pd.DataFrame(results_log)
117
+ return status_message, results_df
118
+ except Exception as e:
119
+ status_message = f"An unexpected error occurred during submission: {e}"
120
+ print(status_message)
121
+ results_df = pd.DataFrame(results_log)
122
+ return status_message, results_df
requirements.txt ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==24.1.0
2
+ aiohappyeyeballs==2.6.1
3
+ aiohttp==3.11.18
4
+ aiosignal==1.3.2
5
+ annotated-types==0.7.0
6
+ anthropic==0.50.0
7
+ anyio==4.9.0
8
+ attrs==25.3.0
9
+ audioop-lts==0.2.1
10
+ Authlib==1.5.2
11
+ beautifulsoup4==4.13.4
12
+ certifi==2025.1.31
13
+ cffi==1.17.1
14
+ charset-normalizer==3.4.1
15
+ chess==1.11.2
16
+ click==8.1.8
17
+ cryptography==44.0.2
18
+ dataclasses-json==0.6.7
19
+ datasets==3.5.0
20
+ defusedxml==0.7.1
21
+ dill==0.3.8
22
+ distro==1.9.0
23
+ duckduckgo_search==8.0.1
24
+ et_xmlfile==2.0.0
25
+ fastapi==0.115.12
26
+ ffmpy==0.5.0
27
+ filelock==3.18.0
28
+ frozenlist==1.6.0
29
+ fsspec==2024.12.0
30
+ gradio==5.27.0
31
+ gradio_client==1.9.0
32
+ greenlet==3.2.1
33
+ groovy==0.1.2
34
+ h11==0.16.0
35
+ httpcore==1.0.9
36
+ httpx==0.28.1
37
+ httpx-sse==0.4.0
38
+ huggingface-hub==0.30.2
39
+ idna==3.10
40
+ itsdangerous==2.2.0
41
+ Jinja2==3.1.6
42
+ jiter==0.9.0
43
+ joblib==1.4.2
44
+ jsonpatch==1.33
45
+ jsonpointer==3.0.0
46
+ langchain==0.3.24
47
+ langchain-anthropic==0.3.12
48
+ langchain-community==0.3.22
49
+ langchain-core==0.3.56
50
+ langchain-experimental==0.3.4
51
+ langchain-huggingface==0.1.2
52
+ langchain-text-splitters==0.3.8
53
+ langgraph==0.3.34
54
+ langgraph-checkpoint==2.0.24
55
+ langgraph-prebuilt==0.1.8
56
+ langgraph-sdk==0.1.63
57
+ langsmith==0.3.34
58
+ llvmlite==0.44.0
59
+ lxml==5.4.0
60
+ markdown-it-py==3.0.0
61
+ MarkupSafe==3.0.2
62
+ marshmallow==3.26.1
63
+ mdurl==0.1.2
64
+ more-itertools==10.7.0
65
+ mpmath==1.3.0
66
+ multidict==6.4.3
67
+ multiprocess==0.70.16
68
+ mypy_extensions==1.1.0
69
+ networkx==3.4.2
70
+ numba==0.61.2
71
+ numpy==2.2.5
72
+ nvidia-cublas-cu12==12.6.4.1
73
+ nvidia-cuda-cupti-cu12==12.6.80
74
+ nvidia-cuda-nvrtc-cu12==12.6.77
75
+ nvidia-cuda-runtime-cu12==12.6.77
76
+ nvidia-cudnn-cu12==9.5.1.17
77
+ nvidia-cufft-cu12==11.3.0.4
78
+ nvidia-cufile-cu12==1.11.1.6
79
+ nvidia-curand-cu12==10.3.7.77
80
+ nvidia-cusolver-cu12==11.7.1.2
81
+ nvidia-cusparse-cu12==12.5.4.2
82
+ nvidia-cusparselt-cu12==0.6.3
83
+ nvidia-nccl-cu12==2.26.2
84
+ nvidia-nvjitlink-cu12==12.6.85
85
+ nvidia-nvtx-cu12==12.6.77
86
+ openai-whisper @ git+https://github.com/openai/whisper.git@517a43ecd132a2089d85f4ebc044728a71d49f6e
87
+ openpyxl==3.1.5
88
+ orjson==3.10.16
89
+ ormsgpack==1.9.1
90
+ packaging==24.2
91
+ pandas==2.2.3
92
+ pillow==11.2.1
93
+ primp==0.15.0
94
+ propcache==0.3.1
95
+ pyarrow==19.0.1
96
+ pycparser==2.22
97
+ pydantic==2.11.3
98
+ pydantic-settings==2.9.1
99
+ pydantic_core==2.33.1
100
+ pydub==0.25.1
101
+ Pygments==2.19.1
102
+ python-chess==1.999
103
+ python-dateutil==2.9.0.post0
104
+ python-dotenv==1.1.0
105
+ python-multipart==0.0.20
106
+ pytube==15.0.0
107
+ pytz==2025.2
108
+ PyYAML==6.0.2
109
+ rank-bm25==0.2.2
110
+ regex==2024.11.6
111
+ requests==2.32.3
112
+ requests-toolbelt==1.0.0
113
+ rich==14.0.0
114
+ ruff==0.11.7
115
+ safehttpx==0.1.6
116
+ safetensors==0.5.3
117
+ scikit-learn==1.6.1
118
+ scipy==1.15.2
119
+ semantic-version==2.10.0
120
+ sentence-transformers==4.1.0
121
+ setuptools==79.0.1
122
+ shellingham==1.5.4
123
+ six==1.17.0
124
+ sniffio==1.3.1
125
+ soupsieve==2.7
126
+ SQLAlchemy==2.0.40
127
+ starlette==0.46.2
128
+ sympy==1.13.3
129
+ tenacity==9.1.2
130
+ threadpoolctl==3.6.0
131
+ tiktoken==0.9.0
132
+ tokenizers==0.21.1
133
+ tomlkit==0.13.2
134
+ torch==2.7.0
135
+ tqdm==4.67.1
136
+ transformers==4.51.3
137
+ triton==3.3.0
138
+ typer==0.15.2
139
+ typing-inspect==0.9.0
140
+ typing-inspection==0.4.0
141
+ typing_extensions==4.13.2
142
+ tzdata==2025.2
143
+ urllib3==2.4.0
144
+ uvicorn==0.34.2
145
+ websockets==15.0.1
146
+ xxhash==3.5.0
147
+ yarl==1.20.0
148
+ youtube-transcript-api==1.0.3
149
+ zstandard==0.23.0
sys_prompt.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ You are a general AI assistant with tools.
2
+ I will ask you a question. Use your tools, and answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER].
3
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings with spavec after the comma.
4
+ 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.
5
+ 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.
6
+ 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.
7
+ If you use the python_repl tool (code interpreter), always end your code with `print(...)` to see the output.
tools.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import whisper
3
+ import mimetypes
4
+ import json
5
+ from langchain_community.tools import DuckDuckGoSearchRun
6
+ from langchain_community.tools import BraveSearch
7
+ from langchain_community.document_loaders import WikipediaLoader
8
+ from langchain_community.document_loaders import ArxivLoader
9
+ from langchain.tools import Tool, tool
10
+ from youtube_transcript_api import YouTubeTranscriptApi
11
+ from pytube import extract
12
+ from pydantic import BaseModel, Field
13
+ from langchain_experimental.utilities import PythonREPL
14
+
15
+ @tool
16
+ def get_youtube_transcript(page_url: str) -> str:
17
+ """Get the transcript of a YouTube video
18
+ Args:
19
+ page_url (str): YouTube URL of the video
20
+ """
21
+ try:
22
+ # get video ID from URL
23
+ video_id = extract.video_id(page_url)
24
+
25
+ # get transcript
26
+ ytt_api = YouTubeTranscriptApi()
27
+ transcript = ytt_api.fetch(video_id)
28
+
29
+ # keep only text
30
+ txt = '\n'.join([s.text for s in transcript.snippets])
31
+ return txt
32
+ except Exception as e:
33
+ return f"get_youtube_transcript failed: {e}"
34
+
35
+ @tool
36
+ def multiply(a: float, b: float) -> float:
37
+ """Multiplies two numbers.
38
+ Args:
39
+ a (float): the first number
40
+ b (float): the second number
41
+ """
42
+ return a * b
43
+
44
+
45
+ @tool
46
+ def add(a: float, b: float) -> float:
47
+ """Adds two numbers.
48
+ Args:
49
+ a (float): the first number
50
+ b (float): the second number
51
+ """
52
+ return a + b
53
+
54
+
55
+ @tool
56
+ def subtract(a: float, b: float) -> int:
57
+ """Subtracts two numbers.
58
+ Args:
59
+ a (float): the first number
60
+ b (float): the second number
61
+ """
62
+ return a - b
63
+
64
+
65
+ @tool
66
+ def divide(a: float, b: float) -> float:
67
+ """Divides two numbers.
68
+ Args:
69
+ a (float): the first float number
70
+ b (float): the second float number
71
+ """
72
+ if b == 0:
73
+ raise ValueError("Cannot divided by zero.")
74
+ return a / b
75
+
76
+
77
+ @tool
78
+ def modulus(a: int, b: int) -> int:
79
+ """Get the modulus of two numbers.
80
+ Args:
81
+ a (int): the first number
82
+ b (int): the second number
83
+ """
84
+ return a % b
85
+
86
+
87
+ @tool
88
+ def power(a: float, b: float) -> float:
89
+ """Get the power of two numbers.
90
+ Args:
91
+ a (float): the first number
92
+ b (float): the second number
93
+ """
94
+ return a**b
95
+
96
+ @tool
97
+ def get_web_search_result(query:str):
98
+ """Fetches information on the web based on quey.
99
+
100
+ Args:
101
+ query: The search query.
102
+
103
+ Returns:
104
+ """
105
+ print("get_web_search_result")
106
+ search_tool = DuckDuckGoSearchRun()
107
+ results = search_tool.invoke(query)
108
+ return results
109
+
110
+ @tool
111
+ def wiki_search(query: str) -> str:
112
+ """Search Wikipedia for a query and return maximum 5 results.
113
+
114
+ Args:
115
+ query: The search query.
116
+
117
+ Returns:
118
+ An array documents
119
+ """
120
+ print("wiki_search")
121
+ search_docs = WikipediaLoader(query=query, load_max_docs=5).load()
122
+ formatted_search_docs = "\n\n---\n\n".join(
123
+ [
124
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
125
+ for doc in search_docs
126
+ ])
127
+ return {"wiki_results": formatted_search_docs}
128
+
129
+ @tool
130
+ def arvix_search(query: str) -> str:
131
+ """Search Arxiv for a query and return maximum 3 result.
132
+
133
+ Args:
134
+ query: The search query.
135
+
136
+ Returns:
137
+ An array documents
138
+ """
139
+ print("arvix_search")
140
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
141
+ formatted_search_docs = "\n\n---\n\n".join(
142
+ [
143
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
144
+ for doc in search_docs
145
+ ])
146
+ return {"arvix_results": formatted_search_docs}
147
+
148
+ @tool
149
+ def transcribe_audio(file_path: str):
150
+ """
151
+ Transcribes an audio file to text using local Whisper model.
152
+
153
+ Args:
154
+ file_path: Path to the audio file
155
+
156
+ Returns:
157
+ A dictionary containing the transcription and metadata
158
+ """
159
+ try:
160
+ print(f"Transcribing audio file: {file_path}")
161
+
162
+ # Validate file exists
163
+ if not os.path.exists(file_path):
164
+ return {
165
+ "status": "error",
166
+ "message": f"File not found: {file_path}"
167
+ }
168
+
169
+ # Load a Whisper model - we'll use the small model for better performance
170
+ # Options include: tiny, base, small, medium, large
171
+ model = whisper.load_model("small")
172
+
173
+ # Transcribe the audio
174
+ result = model.transcribe(file_path)
175
+ print({
176
+ "status": "success",
177
+ "transcription": result["text"],
178
+ "language": result.get("language", "unknown"),
179
+ "file_path": file_path
180
+ })
181
+
182
+ # Return the transcription and metadata
183
+ return {
184
+ "status": "success",
185
+ "transcription": result["text"],
186
+ "language": result.get("language", "unknown"),
187
+ "file_path": file_path
188
+ }
189
+
190
+ except Exception as e:
191
+ print({
192
+ "status": "error",
193
+ "message": f"Error transcribing audio: {str(e)}"
194
+ })
195
+ return {
196
+ "status": "error",
197
+ "message": f"Error transcribing audio: {str(e)}"
198
+ }
199
+
200
+ class PythonREPLInput(BaseModel):
201
+ code: str = Field(description="The Python code string to execute.")
202
+
203
+ python_repl = PythonREPL()
204
+
205
+ python_repl_tool = Tool(
206
+ name="python_repl",
207
+ description="""A Python REPL shell (Read-Eval-Print Loop).
208
+ Use this to execute single or multi-line python commands.
209
+ Input should be syntactically valid Python code.
210
+ Always end your code with `print(...)` to see the output.
211
+ Do NOT execute code that could be harmful to the host system.
212
+ You are allowed to download files from URLs.
213
+ Do NOT send commands that block indefinitely (e.g., `input()`).""",
214
+ func=python_repl.run,
215
+ args_schema=PythonREPLInput
216
+ )
217
+
218
+ available_tools = [
219
+ get_web_search_result,
220
+ wiki_search,
221
+ arvix_search,
222
+ transcribe_audio,
223
+ python_repl_tool,
224
+ multiply,
225
+ add,
226
+ subtract,
227
+ divide,
228
+ modulus,
229
+ power,
230
+ get_youtube_transcript,
231
+ BraveSearch.from_api_key(
232
+ api_key=os.getenv("BRAVE_SEARCH_API_KEY"),
233
+ search_kwargs={"count": 5}
234
+ )
235
+ ]
utils/final_answer.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ def extract_final_answer(text):
2
+ if "FINAL ANSWER:" in text:
3
+ start_index = text.find("FINAL ANSWER:") + len("FINAL ANSWER:")
4
+ return text[start_index:].strip()
5
+ else:
6
+ return None
utils/handle_file.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import mimetypes
3
+ from langchain_core.messages import HumanMessage, SystemMessage
4
+ from pathlib import Path
5
+ from assignment_api import get_files_by_task_id
6
+
7
+ # Function to handle attachments based on file type
8
+ def handle_attachment(task_id, file_name):
9
+ """
10
+ Handles file attachments for a task.
11
+
12
+ Args:
13
+ task_id: The ID of the task
14
+ file_name: The name of the attachment file
15
+
16
+ Returns:
17
+ Dictionary with attachment details, including how it should be handled
18
+ """
19
+ if not file_name or file_name.strip() == "":
20
+ return {"status": "no_attachment"}
21
+
22
+ # Get file content using existing function
23
+ file_data = get_files_by_task_id(task_id)
24
+
25
+ if file_data["status"] == "error":
26
+ print(f"Error fetching attachment: {file_data.get('error')}")
27
+ return {"status": "error", "message": file_data.get("error")}
28
+
29
+ content_type = file_data["content_type"]
30
+ raw_content = file_data["content"]
31
+
32
+ # Determine if this file type can be directly processed by Claude
33
+ # Images, spreadsheets, and PDFs can be processed directly
34
+ direct_process = (
35
+ content_type.startswith("image/") or
36
+ "spreadsheet" in content_type or
37
+ "excel" in content_type or
38
+ content_type == "application/pdf" or
39
+ "csv" in content_type
40
+ )
41
+
42
+ if direct_process:
43
+ # For files that Claude can handle directly, return content for direct inclusion
44
+ print(f"Attachment {file_name} (type: {content_type}) will be passed directly to Claude")
45
+ return {
46
+ "status": "success",
47
+ "handling": "direct",
48
+ "content_type": content_type,
49
+ "raw_content": raw_content,
50
+ "file_name": file_name
51
+ }
52
+ else:
53
+ # For other files, save to disk and return path
54
+ save_dir = os.path.join(os.getcwd(), "task_files")
55
+ os.makedirs(save_dir, exist_ok=True)
56
+
57
+ # Determine file extension
58
+ file_extension = Path(file_name).suffix
59
+ if not file_extension:
60
+ extension = mimetypes.guess_extension(content_type) or ".bin"
61
+ file_path = os.path.join(save_dir, f"{task_id}{extension}")
62
+ else:
63
+ file_path = os.path.join(save_dir, file_name)
64
+
65
+ # Save the file
66
+ with open(file_path, 'wb') as f:
67
+ f.write(raw_content)
68
+
69
+ print(f"Attachment {file_name} (type: {content_type}) saved to {file_path}")
70
+ return {
71
+ "status": "success",
72
+ "handling": "tool",
73
+ "content_type": content_type,
74
+ "file_path": file_path,
75
+ "file_name": file_name
76
+ }
utils/read_file.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ def read_txt_file(file_path):
2
+ try:
3
+ with open(file_path, 'r', encoding='utf-8') as file:
4
+ system_prompt = file.read()
5
+ return system_prompt
6
+ except Exception as e:
7
+ print(f"Error while reading file: {e}")
8
+ return ""