Spaces:
Sleeping
Sleeping
| import contextlib | |
| import io | |
| import os | |
| import re | |
| import tempfile | |
| import traceback | |
| from pathlib import Path | |
| from typing import Annotated, Optional, TypedDict | |
| from urllib.parse import parse_qs, urlparse | |
| import gradio as gr | |
| import pandas as pd | |
| import requests | |
| from langchain_community.tools import DuckDuckGoSearchResults, WikipediaQueryRun | |
| from langchain_community.utilities import DuckDuckGoSearchAPIWrapper, WikipediaAPIWrapper | |
| from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage | |
| from langchain_core.tools import tool | |
| from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint | |
| from langgraph.graph import START, StateGraph | |
| from langgraph.graph.message import add_messages | |
| from langgraph.prebuilt import ToolNode, tools_condition | |
| # (Keep Constants as is) | |
| # --- Constants --- | |
| DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space" | |
| DEFAULT_MODEL_ID = os.getenv("AGENT_MODEL_ID", "Qwen/Qwen2.5-Coder-32B-Instruct") | |
| AGENT_RECURSION_LIMIT = int(os.getenv("AGENT_RECURSION_LIMIT", "30")) | |
| MAX_TOOL_OUTPUT_CHARS = int(os.getenv("MAX_TOOL_OUTPUT_CHARS", "15000")) | |
| HF_TOKEN_ENV_VARS = ("HUGGINGFACEHUB_API_TOKEN", "HF_TOKEN", "HUGGING_FACE_HUB_TOKEN") | |
| SYSTEM_PROMPT = """You are a helpful assistant tasked with answering GAIA benchmark questions using tools. | |
| Use tools aggressively when they can verify the answer: web search, Wikipedia, web pages, YouTube transcripts, task files, downloaded files, and Python calculations. | |
| If the user message includes a task_id and the question mentions an image, spreadsheet, audio, pdf, or other attachment, first call download_task_file(task_id), then inspect it with read_file. | |
| Report your thoughts internally through tool use, but 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 commas and don't use units such as $, percent sign, km, etc. unless explicitly specified. | |
| If you are asked for a string, don't use articles or abbreviations, and write digits in plain text unless explicitly specified. | |
| If you are asked for a comma separated list, apply the rules above for each element and ensure there is exactly one space after each comma. | |
| Your final message should only start with "FINAL ANSWER: ", followed by the answer. Do not write anything after the final answer. | |
| """ | |
| _FINAL_ANSWER_RE = re.compile(r"FINAL\s*ANSWER\s*:\s*(.+?)\s*$", re.IGNORECASE | re.DOTALL) | |
| def _resolve_hf_token() -> Optional[str]: | |
| for var_name in HF_TOKEN_ENV_VARS: | |
| value = os.getenv(var_name) | |
| if value: | |
| return value | |
| return None | |
| def _trim(text: str, limit: int = MAX_TOOL_OUTPUT_CHARS) -> str: | |
| if len(text) <= limit: | |
| return text | |
| return text[:limit] + "\n\n[TRUNCATED]" | |
| def _safe_filename_from_response(response: requests.Response, fallback: str) -> str: | |
| content_disposition = response.headers.get("Content-Disposition", "") | |
| match = re.search(r'filename\*?=(?:UTF-8\'\')?"?([^";]+)"?', content_disposition) | |
| if match: | |
| return Path(match.group(1)).name | |
| parsed = urlparse(response.url) | |
| name = Path(parsed.path).name | |
| return name or fallback | |
| def visit_webpage(url: str) -> str: | |
| """Fetch a webpage and return readable markdown/text. | |
| Args: | |
| url: HTTP or HTTPS URL to fetch. | |
| """ | |
| try: | |
| from markdownify import markdownify as md | |
| response = requests.get( | |
| url, | |
| timeout=25, | |
| headers={"User-Agent": "GAIA-Agent/1.0"}, | |
| ) | |
| response.raise_for_status() | |
| content_type = response.headers.get("Content-Type", "") | |
| if "text/html" in content_type: | |
| text = md(response.text) | |
| else: | |
| text = response.text | |
| text = re.sub(r"\n{3,}", "\n\n", text).strip() | |
| return _trim(text) | |
| except Exception as exc: | |
| return f"Error fetching webpage: {exc}" | |
| def download_file_from_url(url: str, filename: Optional[str] = None) -> str: | |
| """Download a file from a URL to a temporary path and return that path. | |
| Args: | |
| url: Direct URL to the file. | |
| filename: Optional output filename. | |
| """ | |
| try: | |
| response = requests.get(url, timeout=45, stream=True, headers={"User-Agent": "GAIA-Agent/1.0"}) | |
| response.raise_for_status() | |
| filename = filename or _safe_filename_from_response(response, "downloaded_file") | |
| out_dir = Path(tempfile.gettempdir()) / "gaia_downloads" | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| out_path = out_dir / filename | |
| with out_path.open("wb") as f: | |
| for chunk in response.iter_content(chunk_size=8192): | |
| if chunk: | |
| f.write(chunk) | |
| return str(out_path) | |
| except Exception as exc: | |
| return f"Error downloading file: {exc}" | |
| def _make_download_task_file_tool(api_url: str): | |
| def download_task_file(task_id: str) -> str: | |
| """Download the file attached to a GAIA task and return the local path. | |
| Args: | |
| task_id: The task_id returned by the questions API. | |
| """ | |
| try: | |
| response = requests.get(f"{api_url}/files/{task_id}", timeout=45, stream=True) | |
| if response.status_code == 404: | |
| return f"No file is attached to task {task_id}." | |
| response.raise_for_status() | |
| filename = _safe_filename_from_response(response, task_id) | |
| out_dir = Path(tempfile.gettempdir()) / "gaia_task_files" | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| out_path = out_dir / filename | |
| with out_path.open("wb") as f: | |
| for chunk in response.iter_content(chunk_size=8192): | |
| if chunk: | |
| f.write(chunk) | |
| return str(out_path) | |
| except Exception as exc: | |
| return f"Error downloading task file: {exc}" | |
| return download_task_file | |
| def read_file(path: str) -> str: | |
| """Read a local file and return text or a compact structured summary. | |
| Supports text, csv, xlsx/xls, pdf, and basic image metadata/OCR when available. | |
| Args: | |
| path: Local file path. | |
| """ | |
| file_path = Path(path) | |
| if not file_path.exists(): | |
| return f"File does not exist: {path}" | |
| suffix = file_path.suffix.lower() | |
| try: | |
| if suffix == ".csv": | |
| df = pd.read_csv(file_path) | |
| summary = [ | |
| f"CSV rows={len(df)}, columns={len(df.columns)}", | |
| f"Columns: {', '.join(map(str, df.columns))}", | |
| "Preview:", | |
| df.head(20).to_csv(index=False), | |
| ] | |
| return _trim("\n".join(summary)) | |
| if suffix in {".xlsx", ".xls"}: | |
| sheets = pd.read_excel(file_path, sheet_name=None) | |
| chunks = [] | |
| for name, df in sheets.items(): | |
| chunks.append( | |
| "\n".join( | |
| [ | |
| f"Sheet: {name}", | |
| f"rows={len(df)}, columns={len(df.columns)}", | |
| f"Columns: {', '.join(map(str, df.columns))}", | |
| "Preview:", | |
| df.head(20).to_csv(index=False), | |
| ] | |
| ) | |
| ) | |
| return _trim("\n\n---\n\n".join(chunks)) | |
| if suffix == ".pdf": | |
| try: | |
| from pypdf import PdfReader | |
| except ImportError: | |
| return "pypdf is not installed." | |
| reader = PdfReader(str(file_path)) | |
| text = "\n\n".join(page.extract_text() or "" for page in reader.pages) | |
| return _trim(text) | |
| if suffix in {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif"}: | |
| try: | |
| from PIL import Image | |
| image = Image.open(file_path) | |
| lines = [f"Image: {file_path.name}", f"size={image.size}", f"mode={image.mode}"] | |
| try: | |
| import pytesseract | |
| ocr_text = pytesseract.image_to_string(image).strip() | |
| if ocr_text: | |
| lines.extend(["OCR text:", ocr_text]) | |
| else: | |
| lines.append("OCR text: (empty)") | |
| except Exception as exc: | |
| lines.append(f"OCR unavailable: {exc}") | |
| return _trim("\n".join(lines)) | |
| except Exception as exc: | |
| return f"Error reading image: {exc}" | |
| return _trim(file_path.read_text(encoding="utf-8", errors="replace")) | |
| except Exception as exc: | |
| return f"Error reading file: {exc}" | |
| def python_repl(code: str) -> str: | |
| """Run Python code and return stdout or errors. | |
| Use this for math, data wrangling, date logic, parsing, and exact computations. | |
| Args: | |
| code: Python code. Use print(...) for values you need returned. | |
| """ | |
| stdout = io.StringIO() | |
| namespace = {"pd": pd, "requests": requests, "re": re, "Path": Path} | |
| try: | |
| with contextlib.redirect_stdout(stdout): | |
| exec(code, namespace, namespace) # noqa: S102 - intentional agent tool | |
| except Exception: | |
| return _trim(f"ERROR:\n{traceback.format_exc()}\nSTDOUT:\n{stdout.getvalue()}") | |
| output = stdout.getvalue().strip() | |
| return _trim(output or "(no stdout; print the result explicitly)") | |
| def youtube_transcript(url_or_video_id: str) -> str: | |
| """Fetch a YouTube transcript when captions are available. | |
| Args: | |
| url_or_video_id: YouTube URL or video id. | |
| """ | |
| try: | |
| from youtube_transcript_api import YouTubeTranscriptApi | |
| video_id = url_or_video_id.strip() | |
| if "youtube.com" in video_id or "youtu.be" in video_id: | |
| parsed = urlparse(video_id) | |
| if parsed.netloc.endswith("youtu.be"): | |
| video_id = parsed.path.strip("/") | |
| else: | |
| video_id = parse_qs(parsed.query).get("v", [video_id])[0] | |
| transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=["en"]) | |
| text = " ".join(item.get("text", "") for item in transcript) | |
| return _trim(text) | |
| except Exception as exc: | |
| return f"Could not fetch transcript: {exc}" | |
| def arxiv_search(query: str) -> str: | |
| """Search arXiv and return up to three compact results. | |
| Args: | |
| query: Search query. | |
| """ | |
| try: | |
| from langchain_community.document_loaders import ArxivLoader | |
| docs = ArxivLoader(query=query, load_max_docs=3).load() | |
| if not docs: | |
| return "No arXiv results found." | |
| return _trim( | |
| "\n\n---\n\n".join( | |
| f"Title: {doc.metadata.get('Title', '')}\n" | |
| f"Authors: {doc.metadata.get('Authors', '')}\n" | |
| f"Published: {doc.metadata.get('Published', '')}\n" | |
| f"Summary: {doc.page_content[:1500]}" | |
| for doc in docs | |
| ) | |
| ) | |
| except Exception as exc: | |
| return f"Error searching arXiv: {exc}" | |
| class AgentState(TypedDict): | |
| messages: Annotated[list[AnyMessage], add_messages] | |
| def build_graph(chat_model_with_tools, tools): | |
| """Build the explicit LangGraph ReAct loop used by BasicAgent.""" | |
| def assistant(state: AgentState) -> dict: | |
| messages = [SystemMessage(content=SYSTEM_PROMPT), *state["messages"]] | |
| return {"messages": [chat_model_with_tools.invoke(messages)]} | |
| builder = StateGraph(AgentState) | |
| builder.add_node("assistant", assistant) | |
| builder.add_node("tools", ToolNode(tools)) | |
| builder.add_edge(START, "assistant") | |
| builder.add_conditional_edges("assistant", tools_condition) | |
| builder.add_edge("tools", "assistant") | |
| return builder.compile() | |
| # --- Basic Agent Definition --- | |
| # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------ | |
| class BasicAgent: | |
| def __init__(self, api_url: str = DEFAULT_API_URL): | |
| token = _resolve_hf_token() | |
| if not token: | |
| raise RuntimeError("Set HUGGINGFACEHUB_API_TOKEN or HF_TOKEN in your Space secrets.") | |
| print(f"BasicAgent initializing with LangGraph and {DEFAULT_MODEL_ID}.") | |
| llm = HuggingFaceEndpoint( | |
| repo_id=DEFAULT_MODEL_ID, | |
| task="text-generation", | |
| max_new_tokens=1024, | |
| do_sample=False, | |
| repetition_penalty=1.03, | |
| huggingfacehub_api_token=token, | |
| provider="auto", | |
| ) | |
| chat_model = ChatHuggingFace(llm=llm) | |
| search = DuckDuckGoSearchResults( | |
| api_wrapper=DuckDuckGoSearchAPIWrapper(max_results=5), | |
| output_format="list", | |
| ) | |
| wikipedia = WikipediaQueryRun( | |
| api_wrapper=WikipediaAPIWrapper(top_k_results=3, doc_content_chars_max=5000) | |
| ) | |
| download_task_file = _make_download_task_file_tool(api_url) | |
| self.tools = [ | |
| search, | |
| wikipedia, | |
| visit_webpage, | |
| arxiv_search, | |
| youtube_transcript, | |
| download_file_from_url, | |
| download_task_file, | |
| read_file, | |
| python_repl, | |
| ] | |
| self.graph = build_graph(chat_model.bind_tools(self.tools), self.tools) | |
| print("BasicAgent initialized.") | |
| def __call__(self, question: str, task_id: Optional[str] = None) -> str: | |
| print(f"Agent received question (first 50 chars): {question[:50]}...") | |
| user_prompt = f"task_id: {task_id}\n\nQuestion: {question}" if task_id else question | |
| try: | |
| result = self.graph.invoke( | |
| {"messages": [HumanMessage(content=user_prompt)]}, | |
| config={"recursion_limit": AGENT_RECURSION_LIMIT}, | |
| ) | |
| except Exception as exc: | |
| print(f"Agent error: {exc}") | |
| return f"AGENT ERROR: {exc}" | |
| content = result["messages"][-1].content | |
| if isinstance(content, list): | |
| content = "\n".join( | |
| item.get("text", "") if isinstance(item, dict) else str(item) | |
| for item in content | |
| ) | |
| answer = extract_final_answer(str(content)) | |
| print(f"Agent returning answer: {answer}") | |
| return answer | |
| def extract_final_answer(text: str) -> str: | |
| match = _FINAL_ANSWER_RE.search(text.strip()) | |
| if match: | |
| return match.group(1).strip().strip("`").rstrip(".").strip() | |
| lines = [line.strip() for line in text.splitlines() if line.strip()] | |
| return lines[-1] if lines else text.strip() | |
| def run_and_submit_all(profile: gr.OAuthProfile | None): | |
| """ | |
| Fetches all questions, runs the BasicAgent on them, submits all answers, | |
| and displays the results. | |
| """ | |
| # --- Determine HF Space Runtime URL and Repo URL --- | |
| space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code | |
| if profile: | |
| username = f"{profile.username}" | |
| print(f"User logged in: {username}") | |
| else: | |
| print("User not logged in.") | |
| return "Please Login to Hugging Face with the button.", None | |
| api_url = DEFAULT_API_URL | |
| questions_url = f"{api_url}/questions" | |
| submit_url = f"{api_url}/submit" | |
| # 1. Instantiate Agent ( modify this part to create your agent) | |
| try: | |
| agent = BasicAgent(api_url=api_url) | |
| except Exception as e: | |
| print(f"Error instantiating agent: {e}") | |
| return f"Error initializing agent: {e}", None | |
| # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public) | |
| agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" | |
| print(agent_code) | |
| # 2. Fetch Questions | |
| print(f"Fetching questions from: {questions_url}") | |
| try: | |
| response = requests.get(questions_url, timeout=15) | |
| response.raise_for_status() | |
| questions_data = response.json() | |
| if not questions_data: | |
| print("Fetched questions list is empty.") | |
| return "Fetched questions list is empty or invalid format.", None | |
| print(f"Fetched {len(questions_data)} questions.") | |
| except requests.exceptions.RequestException as e: | |
| print(f"Error fetching questions: {e}") | |
| return f"Error fetching questions: {e}", None | |
| except requests.exceptions.JSONDecodeError as e: | |
| print(f"Error decoding JSON response from questions endpoint: {e}") | |
| print(f"Response text: {response.text[:500]}") | |
| return f"Error decoding server response for questions: {e}", None | |
| except Exception as e: | |
| print(f"An unexpected error occurred fetching questions: {e}") | |
| return f"An unexpected error occurred fetching questions: {e}", None | |
| # 3. Run your Agent | |
| results_log = [] | |
| answers_payload = [] | |
| print(f"Running agent on {len(questions_data)} questions...") | |
| for idx, item in enumerate(questions_data, start=1): | |
| task_id = item.get("task_id") | |
| question_text = item.get("question") | |
| if not task_id or question_text is None: | |
| print(f"Skipping item with missing task_id or question: {item}") | |
| continue | |
| try: | |
| print(f"Running task {idx}/{len(questions_data)}: {task_id}") | |
| submitted_answer = agent(question_text, task_id=task_id) | |
| answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer}) | |
| results_log.append( | |
| {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer} | |
| ) | |
| except Exception as e: | |
| print(f"Error running agent on task {task_id}: {e}") | |
| results_log.append( | |
| {"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"} | |
| ) | |
| if not answers_payload: | |
| print("Agent did not produce any answers to submit.") | |
| return "Agent did not produce any answers to submit.", pd.DataFrame(results_log) | |
| # 4. Prepare Submission | |
| submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload} | |
| status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..." | |
| print(status_update) | |
| # 5. Submit | |
| print(f"Submitting {len(answers_payload)} answers to: {submit_url}") | |
| try: | |
| response = requests.post(submit_url, json=submission_data, timeout=60) | |
| response.raise_for_status() | |
| result_data = response.json() | |
| final_status = ( | |
| f"Submission Successful!\n" | |
| f"User: {result_data.get('username')}\n" | |
| f"Overall Score: {result_data.get('score', 'N/A')}% " | |
| f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n" | |
| f"Message: {result_data.get('message', 'No message received.')}" | |
| ) | |
| print("Submission successful.") | |
| results_df = pd.DataFrame(results_log) | |
| return final_status, results_df | |
| except requests.exceptions.HTTPError as e: | |
| error_detail = f"Server responded with status {e.response.status_code}." | |
| try: | |
| error_json = e.response.json() | |
| error_detail += f" Detail: {error_json.get('detail', e.response.text)}" | |
| except requests.exceptions.JSONDecodeError: | |
| error_detail += f" Response: {e.response.text[:500]}" | |
| status_message = f"Submission Failed: {error_detail}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except requests.exceptions.Timeout: | |
| status_message = "Submission Failed: The request timed out." | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except requests.exceptions.RequestException as e: | |
| status_message = f"Submission Failed: Network error - {e}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except Exception as e: | |
| status_message = f"An unexpected error occurred during submission: {e}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| # --- Build Gradio Interface using Blocks --- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Basic Agent Evaluation Runner") | |
| gr.Markdown( | |
| """ | |
| **Instructions:** | |
| 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ... | |
| 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission. | |
| 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score. | |
| --- | |
| **Disclaimers:** | |
| Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions). | |
| This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async. | |
| """ | |
| ) | |
| gr.LoginButton() | |
| run_button = gr.Button("Run Evaluation & Submit All Answers") | |
| status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False) | |
| # Removed max_rows=10 from DataFrame constructor | |
| results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True) | |
| run_button.click( | |
| fn=run_and_submit_all, | |
| outputs=[status_output, results_table] | |
| ) | |
| if __name__ == "__main__": | |
| print("\n" + "-"*30 + " App Starting " + "-"*30) | |
| # Check for SPACE_HOST and SPACE_ID at startup for information | |
| space_host_startup = os.getenv("SPACE_HOST") | |
| space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup | |
| if space_host_startup: | |
| print(f"✅ SPACE_HOST found: {space_host_startup}") | |
| print(f" Runtime URL should be: https://{space_host_startup}.hf.space") | |
| else: | |
| print("ℹ️ SPACE_HOST environment variable not found (running locally?).") | |
| if space_id_startup: # Print repo URLs if SPACE_ID is found | |
| print(f"✅ SPACE_ID found: {space_id_startup}") | |
| print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}") | |
| print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main") | |
| else: | |
| print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.") | |
| if not _resolve_hf_token(): | |
| print("⚠️ No HUGGINGFACEHUB_API_TOKEN / HF_TOKEN set. The agent will fail to initialise until you provide one.") | |
| print("-"*(60 + len(" App Starting ")) + "\n") | |
| print("Launching Gradio Interface for Basic Agent Evaluation...") | |
| demo.launch(debug=True, share=False) | |
| import os | |
| import gradio as gr | |
| import requests | |
| import inspect | |
| import pandas as pd | |
| # (Keep Constants as is) | |
| # --- Constants --- | |
| DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space" | |
| # --- Basic Agent Definition --- | |
| # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------ | |
| class BasicAgent: | |
| def __init__(self): | |
| print("BasicAgent initialized.") | |
| def __call__(self, question: str) -> str: | |
| print(f"Agent received question (first 50 chars): {question[:50]}...") | |
| fixed_answer = "This is a default answer." | |
| print(f"Agent returning fixed answer: {fixed_answer}") | |
| return fixed_answer | |
| def run_and_submit_all( profile: gr.OAuthProfile | None): | |
| """ | |
| Fetches all questions, runs the BasicAgent on them, submits all answers, | |
| and displays the results. | |
| """ | |
| # --- Determine HF Space Runtime URL and Repo URL --- | |
| space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code | |
| if profile: | |
| username= f"{profile.username}" | |
| print(f"User logged in: {username}") | |
| else: | |
| print("User not logged in.") | |
| return "Please Login to Hugging Face with the button.", None | |
| api_url = DEFAULT_API_URL | |
| questions_url = f"{api_url}/questions" | |
| submit_url = f"{api_url}/submit" | |
| # 1. Instantiate Agent ( modify this part to create your agent) | |
| try: | |
| agent = BasicAgent() | |
| except Exception as e: | |
| print(f"Error instantiating agent: {e}") | |
| return f"Error initializing agent: {e}", None | |
| # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public) | |
| agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" | |
| print(agent_code) | |
| # 2. Fetch Questions | |
| print(f"Fetching questions from: {questions_url}") | |
| try: | |
| response = requests.get(questions_url, timeout=15) | |
| response.raise_for_status() | |
| questions_data = response.json() | |
| if not questions_data: | |
| print("Fetched questions list is empty.") | |
| return "Fetched questions list is empty or invalid format.", None | |
| print(f"Fetched {len(questions_data)} questions.") | |
| except requests.exceptions.RequestException as e: | |
| print(f"Error fetching questions: {e}") | |
| return f"Error fetching questions: {e}", None | |
| except requests.exceptions.JSONDecodeError as e: | |
| print(f"Error decoding JSON response from questions endpoint: {e}") | |
| print(f"Response text: {response.text[:500]}") | |
| return f"Error decoding server response for questions: {e}", None | |
| except Exception as e: | |
| print(f"An unexpected error occurred fetching questions: {e}") | |
| return f"An unexpected error occurred fetching questions: {e}", None | |
| # 3. Run your Agent | |
| results_log = [] | |
| answers_payload = [] | |
| print(f"Running agent on {len(questions_data)} questions...") | |
| for item in questions_data: | |
| task_id = item.get("task_id") | |
| question_text = item.get("question") | |
| if not task_id or question_text is None: | |
| print(f"Skipping item with missing task_id or question: {item}") | |
| continue | |
| try: | |
| submitted_answer = agent(question_text) | |
| answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer}) | |
| results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}) | |
| except Exception as e: | |
| print(f"Error running agent on task {task_id}: {e}") | |
| results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"}) | |
| if not answers_payload: | |
| print("Agent did not produce any answers to submit.") | |
| return "Agent did not produce any answers to submit.", pd.DataFrame(results_log) | |
| # 4. Prepare Submission | |
| submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload} | |
| status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..." | |
| print(status_update) | |
| # 5. Submit | |
| print(f"Submitting {len(answers_payload)} answers to: {submit_url}") | |
| try: | |
| response = requests.post(submit_url, json=submission_data, timeout=60) | |
| response.raise_for_status() | |
| result_data = response.json() | |
| final_status = ( | |
| f"Submission Successful!\n" | |
| f"User: {result_data.get('username')}\n" | |
| f"Overall Score: {result_data.get('score', 'N/A')}% " | |
| f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n" | |
| f"Message: {result_data.get('message', 'No message received.')}" | |
| ) | |
| print("Submission successful.") | |
| results_df = pd.DataFrame(results_log) | |
| return final_status, results_df | |
| except requests.exceptions.HTTPError as e: | |
| error_detail = f"Server responded with status {e.response.status_code}." | |
| try: | |
| error_json = e.response.json() | |
| error_detail += f" Detail: {error_json.get('detail', e.response.text)}" | |
| except requests.exceptions.JSONDecodeError: | |
| error_detail += f" Response: {e.response.text[:500]}" | |
| status_message = f"Submission Failed: {error_detail}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except requests.exceptions.Timeout: | |
| status_message = "Submission Failed: The request timed out." | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except requests.exceptions.RequestException as e: | |
| status_message = f"Submission Failed: Network error - {e}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| except Exception as e: | |
| status_message = f"An unexpected error occurred during submission: {e}" | |
| print(status_message) | |
| results_df = pd.DataFrame(results_log) | |
| return status_message, results_df | |
| # --- Build Gradio Interface using Blocks --- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Basic Agent Evaluation Runner") | |
| gr.Markdown( | |
| """ | |
| **Instructions:** | |
| 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ... | |
| 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission. | |
| 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score. | |
| --- | |
| **Disclaimers:** | |
| Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions). | |
| This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async. | |
| """ | |
| ) | |
| gr.LoginButton() | |
| run_button = gr.Button("Run Evaluation & Submit All Answers") | |
| status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False) | |
| # Removed max_rows=10 from DataFrame constructor | |
| results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True) | |
| run_button.click( | |
| fn=run_and_submit_all, | |
| outputs=[status_output, results_table] | |
| ) | |
| if __name__ == "__main__": | |
| print("\n" + "-"*30 + " App Starting " + "-"*30) | |
| # Check for SPACE_HOST and SPACE_ID at startup for information | |
| space_host_startup = os.getenv("SPACE_HOST") | |
| space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup | |
| if space_host_startup: | |
| print(f"✅ SPACE_HOST found: {space_host_startup}") | |
| print(f" Runtime URL should be: https://{space_host_startup}.hf.space") | |
| else: | |
| print("ℹ️ SPACE_HOST environment variable not found (running locally?).") | |
| if space_id_startup: # Print repo URLs if SPACE_ID is found | |
| print(f"✅ SPACE_ID found: {space_id_startup}") | |
| print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}") | |
| print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main") | |
| else: | |
| print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.") | |
| print("-"*(60 + len(" App Starting ")) + "\n") | |
| print("Launching Gradio Interface for Basic Agent Evaluation...") | |
| demo.launch(debug=True, share=False) |