"""GAIA agent for the HF Agents course, Unit 4. smolagents CodeAgent + free Gemini API.""" import mimetypes import os import re from dotenv import load_dotenv from google import genai from google.genai import types import requests from smolagents import CodeAgent, DuckDuckGoSearchTool, LiteLLMModel, WikipediaSearchTool, tool load_dotenv() # Exact model names, tried in order: the free daily quota is per model, so when one runs out the next takes over. GEMINI_MODELS = os.environ.get( "GEMINI_MODELS", "gemini-3-flash-preview,gemini-3.5-flash,gemini-3.6-flash,gemini-3.7-flash,gemini-3.8-flash,gemini-3.5-flash-lite,gemini-3.1-flash-lite", ).split(",") USER_AGENT = "GAIA-course-agent/1.0 (Hugging Face Agents course; https://huggingface.co/brunopaschoal)" # Wikipedia 403s anonymous bots _gemini = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) def _ask_gemini(parts: list) -> str: for model in GEMINI_MODELS[:-1]: try: return _gemini.models.generate_content(model=model, contents=parts).text or "" except Exception: continue # quota or model error: try the next model return _gemini.models.generate_content(model=GEMINI_MODELS[-1], contents=parts).text or "" @tool def visit_webpage(url: str) -> str: """Open a web page (e.g. a Wikipedia article) and return its content as markdown text. Args: url: The full URL of the page. """ from markdownify import markdownify try: r = requests.get(url, headers={"User-Agent": USER_AGENT}, timeout=30) r.raise_for_status() except Exception as e: return f"Error fetching {url}: {e}" text = re.sub(r"\n{3,}", "\n\n", markdownify(r.text)) return text[:40000] + ("\n...[page truncated]" if len(text) > 40000 else "") # keeps tokens/minute under the free limit @tool def youtube_transcript(url: str) -> str: """Get the spoken words (captions) of a YouTube video as text. Cheaper than ask_about_youtube; use it first for questions about what is said. Args: url: The full YouTube URL, e.g. 'https://www.youtube.com/watch?v=...'. """ from youtube_transcript_api import YouTubeTranscriptApi video_id = re.search(r"(?:v=|youtu\.be/)([\w-]{11})", url).group(1) return " ".join(s.text for s in YouTubeTranscriptApi().fetch(video_id)) @tool def ask_about_file(file_path: str, question: str) -> str: """Ask a question about an image or audio file (e.g. a chess board picture, or an mp3 recording to transcribe). Args: file_path: Local path of the image or audio file. question: What you want to know about the file. Be specific, e.g. 'Transcribe this audio word for word.' """ mime = mimetypes.guess_type(file_path)[0] or "application/octet-stream" with open(file_path, "rb") as f: return _ask_gemini([types.Part.from_bytes(data=f.read(), mime_type=mime), question]) @tool def ask_about_youtube(url: str, question: str) -> str: """Watch a YouTube video (images and sound) and answer a question about it. Args: url: The full YouTube URL, e.g. 'https://www.youtube.com/watch?v=...'. question: What you want to know about the video. """ return _ask_gemini([types.Part(file_data=types.FileData(file_uri=url)), question]) INSTRUCTIONS = """You are answering GAIA benchmark questions, scored by EXACT MATCH against the true answer. - Research carefully with the tools: search the web, read Wikipedia and web pages, and double-check facts. - For Wikipedia facts, open the article directly with visit_webpage("https://en.wikipedia.org/wiki/