Final_Assignment_Template / basic_agent.py
volker
Additional websearch tools.
7ea0bd8
raw
history blame
6.97 kB
from smolagents import Tool, CodeAgent, HfApiModel, OpenAIServerModel
import dotenv
from ac_tools import DuckDuckGoSearchToolWH
import requests
import os
from PIL import Image
import wikipedia
from transformers import pipeline
import requests
from bs4 import BeautifulSoup
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
def init_agent():
dotenv.load_dotenv()
model = OpenAIServerModel(model_id="gpt-4o")
agent = BasicSmolAgent(model=model)
return agent
def download_file(task_id: str, filename: str) -> str:
"""
Downloads a file associated with the given task_id and saves it to the specified filename.
Args:
task_id (str): The task identifier used to fetch the file.
filename (str): The desired filename to save the file as.
Returns:
str: The absolute path to the saved file.
"""
api_url = DEFAULT_API_URL
file_url = f"{api_url}/files/{task_id}"
folder = 'data'
print(f"📡 Fetching file from: {file_url}")
try:
response = requests.get(file_url, timeout=15)
response.raise_for_status()
# Save binary content to the given filename
fpath = os.path.join(folder, filename)
with open(fpath, "wb") as f:
f.write(response.content)
abs_path = os.path.abspath(fpath)
print(f"✅ File saved as: {abs_path}")
return abs_path
except requests.exceptions.RequestException as e:
error_msg = f"❌ Failed to download file for task {task_id}: {e}"
print(error_msg)
raise RuntimeError(error_msg)
class WikipediaSearchTool(Tool):
name = "wikipedia_search"
description = "Searches Wikipedia and returns a short summary of the most relevant article."
inputs = {
"query": {"type": "string", "description": "The search term or topic to look up on Wikipedia."}
}
output_type = "string"
def __init__(self, summary_sentences=3):
super().__init__()
self.summary_sentences = summary_sentences
def forward(self, query: str) -> str:
try:
page_title = wikipedia.search(query)[0]
page = wikipedia.page(page_title)
return f"**{page.title}**\n\n{page.content}"
except IndexError:
return "No Wikipedia results found for that query."
except Exception as e:
return f"Error during Wikipedia search: {e}"
class WebpageReaderTool(Tool):
name = "read_webpage"
description = "Fetches the text content from a given URL and returns the main body text."
inputs = {
"url": {"type": "string", "description": "The URL of the webpage to read."}
}
output_type = "string"
def forward(self, url: str) -> str:
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
# Extract visible text (ignore scripts/styles)
for tag in soup(["script", "style", "noscript"]):
tag.extract()
text = soup.get_text(separator="\n")
cleaned = "\n".join(line.strip() for line in text.splitlines() if line.strip())
return cleaned[:5000] # Optionally limit to 5,000 chars
except Exception as e:
return f"Error reading webpage: {e}"
class BasicAgent:
def __init__(self):
print("BasicAgent initialized.")
def __call__(self, question_item: dict) -> str:
task_id = question_item.get("task_id")
question_text = question_item.get("question")
file_name = question_item.get("file_name")
print(f"Agent received question (first 50 chars): {question_text[:50]}...")
fixed_answer = "This is a default answer."
print(f"Agent returning fixed answer: {fixed_answer}")
return fixed_answer
class BasicSmolAgent:
def __init__(self, model=None):
print("BasicSmolAgent initialized.")
if not model:
model = HfApiModel()
search_tool = DuckDuckGoSearchToolWH()
wiki_tool = WikipediaSearchTool()
webpage_tool = WebpageReaderTool()
self.agent = CodeAgent(tools=[search_tool, wiki_tool, webpage_tool], model=model, max_steps=10, additional_authorized_imports=['pandas'])
self.prompt = ("The question is the following:\n ```{}```"
" 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."
)
# Load the Whisper pipeline
self.mp3_pipe = pipeline("automatic-speech-recognition", model="openai/whisper-tiny")
def get_logs(self):
return self.agent.memory.steps
def __call__(self, question_item: dict) -> str:
task_id = question_item.get("task_id")
question_text = question_item.get("question")
file_name = question_item.get("file_name")
print(f"Agent received question (first 50 chars): {question_text[:50]}...")
prompted_question = self.prompt.format(question_text)
images = []
if file_name:
fpath = download_file(task_id, file_name)
if fpath.endswith('.png'):
image = Image.open(fpath).convert("RGB")
images.append(image)
if fpath.endswith('xlsx') or fpath.endswith('.py'):
data = open(fpath, "rb").read().decode("utf-8", errors="ignore")
prompted_question += f"\nThere is textual data included with the question, it is from a file {fpath} and is: ```{data}```"
if fpath.endswith('.mp3'):
try:
result = self.mp3_pipe(fpath, return_timestamps=True)
text = result["text"]
prompted_question += f"\nThere is textual data included with the question, it is from a file {fpath} and is: ```{text}```"
except Exception as e:
print("Exception occurred during mp3 transcription: ", e)
result = self.agent.run(prompted_question, images=images)
print(f"Agent returning answer: {result}")
return result