Viske's picture
Update tools.py
c82a650 verified
Raw History Blame Contribute Delete
15.2 kB
import os
import re
import json
import base64
import tempfile
import subprocess
import mimetypes
import requests
import pandas as pd
from bs4 import BeautifulSoup
from groq import Groq
SCORING_API = "https://agents-course-unit4-scoring.hf.space"
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
VISION_MODEL = os.getenv(
"GROQ_VISION_MODEL",
"qwen/qwen3.8-27b"
)
AUDIO_MODEL = os.getenv(
"GROQ_AUDIO_MODEL",
"whisper-large-v3-turbo"
)
groq_client = Groq(
api_key=GROQ_API_KEY
)
# ---------------------------------------------------------
# Web search state
# ---------------------------------------------------------
LAST_SEARCH_RESULTS = []
# ---------------------------------------------------------
# Web search
# ---------------------------------------------------------
def web_search(query):
global LAST_SEARCH_RESULTS
try:
from ddgs import DDGS
results = []
with DDGS() as ddgs:
search_results = ddgs.text(
query,
max_results=6
)
for item in search_results:
result = {
"title": item.get("title", ""),
"url": item.get("href", ""),
"snippet": item.get("body", "")
}
results.append(result)
LAST_SEARCH_RESULTS = results
if not results:
return "No search results found."
output = []
for i, item in enumerate(results):
output.append(
f"[{i}]\n"
f"TITLE: {item['title']}\n"
f"URL: {item['url']}\n"
f"SNIPPET: {item['snippet']}"
)
return "\n\n".join(output)
except Exception as e:
return f"Web search error: {type(e).__name__}: {e}"
# ---------------------------------------------------------
# Read webpage
# ---------------------------------------------------------
def read_webpage(
url=None,
cursor=None,
pattern=None
):
global LAST_SEARCH_RESULTS
# -----------------------------------------------------
# Resolve missing URL using latest search results
# -----------------------------------------------------
if not url:
if cursor is None:
cursor = 0
try:
cursor = int(cursor)
except Exception:
cursor = 0
if (
cursor < 0
or cursor >= len(LAST_SEARCH_RESULTS)
):
return (
"ERROR: No valid URL was supplied and the requested "
"search-result cursor does not exist."
)
url = LAST_SEARCH_RESULTS[cursor].get("url", "")
if not url:
return "ERROR: Could not resolve webpage URL."
# -----------------------------------------------------
# Fetch webpage
# -----------------------------------------------------
try:
response = requests.get(
url,
timeout=20,
headers={
"User-Agent": (
"Mozilla/5.0 "
"(compatible; GAIA-Agent/1.0)"
)
}
)
response.raise_for_status()
soup = BeautifulSoup(
response.text,
"html.parser"
)
for tag in soup(
[
"script",
"style",
"noscript",
"svg",
"nav",
"footer",
"header"
]
):
tag.decompose()
text = soup.get_text(
"\n",
strip=True
)
# -------------------------------------------------
# If model supplied a pattern, return context
# around occurrences rather than the whole page.
# -------------------------------------------------
if pattern:
pattern_lower = pattern.lower()
text_lower = text.lower()
positions = []
start = 0
while True:
position = text_lower.find(
pattern_lower,
start
)
if position == -1:
break
positions.append(position)
start = position + len(pattern_lower)
if len(positions) >= 5:
break
if positions:
chunks = []
for position in positions:
left = max(
0,
position - 1800
)
right = min(
len(text),
position + 3500
)
chunks.append(
text[left:right]
)
text = "\n\n--- MATCH ---\n\n".join(
chunks
)
if len(text) > 18000:
text = text[:18000]
return (
f"URL: {url}\n\n"
f"CONTENT:\n{text}"
)
except Exception as e:
return (
f"Webpage read error: "
f"{type(e).__name__}: {e}"
)
# ---------------------------------------------------------
# Download GAIA attachment
# ---------------------------------------------------------
def download_file(task_id):
try:
url = f"{SCORING_API}/files/{task_id}"
response = requests.get(
url,
timeout=60
)
response.raise_for_status()
content_type = response.headers.get(
"content-type",
""
).lower()
disposition = response.headers.get(
"content-disposition",
""
)
filename = None
# -------------------------------------------------
# Try Content-Disposition
# -------------------------------------------------
match = re.search(
r'filename="?([^";]+)"?',
disposition,
re.IGNORECASE
)
if match:
filename = match.group(1)
# -------------------------------------------------
# Try URL filename
# -------------------------------------------------
if not filename:
url_name = url.rstrip("/").split("/")[-1]
if "." in url_name:
filename = url_name
# -------------------------------------------------
# Content-type fallback
# -------------------------------------------------
if not filename:
extension_map = {
"audio/mpeg": ".mp3",
"audio/mp3": ".mp3",
"audio/wav": ".wav",
"audio/x-wav": ".wav",
"image/png": ".png",
"image/jpeg": ".jpg",
"image/jpg": ".jpg",
"application/pdf": ".pdf",
"text/plain": ".txt",
"text/x-python": ".py",
"application/json": ".json",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
"application/vnd.ms-excel": ".xls"
}
filename = (
"gaia_attachment"
+ extension_map.get(
content_type,
".bin"
)
)
# -------------------------------------------------
# Save
# -------------------------------------------------
safe_filename = os.path.basename(
filename
)
path = os.path.join(
tempfile.gettempdir(),
safe_filename
)
with open(
path,
"wb"
) as f:
f.write(response.content)
return path
except Exception as e:
raise RuntimeError(
f"Could not download task file: "
f"{type(e).__name__}: {e}"
)
# ---------------------------------------------------------
# Inspect text
# ---------------------------------------------------------
def inspect_text(path):
with open(
path,
"r",
encoding="utf-8",
errors="replace"
) as f:
text = f.read()
if len(text) > 20000:
text = text[:20000]
return text
# ---------------------------------------------------------
# Inspect Excel
# ---------------------------------------------------------
def inspect_excel(path):
sheets = pd.read_excel(
path,
sheet_name=None
)
output = []
for sheet_name, df in sheets.items():
output.append(
f"=== SHEET: {sheet_name} ==="
)
output.append(
f"Rows: {len(df)}"
)
output.append(
f"Columns: {list(df.columns)}"
)
output.append(
df.head(100).to_string(
index=False
)
)
return "\n\n".join(output)
# ---------------------------------------------------------
# Inspect PDF
# ---------------------------------------------------------
def inspect_pdf(path):
from PyPDF2 import PdfReader
reader = PdfReader(path)
pages = []
for page in reader.pages:
text = page.extract_text() or ""
pages.append(text)
if len("\n".join(pages)) > 20000:
break
text = "\n".join(pages)
return text[:20000]
# ---------------------------------------------------------
# Inspect Python / source
# ---------------------------------------------------------
def inspect_source(path):
return inspect_text(path)
# ---------------------------------------------------------
# Generic file inspection
# ---------------------------------------------------------
def inspect_file(task_id):
path = download_file(task_id)
extension = (
os.path.splitext(path)[1]
.lower()
)
if extension in [
".xlsx",
".xls"
]:
return inspect_excel(path)
if extension == ".pdf":
return inspect_pdf(path)
if extension in [
".txt",
".py",
".json",
".csv",
".md",
".xml",
".html"
]:
return inspect_source(path)
if extension in [
".png",
".jpg",
".jpeg"
]:
return (
f"Image file downloaded successfully: {path}\n"
"Use analyze_image for visual analysis."
)
if extension in [
".mp3",
".wav",
".m4a",
".ogg"
]:
return (
f"Audio file downloaded successfully: {path}\n"
"Use transcribe_audio for speech analysis."
)
return (
f"Downloaded file: {path}\n"
f"Extension: {extension}"
)
# ---------------------------------------------------------
# Run Python
# ---------------------------------------------------------
def run_python(code):
path = os.path.join(
tempfile.gettempdir(),
"gaia_agent_code.py"
)
with open(
path,
"w",
encoding="utf-8"
) as f:
f.write(code)
try:
result = subprocess.run(
[
"python",
path
],
capture_output=True,
text=True,
timeout=30
)
output = (
result.stdout
+ "\n"
+ result.stderr
).strip()
if len(output) > 12000:
output = output[:12000]
return output
except subprocess.TimeoutExpired:
return "Python execution timed out after 30 seconds."
except Exception as e:
return (
f"Python execution error: "
f"{type(e).__name__}: {e}"
)
# ---------------------------------------------------------
# Audio transcription
# ---------------------------------------------------------
def transcribe_audio(task_id):
path = download_file(task_id)
with open(
path,
"rb"
) as audio_file:
transcription = groq_client.audio.transcriptions.create(
file=audio_file,
model=AUDIO_MODEL,
response_format="text"
)
return str(transcription)
# ---------------------------------------------------------
# Image analysis
# ---------------------------------------------------------
def analyze_image(
task_id,
question
):
path = download_file(task_id)
extension = (
os.path.splitext(path)[1]
.lower()
)
mime_map = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp"
}
mime_type = mime_map.get(
extension,
"image/png"
)
with open(
path,
"rb"
) as f:
encoded = base64.b64encode(
f.read()
).decode("utf-8")
response = groq_client.chat.completions.create(
model=VISION_MODEL,
temperature=0,
max_completion_tokens=1500,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": (
"Analyze this image carefully.\n\n"
f"Question: {question}\n\n"
"Return the factual answer needed for the question."
)
},
{
"type": "image_url",
"image_url": {
"url": (
f"data:{mime_type};base64,"
f"{encoded}"
)
}
}
]
}
]
)
return response.choices[0].message.content
# ---------------------------------------------------------
# Tool dispatcher
# ---------------------------------------------------------
def execute_tool(
name,
args,
task_id=None
):
if name == "web_search":
return web_search(
args.get("query", "")
)
if name == "read_webpage":
return read_webpage(
url=args.get("url"),
cursor=args.get("cursor"),
pattern=args.get("pattern")
)
if name == "download_file":
return download_file(
args.get("task_id") or task_id
)
if name == "inspect_file":
return inspect_file(
args.get("task_id") or task_id
)
if name == "run_python":
return run_python(
args.get("code", "")
)
if name == "transcribe_audio":
return transcribe_audio(
args.get("task_id") or task_id
)
if name == "analyze_image":
return analyze_image(
task_id=args.get("task_id") or task_id,
question=args.get("question", "")
)
return f"Unknown tool: {name}"