Commit
·
f4c14e9
1
Parent(s):
856f7b8
app_build_v1
Browse files- agent.py +80 -0
- app.py +63 -7
- requirements.txt +21 -9
- tools/PLACEHOLDER.txt +0 -0
- tools/download_file.py +37 -0
- tools/files_to_dict.py +0 -62
- tools/files_to_text.py +20 -18
agent.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from smolagents import tool
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
from smolagents import (
|
| 6 |
+
CodeAgent,
|
| 7 |
+
InferenceClientModel,
|
| 8 |
+
Tool,
|
| 9 |
+
DuckDuckGoSearchTool,
|
| 10 |
+
VisitWebpageTool,
|
| 11 |
+
WikipediaSearchTool,
|
| 12 |
+
PythonInterpreterTool,
|
| 13 |
+
FinalAnswerTool,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
# Import your custom tools (to be used in app, not in local notebook)
|
| 17 |
+
from tools.download_file import download_file_from_url
|
| 18 |
+
from tools.files_to_text import image_to_text, pdf_to_text, text_file_to_string
|
| 19 |
+
|
| 20 |
+
def create_agent(
|
| 21 |
+
model_path: str = "Qwen/Qwen2.5-Coder-32B-Instruct"
|
| 22 |
+
):
|
| 23 |
+
"""
|
| 24 |
+
Creates and configures a CodeAgent.
|
| 25 |
+
|
| 26 |
+
This function initializes a smolagents CodeAgent equipped with the
|
| 27 |
+
recommended default tools (web search, browser, and Python interpreter),
|
| 28 |
+
together with any custom tools you may define.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
model_path (str): The identifier or local path of the Hugging Face
|
| 32 |
+
model to be loaded. By default, it uses `Qwen/Qwen2.5-32B-Instruct`,
|
| 33 |
+
but any compatible model can be substituted.
|
| 34 |
+
|
| 35 |
+
Returns:
|
| 36 |
+
CodeAgent: A fully initialized agent ready to run code, query tools,
|
| 37 |
+
and perform multi-step reasoning using the selected model.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
# Choose a lightweight but reasoning-capable model
|
| 41 |
+
model = InferenceClientModel(
|
| 42 |
+
model_id=model_path,
|
| 43 |
+
temperature = 0.0,
|
| 44 |
+
top_p = 1.0, # NEW
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
# Default smolagents tools (high-level)
|
| 48 |
+
default_tools = [
|
| 49 |
+
DuckDuckGoSearchTool(), # Internet search
|
| 50 |
+
VisitWebpageTool(), # Retrieve webpage content
|
| 51 |
+
PythonInterpreterTool(), # Executes agent-generated Python code
|
| 52 |
+
FinalAnswerTool(), # Ends agent reasoning and returns final answer
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
# Custom tools (critical for GAIA)
|
| 56 |
+
custom_tools = [
|
| 57 |
+
download_file_from_url, # file downloader
|
| 58 |
+
text_file_to_string, # .txt, .md, .json, etc.
|
| 59 |
+
pdf_to_text, # PyMuPDF-based safe PDF parser
|
| 60 |
+
image_to_text, # OCR for images
|
| 61 |
+
]
|
| 62 |
+
|
| 63 |
+
tools = default_tools + custom_tools
|
| 64 |
+
|
| 65 |
+
# Create the CodeAgent (best for GAIA because it supports Python)
|
| 66 |
+
agent = CodeAgent(
|
| 67 |
+
model=model,
|
| 68 |
+
tools=tools,
|
| 69 |
+
add_base_tools=True, # probably redundant, but it does not hurt
|
| 70 |
+
max_steps=7,
|
| 71 |
+
additional_authorized_imports = ['numpy','subprocess', 're', 'pandas',
|
| 72 |
+
'json', 'os', 'pathlib', 'tempfile',
|
| 73 |
+
'matplotlib.pyplot', 'seaborn'],
|
| 74 |
+
verbosity_level = 1,
|
| 75 |
+
max_print_outputs_length=1_000_000
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
return agent
|
| 79 |
+
|
| 80 |
+
# WIP: Agentic RAG Systems
|
app.py
CHANGED
|
@@ -3,21 +3,77 @@ import gradio as gr
|
|
| 3 |
import requests
|
| 4 |
import inspect
|
| 5 |
import pandas as pd
|
|
|
|
|
|
|
| 6 |
|
| 7 |
# (Keep Constants as is)
|
| 8 |
# --- Constants ---
|
| 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 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
def run_and_submit_all( profile: gr.OAuthProfile | None):
|
| 23 |
"""
|
|
|
|
| 3 |
import requests
|
| 4 |
import inspect
|
| 5 |
import pandas as pd
|
| 6 |
+
from agent import create_agent
|
| 7 |
+
from typing import Optional
|
| 8 |
|
| 9 |
# (Keep Constants as is)
|
| 10 |
# --- Constants ---
|
| 11 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 12 |
|
| 13 |
# --- Basic Agent Definition ---
|
|
|
|
| 14 |
class BasicAgent:
|
| 15 |
def __init__(self):
|
| 16 |
+
self.agent = create_agent()
|
| 17 |
+
self.system_prompt = """
|
| 18 |
+
You are an expert **General AI Assistant** and **Python Programmer** tasked with solving complex GAIA benchmark problems.
|
| 19 |
+
|
| 20 |
+
### 1. Reason-Act-Observe
|
| 21 |
+
Follow a **PLAN → ACT → OBSERVE** loop:
|
| 22 |
+
- **PLAN:** Break the task into 1–3 logical steps. Identify tools for each step.
|
| 23 |
+
- **ACT:** Write and run one self-contained Python block per step.
|
| 24 |
+
- **OBSERVE:** Examine outputs or errors before proceeding.
|
| 25 |
+
|
| 26 |
+
### 2. File Handling
|
| 27 |
+
- When a tool like `download_file_from_url` returns a local file path (e.g., `/tmp/data.csv`), you **MUST** save this path to a descriptive variable (e.g., `filepath`) and **immediately use that variable** as the argument for the next file-reading tool.
|
| 28 |
+
|
| 29 |
+
You must select the reading method based strictly on the file extension:
|
| 30 |
+
| File Extension | Tool / Method to Use |
|
| 31 |
+
| :--- | :--- |
|
| 32 |
+
| .csv | `pd.read_csv(filepath)` |
|
| 33 |
+
| .xlsx, .xls | `pd.read_excel(filepath)` |
|
| 34 |
+
| .pdf | `pdf_to_text(filepath)` |
|
| 35 |
+
| .txt, .md, .json | `text_file_to_string(filepath)` |
|
| 36 |
+
| .png, .jpg, .jpeg | `image_to_text(filepath)` |
|
| 37 |
+
|
| 38 |
+
### 3. Data Analysis & Answer
|
| 39 |
+
- Inspect loaded datasets first (`.head()`, `.info()`, `.describe()`) before analysis.
|
| 40 |
+
- Write clean, idiomatic Python code. Before that, check if there is any pre-made tool that would work for the task.
|
| 41 |
+
- Use `FinalAnswerTool` **only once the problem is fully solved** to give a concise final answer.
|
| 42 |
+
|
| 43 |
+
### 4. Additional instructions for the following tasks provided by GAIA team
|
| 44 |
+
- You are a general AI assistant. I will ask you a question. Do not reveal your internal reasoning. Only the content inside FinalAnswerTool will be evaluated.
|
| 45 |
+
- 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.
|
| 46 |
+
|
| 47 |
+
### 5. To provide the final answer, you MUST call the final_answer tool inside a <code> block.
|
| 48 |
+
|
| 49 |
+
- Example of how to end the task:
|
| 50 |
+
|
| 51 |
+
Thought: I have found the answer. I will now provide it.
|
| 52 |
+
<code>
|
| 53 |
+
final_answer("FINAL ANSWER: The capital of France is Paris")
|
| 54 |
+
</code>
|
| 55 |
+
|
| 56 |
+
\n\n
|
| 57 |
+
"""
|
| 58 |
+
# print("Agent initialized.")
|
| 59 |
+
|
| 60 |
+
def __call__(self, question: str, file_path: Optional[str] = None) -> str:
|
| 61 |
+
|
| 62 |
+
if file_path:
|
| 63 |
+
# Inject system prompt + question and (optional) file path
|
| 64 |
+
prompt = (
|
| 65 |
+
f"{self.system_prompt}\n\n"
|
| 66 |
+
f"Question: {question}\n\n"
|
| 67 |
+
f"There is an associated file at path: {file_path}.\n"
|
| 68 |
+
f"Use the appropriate tool to download it (if necessary) and read it before answering"
|
| 69 |
+
)
|
| 70 |
+
else:
|
| 71 |
+
prompt = (
|
| 72 |
+
f"{self.system_prompt}\n\n"
|
| 73 |
+
f"Question: {question}\n\n"
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
return self.agent.run(prompt)
|
| 77 |
|
| 78 |
def run_and_submit_all( profile: gr.OAuthProfile | None):
|
| 79 |
"""
|
requirements.txt
CHANGED
|
@@ -1,11 +1,23 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
gradio
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
pandas
|
| 5 |
-
openpyxl
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
Pillow
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core agent framework (PINNED)
|
| 2 |
+
smolagents==1.23.0
|
| 3 |
+
transformers==4.53.3
|
| 4 |
+
huggingface-hub==0.36.0
|
| 5 |
+
|
| 6 |
+
# UI
|
| 7 |
gradio
|
| 8 |
+
|
| 9 |
+
# Networking & retrieval
|
| 10 |
+
requests==2.32.5
|
| 11 |
+
ddgs==9.10.0
|
| 12 |
+
|
| 13 |
+
# Data handling
|
| 14 |
pandas
|
| 15 |
+
openpyxl==3.1.5
|
| 16 |
+
|
| 17 |
+
# File & document parsing (PINNED: brittle)
|
| 18 |
+
Pillow==11.3.0
|
| 19 |
+
pdfplumber==0.11.8
|
| 20 |
+
PyMuPDF==1.26.7
|
| 21 |
+
|
| 22 |
+
# OCR (OPTIONAL, disabled)
|
| 23 |
+
# pytesseract==0.3.13
|
tools/PLACEHOLDER.txt
DELETED
|
File without changes
|
tools/download_file.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
|
| 3 |
+
@tool
|
| 4 |
+
def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
|
| 5 |
+
"""
|
| 6 |
+
Downloads a file from the given URL to a temporary local location.
|
| 7 |
+
|
| 8 |
+
The file is saved in the system's temporary directory. The filename is either passed as argument or
|
| 9 |
+
inferred from the URL's path; if it cannot be determined, a generic name is used.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
url: The URL of the file to download (str).
|
| 13 |
+
filename: Optional filename, will generate one based on URL if not provided
|
| 14 |
+
|
| 15 |
+
Returns:
|
| 16 |
+
The full local file path (str) of the downloaded file if successful,
|
| 17 |
+
or an error message string detailing the failure.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import requests, tempfile, os
|
| 21 |
+
from urllib.parse import urlparse
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
if not filename:
|
| 25 |
+
filename = os.path.basename(urlparse(url).path) or "downloaded_file"
|
| 26 |
+
filepath = os.path.join(tempfile.gettempdir(), filename)
|
| 27 |
+
|
| 28 |
+
response = requests.get(url, stream=True)
|
| 29 |
+
response.raise_for_status()
|
| 30 |
+
|
| 31 |
+
with open(filepath, "wb") as f:
|
| 32 |
+
for chunk in response.iter_content(chunk_size=8192):
|
| 33 |
+
f.write(chunk)
|
| 34 |
+
|
| 35 |
+
return filepath
|
| 36 |
+
except Exception as e:
|
| 37 |
+
return f"Download error: {e}"
|
tools/files_to_dict.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
| 1 |
-
from smolagents import tool
|
| 2 |
-
import pandas as pd
|
| 3 |
-
import pymupdf
|
| 4 |
-
|
| 5 |
-
@tool
|
| 6 |
-
def csv_to_dict(csv_file_path: str) -> str:
|
| 7 |
-
"""
|
| 8 |
-
Reads a CSV file from the given path and returns:
|
| 9 |
-
- the data as a list of dictionaries,
|
| 10 |
-
- the list of column names,
|
| 11 |
-
- a basic descriptive summary of numeric columns.
|
| 12 |
-
|
| 13 |
-
Args:
|
| 14 |
-
csv_file_path (str): Path to the CSV file.
|
| 15 |
-
|
| 16 |
-
Returns:
|
| 17 |
-
str: A dictionary-like structure containing:
|
| 18 |
-
"data", "columns", and "describe".
|
| 19 |
-
"""
|
| 20 |
-
try:
|
| 21 |
-
df = pd.read_csv(csv_file_path)
|
| 22 |
-
|
| 23 |
-
output = {
|
| 24 |
-
"columns" : df.columns.tolist(),
|
| 25 |
-
"describe": df.describe(include="all",percentiles=[.5]).to_dict(),
|
| 26 |
-
"data" : df.to_dict(orient="records")
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
-
return output
|
| 30 |
-
except FileNotFoundError:
|
| 31 |
-
return f"Error: The file at '{csv_file_path}' was not found."
|
| 32 |
-
except Exception as e:
|
| 33 |
-
return f"An error occurred: {e}"
|
| 34 |
-
|
| 35 |
-
@tool
|
| 36 |
-
def excel_to_dict(xlsx_file_path: str) -> str:
|
| 37 |
-
"""
|
| 38 |
-
Reads an Excel (xlsx) file from the given path and returns:
|
| 39 |
-
- the data as a list of dictionaries,
|
| 40 |
-
- the list of column names,
|
| 41 |
-
- a basic descriptive summary of numeric columns.
|
| 42 |
-
|
| 43 |
-
Args:
|
| 44 |
-
xlsx_file_path (str): Path to the Excel file.
|
| 45 |
-
|
| 46 |
-
Returns:
|
| 47 |
-
str: A dictionary-like structure containing:
|
| 48 |
-
"data", "columns", and "describe".
|
| 49 |
-
"""
|
| 50 |
-
try:
|
| 51 |
-
df = pd.read_excel(xlsx_file_path)
|
| 52 |
-
output = {
|
| 53 |
-
"columns" : df.columns.tolist(),
|
| 54 |
-
"describe": df.describe(include="all",percentiles=[.5]).to_dict(),
|
| 55 |
-
"data" : df.to_dict(orient="records")
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
return output
|
| 59 |
-
except FileNotFoundError:
|
| 60 |
-
return f"Error: The file at '{xlsx_file_path}' was not found."
|
| 61 |
-
except Exception as e:
|
| 62 |
-
return f"An error occurred: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tools/files_to_text.py
CHANGED
|
@@ -10,16 +10,16 @@ def image_to_text(image_path: str) -> str:
|
|
| 10 |
Extracted text or error message
|
| 11 |
"""
|
| 12 |
try:
|
| 13 |
-
|
| 14 |
-
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
|
| 22 |
-
|
| 23 |
except ImportError:
|
| 24 |
return "Error: pytesseract is not installed. Please install it with 'pip install pytesseract' and ensure Tesseract OCR is installed on your system."
|
| 25 |
except Exception as e:
|
|
@@ -34,13 +34,15 @@ def pdf_to_text(pdf_file_path: str) -> str:
|
|
| 34 |
Returns:
|
| 35 |
str: The text content of the PDF.
|
| 36 |
"""
|
|
|
|
| 37 |
try:
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
|
|
|
| 44 |
except FileNotFoundError:
|
| 45 |
return f"Error: The file at '{pdf_file_path}' was not found."
|
| 46 |
except Exception as e:
|
|
@@ -65,10 +67,10 @@ def text_file_to_string(path: str) -> str:
|
|
| 65 |
If the file contains binary data, the returned string may be partially decoded.
|
| 66 |
"""
|
| 67 |
try:
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
except FileNotFoundError:
|
| 72 |
-
|
| 73 |
except Exception as e:
|
| 74 |
-
|
|
|
|
| 10 |
Extracted text or error message
|
| 11 |
"""
|
| 12 |
try:
|
| 13 |
+
import pytesseract
|
| 14 |
+
from PIL import Image
|
| 15 |
|
| 16 |
+
# Open the image using PIL
|
| 17 |
+
img = Image.open(image_path)
|
| 18 |
|
| 19 |
+
# Use pytesseract to extract text from the image
|
| 20 |
+
extracted_text = pytesseract.image_to_string(img)
|
| 21 |
|
| 22 |
+
return f"Extracted text from image: {extracted_text}"
|
| 23 |
except ImportError:
|
| 24 |
return "Error: pytesseract is not installed. Please install it with 'pip install pytesseract' and ensure Tesseract OCR is installed on your system."
|
| 25 |
except Exception as e:
|
|
|
|
| 34 |
Returns:
|
| 35 |
str: The text content of the PDF.
|
| 36 |
"""
|
| 37 |
+
|
| 38 |
try:
|
| 39 |
+
import pymupdf
|
| 40 |
+
doc = pymupdf.open(pdf_file_path)
|
| 41 |
+
text = ""
|
| 42 |
+
for page in doc:
|
| 43 |
+
text += page.get_text("text")
|
| 44 |
+
text += "\n"
|
| 45 |
+
return text
|
| 46 |
except FileNotFoundError:
|
| 47 |
return f"Error: The file at '{pdf_file_path}' was not found."
|
| 48 |
except Exception as e:
|
|
|
|
| 67 |
If the file contains binary data, the returned string may be partially decoded.
|
| 68 |
"""
|
| 69 |
try:
|
| 70 |
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
| 71 |
+
content = f.read()
|
| 72 |
+
return content
|
| 73 |
except FileNotFoundError:
|
| 74 |
+
return f"Error: The file at '{path}' was not found."
|
| 75 |
except Exception as e:
|
| 76 |
+
return f"An error occurred: {e}"
|