Spaces:
Configuration error
Configuration error
Enrico Mannarino commited on
Commit ·
6859205
1
Parent(s): 81917a3
hf final assignment agent
Browse files- .gitignore +5 -0
- agent.py +96 -0
- app.py +31 -16
- hf_agents_course_assignment.code-workspace +7 -0
- question_analyzer.py +164 -0
- questions_data.json +122 -0
- requirements.txt +154 -2
- system_prompt.txt +7 -0
- tools.py +132 -0
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
.venv/
|
| 3 |
+
.vscode/
|
| 4 |
+
.gitattributes
|
| 5 |
+
.hf_agents_course_assignment.code-workspace
|
agent.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import TypedDict, Annotated, Sequence
|
| 2 |
+
from langgraph.graph.message import add_messages
|
| 3 |
+
from langchain_core.messages import AnyMessage, HumanMessage, AIMessage, SystemMessage, BaseMessage
|
| 4 |
+
from langgraph.prebuilt import ToolNode
|
| 5 |
+
from langgraph.graph import START, StateGraph, MessagesState, END
|
| 6 |
+
from langgraph.prebuilt import tools_condition
|
| 7 |
+
from langchain_huggingface import HuggingFaceEndpoint, ChatHuggingFace
|
| 8 |
+
from langchain_core.runnables import RunnableConfig
|
| 9 |
+
|
| 10 |
+
from langchain_community.tools import DuckDuckGoSearchRun, WikipediaQueryRun, ArxivQueryRun
|
| 11 |
+
from langchain_community.utilities import WikipediaAPIWrapper, ArxivAPIWrapper
|
| 12 |
+
from langchain_community.tools.wikidata.tool import WikidataAPIWrapper, WikidataQueryRun
|
| 13 |
+
|
| 14 |
+
from langchain_openai import ChatOpenAI
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
from tools import GetYouTubeTranscriptTool, ImageRecognitionTool
|
| 18 |
+
from dotenv import load_dotenv
|
| 19 |
+
import os
|
| 20 |
+
|
| 21 |
+
load_dotenv()
|
| 22 |
+
HUGGINGFACEHUB_API_TOKEN = os.getenv("HF_TOKEN")
|
| 23 |
+
|
| 24 |
+
# Define your state class if needed
|
| 25 |
+
class AgentState(TypedDict):
|
| 26 |
+
"""The state of the agent."""
|
| 27 |
+
|
| 28 |
+
messages: Annotated[Sequence[BaseMessage], add_messages]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def build_graph():
|
| 33 |
+
"""
|
| 34 |
+
Build and return the compiled LangGraph Runnable agent.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def call_model(
|
| 38 |
+
state: AgentState,
|
| 39 |
+
config: RunnableConfig,
|
| 40 |
+
):
|
| 41 |
+
system_prompt = SystemMessage("You are a general AI assistant. I will ask you a question. Report only your final answer without the thoughts or any other text. 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. Also be direct when doing a tool call. Try using other tools before using the DuckDuckGoTool. If you can't find informations, answer based on your personal knowledge.")
|
| 42 |
+
|
| 43 |
+
response = model.invoke([system_prompt] + state["messages"], config)
|
| 44 |
+
# We return a list, because this will get added to the existing list
|
| 45 |
+
return {"messages": [response]}
|
| 46 |
+
|
| 47 |
+
def should_continue(state: AgentState):
|
| 48 |
+
messages = state["messages"]
|
| 49 |
+
last_message = messages[-1]
|
| 50 |
+
print(last_message)
|
| 51 |
+
# If there is no function call, then we finish
|
| 52 |
+
if not last_message.tool_calls:
|
| 53 |
+
return "end"
|
| 54 |
+
# Otherwise if there is, we continue
|
| 55 |
+
else:
|
| 56 |
+
return "continue"
|
| 57 |
+
|
| 58 |
+
model = ChatOpenAI(model="o1")
|
| 59 |
+
image_model = ChatOpenAI(model="gpt-4o")
|
| 60 |
+
|
| 61 |
+
tools = [GetYouTubeTranscriptTool(),
|
| 62 |
+
WikidataQueryRun(api_wrapper=WikidataAPIWrapper()),
|
| 63 |
+
DuckDuckGoSearchRun(),
|
| 64 |
+
ArxivQueryRun(api_wrapper=ArxivAPIWrapper()),
|
| 65 |
+
WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()),
|
| 66 |
+
ImageRecognitionTool(hf_endpoint=image_model)]
|
| 67 |
+
model = model.bind_tools(tools)
|
| 68 |
+
|
| 69 |
+
tool_node = ToolNode(tools)
|
| 70 |
+
|
| 71 |
+
workflow = StateGraph(AgentState)
|
| 72 |
+
|
| 73 |
+
# Define the two nodes we will cycle between
|
| 74 |
+
workflow.add_node("agent", call_model)
|
| 75 |
+
workflow.add_node("tools", tool_node)
|
| 76 |
+
|
| 77 |
+
# Set the entrypoint as `agent`
|
| 78 |
+
# This means that this node is the first one called
|
| 79 |
+
workflow.set_entry_point("agent")
|
| 80 |
+
|
| 81 |
+
# We now add a conditional edge
|
| 82 |
+
workflow.add_conditional_edges(
|
| 83 |
+
"agent",
|
| 84 |
+
should_continue,
|
| 85 |
+
{
|
| 86 |
+
# If `tools`, then we call the tool node.
|
| 87 |
+
"continue": "tools",
|
| 88 |
+
"end": END
|
| 89 |
+
},
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
workflow.add_edge("tools", "agent")
|
| 93 |
+
|
| 94 |
+
graph = workflow.compile()
|
| 95 |
+
|
| 96 |
+
return graph
|
app.py
CHANGED
|
@@ -3,23 +3,36 @@ import gradio as gr
|
|
| 3 |
import requests
|
| 4 |
import inspect
|
| 5 |
import pandas as pd
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
| 9 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 10 |
|
|
|
|
|
|
|
| 11 |
# --- Basic Agent Definition ---
|
| 12 |
# ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
|
| 13 |
class BasicAgent:
|
| 14 |
def __init__(self):
|
|
|
|
| 15 |
print("BasicAgent initialized.")
|
| 16 |
-
def __call__(self,
|
| 17 |
-
print(f"Agent received question (first 50 chars): {
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
"""
|
| 24 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|
| 25 |
and displays the results.
|
|
@@ -42,7 +55,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
| 42 |
try:
|
| 43 |
agent = BasicAgent()
|
| 44 |
except Exception as e:
|
| 45 |
-
|
| 46 |
return f"Error initializing agent: {e}", None
|
| 47 |
# 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)
|
| 48 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
|
|
@@ -59,14 +72,14 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
| 59 |
return "Fetched questions list is empty or invalid format.", None
|
| 60 |
print(f"Fetched {len(questions_data)} questions.")
|
| 61 |
except requests.exceptions.RequestException as e:
|
| 62 |
-
|
| 63 |
return f"Error fetching questions: {e}", None
|
| 64 |
except requests.exceptions.JSONDecodeError as e:
|
| 65 |
-
|
| 66 |
print(f"Response text: {response.text[:500]}")
|
| 67 |
return f"Error decoding server response for questions: {e}", None
|
| 68 |
except Exception as e:
|
| 69 |
-
|
| 70 |
return f"An unexpected error occurred fetching questions: {e}", None
|
| 71 |
|
| 72 |
# 3. Run your Agent
|
|
@@ -74,17 +87,19 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
| 74 |
answers_payload = []
|
| 75 |
print(f"Running agent on {len(questions_data)} questions...")
|
| 76 |
for item in questions_data:
|
|
|
|
| 77 |
task_id = item.get("task_id")
|
| 78 |
question_text = item.get("question")
|
|
|
|
| 79 |
if not task_id or question_text is None:
|
| 80 |
print(f"Skipping item with missing task_id or question: {item}")
|
| 81 |
continue
|
| 82 |
try:
|
| 83 |
-
submitted_answer = agent(question_text)
|
| 84 |
answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
|
| 85 |
results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
|
| 86 |
except Exception as e:
|
| 87 |
-
|
| 88 |
results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
|
| 89 |
|
| 90 |
if not answers_payload:
|
|
@@ -99,7 +114,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
|
|
| 99 |
# 5. Submit
|
| 100 |
print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
|
| 101 |
try:
|
| 102 |
-
response = requests.post(submit_url, json=submission_data, timeout=
|
| 103 |
response.raise_for_status()
|
| 104 |
result_data = response.json()
|
| 105 |
final_status = (
|
|
|
|
| 3 |
import requests
|
| 4 |
import inspect
|
| 5 |
import pandas as pd
|
| 6 |
+
import logging
|
| 7 |
+
from time import sleep
|
| 8 |
+
from typing import Optional
|
| 9 |
|
| 10 |
+
from tools import fetch_and_process_file
|
| 11 |
+
from agent import build_graph
|
| 12 |
+
|
| 13 |
+
# --- Environment Variables ---
|
| 14 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 15 |
|
| 16 |
+
logging.basicConfig(level=logging.ERROR)
|
| 17 |
+
|
| 18 |
# --- Basic Agent Definition ---
|
| 19 |
# ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
|
| 20 |
class BasicAgent:
|
| 21 |
def __init__(self):
|
| 22 |
+
self.graph = build_graph()
|
| 23 |
print("BasicAgent initialized.")
|
| 24 |
+
def __call__(self, task_id: str, question_text: str, file_name: str) -> str:
|
| 25 |
+
print(f"Agent received question (first 50 chars): {question_text[:50]}...")
|
| 26 |
+
if len(file_name)>0:
|
| 27 |
+
print("A file is present, fetching it")
|
| 28 |
+
file_content = " \n " + fetch_and_process_file(task_id)
|
| 29 |
+
else:
|
| 30 |
+
file_content = ""
|
| 31 |
+
response = self.graph.invoke({"messages": [("user", question_text + file_content)]})
|
| 32 |
+
print(f"Agent returning answer: {response['messages'][-1].content}")
|
| 33 |
+
return response['messages'][-1].content
|
| 34 |
+
|
| 35 |
+
def run_and_submit_all( profile: Optional[gr.OAuthProfile]):
|
| 36 |
"""
|
| 37 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|
| 38 |
and displays the results.
|
|
|
|
| 55 |
try:
|
| 56 |
agent = BasicAgent()
|
| 57 |
except Exception as e:
|
| 58 |
+
logging.exception("Error instantiating agent:")
|
| 59 |
return f"Error initializing agent: {e}", None
|
| 60 |
# 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)
|
| 61 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
|
|
|
|
| 72 |
return "Fetched questions list is empty or invalid format.", None
|
| 73 |
print(f"Fetched {len(questions_data)} questions.")
|
| 74 |
except requests.exceptions.RequestException as e:
|
| 75 |
+
logging.exception(f"Error fetching questions: {e}")
|
| 76 |
return f"Error fetching questions: {e}", None
|
| 77 |
except requests.exceptions.JSONDecodeError as e:
|
| 78 |
+
logging.exception(f"Error decoding JSON response from questions endpoint: {e}")
|
| 79 |
print(f"Response text: {response.text[:500]}")
|
| 80 |
return f"Error decoding server response for questions: {e}", None
|
| 81 |
except Exception as e:
|
| 82 |
+
logging.exception(f"An unexpected error occurred fetching questions: {e}")
|
| 83 |
return f"An unexpected error occurred fetching questions: {e}", None
|
| 84 |
|
| 85 |
# 3. Run your Agent
|
|
|
|
| 87 |
answers_payload = []
|
| 88 |
print(f"Running agent on {len(questions_data)} questions...")
|
| 89 |
for item in questions_data:
|
| 90 |
+
sleep(30)
|
| 91 |
task_id = item.get("task_id")
|
| 92 |
question_text = item.get("question")
|
| 93 |
+
file_name = item.get("file_name")
|
| 94 |
if not task_id or question_text is None:
|
| 95 |
print(f"Skipping item with missing task_id or question: {item}")
|
| 96 |
continue
|
| 97 |
try:
|
| 98 |
+
submitted_answer = agent(task_id, question_text, file_name)
|
| 99 |
answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
|
| 100 |
results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
|
| 101 |
except Exception as e:
|
| 102 |
+
logging.exception(f"Error running agent on task {task_id}: {e}")
|
| 103 |
results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
|
| 104 |
|
| 105 |
if not answers_payload:
|
|
|
|
| 114 |
# 5. Submit
|
| 115 |
print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
|
| 116 |
try:
|
| 117 |
+
response = requests.post(submit_url, json=submission_data, timeout=600)
|
| 118 |
response.raise_for_status()
|
| 119 |
result_data = response.json()
|
| 120 |
final_status = (
|
hf_agents_course_assignment.code-workspace
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"folders": [
|
| 3 |
+
{
|
| 4 |
+
"path": "."
|
| 5 |
+
}
|
| 6 |
+
]
|
| 7 |
+
}
|
question_analyzer.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from typing import List, Dict, Any
|
| 5 |
+
|
| 6 |
+
class QuestionAnalyzer:
|
| 7 |
+
def __init__(self, api_url: str = "https://agents-course-unit4-scoring.hf.space"):
|
| 8 |
+
self.api_url = api_url
|
| 9 |
+
self.questions_url = f"{api_url}/questions"
|
| 10 |
+
self.questions_data = None
|
| 11 |
+
|
| 12 |
+
def fetch_questions(self) -> bool:
|
| 13 |
+
"""
|
| 14 |
+
Recupera le domande dal server e le salva nell'istanza
|
| 15 |
+
Returns: True se il fetch è andato a buon fine, False altrimenti
|
| 16 |
+
"""
|
| 17 |
+
print(f"Fetching questions from: {self.questions_url}")
|
| 18 |
+
try:
|
| 19 |
+
response = requests.get(self.questions_url, timeout=15)
|
| 20 |
+
response.raise_for_status()
|
| 21 |
+
self.questions_data = response.json()
|
| 22 |
+
|
| 23 |
+
if not self.questions_data:
|
| 24 |
+
print("❌ Questions list is empty.")
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
print(f"✅ Successfully fetched {len(self.questions_data)} questions.")
|
| 28 |
+
return True
|
| 29 |
+
|
| 30 |
+
except requests.exceptions.RequestException as e:
|
| 31 |
+
print(f"❌ Error fetching questions: {e}")
|
| 32 |
+
return False
|
| 33 |
+
except json.JSONDecodeError as e:
|
| 34 |
+
print(f"❌ Error decoding JSON response: {e}")
|
| 35 |
+
return False
|
| 36 |
+
except Exception as e:
|
| 37 |
+
print(f"❌ Unexpected error: {e}")
|
| 38 |
+
return False
|
| 39 |
+
|
| 40 |
+
def analyze_structure(self) -> None:
|
| 41 |
+
"""Analizza la struttura delle domande"""
|
| 42 |
+
if not self.questions_data:
|
| 43 |
+
print("❌ No questions data available. Run fetch_questions() first.")
|
| 44 |
+
return
|
| 45 |
+
|
| 46 |
+
print("\n" + "="*50)
|
| 47 |
+
print("QUESTION STRUCTURE ANALYSIS")
|
| 48 |
+
print("="*50)
|
| 49 |
+
|
| 50 |
+
# Analisi generale
|
| 51 |
+
print(f"📊 Total questions: {len(self.questions_data)}")
|
| 52 |
+
|
| 53 |
+
if len(self.questions_data) > 0:
|
| 54 |
+
# Analizza il primo elemento per capire la struttura
|
| 55 |
+
first_question = self.questions_data[0]
|
| 56 |
+
print(f"\n🔍 Keys in each question: {list(first_question.keys())}")
|
| 57 |
+
|
| 58 |
+
# Mostra il primo esempio completo
|
| 59 |
+
print(f"\n📝 First question example:")
|
| 60 |
+
for key, value in first_question.items():
|
| 61 |
+
if isinstance(value, str) and len(value) > 100:
|
| 62 |
+
print(f" {key}: {value[:100]}...")
|
| 63 |
+
else:
|
| 64 |
+
print(f" {key}: {value}")
|
| 65 |
+
|
| 66 |
+
def show_sample_questions(self, n: int = 5) -> None:
|
| 67 |
+
"""Mostra n domande di esempio"""
|
| 68 |
+
if not self.questions_data:
|
| 69 |
+
print("❌ No questions data available. Run fetch_questions() first.")
|
| 70 |
+
return
|
| 71 |
+
|
| 72 |
+
print(f"\n" + "="*50)
|
| 73 |
+
print(f"SAMPLE QUESTIONS (showing first {min(n, len(self.questions_data))})")
|
| 74 |
+
print("="*50)
|
| 75 |
+
|
| 76 |
+
for i, question in enumerate(self.questions_data[:n]):
|
| 77 |
+
print(f"\n--- Question {i+1} ---")
|
| 78 |
+
task_id = question.get('task_id', 'N/A')
|
| 79 |
+
question_text = question.get('question', 'N/A')
|
| 80 |
+
|
| 81 |
+
print(f"Task ID: {task_id}")
|
| 82 |
+
print(f"Question: {question_text[:200]}{'...' if len(question_text) > 200 else ''}")
|
| 83 |
+
|
| 84 |
+
# Mostra altri campi se presenti
|
| 85 |
+
for key, value in question.items():
|
| 86 |
+
if key not in ['task_id', 'question']:
|
| 87 |
+
if isinstance(value, str) and len(value) > 50:
|
| 88 |
+
print(f"{key}: {value[:50]}...")
|
| 89 |
+
else:
|
| 90 |
+
print(f"{key}: {value}")
|
| 91 |
+
|
| 92 |
+
def create_summary_dataframe(self) -> pd.DataFrame:
|
| 93 |
+
"""Crea un DataFrame riassuntivo delle domande"""
|
| 94 |
+
if not self.questions_data:
|
| 95 |
+
print("❌ No questions data available. Run fetch_questions() first.")
|
| 96 |
+
return pd.DataFrame()
|
| 97 |
+
|
| 98 |
+
summary_data = []
|
| 99 |
+
for question in self.questions_data:
|
| 100 |
+
summary_row = {
|
| 101 |
+
'task_id': question.get('task_id', 'N/A'),
|
| 102 |
+
'question_length': len(question.get('question', '')),
|
| 103 |
+
'question_preview': question.get('question', '')[:100] + '...' if len(question.get('question', '')) > 100 else question.get('question', ''),
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
# Aggiungi altri campi se presenti
|
| 107 |
+
for key, value in question.items():
|
| 108 |
+
if key not in ['task_id', 'question']:
|
| 109 |
+
summary_row[key] = value
|
| 110 |
+
|
| 111 |
+
summary_data.append(summary_row)
|
| 112 |
+
|
| 113 |
+
return pd.DataFrame(summary_data)
|
| 114 |
+
|
| 115 |
+
def save_questions_to_file(self, filename: str = "questions_data.json") -> bool:
|
| 116 |
+
"""Salva le domande in un file JSON locale"""
|
| 117 |
+
if not self.questions_data:
|
| 118 |
+
print("❌ No questions data available. Run fetch_questions() first.")
|
| 119 |
+
return False
|
| 120 |
+
|
| 121 |
+
try:
|
| 122 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 123 |
+
json.dump(self.questions_data, f, indent=2, ensure_ascii=False)
|
| 124 |
+
print(f"✅ Questions saved to {filename}")
|
| 125 |
+
return True
|
| 126 |
+
except Exception as e:
|
| 127 |
+
print(f"❌ Error saving questions: {e}")
|
| 128 |
+
return False
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def main():
|
| 132 |
+
"""Funzione principale per testare il QuestionAnalyzer"""
|
| 133 |
+
print("🚀 Starting Question Analysis...")
|
| 134 |
+
|
| 135 |
+
# Crea l'analyzer
|
| 136 |
+
analyzer = QuestionAnalyzer()
|
| 137 |
+
|
| 138 |
+
# Fetch delle domande
|
| 139 |
+
if not analyzer.fetch_questions():
|
| 140 |
+
print("❌ Failed to fetch questions. Exiting.")
|
| 141 |
+
return
|
| 142 |
+
|
| 143 |
+
# Analizza la struttura
|
| 144 |
+
analyzer.analyze_structure()
|
| 145 |
+
|
| 146 |
+
# Mostra domande di esempio
|
| 147 |
+
analyzer.show_sample_questions(3)
|
| 148 |
+
|
| 149 |
+
# Crea e mostra DataFrame riassuntivo
|
| 150 |
+
df = analyzer.create_summary_dataframe()
|
| 151 |
+
if not df.empty:
|
| 152 |
+
print(f"\n" + "="*50)
|
| 153 |
+
print("SUMMARY DATAFRAME")
|
| 154 |
+
print("="*50)
|
| 155 |
+
print(df.head(10))
|
| 156 |
+
|
| 157 |
+
# Salva le domande localmente
|
| 158 |
+
analyzer.save_questions_to_file()
|
| 159 |
+
|
| 160 |
+
print(f"\n✅ Analysis complete!")
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
if __name__ == "__main__":
|
| 164 |
+
main()
|
questions_data.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 |
+
]
|
requirements.txt
CHANGED
|
@@ -1,2 +1,154 @@
|
|
| 1 |
-
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
agent==0.1.3
|
| 2 |
+
aiofiles==23.2.1
|
| 3 |
+
aiohappyeyeballs==2.6.1
|
| 4 |
+
aiohttp==3.12.13
|
| 5 |
+
aiosignal==1.3.2
|
| 6 |
+
annotated-types==0.7.0
|
| 7 |
+
anyio==4.9.0
|
| 8 |
+
async-timeout==4.0.3
|
| 9 |
+
attrs==25.3.0
|
| 10 |
+
Authlib==1.6.0
|
| 11 |
+
cachetools==5.5.2
|
| 12 |
+
certifi==2025.6.15
|
| 13 |
+
cffi==1.17.1
|
| 14 |
+
charset-normalizer==3.4.2
|
| 15 |
+
click==8.1.8
|
| 16 |
+
contourpy==1.3.0
|
| 17 |
+
cryptography==45.0.5
|
| 18 |
+
cycler==0.12.1
|
| 19 |
+
dataclasses-json==0.6.7
|
| 20 |
+
defusedxml==0.7.1
|
| 21 |
+
deprecation==2.1.0
|
| 22 |
+
distro==1.9.0
|
| 23 |
+
dotenv==0.9.9
|
| 24 |
+
exceptiongroup==1.3.0
|
| 25 |
+
fastapi==0.115.14
|
| 26 |
+
ffmpy==0.6.0
|
| 27 |
+
filelock==3.18.0
|
| 28 |
+
filetype==1.2.0
|
| 29 |
+
fonttools==4.58.4
|
| 30 |
+
frozenlist==1.7.0
|
| 31 |
+
fsspec==2025.5.1
|
| 32 |
+
google-ai-generativelanguage==0.6.18
|
| 33 |
+
google-api-core==2.25.1
|
| 34 |
+
google-auth==2.40.3
|
| 35 |
+
googleapis-common-protos==1.70.0
|
| 36 |
+
gotrue==2.12.2
|
| 37 |
+
gradio==4.44.1
|
| 38 |
+
gradio_client==1.3.0
|
| 39 |
+
grpcio==1.73.1
|
| 40 |
+
grpcio-status==1.73.1
|
| 41 |
+
h11==0.16.0
|
| 42 |
+
h2==4.2.0
|
| 43 |
+
hf-xet==1.1.5
|
| 44 |
+
hpack==4.1.0
|
| 45 |
+
httpcore==1.0.9
|
| 46 |
+
httpx==0.28.1
|
| 47 |
+
httpx-sse==0.4.1
|
| 48 |
+
huggingface-hub==0.33.1
|
| 49 |
+
hyperframe==6.1.0
|
| 50 |
+
idna==3.10
|
| 51 |
+
importlib_resources==6.5.2
|
| 52 |
+
iniconfig==2.1.0
|
| 53 |
+
itsdangerous==2.2.0
|
| 54 |
+
Jinja2==3.1.6
|
| 55 |
+
jiter==0.10.0
|
| 56 |
+
joblib==1.5.1
|
| 57 |
+
jsonpatch==1.33
|
| 58 |
+
jsonpointer==3.0.0
|
| 59 |
+
kiwisolver==1.4.7
|
| 60 |
+
langchain==0.3.26
|
| 61 |
+
langchain-community==0.3.26
|
| 62 |
+
langchain-core==0.3.66
|
| 63 |
+
langchain-google-genai==2.1.5
|
| 64 |
+
langchain-huggingface==0.3.0
|
| 65 |
+
langchain-openai==0.3.27
|
| 66 |
+
langchain-tavily==0.2.5
|
| 67 |
+
langchain-text-splitters==0.3.8
|
| 68 |
+
langgraph==0.5.0
|
| 69 |
+
langgraph-checkpoint==2.1.0
|
| 70 |
+
langgraph-prebuilt==0.5.1
|
| 71 |
+
langgraph-sdk==0.1.72
|
| 72 |
+
langsmith==0.4.4
|
| 73 |
+
markdown-it-py==3.0.0
|
| 74 |
+
MarkupSafe==2.1.5
|
| 75 |
+
marshmallow==3.26.1
|
| 76 |
+
matplotlib==3.9.4
|
| 77 |
+
mdurl==0.1.2
|
| 78 |
+
mpmath==1.3.0
|
| 79 |
+
multidict==6.6.2
|
| 80 |
+
mypy_extensions==1.1.0
|
| 81 |
+
networkx==3.2.1
|
| 82 |
+
numpy==2.0.2
|
| 83 |
+
openai==1.93.0
|
| 84 |
+
orjson==3.10.18
|
| 85 |
+
ormsgpack==1.10.0
|
| 86 |
+
packaging==24.2
|
| 87 |
+
pandas==2.3.0
|
| 88 |
+
pillow==10.4.0
|
| 89 |
+
pluggy==1.6.0
|
| 90 |
+
postgrest==1.1.1
|
| 91 |
+
propcache==0.3.2
|
| 92 |
+
proto-plus==1.26.1
|
| 93 |
+
protobuf==6.31.1
|
| 94 |
+
pyasn1==0.6.1
|
| 95 |
+
pyasn1_modules==0.4.2
|
| 96 |
+
pycparser==2.22
|
| 97 |
+
pydantic==2.11.7
|
| 98 |
+
pydantic-settings==2.10.1
|
| 99 |
+
pydantic_core==2.33.2
|
| 100 |
+
pydub==0.25.1
|
| 101 |
+
Pygments==2.19.2
|
| 102 |
+
PyJWT==2.10.1
|
| 103 |
+
pyparsing==3.2.3
|
| 104 |
+
pytest==8.4.1
|
| 105 |
+
pytest-mock==3.14.1
|
| 106 |
+
python-dateutil==2.9.0.post0
|
| 107 |
+
python-dotenv==1.1.1
|
| 108 |
+
python-multipart==0.0.20
|
| 109 |
+
pytz==2025.2
|
| 110 |
+
PyYAML==6.0.2
|
| 111 |
+
realtime==2.5.3
|
| 112 |
+
regex==2024.11.6
|
| 113 |
+
requests==2.32.4
|
| 114 |
+
requests-toolbelt==1.0.0
|
| 115 |
+
rich==14.0.0
|
| 116 |
+
rsa==4.9.1
|
| 117 |
+
ruff==0.12.1
|
| 118 |
+
safetensors==0.5.3
|
| 119 |
+
scikit-learn==1.6.1
|
| 120 |
+
scipy==1.13.1
|
| 121 |
+
semantic-version==2.10.0
|
| 122 |
+
sentence-transformers==4.1.0
|
| 123 |
+
shellingham==1.5.4
|
| 124 |
+
six==1.17.0
|
| 125 |
+
sniffio==1.3.1
|
| 126 |
+
SQLAlchemy==2.0.41
|
| 127 |
+
starlette==0.46.2
|
| 128 |
+
storage3==0.12.0
|
| 129 |
+
StrEnum==0.4.15
|
| 130 |
+
supabase==2.16.0
|
| 131 |
+
supafunc==0.10.1
|
| 132 |
+
sympy==1.14.0
|
| 133 |
+
tenacity==9.1.2
|
| 134 |
+
threadpoolctl==3.6.0
|
| 135 |
+
tiktoken==0.9.0
|
| 136 |
+
tokenizers==0.21.2
|
| 137 |
+
tomli==2.2.1
|
| 138 |
+
tomlkit==0.12.0
|
| 139 |
+
torch==2.7.1
|
| 140 |
+
tqdm==4.67.1
|
| 141 |
+
transformers==4.53.0
|
| 142 |
+
typer==0.16.0
|
| 143 |
+
typing-inspect==0.9.0
|
| 144 |
+
typing-inspection==0.4.1
|
| 145 |
+
typing_extensions==4.14.0
|
| 146 |
+
tzdata==2025.2
|
| 147 |
+
urllib3==2.5.0
|
| 148 |
+
uvicorn==0.35.0
|
| 149 |
+
websockets==12.0
|
| 150 |
+
xxhash==3.5.0
|
| 151 |
+
yarl==1.20.1
|
| 152 |
+
youtube-transcript-api==1.1.1
|
| 153 |
+
zipp==3.23.0
|
| 154 |
+
zstandard==0.23.0
|
system_prompt.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
You are a general AI assistant. I will ask you a question.
|
| 2 |
+
Report your thoughts, and finish your answer with the following template:
|
| 3 |
+
FINAL ANSWER: [YOUR FINAL ANSWER].
|
| 4 |
+
YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
|
| 5 |
+
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.
|
| 6 |
+
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.
|
| 7 |
+
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.
|
tools.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import base64
|
| 3 |
+
import tempfile
|
| 4 |
+
import requests
|
| 5 |
+
|
| 6 |
+
from typing import Type, Dict, Any
|
| 7 |
+
|
| 8 |
+
from pydantic import BaseModel, Field, PrivateAttr
|
| 9 |
+
|
| 10 |
+
from youtube_transcript_api import YouTubeTranscriptApi
|
| 11 |
+
|
| 12 |
+
from langchain.tools import BaseTool, tool
|
| 13 |
+
from langchain_community.document_loaders import UnstructuredExcelLoader
|
| 14 |
+
from langchain_community.llms import HuggingFaceEndpoint
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class ImageRecognitionTool(BaseTool):
|
| 18 |
+
name: str = "image_recognition"
|
| 19 |
+
description: str = (
|
| 20 |
+
"Recognizes the contents of a base64-encoded image using a Qwen-VL model. "
|
| 21 |
+
"Input should be a dictionary with keys: 'image' (base64 string), and optionally 'prompt'."
|
| 22 |
+
)
|
| 23 |
+
_hf_endpoint: HuggingFaceEndpoint = PrivateAttr()
|
| 24 |
+
|
| 25 |
+
def __init__(self, hf_endpoint: HuggingFaceEndpoint):
|
| 26 |
+
super().__init__()
|
| 27 |
+
self._hf_endpoint = hf_endpoint
|
| 28 |
+
|
| 29 |
+
def _run(self, input: Dict[str, Any]) -> str:
|
| 30 |
+
image = input.get("image")
|
| 31 |
+
if not image:
|
| 32 |
+
raise ValueError("Input must include 'image' as a base64-encoded string.")
|
| 33 |
+
prompt = input.get("prompt", "What is in this image?")
|
| 34 |
+
|
| 35 |
+
# The Qwen-VL model expects {"image": base64, "prompt": string}
|
| 36 |
+
payload = {
|
| 37 |
+
"image": image,
|
| 38 |
+
"prompt": prompt
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
result = self.hf_endpoint.invoke(payload)
|
| 42 |
+
|
| 43 |
+
# Handle different possible formats
|
| 44 |
+
if isinstance(result, dict):
|
| 45 |
+
return result.get("generated_text", str(result))
|
| 46 |
+
return str(result)
|
| 47 |
+
|
| 48 |
+
@tool
|
| 49 |
+
def fetch_and_process_file(task_id: str) -> str:
|
| 50 |
+
"""
|
| 51 |
+
Fetches and processes a file from the given task_id URL.
|
| 52 |
+
|
| 53 |
+
- .py → returns the full code as a string.
|
| 54 |
+
- .png → returns base64-encoded string.
|
| 55 |
+
- .xlsx → returns extracted content using UnstructuredExcelLoader.
|
| 56 |
+
- .mp3 → transcribes audio using Hugging Face Whisper model.
|
| 57 |
+
"""
|
| 58 |
+
url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
|
| 59 |
+
response = requests.get(url)
|
| 60 |
+
if response.status_code != 200:
|
| 61 |
+
return f"Error fetching file: HTTP {response.status_code}"
|
| 62 |
+
|
| 63 |
+
content_disposition = response.headers.get("Content-Disposition", "")
|
| 64 |
+
filename = content_disposition.split("filename=")[-1].strip('"')
|
| 65 |
+
extension = filename.split(".")[-1].lower()
|
| 66 |
+
|
| 67 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=f".{extension}") as tmp:
|
| 68 |
+
tmp.write(response.content)
|
| 69 |
+
tmp_path = tmp.name
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
if extension == "py":
|
| 73 |
+
# Return code as a single string
|
| 74 |
+
with open(tmp_path, "r", encoding="utf-8") as f:
|
| 75 |
+
return "Python file content:\n" + f.read()
|
| 76 |
+
|
| 77 |
+
elif extension in ("png", "jpg", "jpeg"):
|
| 78 |
+
# Base64 encode image
|
| 79 |
+
with open(tmp_path, "rb") as f:
|
| 80 |
+
return "Image base64:\n" + base64.b64encode(f.read()).decode("utf-8")
|
| 81 |
+
|
| 82 |
+
elif extension == "xlsx":
|
| 83 |
+
loader = UnstructuredExcelLoader(tmp_path)
|
| 84 |
+
docs = loader.load()
|
| 85 |
+
return "Excel file content:\n" + "\n".join(doc.page_content for doc in docs)
|
| 86 |
+
|
| 87 |
+
elif extension == "mp3":
|
| 88 |
+
whisper_endpoint = HuggingFaceEndpoint(
|
| 89 |
+
repo_id="openai/whisper-base",
|
| 90 |
+
task="automatic-speech-recognition"
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
with open(tmp_path, "rb") as f:
|
| 94 |
+
audio_b64 = base64.b64encode(f.read()).decode()
|
| 95 |
+
|
| 96 |
+
# Call the HuggingFaceEndpoint with base64 audio input
|
| 97 |
+
transcription = whisper_endpoint.invoke(audio_b64)
|
| 98 |
+
|
| 99 |
+
# The response is normally a dict or string, might need adjustment
|
| 100 |
+
if isinstance(transcription, dict) and "text" in transcription:
|
| 101 |
+
return transcription["text"]
|
| 102 |
+
|
| 103 |
+
return "MP3 transcription:\n" + str(transcription)
|
| 104 |
+
|
| 105 |
+
else:
|
| 106 |
+
return f"Unsupported file type: .{extension}"
|
| 107 |
+
|
| 108 |
+
finally:
|
| 109 |
+
os.remove(tmp_path)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class YouTubeTranscriptInput(BaseModel):
|
| 115 |
+
url: str = Field(..., description="A YouTube video URL")
|
| 116 |
+
|
| 117 |
+
class GetYouTubeTranscriptTool(BaseTool):
|
| 118 |
+
name: str = "get_youtube_transcript"
|
| 119 |
+
description: str = "Fetches transcript from a YouTube video, if available."
|
| 120 |
+
args_schema: Type[BaseModel] = YouTubeTranscriptInput
|
| 121 |
+
|
| 122 |
+
def _run(self, url: str) -> str:
|
| 123 |
+
try:
|
| 124 |
+
video_id = url.split("v=")[-1].split("&")[0]
|
| 125 |
+
transcript_list = YouTubeTranscriptApi.get_transcript(video_id)
|
| 126 |
+
transcript = " ".join([t["text"] for t in transcript_list])
|
| 127 |
+
return transcript
|
| 128 |
+
except Exception as e:
|
| 129 |
+
return f"Failed to get transcript: {str(e)}"
|
| 130 |
+
|
| 131 |
+
def _arun(self, url: str) -> str:
|
| 132 |
+
raise NotImplementedError("Async not supported yet.")
|