| import os |
| import gradio as gr |
| import requests |
| import pandas as pd |
| import math |
| import statistics |
| import ast |
| import pathlib |
| import io |
| import tempfile |
| import base64 |
| import urllib.request |
| import time |
| import re |
| import json |
|
|
| from huggingface_hub import InferenceClient |
| from smolagents import CodeAgent, InferenceClientModel, tool |
| from smolagents import DuckDuckGoSearchTool, VisitWebpageTool |
|
|
|
|
| |
| @tool |
| def calculator(expression: str) -> str: |
| """ |
| Evaluate a safe arithmetic or mathematical expression. |
| Use this for numeric computations: arithmetic, trig, sqrt, logarithms, etc. |
| |
| Args: |
| expression: A Python-style math expression, e.g. "sqrt(144) + 2**10" or "mean([3,5,7])" |
| """ |
| _ALLOWED_NODES = { |
| ast.Expression, ast.BinOp, ast.UnaryOp, ast.Num, ast.Constant, |
| ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Pow, ast.Mod, ast.USub, ast.UAdd, |
| ast.FloorDiv, ast.Load, ast.Compare, ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE, |
| ast.Call, ast.Name, ast.Tuple, ast.List, |
| } |
| _math_funcs = {k: getattr(math, k) for k in dir(math) if not k.startswith("_")} |
| _math_funcs.update({"mean": statistics.mean, "median": statistics.median, |
| "sum": sum, "min": min, "max": max, "round": round, "abs": abs}) |
|
|
| def _check(n): |
| if type(n) not in _ALLOWED_NODES: |
| raise ValueError(f"Disallowed expression: {type(n).__name__}") |
| for child in ast.iter_child_nodes(n): |
| _check(child) |
|
|
| try: |
| node = ast.parse(expression, mode="eval") |
| _check(node) |
| val = eval(compile(node, "<calc>", "eval"), {"__builtins__": {}}, _math_funcs) |
| return str(val) |
| except Exception as e: |
| return f"ERROR: calculator failed: {e}" |
|
|
|
|
| |
| @tool |
| def ocr_image(image_source: str) -> str: |
| """ |
| Extract all text visible in an image using FireRed-OCR (a VLM-based OCR model). |
| Accepts an HTTP/HTTPS image URL or a local file path. |
| |
| Args: |
| image_source: HTTP URL or absolute local file path of the image to process. |
| """ |
| try: |
| client = InferenceClient("FireRedTeam/FireRed-OCR", token=os.getenv("HF_TOKEN") or os.getenv("HF_API_TOKEN")) |
| if image_source.startswith("http"): |
| image_content = {"type": "image_url", "image_url": {"url": image_source}} |
| else: |
| with open(image_source, "rb") as f: |
| b64 = base64.b64encode(f.read()).decode() |
| ext = pathlib.Path(image_source).suffix.lstrip(".") or "png" |
| image_content = { |
| "type": "image_url", |
| "image_url": {"url": f"data:image/{ext};base64,{b64}"}, |
| } |
| messages = [{ |
| "role": "user", |
| "content": [ |
| image_content, |
| {"type": "text", "text": "Extract and return ALL text visible in this image. Output only the extracted text, and a full description of the image."}, |
| ], |
| }] |
| resp = client.chat_completion(messages=messages, max_tokens=1024) |
| return resp.choices[0].message.content.strip() or "(no text detected)" |
| except Exception as e: |
| return f"ERROR: ocr_image failed: {e}" |
|
|
|
|
| |
| @tool |
| def analyze_video(video_url: str, question: str = "Describe this video in detail.") -> str: |
| """ |
| Analyze a video and answer a question about it using LLaVA-Video-7B-Qwen2. |
| |
| Args: |
| video_url: Direct HTTP/HTTPS URL to the video file (mp4, avi, webm, mov, etc.). |
| question: The question to ask about the video content. |
| """ |
| try: |
| client = InferenceClient("lmms-lab/LLaVA-Video-7B-Qwen2", token=os.getenv("HF_TOKEN") or os.getenv("HF_API_TOKEN")) |
| |
| |
| if "youtube.com/watch" in video_url or "youtu.be/" in video_url: |
| return ( |
| "ERROR: analyze_video does not support YouTube watch URLs. " |
| "Call get_youtube_transcript(url) instead to get the spoken content, " |
| "or use DuckDuckGoSearchTool to search for information about the video." |
| ) |
| messages = [{ |
| "role": "user", |
| "content": [ |
| {"type": "video_url", "video_url": {"url": video_url}}, |
| {"type": "text", "text": question}, |
| ], |
| }] |
| resp = client.chat_completion(messages=messages, max_tokens=768) |
| return resp.choices[0].message.content.strip() |
| except Exception as e: |
| return f"ERROR: analyze_video failed: {e if e else 'model returned empty response'}" |
|
|
|
|
| |
| @tool |
| def get_youtube_transcript(url: str) -> str: |
| """ |
| Retrieve the spoken transcript of a YouTube video. |
| Works with standard youtube.com/watch?v=... and youtu.be/... URLs. |
| Use this for any question about what is said or shown in a YouTube video. |
| |
| Args: |
| url: Full YouTube video URL, e.g. 'https://www.youtube.com/watch?v=abcd1234' |
| """ |
| try: |
| from youtube_transcript_api import YouTubeTranscriptApi |
| |
| match = re.search(r"(?:v=|youtu\.be/)([A-Za-z0-9_-]{11})", url) |
| if not match: |
| return f"ERROR: could not extract YouTube video ID from URL: {url}" |
| video_id = match.group(1) |
| |
| try: |
| transcript_list = YouTubeTranscriptApi.get_transcript(video_id, languages=["en", "en-US", "en-GB"]) |
| except Exception: |
| |
| transcripts = YouTubeTranscriptApi.list_transcripts(video_id) |
| transcript_list = transcripts.find_transcript( |
| [t.language_code for t in transcripts] |
| ).fetch() |
| text = " ".join(entry["text"] for entry in transcript_list) |
| return text[:8000] if len(text) > 8000 else text |
| except Exception as e: |
| return f"ERROR: get_youtube_transcript failed: {e}" |
|
|
|
|
| |
| @tool |
| def transcribe_audio(audio_source: str) -> str: |
| """ |
| Transcribe speech in an audio file to text using openai/whisper-large-v3. |
| Accepts an HTTP/HTTPS URL or a local file path. |
| |
| Args: |
| audio_source: HTTP URL or local path to an audio file (mp3, wav, flac, ogg, m4a). |
| """ |
| try: |
| client = InferenceClient( |
| "openai/whisper-large-v3", |
| token=os.getenv("HF_TOKEN") or os.getenv("HF_API_TOKEN"), |
| provider="hf-inference", |
| ) |
| result = client.automatic_speech_recognition(audio_source) |
| return result.text if hasattr(result, "text") else str(result) |
| except Exception as e: |
| return f"ERROR: transcribe_audio failed: {e}" |
|
|
|
|
| |
| @tool |
| def read_task_file(task_id: str, file_name: str, file_path: str = "") -> str: |
| """ |
| Download and parse the file attached to a GAIA task question. |
| Automatically handles: PDF (text extraction), CSV/Excel (table as text), |
| plain text/JSON/HTML, images (OCR), audio (transcription), video (analysis). |
| |
| Args: |
| task_id: The GAIA task ID whose attached file should be read. |
| file_name: The original file name including extension (e.g. 'data.csv', 'chart.png'). |
| file_path: Optional relative file path from the task metadata (e.g. '2023/test/uuid.jpg'). |
| When provided this is tried first as the download URL. |
| """ |
| hf_token = os.getenv("HF_TOKEN") or os.getenv("HF_API_TOKEN") |
| hf_headers = {"User-Agent": "HF-AgentsCourse/1.0"} |
| if hf_token: |
| hf_headers["Authorization"] = f"Bearer {hf_token}" |
|
|
| |
| |
| |
| candidates = [] |
| if file_path: |
| candidates.append( |
| f"https://huggingface.co/datasets/gaia-benchmark/GAIA/resolve/main/{file_path}" |
| ) |
| candidates.append(f"https://agents-course-unit4-scoring.hf.space/files/{task_id}") |
|
|
| data = None |
| last_err = "" |
| successful_url = candidates[0] |
| for url in candidates: |
| try: |
| req = urllib.request.Request(url, headers=hf_headers) |
| with urllib.request.urlopen(req, timeout=30) as resp: |
| data = resp.read() |
| successful_url = url |
| break |
| except Exception as e: |
| last_err = str(e) |
| if data is None: |
| return f"ERROR: could not download file for task '{task_id}': {last_err}" |
|
|
| ext = pathlib.Path(file_name).suffix.lower() |
| try: |
| if ext == ".pdf": |
| import pypdf |
| reader = pypdf.PdfReader(io.BytesIO(data)) |
| pages = [p.extract_text() or "" for p in reader.pages] |
| text = "\n\n--- Page Break ---\n\n".join(pages).strip() |
| return text[:8000] if text else "(no text extracted from PDF)" |
|
|
| elif ext == ".csv": |
| df = pd.read_csv(io.BytesIO(data)) |
| return df.to_string(max_rows=200, index=False) |
|
|
| elif ext in (".xlsx", ".xls"): |
| df = pd.read_excel(io.BytesIO(data)) |
| return df.to_string(max_rows=200, index=False) |
|
|
| elif ext in (".txt", ".md", ".json", ".xml", ".html", ".htm", ".py", ".tsv"): |
| return data.decode("utf-8", errors="replace")[:8000] |
|
|
| elif ext in (".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff"): |
| suffix = ext or ".png" |
| with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp: |
| tmp.write(data) |
| tmp_path = tmp.name |
| try: |
| return ocr_image(tmp_path) |
| finally: |
| os.unlink(tmp_path) |
|
|
| elif ext in (".mp3", ".wav", ".flac", ".ogg", ".m4a"): |
| return transcribe_audio(successful_url) |
|
|
| elif ext in (".mp4", ".avi", ".mov", ".mkv", ".webm"): |
| return analyze_video(successful_url) |
|
|
| else: |
| |
| try: |
| return data.decode("utf-8", errors="replace")[:4000] |
| except Exception: |
| return f"[binary file, {len(data)} bytes, extension='{ext}']" |
| except Exception as e: |
| return f"ERROR: read_task_file parsing failed (ext='{ext}'): {e}" |
|
|
|
|
| |
| @tool |
| def wikipedia_search(query: str, sentences: int = 10) -> str: |
| """ |
| Search Wikipedia and return a plain-text summary of the most relevant article. |
| Preferred over VisitWebpageTool for Wikipedia questions — never returns 403. |
| For full article sections (e.g. discography, revisions history), call with a |
| precise article title and use the 'sections' parameter via VisitWebpageTool as |
| fallback if you need more than the summary. |
| |
| Args: |
| query: Search term or exact Wikipedia article title. |
| sentences: How many sentences of summary to return (default 10). |
| """ |
| try: |
| |
| search_url = ( |
| "https://en.wikipedia.org/w/api.php" |
| f"?action=opensearch&search={urllib.request.quote(query)}&limit=1&format=json" |
| ) |
| req = urllib.request.Request(search_url, headers={"User-Agent": "HF-AgentsCourse/1.0 (research bot)"}) |
| with urllib.request.urlopen(req, timeout=15) as r: |
| results = json.loads(r.read()) |
| if not results[1]: |
| return f"No Wikipedia article found for query: '{query}'" |
| title = results[1][0] |
|
|
| |
| extract_url = ( |
| "https://en.wikipedia.org/w/api.php" |
| f"?action=query&titles={urllib.request.quote(title)}" |
| f"&prop=extracts&explaintext=true&exsentences={sentences}&format=json" |
| ) |
| req2 = urllib.request.Request(extract_url, headers={"User-Agent": "HF-AgentsCourse/1.0 (research bot)"}) |
| with urllib.request.urlopen(req2, timeout=15) as r2: |
| data = json.loads(r2.read()) |
| pages = data.get("query", {}).get("pages", {}) |
| page = next(iter(pages.values())) |
| extract = page.get("extract", "").strip() |
| return extract[:6000] if extract else f"No content found for article: '{title}'" |
| except Exception as e: |
| return f"ERROR: wikipedia_search failed: {e}" |
|
|
|
|
| |
| DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space" |
|
|
|
|
| |
| |
| |
| REACT_INSTRUCTIONS = ( |
| "\n\nYou are a general AI assistant. I will ask you a question. " |
| "Report your thoughts, and finish your answer with the following template: " |
| "FINAL ANSWER: [YOUR FINAL ANSWER].\n" |
| "YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma " |
| "separated list of numbers and/or strings.\n" |
| "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.\n" |
| "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.\n" |
| "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.\n\n" |
| "Additional execution rules:\n" |
| "- Reason step-by-step in code comments before calling tools.\n" |
| "- Use DuckDuckGoSearchTool / VisitWebpageTool to look up facts.\n" |
| "- Use calculator for any arithmetic; never compute in your head.\n" |
| "- If the question mentions an attached file, call read_task_file first.\n" |
| "- For images call ocr_image, for audio call transcribe_audio, " |
| "for video call analyze_video.\n" |
| "- For YouTube video questions, call get_youtube_transcript(url) — " |
| "analyze_video does NOT work with YouTube URLs.\n" |
| "- For Wikipedia questions, prefer wikipedia_search over VisitWebpageTool " |
| "(it uses the API and never gets 403).\n" |
| "- When you are confident, call final_answer() with ONLY the bare answer value " |
| "(no 'FINAL ANSWER:' prefix — the prefix is for your reasoning trace only)." |
| ) |
|
|
|
|
| def _extract_final_answer(raw: str) -> str: |
| """ |
| Pull the answer out of the agent's output. |
| Handles both: |
| - CodeAgent returning a plain string from final_answer() |
| - A string containing 'FINAL ANSWER: ...' anywhere in it |
| """ |
| if not isinstance(raw, str): |
| raw = str(raw) |
| |
| marker = "FINAL ANSWER:" |
| idx = raw.upper().rfind(marker) |
| if idx != -1: |
| answer = raw[idx + len(marker):].strip() |
| |
| answer = answer.rstrip(".") |
| return answer |
| |
| return raw.strip() |
|
|
|
|
| def build_agent() -> CodeAgent: |
| """ |
| Build a ReAct CodeAgent (Thought → Code → Observation loop) powered by |
| Qwen2.5-72B-Instruct with the following tools: |
| - DuckDuckGoSearchTool : web search |
| - VisitWebpageTool : fetch and read a web page |
| - calculator : safe AST-based arithmetic / math |
| - ocr_image : image text extraction (FireRedTeam/FireRed-OCR) |
| - analyze_video : video understanding (lmms-lab/LLaVA-Video-7B-Qwen2) |
| - transcribe_audio : speech-to-text (openai/whisper-large-v3) |
| - read_task_file : download & parse task attachments |
| (PDF, CSV, Excel, text, image, audio, video) |
| """ |
| |
| |
| |
| |
| hf_token = os.getenv("HF_TOKEN") or os.getenv("HF_API_TOKEN") |
| model = InferenceClientModel( |
| model_id="Qwen/Qwen2.5-72B-Instruct", |
| token=hf_token, |
| ) |
| return CodeAgent( |
| tools=[ |
| DuckDuckGoSearchTool(max_results=5), |
| VisitWebpageTool(), |
| calculator, |
| wikipedia_search, |
| get_youtube_transcript, |
| ocr_image, |
| analyze_video, |
| transcribe_audio, |
| read_task_file, |
| ], |
| model=model, |
| max_steps=10, |
| additional_authorized_imports=[ |
| "math", "statistics", "json", "re", |
| "datetime", "collections", "itertools", |
| "pandas", "io", "base64", "pathlib", |
| "urllib", "urllib.request", "urllib.parse", |
| ], |
| ) |
|
|
|
|
| def _run_with_retry(agent: CodeAgent, task_input: str, max_retries: int = 2) -> str: |
| """ |
| Run the agent with automatic retry on transient server errors (504, 503, 502). |
| Returns the raw answer string, or raises on non-transient errors. |
| """ |
| transient_codes = ("504", "503", "502", "timeout", "Timeout", "timed out") |
| for attempt in range(max_retries + 1): |
| try: |
| result = agent.run(task_input) |
| |
| if result is None or (isinstance(result, str) and not result.strip()): |
| return "I could not determine the answer." |
| return result |
| except Exception as e: |
| err = str(e) |
| is_transient = any(code in err for code in transient_codes) |
| if is_transient and attempt < max_retries: |
| wait = 15 * (attempt + 1) |
| print(f"Transient error on attempt {attempt + 1}, retrying in {wait}s: {err[:120]}") |
| time.sleep(wait) |
| continue |
| raise |
|
|
|
|
|
|
|
|
| def run_and_submit_all( profile: gr.OAuthProfile | None): |
| """ |
| Fetches all questions, runs the BasicAgent on them, submits all answers, |
| and displays the results. |
| """ |
| |
| space_id = os.getenv("SPACE_ID") |
|
|
| 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" |
|
|
| |
| try: |
| agent = build_agent() |
| except Exception as e: |
| print(f"Error instantiating agent: {e}") |
| return f"Error initializing agent: {e}", None |
|
|
| |
| agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" |
| print(agent_code) |
|
|
| |
| 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 |
|
|
| |
| 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") or item.get("question") |
| file_name = item.get("file_name", "") |
| file_path = item.get("file_path", "") |
| if not task_id or not question_text: |
| print(f"Skipping item with missing task_id or question: {item}") |
| continue |
| |
| task_input = question_text |
| if file_name: |
| fp_arg = f", file_path='{file_path}'" if file_path else "" |
| task_input += ( |
| f"\n\n[Attached file: '{file_name}'. " |
| f"Call read_task_file(task_id='{task_id}', file_name='{file_name}'{fp_arg}) " |
| f"to download and read its contents before answering.]" |
| ) |
| task_input += REACT_INSTRUCTIONS |
| try: |
| raw_answer = _run_with_retry(agent, task_input) |
| submitted_answer = _extract_final_answer(raw_answer) |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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 |
|
|
|
|
| |
| 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) |
| |
| 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) |
| |
| space_host_startup = os.getenv("SPACE_HOST") |
| space_id_startup = os.getenv("SPACE_ID") |
|
|
| 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(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) |