import os import logging import gradio as gr import requests import pandas as pd import openai from openai import OpenAI from smolagents import CodeAgent, DuckDuckGoSearchTool, tool from smolagents.models import OpenAIServerModel # --- Logging --- logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logger = logging.getLogger(__name__) # --- Constants --- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space" OPENAI_MODEL_ID = os.getenv("OPENAI_MODEL_ID", "gpt-4.1") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") if not OPENAI_API_KEY: raise RuntimeError("Please set OPENAI_API_KEY in your Space secrets.") # --- Configure OpenAI SDK (for tools if needed) --- openai.api_key = "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" client = OpenAI() # --- Tools --- @tool def summarize_query(query: str) -> str: """ Reframes an unclear search query to improve relevance. Args: query (str): The original search query. Returns: str: A concise, improved version. """ return f"Summarize and reframe: {query}" @tool def wikipedia_search(page: str) -> str: """ Fetches the summary extract of an English Wikipedia page. Args: page (str): e.g. 'Mercedes_Sosa_discography' Returns: str: The page’s extract text. """ try: url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{page}" r = requests.get(url, timeout=10) r.raise_for_status() return r.json().get("extract", "") except Exception as e: logger.exception("Wikipedia lookup failed") return f"Wikipedia error: {e}" search_tool = DuckDuckGoSearchTool() wiki_tool = wikipedia_search summarize_tool = summarize_query # --- ReACT Prompt --- instruction_prompt = """ You are a ReACT agent with three tools: • DuckDuckGoSearchTool(query: str) • wikipedia_search(page: str) • summarize_query(query: str) Internally, for each question: 1. Thought: decide which tool to call. 2. Action: call the chosen tool. 3. Observation: record the result. 4. If empty/irrelevant: Thought: retry with summarize_query + DuckDuckGoSearchTool. Record new Observation. 5. Thought: integrate observations. Finally, output 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. """ # --- Build the Agent with OpenAIServerModel --- model = OpenAIServerModel( model_id=OPENAI_MODEL_ID, api_key=OPENAI_API_KEY ) smart_agent = CodeAgent( tools=[search_tool, wiki_tool, summarize_tool], model=model ) # --- Gradio Wrapper --- class BasicAgent: def __init__(self): logger.info("Initialized SmolAgent with OpenAI GPT-4.1") def __call__(self, question: str) -> str: if not question.strip(): return "AGENT ERROR: empty question" prompt = instruction_prompt.strip() + "\n\nQUESTION: " + question.strip() try: return smart_agent.run(prompt) except Exception as e: logger.exception("Agent run error") return f"AGENT ERROR: {e}" # --- Submission Logic --- def run_and_submit_all(profile: gr.OAuthProfile | None): if not profile: return "Please log in to Hugging Face.", None username = profile.username space_id = os.getenv("SPACE_ID", "") agent_code = f"https://huggingface.co./spaces/{space_id}/tree/main" agent = BasicAgent() # fetch try: resp = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15) resp.raise_for_status() questions = resp.json() or [] except Exception as e: logger.exception("Failed fetch") return f"Error fetching questions: {e}", None logs, payload = [], [] for item in questions: tid = item.get("task_id") q = item.get("question") if not tid or not q: continue ans = agent(q) logs.append({"Task ID": tid, "Question": q, "Submitted Answer": ans}) payload.append({"task_id": tid, "submitted_answer": ans}) if not payload: return "Agent did not produce any answers.", pd.DataFrame(logs) # submit try: post = requests.post( f"{DEFAULT_API_URL}/submit", json={"username": username, "agent_code": agent_code, "answers": payload}, timeout=60 ) post.raise_for_status() result = post.json() status = ( f"Submission Successful!\n" f"User: {result.get('username')}\n" f"Score: {result.get('score','N/A')}%\n" f"({result.get('correct_count','?')}/" f"{result.get('total_attempted','?')})\n" f"Message: {result.get('message','')}" ) return status, pd.DataFrame(logs) except Exception as e: logger.exception("Submit failed") return f"Submission Failed: {e}", pd.DataFrame(logs) # --- Gradio App --- with gr.Blocks() as demo: gr.Markdown("# SmolAgent GAIA Runner 🚀") gr.Markdown(""" **Instructions:** 1. Clone this space. 2. In Settings → Secrets, add `OPENAI_API_KEY` and (optionally) `OPENAI_MODEL_ID`. 3. Log in to Hugging Face. 4. Click **Run Evaluation & Submit All Answers**. """) gr.LoginButton() btn = gr.Button("Run Evaluation & Submit All Answers") out_status = gr.Textbox(label="Status", lines=5, interactive=False) out_table = gr.DataFrame(label="Questions & Answers", wrap=True) btn.click(run_and_submit_all, outputs=[out_status, out_table]) if __name__ == "__main__": demo.launch(debug=True, share=False)