Spaces:
Sleeping
Update app.py
Browse filesimport glob
import json
import os
import re
import threading
import time
from collections import deque
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
import gradio as gr
import pandas as pd
import requests
from smolagents import (
CodeAgent,
DuckDuckGoSearchTool,
InferenceClientModel,
LiteLLMModel,
VisitWebpageTool,
tool,
)
# ZeroGPU ํ๋์จ์ด์์ ์์ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํ ๋๋ฏธ ํจ์ (CPU ํ๋์จ์ด์์๋ ๋ฌด์๋จ)
try:
import spaces
@spaces.GPU
def _gpu_placeholder():
return "ok"
except ImportError:
pass
# ---------------------------------------------------------------------------
# ์ค์ (Space์ Variables์์ ์กฐ์ ๊ฐ๋ฅ)
# ---------------------------------------------------------------------------
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
FILES_DIR = "/tmp/task_files"
CACHE_PATH = "/tmp/answers_cache.json"
os.makedirs(FILES_DIR, exist_ok=True)
WORKERS = int(os.getenv("WORKERS", "2")) # ๋์ ์คํ ๋ฌธํญ ์
QUESTION_TIMEOUT = int(os.getenv("QUESTION_TIMEOUT", "300")) # ๋ฌธํญ๋น ์ ํ ์๊ฐ(์ด)
MAX_STEPS = int(os.getenv("MAX_STEPS", "8")) # ์์ด์ ํธ ์ต๋ ๋จ๊ณ
MODEL_TIMEOUT = int(os.getenv("MODEL_TIMEOUT", "90")) # LLM 1ํ ํธ์ถ ์ ํ(์ด)
HEARTBEAT = 3 # ํ๋ฉด ๊ฐฑ์ ์ฃผ๊ธฐ(์ด)
ANSWER_RULES = """You are a careful research and reasoning agent solving GAIA-style questions.
Rules:
1. Use the tools (web search, webpage visit, file reading, python code) whenever the
question needs facts you are not 100% sure about. Do not guess.
2. The final answer is graded by EXACT MATCH. Return ONLY the answer value, with no
explanation, no units unless asked, no trailing period, no "FINAL ANSWER:" prefix.
3. If the answer is a number: no thousands separators, no currency/percent symbols
unless the question asks for them.
4. If the answer is a list: comma-separated, in the order the question requests.
5. If the answer is a string: no articles (a, an, the) and no abbreviations unless
the question asks for them.
6. If a file path is given, read it with the read_local_file tool or with python code.
7. Be efficient: stop researching as soon as you have a reliable answer.
8. Finish by calling final_answer(...) with the answer only."""
# ---------------------------------------------------------------------------
# ์ด๋ฒคํธ ๋ก๊ทธ (ํ๋ฉด ์ํ์ฐฝ์ ์ต๊ทผ ๊ธฐ๋ก์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํจ)
# ---------------------------------------------------------------------------
_events = deque(maxlen=10)
_active: dict = {} # task_id -> ์์ ์๊ฐ
_state_lock = threading.Lock()
def log(msg: str):
line = f"[{time.strftime('%H:%M:%S')}] {msg}"
print(line, flush=True)
with _state_lock:
_events.append(line)
def recent_log() -> str:
with _state_lock:
return "\n".join(_events)
# ---------------------------------------------------------------------------
# ์บ์
# ---------------------------------------------------------------------------
_cache_lock = threading.Lock()
def load_cache() -> dict:
try:
with open(CACHE_PATH, "r", encoding="utf-8") as f:
return json.load(f)
except Exception: # noqa: BLE001
return {}
def save_cache_entry(task_id: str, answer: str):
with _cache_lock:
cache = load_cache()
cache[task_id] = answer
with open(CACHE_PATH, "w", encoding="utf-8") as f:
json.dump(cache, f, ensure_ascii=False)
# ---------------------------------------------------------------------------
# ๋๊ตฌ
# ---------------------------------------------------------------------------
@tool
def read_local_file(path: str) -> str:
"""Reads a local file (txt, csv, json, py, md, xlsx) and returns its content as text.
Args:
path: Absolute or relative path of the file to read.
"""
try:
if not os.path.exists(path):
basename = os.path.basename(path)
matches = glob.glob(os.path.join(FILES_DIR, f"*{basename}"))
if matches:
path = matches[0]
lower = path.lower()
if lower.endswith((".xlsx", ".xls")):
sheets = pd.read_excel(path, sheet_name=None)
return "\n\n".join(
f"## Sheet: {n}\n{df.to_string(max_rows=200)}" for n, df in sheets.items()
)
if lower.endswith(".csv"):
return pd.read_csv(path).to_string(max_rows=200)
with open(path, "r", encoding="utf-8", errors="ignore") as f:
return f.read()[:20000]
except Exception as e: # noqa: BLE001
return f"Could not read file: {e}"
# ---------------------------------------------------------------------------
# ๋ชจ๋ธ / ์์ด์ ํธ
# ---------------------------------------------------------------------------
def gemini_model_id() -> str:
return os.getenv("GEMINI_MODEL", "gemini-3.8-flash").replace("gemini/", "")
def build_model():
gemini_key = os.getenv("GEMINI_API_KEY")
if gemini_key:
return LiteLLMModel(
model_id=f"gemini/{gemini_model_id()}",
api_key=gemini_key,
timeout=MODEL_TIMEOUT, # LLM ํธ์ถ์ด ๋ฌดํ์ ๋ฉ์ถ์ง ์๋๋ก ์ ํ
num_retries=2,
)
openai_key = os.getenv("OPENAI_API_KEY")
if openai_key:
return LiteLLMModel(
model_id=f"openai/{os.getenv('OPENAI_MODEL', 'gpt-4o-mini')}",
api_key=openai_key,
timeout=MODEL_TIMEOUT,
num_retries=2,
)
return InferenceClientModel(
model_id=os.getenv("HF_MODEL", "Qwen/Qwen2.5-Coder-32B-Instruct"),
token=os.getenv("HF_TOKEN"),
)
def build_agent() -> CodeAgent:
return CodeAgent(
tools=[DuckDuckGoSearchTool(), VisitWebpageTool(), read_local_file],
model=build_model(),
additional_authorized_imports=[
"pandas", "numpy", "re", "json", "math", "datetime",
"collections", "itertools", "statistics", "requests", "bs4",
],
max_steps=MAX_STEPS,
verbosity_level=1,
)
def clean_answer(answer) -> str:
text = str(answer).strip()
text = re.sub(r"^\s*final answer\s*:\s*", "", text, flags=re.IGNORECASE)
return text.strip().rstrip(".").strip()
# ---------------------------------------------------------------------------
# ์ฑ์ ์๋ฒ ํต์
# ---------------------------------------------------------------------------
def fetch_questions(api_url: str) -> list[dict]:
resp = requests.get(f"{api_url}/questions", timeout=30)
resp.raise_for_status()
data = resp.json()
if not data:
raise ValueError("์ง๋ฌธ ๋ชฉ๋ก์ด ๋น์ด ์์ต๋๋ค.")
return data
def download_task_file(api_url: str, task_id: str, file_name: str):
if not file_name:
return None
try:
resp = requests.get(f"{api_url}/files/{task_id}", timeout=60)
resp.raise_for_status()
path = os.path.join(FILES_DIR, f"{task_id}_{file_name}")
with open(path, "wb") as f:
f.write(resp.content)
return path
except Exception as e: # noqa: BLE001
log(f"ํ์ผ ๋ค์ด๋ก๋ ์คํจ {task_id[:8]}: {e}")
return None
# ---------------------------------------------------------------------------
# ๋ฌธํญ ํ์ด (์บ์ + ์๊ฐ ์ ํ)
# ---------------------------------------------------------------------------
def solve_one(item: dict, api_url: str) -> str:
prompt = ANSWER_RULES + "\n\nQuestion:\n" + item["question"]
file_path = download_task_file(api_url, item["task_id"], item.get("file_name", ""))
if file_path:
prompt += f"\n\nAn attached file is available at: {file_path}"
return clean_answer(build_agent().run(prompt))
def solve_with_timeout(item: dict, api_url: str):
"""(๋ต๋ณ, ์ฑ๊ณต ์ฌ๋ถ, ๋น๊ณ )๋ฅผ ๋ฐํํฉ๋๋ค."""
task_id = item["task_id"]
short = task_id[:8]
cached = load_cache().get(task_id)
if cached:
log(f"์บ์ ์ฌ์ฉ {short} -> {cached}")
return cached, True, "cache"
box: dict = {}
def target():
try:
box["answer"] = solve_one(item, api_url)
except Exception as e: # noqa: BLE001
box["error"] = f"{type(e).__name__}: {e}"[:300]
log(f"์์ {short}")
with _state_lock:
_active[task_id] = time.time()
t = threading.Thread(target=target, daemon=True)
t.start()
t.join(QUESTION_TIMEOUT)
with _state_lock:
_active.pop(task_id, None)
if t.is_alive():
log(f"์๊ฐ ์ด๊ณผ {short} ({QUESTION_TIMEOUT}์ด)")
return "unknown", False, f"timeout {QUESTION_TIMEOUT}s"
if "error" in box:
log(f"์ค๋ฅ {short}: {box['error']}")
return "unknown", False, box["error"]
save_cache_entry(task_id, box["answer"])
log(f"์๋ฃ {short} -> {box['answer']}")
return box["answer"], True, ""
def iter_solve(questions: list[dict], api_url: str):
"""๋๋ ๋ฌธํญ์ (item, answer, ok, note)๋ก, ๋๊ธฐ ์ค์๋ ์ฃผ๊ธฐ์ ์ผ๋ก None์ ๋ด๋ณด๋
๋๋ค."""
valid = [q for q in questions if q.get("task_id") and q.get("question") is not None]
pool = ThreadPoolExecutor(max_workers=WORKERS)
try:
pending = {pool.submit(solve_with_timeout, q, api_url): q for q in valid}
while pending:
done, _ = wait(list(pending), timeout=HEARTBEAT, return_when=FIRST_COMPLETED)
if not done:
yield None
continue
for fut in done:
item = pending.pop(fut)
answer, ok, note = fut.result()
yield item, answer, ok, note
finally:
pool.shutdown(wait=False)
def make_df(rows: dict, order: list[str]) -> pd.DataFrame:
data = [rows[tid] for tid in order if tid in rows]
return pd.DataFrame(data, columns=["Task ID", "Question", "Submitted Answer", "OK", "Note"])
def status_text(label: str, done: int, total: int, start: float) -> str:
with _state_lock:
running = len(_active)
oldest = int(time.time() - min
|
@@ -1,9 +1,12 @@
|
|
|
|
|
| 1 |
import json
|
| 2 |
import os
|
| 3 |
import re
|
| 4 |
import threading
|
| 5 |
import time
|
| 6 |
-
from
|
|
|
|
|
|
|
| 7 |
import gradio as gr
|
| 8 |
import pandas as pd
|
| 9 |
import requests
|
|
@@ -16,9 +19,10 @@ from smolagents import (
|
|
| 16 |
tool,
|
| 17 |
)
|
| 18 |
|
| 19 |
-
# ZeroGPU ํ๋์จ์ด์์ ์์ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํ
|
| 20 |
try:
|
| 21 |
import spaces
|
|
|
|
| 22 |
@spaces.GPU
|
| 23 |
def _gpu_placeholder():
|
| 24 |
return "ok"
|
|
@@ -26,16 +30,18 @@ except ImportError:
|
|
| 26 |
pass
|
| 27 |
|
| 28 |
# ---------------------------------------------------------------------------
|
| 29 |
-
# ์ค์ (Space์ Variables
|
| 30 |
# ---------------------------------------------------------------------------
|
| 31 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 32 |
FILES_DIR = "/tmp/task_files"
|
| 33 |
CACHE_PATH = "/tmp/answers_cache.json"
|
| 34 |
os.makedirs(FILES_DIR, exist_ok=True)
|
| 35 |
|
| 36 |
-
WORKERS = int(os.getenv("WORKERS", "
|
| 37 |
-
QUESTION_TIMEOUT = int(os.getenv("QUESTION_TIMEOUT", "
|
| 38 |
-
MAX_STEPS = int(os.getenv("MAX_STEPS", "
|
|
|
|
|
|
|
| 39 |
|
| 40 |
ANSWER_RULES = """You are a careful research and reasoning agent solving GAIA-style questions.
|
| 41 |
Rules:
|
|
@@ -53,10 +59,31 @@ Rules:
|
|
| 53 |
8. Finish by calling final_answer(...) with the answer only."""
|
| 54 |
|
| 55 |
# ---------------------------------------------------------------------------
|
| 56 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
# ---------------------------------------------------------------------------
|
| 58 |
_cache_lock = threading.Lock()
|
| 59 |
|
|
|
|
| 60 |
def load_cache() -> dict:
|
| 61 |
try:
|
| 62 |
with open(CACHE_PATH, "r", encoding="utf-8") as f:
|
|
@@ -64,6 +91,7 @@ def load_cache() -> dict:
|
|
| 64 |
except Exception: # noqa: BLE001
|
| 65 |
return {}
|
| 66 |
|
|
|
|
| 67 |
def save_cache_entry(task_id: str, answer: str):
|
| 68 |
with _cache_lock:
|
| 69 |
cache = load_cache()
|
|
@@ -71,23 +99,23 @@ def save_cache_entry(task_id: str, answer: str):
|
|
| 71 |
with open(CACHE_PATH, "w", encoding="utf-8") as f:
|
| 72 |
json.dump(cache, f, ensure_ascii=False)
|
| 73 |
|
|
|
|
| 74 |
# ---------------------------------------------------------------------------
|
| 75 |
-
# ๋๊ตฌ
|
| 76 |
# ---------------------------------------------------------------------------
|
| 77 |
@tool
|
| 78 |
def read_local_file(path: str) -> str:
|
| 79 |
"""Reads a local file (txt, csv, json, py, md, xlsx) and returns its content as text.
|
|
|
|
| 80 |
Args:
|
| 81 |
path: Absolute or relative path of the file to read.
|
| 82 |
"""
|
| 83 |
try:
|
| 84 |
-
# ์์ด์ ํธ๊ฐ ํ์ผ๋ช
๋ง ์
๋ ฅํ๊ฑฐ๋ ๊ฒฝ๋ก๊ฐ ์๋ชป๋ ๊ฒฝ์ฐ FILES_DIR์์ ํ์
|
| 85 |
if not os.path.exists(path):
|
| 86 |
basename = os.path.basename(path)
|
| 87 |
-
|
| 88 |
-
if
|
| 89 |
-
path =
|
| 90 |
-
|
| 91 |
lower = path.lower()
|
| 92 |
if lower.endswith((".xlsx", ".xls")):
|
| 93 |
sheets = pd.read_excel(path, sheet_name=None)
|
|
@@ -101,28 +129,38 @@ def read_local_file(path: str) -> str:
|
|
| 101 |
except Exception as e: # noqa: BLE001
|
| 102 |
return f"Could not read file: {e}"
|
| 103 |
|
|
|
|
| 104 |
# ---------------------------------------------------------------------------
|
| 105 |
-
#
|
| 106 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
def build_model():
|
| 108 |
gemini_key = os.getenv("GEMINI_API_KEY")
|
| 109 |
if gemini_key:
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
| 114 |
openai_key = os.getenv("OPENAI_API_KEY")
|
| 115 |
if openai_key:
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
return InferenceClientModel(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
def build_agent() -> CodeAgent:
|
| 125 |
-
# ์ค๋ ๋ ์์ ๋ฐ ๋ฌธํญ๋ณ ๋
๋ฆฝ ์ปจํ
์คํธ ์ ์ง๋ฅผ ์ํด ๋งค๋ฒ ์ ์์ด์ ํธ๋ฅผ ์์ฑํฉ๋๋ค.
|
| 126 |
return CodeAgent(
|
| 127 |
tools=[DuckDuckGoSearchTool(), VisitWebpageTool(), read_local_file],
|
| 128 |
model=build_model(),
|
|
@@ -134,13 +172,15 @@ def build_agent() -> CodeAgent:
|
|
| 134 |
verbosity_level=1,
|
| 135 |
)
|
| 136 |
|
|
|
|
| 137 |
def clean_answer(answer) -> str:
|
| 138 |
text = str(answer).strip()
|
| 139 |
text = re.sub(r"^\s*final answer\s*:\s*", "", text, flags=re.IGNORECASE)
|
| 140 |
return text.strip().rstrip(".").strip()
|
| 141 |
|
|
|
|
| 142 |
# ---------------------------------------------------------------------------
|
| 143 |
-
# ์ฑ์ ์๋ฒ ํต์
|
| 144 |
# ---------------------------------------------------------------------------
|
| 145 |
def fetch_questions(api_url: str) -> list[dict]:
|
| 146 |
resp = requests.get(f"{api_url}/questions", timeout=30)
|
|
@@ -150,6 +190,7 @@ def fetch_questions(api_url: str) -> list[dict]:
|
|
| 150 |
raise ValueError("์ง๋ฌธ ๋ชฉ๋ก์ด ๋น์ด ์์ต๋๋ค.")
|
| 151 |
return data
|
| 152 |
|
|
|
|
| 153 |
def download_task_file(api_url: str, task_id: str, file_name: str):
|
| 154 |
if not file_name:
|
| 155 |
return None
|
|
@@ -161,66 +202,118 @@ def download_task_file(api_url: str, task_id: str, file_name: str):
|
|
| 161 |
f.write(resp.content)
|
| 162 |
return path
|
| 163 |
except Exception as e: # noqa: BLE001
|
| 164 |
-
|
| 165 |
return None
|
| 166 |
|
|
|
|
| 167 |
# ---------------------------------------------------------------------------
|
| 168 |
-
#
|
| 169 |
# ---------------------------------------------------------------------------
|
| 170 |
def solve_one(item: dict, api_url: str) -> str:
|
| 171 |
prompt = ANSWER_RULES + "\n\nQuestion:\n" + item["question"]
|
| 172 |
file_path = download_task_file(api_url, item["task_id"], item.get("file_name", ""))
|
| 173 |
if file_path:
|
| 174 |
prompt += f"\n\nAn attached file is available at: {file_path}"
|
| 175 |
-
|
| 176 |
-
agent = build_agent()
|
| 177 |
-
result = agent.run(prompt)
|
| 178 |
-
return clean_answer(result)
|
| 179 |
|
| 180 |
-
|
|
|
|
|
|
|
| 181 |
task_id = item["task_id"]
|
|
|
|
| 182 |
cached = load_cache().get(task_id)
|
| 183 |
if cached:
|
| 184 |
-
|
| 185 |
-
return cached, True
|
| 186 |
|
| 187 |
box: dict = {}
|
|
|
|
| 188 |
def target():
|
| 189 |
try:
|
| 190 |
box["answer"] = solve_one(item, api_url)
|
| 191 |
except Exception as e: # noqa: BLE001
|
| 192 |
-
box["error"] =
|
| 193 |
|
| 194 |
-
|
|
|
|
|
|
|
| 195 |
t = threading.Thread(target=target, daemon=True)
|
| 196 |
t.start()
|
| 197 |
t.join(QUESTION_TIMEOUT)
|
|
|
|
|
|
|
| 198 |
|
| 199 |
if t.is_alive():
|
| 200 |
-
|
| 201 |
-
return "unknown", False
|
| 202 |
if "error" in box:
|
| 203 |
-
|
| 204 |
-
return "unknown", False
|
| 205 |
|
| 206 |
save_cache_entry(task_id, box["answer"])
|
| 207 |
-
|
| 208 |
-
return box["answer"], True
|
|
|
|
| 209 |
|
| 210 |
def iter_solve(questions: list[dict], api_url: str):
|
|
|
|
| 211 |
valid = [q for q in questions if q.get("task_id") and q.get("question") is not None]
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
def make_df(rows: dict, order: list[str]) -> pd.DataFrame:
|
| 219 |
data = [rows[tid] for tid in order if tid in rows]
|
| 220 |
-
return pd.DataFrame(data, columns=["Task ID", "Question", "Submitted Answer", "OK"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
# ---------------------------------------------------------------------------
|
| 223 |
-
# Gradio ์ฝ๋ฐฑ
|
| 224 |
# ---------------------------------------------------------------------------
|
| 225 |
def run_and_submit_all(profile: gr.OAuthProfile | None):
|
| 226 |
space_id = os.getenv("SPACE_ID")
|
|
@@ -235,27 +328,14 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
|
|
| 235 |
api_url = DEFAULT_API_URL
|
| 236 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
|
| 237 |
|
| 238 |
-
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค...", None
|
| 239 |
try:
|
| 240 |
questions = fetch_questions(api_url)
|
| 241 |
except Exception as e: # noqa: BLE001
|
| 242 |
yield f"์ง๋ฌธ ๊ฐ์ ธ์ค๊ธฐ ์ค๋ฅ: {e}", None
|
| 243 |
return
|
| 244 |
|
| 245 |
-
order =
|
| 246 |
-
total = len(order)
|
| 247 |
-
rows: dict = {}
|
| 248 |
-
start = time.time()
|
| 249 |
-
|
| 250 |
-
for item, answer, ok in iter_solve(questions, api_url):
|
| 251 |
-
rows[item["task_id"]] = {
|
| 252 |
-
"Task ID": item["task_id"],
|
| 253 |
-
"Question": item["question"],
|
| 254 |
-
"Submitted Answer": answer,
|
| 255 |
-
"OK": "O" if ok else "X",
|
| 256 |
-
}
|
| 257 |
-
elapsed = int(time.time() - start)
|
| 258 |
-
yield f"์งํ {len(rows)}/{total} ์๋ฃ (๊ฒฝ๊ณผ {elapsed}์ด)", make_df(rows, order)
|
| 259 |
|
| 260 |
answers_payload = [
|
| 261 |
{"task_id": tid, "submitted_answer": rows[tid]["Submitted Answer"]}
|
|
@@ -285,35 +365,74 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
|
|
| 285 |
status = f"์ ์ถ ์คํจ: {detail}"
|
| 286 |
except Exception as e: # noqa: BLE001
|
| 287 |
status = f"์ ์ถ ์ค ์ค๋ฅ: {e}"
|
| 288 |
-
|
| 289 |
yield status, make_df(rows, order)
|
| 290 |
|
|
|
|
| 291 |
def run_test_only(n_questions: int):
|
| 292 |
api_url = DEFAULT_API_URL
|
| 293 |
-
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค...", None
|
| 294 |
try:
|
| 295 |
questions = fetch_questions(api_url)[: int(n_questions)]
|
| 296 |
except Exception as e: # noqa: BLE001
|
| 297 |
yield f"์ค๋น ์ค ์ค๋ฅ: {e}", None
|
| 298 |
return
|
|
|
|
|
|
|
| 299 |
|
| 300 |
-
order = [q["task_id"] for q in questions if q.get("task_id")]
|
| 301 |
-
rows: dict = {}
|
| 302 |
-
start = time.time()
|
| 303 |
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
"
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
|
| 316 |
-
yield f"ํ
์คํธ ์๋ฃ ({int(time.time() - start)}์ด, ์ ์ถ ์ ํจ)", make_df(rows, order)
|
| 317 |
|
| 318 |
def clear_cache():
|
| 319 |
try:
|
|
@@ -322,28 +441,31 @@ def clear_cache():
|
|
| 322 |
except FileNotFoundError:
|
| 323 |
return "์ญ์ ํ ์บ์๊ฐ ์์ต๋๋ค."
|
| 324 |
|
|
|
|
| 325 |
# ---------------------------------------------------------------------------
|
| 326 |
-
#
|
| 327 |
# ---------------------------------------------------------------------------
|
| 328 |
with gr.Blocks() as demo:
|
| 329 |
gr.Markdown("# Basic Agent Evaluation Runner (Gemini 3.8 Flash)")
|
| 330 |
gr.Markdown(
|
| 331 |
"""**Instructions:**
|
| 332 |
-
1.
|
| 333 |
-
2.
|
| 334 |
-
3.
|
| 335 |
)
|
| 336 |
gr.LoginButton()
|
| 337 |
-
|
| 338 |
with gr.Row():
|
|
|
|
| 339 |
test_n = gr.Number(value=2, label="ํ
์คํธํ ๋ฌธํญ ์ (์ ์ถ ์ ํจ)", precision=0)
|
| 340 |
test_button = gr.Button("ํ
์คํธ ์คํ (์ ์ถ ์ ํจ)")
|
| 341 |
cache_button = gr.Button("์บ์ ์ด๊ธฐํ")
|
| 342 |
-
|
| 343 |
run_button = gr.Button("Run Evaluation & Submit All Answers")
|
| 344 |
-
status_output = gr.Textbox(label="Run Status / Submission Result", lines=
|
| 345 |
results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
|
| 346 |
|
|
|
|
| 347 |
test_button.click(fn=run_test_only, inputs=[test_n], outputs=[status_output, results_table])
|
| 348 |
run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
|
| 349 |
cache_button.click(fn=clear_cache, outputs=[status_output])
|
|
|
|
| 1 |
+
import glob
|
| 2 |
import json
|
| 3 |
import os
|
| 4 |
import re
|
| 5 |
import threading
|
| 6 |
import time
|
| 7 |
+
from collections import deque
|
| 8 |
+
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
|
| 9 |
+
|
| 10 |
import gradio as gr
|
| 11 |
import pandas as pd
|
| 12 |
import requests
|
|
|
|
| 19 |
tool,
|
| 20 |
)
|
| 21 |
|
| 22 |
+
# ZeroGPU ํ๋์จ์ด์์ ์์ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํ ๋๋ฏธ ํจ์ (CPU ํ๋์จ์ด์์๋ ๋ฌด์๋จ)
|
| 23 |
try:
|
| 24 |
import spaces
|
| 25 |
+
|
| 26 |
@spaces.GPU
|
| 27 |
def _gpu_placeholder():
|
| 28 |
return "ok"
|
|
|
|
| 30 |
pass
|
| 31 |
|
| 32 |
# ---------------------------------------------------------------------------
|
| 33 |
+
# ์ค์ (Space์ Variables์์ ์กฐ์ ๊ฐ๋ฅ)
|
| 34 |
# ---------------------------------------------------------------------------
|
| 35 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 36 |
FILES_DIR = "/tmp/task_files"
|
| 37 |
CACHE_PATH = "/tmp/answers_cache.json"
|
| 38 |
os.makedirs(FILES_DIR, exist_ok=True)
|
| 39 |
|
| 40 |
+
WORKERS = int(os.getenv("WORKERS", "2")) # ๋์ ์คํ ๋ฌธํญ ์
|
| 41 |
+
QUESTION_TIMEOUT = int(os.getenv("QUESTION_TIMEOUT", "300")) # ๋ฌธํญ๋น ์ ํ ์๊ฐ(์ด)
|
| 42 |
+
MAX_STEPS = int(os.getenv("MAX_STEPS", "8")) # ์์ด์ ํธ ์ต๋ ๋จ๊ณ
|
| 43 |
+
MODEL_TIMEOUT = int(os.getenv("MODEL_TIMEOUT", "90")) # LLM 1ํ ํธ์ถ ์ ํ(์ด)
|
| 44 |
+
HEARTBEAT = 3 # ํ๋ฉด ๊ฐฑ์ ์ฃผ๊ธฐ(์ด)
|
| 45 |
|
| 46 |
ANSWER_RULES = """You are a careful research and reasoning agent solving GAIA-style questions.
|
| 47 |
Rules:
|
|
|
|
| 59 |
8. Finish by calling final_answer(...) with the answer only."""
|
| 60 |
|
| 61 |
# ---------------------------------------------------------------------------
|
| 62 |
+
# ์ด๋ฒคํธ ๋ก๊ทธ (ํ๋ฉด ์ํ์ฐฝ์ ์ต๊ทผ ๊ธฐ๋ก์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํจ)
|
| 63 |
+
# ---------------------------------------------------------------------------
|
| 64 |
+
_events = deque(maxlen=10)
|
| 65 |
+
_active: dict = {} # task_id -> ์์ ์๊ฐ
|
| 66 |
+
_state_lock = threading.Lock()
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def log(msg: str):
|
| 70 |
+
line = f"[{time.strftime('%H:%M:%S')}] {msg}"
|
| 71 |
+
print(line, flush=True)
|
| 72 |
+
with _state_lock:
|
| 73 |
+
_events.append(line)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def recent_log() -> str:
|
| 77 |
+
with _state_lock:
|
| 78 |
+
return "\n".join(_events)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
# ---------------------------------------------------------------------------
|
| 82 |
+
# ์บ์
|
| 83 |
# ---------------------------------------------------------------------------
|
| 84 |
_cache_lock = threading.Lock()
|
| 85 |
|
| 86 |
+
|
| 87 |
def load_cache() -> dict:
|
| 88 |
try:
|
| 89 |
with open(CACHE_PATH, "r", encoding="utf-8") as f:
|
|
|
|
| 91 |
except Exception: # noqa: BLE001
|
| 92 |
return {}
|
| 93 |
|
| 94 |
+
|
| 95 |
def save_cache_entry(task_id: str, answer: str):
|
| 96 |
with _cache_lock:
|
| 97 |
cache = load_cache()
|
|
|
|
| 99 |
with open(CACHE_PATH, "w", encoding="utf-8") as f:
|
| 100 |
json.dump(cache, f, ensure_ascii=False)
|
| 101 |
|
| 102 |
+
|
| 103 |
# ---------------------------------------------------------------------------
|
| 104 |
+
# ๋๊ตฌ
|
| 105 |
# ---------------------------------------------------------------------------
|
| 106 |
@tool
|
| 107 |
def read_local_file(path: str) -> str:
|
| 108 |
"""Reads a local file (txt, csv, json, py, md, xlsx) and returns its content as text.
|
| 109 |
+
|
| 110 |
Args:
|
| 111 |
path: Absolute or relative path of the file to read.
|
| 112 |
"""
|
| 113 |
try:
|
|
|
|
| 114 |
if not os.path.exists(path):
|
| 115 |
basename = os.path.basename(path)
|
| 116 |
+
matches = glob.glob(os.path.join(FILES_DIR, f"*{basename}"))
|
| 117 |
+
if matches:
|
| 118 |
+
path = matches[0]
|
|
|
|
| 119 |
lower = path.lower()
|
| 120 |
if lower.endswith((".xlsx", ".xls")):
|
| 121 |
sheets = pd.read_excel(path, sheet_name=None)
|
|
|
|
| 129 |
except Exception as e: # noqa: BLE001
|
| 130 |
return f"Could not read file: {e}"
|
| 131 |
|
| 132 |
+
|
| 133 |
# ---------------------------------------------------------------------------
|
| 134 |
+
# ๋ชจ๋ธ / ์์ด์ ํธ
|
| 135 |
# ---------------------------------------------------------------------------
|
| 136 |
+
def gemini_model_id() -> str:
|
| 137 |
+
return os.getenv("GEMINI_MODEL", "gemini-3.8-flash").replace("gemini/", "")
|
| 138 |
+
|
| 139 |
+
|
| 140 |
def build_model():
|
| 141 |
gemini_key = os.getenv("GEMINI_API_KEY")
|
| 142 |
if gemini_key:
|
| 143 |
+
return LiteLLMModel(
|
| 144 |
+
model_id=f"gemini/{gemini_model_id()}",
|
| 145 |
+
api_key=gemini_key,
|
| 146 |
+
timeout=MODEL_TIMEOUT, # LLM ํธ์ถ์ด ๋ฌดํ์ ๋ฉ์ถ์ง ์๋๋ก ์ ํ
|
| 147 |
+
num_retries=2,
|
| 148 |
+
)
|
| 149 |
openai_key = os.getenv("OPENAI_API_KEY")
|
| 150 |
if openai_key:
|
| 151 |
+
return LiteLLMModel(
|
| 152 |
+
model_id=f"openai/{os.getenv('OPENAI_MODEL', 'gpt-4o-mini')}",
|
| 153 |
+
api_key=openai_key,
|
| 154 |
+
timeout=MODEL_TIMEOUT,
|
| 155 |
+
num_retries=2,
|
| 156 |
+
)
|
| 157 |
+
return InferenceClientModel(
|
| 158 |
+
model_id=os.getenv("HF_MODEL", "Qwen/Qwen2.5-Coder-32B-Instruct"),
|
| 159 |
+
token=os.getenv("HF_TOKEN"),
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
|
| 163 |
def build_agent() -> CodeAgent:
|
|
|
|
| 164 |
return CodeAgent(
|
| 165 |
tools=[DuckDuckGoSearchTool(), VisitWebpageTool(), read_local_file],
|
| 166 |
model=build_model(),
|
|
|
|
| 172 |
verbosity_level=1,
|
| 173 |
)
|
| 174 |
|
| 175 |
+
|
| 176 |
def clean_answer(answer) -> str:
|
| 177 |
text = str(answer).strip()
|
| 178 |
text = re.sub(r"^\s*final answer\s*:\s*", "", text, flags=re.IGNORECASE)
|
| 179 |
return text.strip().rstrip(".").strip()
|
| 180 |
|
| 181 |
+
|
| 182 |
# ---------------------------------------------------------------------------
|
| 183 |
+
# ์ฑ์ ์๋ฒ ํต์
|
| 184 |
# ---------------------------------------------------------------------------
|
| 185 |
def fetch_questions(api_url: str) -> list[dict]:
|
| 186 |
resp = requests.get(f"{api_url}/questions", timeout=30)
|
|
|
|
| 190 |
raise ValueError("์ง๋ฌธ ๋ชฉ๋ก์ด ๋น์ด ์์ต๋๋ค.")
|
| 191 |
return data
|
| 192 |
|
| 193 |
+
|
| 194 |
def download_task_file(api_url: str, task_id: str, file_name: str):
|
| 195 |
if not file_name:
|
| 196 |
return None
|
|
|
|
| 202 |
f.write(resp.content)
|
| 203 |
return path
|
| 204 |
except Exception as e: # noqa: BLE001
|
| 205 |
+
log(f"ํ์ผ ๋ค์ด๋ก๋ ์คํจ {task_id[:8]}: {e}")
|
| 206 |
return None
|
| 207 |
|
| 208 |
+
|
| 209 |
# ---------------------------------------------------------------------------
|
| 210 |
+
# ๋ฌธํญ ํ์ด (์บ์ + ์๊ฐ ์ ํ)
|
| 211 |
# ---------------------------------------------------------------------------
|
| 212 |
def solve_one(item: dict, api_url: str) -> str:
|
| 213 |
prompt = ANSWER_RULES + "\n\nQuestion:\n" + item["question"]
|
| 214 |
file_path = download_task_file(api_url, item["task_id"], item.get("file_name", ""))
|
| 215 |
if file_path:
|
| 216 |
prompt += f"\n\nAn attached file is available at: {file_path}"
|
| 217 |
+
return clean_answer(build_agent().run(prompt))
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
+
|
| 220 |
+
def solve_with_timeout(item: dict, api_url: str):
|
| 221 |
+
"""(๋ต๋ณ, ์ฑ๊ณต ์ฌ๋ถ, ๋น๊ณ )๋ฅผ ๋ฐํํฉ๋๋ค."""
|
| 222 |
task_id = item["task_id"]
|
| 223 |
+
short = task_id[:8]
|
| 224 |
cached = load_cache().get(task_id)
|
| 225 |
if cached:
|
| 226 |
+
log(f"์บ์ ์ฌ์ฉ {short} -> {cached}")
|
| 227 |
+
return cached, True, "cache"
|
| 228 |
|
| 229 |
box: dict = {}
|
| 230 |
+
|
| 231 |
def target():
|
| 232 |
try:
|
| 233 |
box["answer"] = solve_one(item, api_url)
|
| 234 |
except Exception as e: # noqa: BLE001
|
| 235 |
+
box["error"] = f"{type(e).__name__}: {e}"[:300]
|
| 236 |
|
| 237 |
+
log(f"์์ {short}")
|
| 238 |
+
with _state_lock:
|
| 239 |
+
_active[task_id] = time.time()
|
| 240 |
t = threading.Thread(target=target, daemon=True)
|
| 241 |
t.start()
|
| 242 |
t.join(QUESTION_TIMEOUT)
|
| 243 |
+
with _state_lock:
|
| 244 |
+
_active.pop(task_id, None)
|
| 245 |
|
| 246 |
if t.is_alive():
|
| 247 |
+
log(f"์๊ฐ ์ด๊ณผ {short} ({QUESTION_TIMEOUT}์ด)")
|
| 248 |
+
return "unknown", False, f"timeout {QUESTION_TIMEOUT}s"
|
| 249 |
if "error" in box:
|
| 250 |
+
log(f"์ค๋ฅ {short}: {box['error']}")
|
| 251 |
+
return "unknown", False, box["error"]
|
| 252 |
|
| 253 |
save_cache_entry(task_id, box["answer"])
|
| 254 |
+
log(f"์๋ฃ {short} -> {box['answer']}")
|
| 255 |
+
return box["answer"], True, ""
|
| 256 |
+
|
| 257 |
|
| 258 |
def iter_solve(questions: list[dict], api_url: str):
|
| 259 |
+
"""๋๋ ๋ฌธํญ์ (item, answer, ok, note)๋ก, ๋๊ธฐ ์ค์๋ ์ฃผ๊ธฐ์ ์ผ๋ก None์ ๋ด๋ณด๋
๋๋ค."""
|
| 260 |
valid = [q for q in questions if q.get("task_id") and q.get("question") is not None]
|
| 261 |
+
pool = ThreadPoolExecutor(max_workers=WORKERS)
|
| 262 |
+
try:
|
| 263 |
+
pending = {pool.submit(solve_with_timeout, q, api_url): q for q in valid}
|
| 264 |
+
while pending:
|
| 265 |
+
done, _ = wait(list(pending), timeout=HEARTBEAT, return_when=FIRST_COMPLETED)
|
| 266 |
+
if not done:
|
| 267 |
+
yield None
|
| 268 |
+
continue
|
| 269 |
+
for fut in done:
|
| 270 |
+
item = pending.pop(fut)
|
| 271 |
+
answer, ok, note = fut.result()
|
| 272 |
+
yield item, answer, ok, note
|
| 273 |
+
finally:
|
| 274 |
+
pool.shutdown(wait=False)
|
| 275 |
+
|
| 276 |
|
| 277 |
def make_df(rows: dict, order: list[str]) -> pd.DataFrame:
|
| 278 |
data = [rows[tid] for tid in order if tid in rows]
|
| 279 |
+
return pd.DataFrame(data, columns=["Task ID", "Question", "Submitted Answer", "OK", "Note"])
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def status_text(label: str, done: int, total: int, start: float) -> str:
|
| 283 |
+
with _state_lock:
|
| 284 |
+
running = len(_active)
|
| 285 |
+
oldest = int(time.time() - min(_active.values())) if _active else 0
|
| 286 |
+
return (
|
| 287 |
+
f"{label}: {done}/{total} ์๋ฃ | ์คํ ์ค {running}๊ฐ "
|
| 288 |
+
f"(๊ฐ์ฅ ์ค๋๋ ๋ฌธํญ {oldest}์ด) | ์ด ๊ฒฝ๊ณผ {int(time.time() - start)}์ด\n"
|
| 289 |
+
f"--- ์ต๊ทผ ๊ธฐ๋ก ---\n{recent_log()}"
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def run_batch(questions: list[dict], api_url: str, label: str):
|
| 294 |
+
"""์งํ ์ํฉ์ ํ๋ฉด์ ๋ด๋ณด๋ด๊ณ , ๋ง์ง๋ง์ (rows, order)๋ฅผ ๋ฐํํฉ๋๋ค."""
|
| 295 |
+
order = [q["task_id"] for q in questions if q.get("task_id")]
|
| 296 |
+
rows: dict = {}
|
| 297 |
+
start = time.time()
|
| 298 |
+
log(f"{label} ์์: ์ง๋ฌธ {len(order)}๊ฐ, WORKERS={WORKERS}, ๋ชจ๋ธ={gemini_model_id()}")
|
| 299 |
+
yield status_text(label, 0, len(order), start), None
|
| 300 |
+
|
| 301 |
+
for event in iter_solve(questions, api_url):
|
| 302 |
+
if event is not None:
|
| 303 |
+
item, answer, ok, note = event
|
| 304 |
+
rows[item["task_id"]] = {
|
| 305 |
+
"Task ID": item["task_id"],
|
| 306 |
+
"Question": item["question"],
|
| 307 |
+
"Submitted Answer": answer,
|
| 308 |
+
"OK": "O" if ok else "X",
|
| 309 |
+
"Note": note,
|
| 310 |
+
}
|
| 311 |
+
yield status_text(label, len(rows), len(order), start), make_df(rows, order)
|
| 312 |
+
return rows, order
|
| 313 |
+
|
| 314 |
|
| 315 |
# ---------------------------------------------------------------------------
|
| 316 |
+
# Gradio ์ฝ๋ฐฑ
|
| 317 |
# ---------------------------------------------------------------------------
|
| 318 |
def run_and_submit_all(profile: gr.OAuthProfile | None):
|
| 319 |
space_id = os.getenv("SPACE_ID")
|
|
|
|
| 328 |
api_url = DEFAULT_API_URL
|
| 329 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
|
| 330 |
|
| 331 |
+
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค... (์ต๋ 30์ด)", None
|
| 332 |
try:
|
| 333 |
questions = fetch_questions(api_url)
|
| 334 |
except Exception as e: # noqa: BLE001
|
| 335 |
yield f"์ง๋ฌธ ๊ฐ์ ธ์ค๊ธฐ ์ค๋ฅ: {e}", None
|
| 336 |
return
|
| 337 |
|
| 338 |
+
rows, order = yield from run_batch(questions, api_url, "๋ณธ ์คํ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
|
| 340 |
answers_payload = [
|
| 341 |
{"task_id": tid, "submitted_answer": rows[tid]["Submitted Answer"]}
|
|
|
|
| 365 |
status = f"์ ์ถ ์คํจ: {detail}"
|
| 366 |
except Exception as e: # noqa: BLE001
|
| 367 |
status = f"์ ์ถ ์ค ์ค๋ฅ: {e}"
|
|
|
|
| 368 |
yield status, make_df(rows, order)
|
| 369 |
|
| 370 |
+
|
| 371 |
def run_test_only(n_questions: int):
|
| 372 |
api_url = DEFAULT_API_URL
|
| 373 |
+
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค... (์ต๋ 30์ด)", None
|
| 374 |
try:
|
| 375 |
questions = fetch_questions(api_url)[: int(n_questions)]
|
| 376 |
except Exception as e: # noqa: BLE001
|
| 377 |
yield f"์ค๋น ์ค ์ค๋ฅ: {e}", None
|
| 378 |
return
|
| 379 |
+
rows, order = yield from run_batch(questions, api_url, "ํ
์คํธ")
|
| 380 |
+
yield f"ํ
์คํธ ์๋ฃ (์ ์ถ ์ ํจ)\n--- ์ต๊ทผ ๊ธฐ๋ก ---\n{recent_log()}", make_df(rows, order)
|
| 381 |
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
+
def _call_with_timeout(fn, seconds: int):
|
| 384 |
+
box: dict = {}
|
| 385 |
+
|
| 386 |
+
def target():
|
| 387 |
+
try:
|
| 388 |
+
box["r"] = fn()
|
| 389 |
+
except Exception as e: # noqa: BLE001
|
| 390 |
+
box["e"] = f"{type(e).__name__}: {e}"[:300]
|
| 391 |
+
|
| 392 |
+
th = threading.Thread(target=target, daemon=True)
|
| 393 |
+
th.start()
|
| 394 |
+
th.join(seconds)
|
| 395 |
+
if th.is_alive():
|
| 396 |
+
return False, f"{seconds}์ด ์์ ์๋ต ์์"
|
| 397 |
+
if "e" in box:
|
| 398 |
+
return False, box["e"]
|
| 399 |
+
return True, str(box.get("r"))[:150]
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def diagnose():
|
| 403 |
+
"""์ฑ์ ์๋ฒ / Gemini / ์น ๊ฒ์ ์ฐ๊ฒฐ์ ๊ฐ๊ฐ ์ ๊ฒํฉ๋๋ค."""
|
| 404 |
+
lines = [
|
| 405 |
+
f"์ค์ : ๋ชจ๋ธ={gemini_model_id()}, GEMINI_API_KEY={'์์' if os.getenv('GEMINI_API_KEY') else '์์'}, "
|
| 406 |
+
f"WORKERS={WORKERS}, MAX_STEPS={MAX_STEPS}, QUESTION_TIMEOUT={QUESTION_TIMEOUT}"
|
| 407 |
+
]
|
| 408 |
+
yield "\n".join(lines + ["", "1/3 ์ฑ์ ์๋ฒ ํ์ธ ์ค..."])
|
| 409 |
+
|
| 410 |
+
ok, msg = _call_with_timeout(lambda: len(fetch_questions(DEFAULT_API_URL)), 35)
|
| 411 |
+
lines.append(f"1) ์ฑ์ ์๋ฒ ์ง๋ฌธ ๋ชฉ๋ก: {'์ ์' if ok else '์คํจ'} - {msg}")
|
| 412 |
+
yield "\n".join(lines + ["", "2/3 Gemini ํธ์ถ ํ์ธ ์ค..."])
|
| 413 |
+
|
| 414 |
+
key = os.getenv("GEMINI_API_KEY")
|
| 415 |
+
if key:
|
| 416 |
+
def ping():
|
| 417 |
+
import litellm
|
| 418 |
+
r = litellm.completion(
|
| 419 |
+
model=f"gemini/{gemini_model_id()}",
|
| 420 |
+
api_key=key,
|
| 421 |
+
messages=[{"role": "user", "content": "Reply with the single word OK."}],
|
| 422 |
+
timeout=45,
|
| 423 |
+
)
|
| 424 |
+
return r.choices[0].message.content
|
| 425 |
+
|
| 426 |
+
ok, msg = _call_with_timeout(ping, 60)
|
| 427 |
+
lines.append(f"2) Gemini ํธ์ถ: {'์ ์' if ok else '์คํจ'} - {msg}")
|
| 428 |
+
else:
|
| 429 |
+
lines.append("2) Gemini ํธ์ถ: GEMINI_API_KEY๊ฐ ์ค์ ๋์ง ์์ ๊ฑด๋๋")
|
| 430 |
+
yield "\n".join(lines + ["", "3/3 ์น ๊ฒ์ ํ์ธ ์ค..."])
|
| 431 |
+
|
| 432 |
+
ok, msg = _call_with_timeout(lambda: DuckDuckGoSearchTool()("Hugging Face"), 40)
|
| 433 |
+
lines.append(f"3) ์น ๊ฒ์(DuckDuckGo): {'์ ์' if ok else '์คํจ'} - {msg}")
|
| 434 |
+
yield "\n".join(lines)
|
| 435 |
|
|
|
|
| 436 |
|
| 437 |
def clear_cache():
|
| 438 |
try:
|
|
|
|
| 441 |
except FileNotFoundError:
|
| 442 |
return "์ญ์ ํ ์บ์๊ฐ ์์ต๋๋ค."
|
| 443 |
|
| 444 |
+
|
| 445 |
# ---------------------------------------------------------------------------
|
| 446 |
+
# UI
|
| 447 |
# ---------------------------------------------------------------------------
|
| 448 |
with gr.Blocks() as demo:
|
| 449 |
gr.Markdown("# Basic Agent Evaluation Runner (Gemini 3.8 Flash)")
|
| 450 |
gr.Markdown(
|
| 451 |
"""**Instructions:**
|
| 452 |
+
1. ๋จผ์ '์ฐ๊ฒฐ ์ง๋จ'์ผ๋ก ์ฑ์ ์๋ฒ, Gemini, ์น ๊ฒ์์ด ์ ์์ธ์ง ํ์ธํฉ๋๋ค.
|
| 453 |
+
2. ๋ก๊ทธ์ธ ํ 'ํ
์คํธ ์คํ'์ผ๋ก ์์ ๋ฌธํญ์ ํ์ธํฉ๋๋ค. (์ ์ถ ์ ํจ)
|
| 454 |
+
3. ๋ฌธ์ ์์ผ๋ฉด 'Run Evaluation & Submit All Answers'๋ฅผ ๋๋ฆ
๋๋ค."""
|
| 455 |
)
|
| 456 |
gr.LoginButton()
|
| 457 |
+
|
| 458 |
with gr.Row():
|
| 459 |
+
diag_button = gr.Button("์ฐ๊ฒฐ ์ง๋จ")
|
| 460 |
test_n = gr.Number(value=2, label="ํ
์คํธํ ๋ฌธํญ ์ (์ ์ถ ์ ํจ)", precision=0)
|
| 461 |
test_button = gr.Button("ํ
์คํธ ์คํ (์ ์ถ ์ ํจ)")
|
| 462 |
cache_button = gr.Button("์บ์ ์ด๊ธฐํ")
|
| 463 |
+
|
| 464 |
run_button = gr.Button("Run Evaluation & Submit All Answers")
|
| 465 |
+
status_output = gr.Textbox(label="Run Status / Submission Result", lines=14, interactive=False)
|
| 466 |
results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
|
| 467 |
|
| 468 |
+
diag_button.click(fn=diagnose, outputs=[status_output])
|
| 469 |
test_button.click(fn=run_test_only, inputs=[test_n], outputs=[status_output, results_table])
|
| 470 |
run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
|
| 471 |
cache_button.click(fn=clear_cache, outputs=[status_output])
|