Spaces:
Sleeping
Update app.py
Browse files"""HF Agents Course ์ต์ข
๊ณผ์ - Gemini ๋๋ Hugging Face ์ ํํ ๊ฒฝ๋ ๋ฒ์
์ค๊ณ ์์น: ๋ฌธํญ๋น LLM ํธ์ถ 1ํ.
์น ๊ฒ์(ํธ์ถ ์์) -> ๊ฒ์ ๊ฒฐ๊ณผ + ์ฒจ๋ถ ํ์ผ์ ํ ๋ฒ์ Gemini์ ์ ๋ฌ -> ์ต์ข
๋ต๋ง ๋ฐ์
"""
import base64
import json
import mimetypes
import os
import re
import time
from collections import deque
import gradio as gr
import pandas as pd
import requests
# ZeroGPU ํ๋์จ์ด์์ ์์ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํ ๋๋ฏธ ํจ์ (CPU ํ๋์จ์ด์์๋ ๋ฌด์๋จ)
try:
import spaces
@spaces.GPU
def _gpu_placeholder():
return "ok"
except ImportError:
pass
try:
from ddgs import DDGS
except ImportError:
try:
from duckduckgo_search import DDGS
except ImportError:
DDGS = None
# ---------------------------------------------------------------------------
# ์ค์ (Space Variables/Secrets๋ก ์กฐ์ )
# ---------------------------------------------------------------------------
SCORING_API = "https://agents-course-unit4-scoring.hf.space"
GEMINI_API = "https://generativelanguage.googleapis.com/v1beta"
FILES_DIR = "/tmp/task_files"
CACHE_PATH = "/tmp/answers_cache.json"
os.makedirs(FILES_DIR, exist_ok=True)
PROVIDER = (os.getenv("PROVIDER", "") or ("gemini" if os.getenv("GEMINI_API_KEY") else "hf")).lower() # gemini | hf
MIN_INTERVAL = float(os.getenv("MIN_INTERVAL", "7" if PROVIDER == "gemini" else "2")) # LLM ํธ์ถ ๊ฐ ์ต์ ๊ฐ๊ฒฉ(์ด)
HF_ROUTER = "https://router.huggingface.co/v1"
HF_MODELS = [m.strip() for m in (os.getenv("HF_MODELS") or os.getenv("HF_MODEL") or "Qwen/Qwen2.5-72B-Instruct").split(",") if m.strip()]
MAX_RETRY = int(os.getenv("MAX_RETRY", "3")) # 429 ์ฌ์๋ ํ์
FETCH_PAGES = int(os.getenv("FETCH_PAGES", "2")) # ๋ณธ๋ฌธ์ ์ฝ์ ๊ฒ์ ๊ฒฐ๊ณผ ์
PAGE_CHARS = int(os.getenv("PAGE_CHARS", "5000")) # ํ์ด์ง๋น ์ต๋ ๊ธ์ ์
LLM_QUERY = os.getenv("LLM_QUERY", "0") == "1" # 1์ด๋ฉด ๊ฒ์์ด๋ Gemini๋ก ์์ฑ(ํธ์ถ 2๋ฐฐ)
GEMINI_MODELS = os.getenv("GEMINI_MODELS", "") # ์ ํ: ์ผํ๋ก ๊ตฌ๋ถํ ๋ชจ๋ธ ์ด๋ฆ ๋ชฉ๋ก
PROMPT = """You are answering a GAIA-style benchmark question. The answer is graded by EXACT MATCH.
Rules:
- Use the provided web context and attached file. If the context is insufficient, use your own knowledge and reason carefully.
- Give ONLY the answer value: no explanation, no trailing period, no units unless asked.
- Numbers: digits only, no thousands separators, no currency or percent symbols unless asked.
- Lists: comma-separated, in the order the question asks.
- Strings: no leading articles (a, an, the), no abbreviations unless asked.
- End your reply with one final line exactly in the form:
FINAL ANSWER: <answer>
Question:
{question}
{extra}"""
# ---------------------------------------------------------------------------
# ๋ก๊ทธ
# ---------------------------------------------------------------------------
_events = deque(maxlen=12)
def log(msg: str):
line = f"[{time.strftime('%H:%M:%S')}] {msg}"
print(line, flush=True)
_events.append(line)
def recent_log() -> str:
return "\n".join(_events)
# ---------------------------------------------------------------------------
# ์บ์
# ---------------------------------------------------------------------------
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(task_id: str, answer: str):
cache = load_cache()
cache[task_id] = answer
with open(CACHE_PATH, "w", encoding="utf-8") as f:
json.dump(cache, f, ensure_ascii=False)
# ---------------------------------------------------------------------------
# Gemini REST ํธ์ถ (๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด requests๋ง ์ฌ์ฉ)
# ---------------------------------------------------------------------------
class LLMUnavailable(Exception):
pass
def api_key() -> str:
return os.getenv("GEMINI_API_KEY", "")
def list_models() -> list[str]:
r = requests.get(
f"{GEMINI_API}/models",
headers={"x-goog-api-key": api_key()},
params={"pageSize": 200},
timeout=30,
)
r.raise_for_status()
names = []
for m in r.json().get("models", []):
if "generateContent" in m.get("supportedGenerationMethods", []):
names.append(m["name"].replace("models/", ""))
return names
_BAD = ("image", "tts", "audio", "live", "embed", "vision", "robotics", "computer", "imagen", "veo")
_candidates: list[str] = []
def candidate_models() -> list[str]:
"""GEMINI_MODELS๊ฐ ์์ผ๋ฉด ๊ทธ ๋ชฉ๋ก, ์์ผ๋ฉด ํค๋ก ์ธ ์ ์๋ ๋ชจ๋ธ์์ ์๋ ์ ํ."""
global _candidates
if _candidates:
return _candidates
if GEMINI_MODELS.strip():
_candidates = [m.strip().replace("models/", "") for m in GEMINI_MODELS.split(",") if m.strip()]
return _candidates
names = [n for n in list_models() if n.startswith("gemini") and not any(b in n for b in _BAD)]
lite = [n for n in names if "flash-lite" in n]
flash = [n for n in names if "flash" in n and "flash-lite" not in n]
_candidates = (lite[:2] + flash[:2]) or names[:3]
log(f"์๋ ์ ํ๋ ๋ชจ๋ธ ํ๋ณด: {_candidates}")
return _candidates
_last_call = 0.0
def throttle():
global _last_call
wait = MIN_INTERVAL - (time.time() - _last_call)
if wait > 0:
time.sleep(wait)
_last_call = time.time()
def call_gemini(parts: list[dict]) -> tuple[str, str]:
"""(์๋ต ํ
์คํธ, ์ฌ์ฉํ ๋ชจ๋ธ)์ ๋ฐํ. ๋ชจ๋ ํ๋ณด๊ฐ ์คํจํ๋ฉด LLMUnavailable."""
if not api_key():
raise LLMUnavailable("GEMINI_API_KEY๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.")
last_err = ""
for model in candidate_models():
for attempt in range(MAX_RETRY + 1):
throttle()
try:
r = requests.post(
f"{GEMINI_API}/models/{model}:generateContent",
headers={"x-goog-api-key": api_key(), "Content-Type": "application/json"},
json={
"contents": [{"role": "user", "parts": parts}],
"generationConfig": {"temperature": 0, "maxOutputTokens": 2048},
},
timeout=120,
)
except requests.RequestException as e:
last_err = f"{model}: ๋คํธ์ํฌ ์ค๋ฅ {e}"
time.sleep(5)
continue
if r.status_code == 200:
cand = (r.json().get("candidates") or [{}])[0]
text = "".join(
p.get("text", "")
for p in cand.get("content", {}).get("parts", [])
if not p.get("thought")
)
if text.strip():
return text, model
last_err = f"{model}: ๋น ์๋ต({cand.get('finishReason')})"
break
body = r.text
last_err = f"{model}: HTTP {r.status_code} {body[:180].replace(chr(10), ' ')}"
if r.status_code == 429:
# ์ผ์ผ ํ๋ ์์ง ๋๋ ํ๋ 0์ด๋ฉด ์ฌ์๋ํด๋ ์์ฉ์์ผ๋ฏ๋ก ๋ค์ ๋ชจ๋ธ๋ก
if "PerDay" in body or "limit: 0" in body:
log(f"{model}: ์ผ์ผ ํ๋ ์์ง ๋๋ ํ๋ 0 -> ๋ค์ ๋ชจ๋ธ")
break
if attempt == MAX_RETRY:
break
m = re.search(r'"retryDelay":\s*"(\d+(?:\.\d+)?)s"', body)
wait = min(float(m.group(1)) + 1, 60) if m else 15 * (attempt + 1)
log(f"{model}: 429, {int(wait)}์ด ํ ์ฌ์๋ ({attempt + 1}/{MAX_RETRY})")
time.sleep(wait)
continue
if r.status_code >= 500:
time.sleep(5)
continue
break # 400, 403, 404 ๋ฑ์ ์ด ๋ชจ๋ธ ํฌ๊ธฐ
raise LLMUnavailable(last_err or "์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ธ์ด ์์ต๋๋ค.")
# ---------------------------------------------------------------------------
# Hugging Face Inference Providers (OpenAI ํธํ ๋ผ์ฐํฐ)
# ---------------------------------------------------------------------------
def hf_token() -> str:
return os.getenv("HF_TOKEN", "")
def call_hf(parts: list[dict]) -> tuple[str, str]:
"""(์๋ต ํ
์คํธ, ์ฌ์ฉํ ๋ชจ๋ธ)์ ๋ฐํ. ํ
์คํธ ์ ์ฉ์ด๋ฏ๋ก ์ด๋ฏธ์ง/์ค๋์ค ํํธ๋ ์ ๋ฌํ์ง ๋ชปํฉ๋๋ค."""
if not hf_token():
raise LLMUnavailable("HF_TOKEN์ด ์ค์ ๋์ง ์์์ต๋๋ค.")
text = "\n".join(p["text"] for p in parts if "text" in p)
if any("inline_data" in p for p in parts):
text = ("NOTE: An image/audio attachment exists but you cannot view it; "
"answer from the other information.\n\n" + text)
last_err = ""
for model in HF_MODELS:
for attempt in range(MAX_RETRY + 1):
throttle()
try:
r = requests.post(
f"{HF_ROUTER}/chat/completions",
headers={"Authorization": f"Bearer {hf_token()}", "Content-Type": "application/json"},
json={"model": model, "messages": [{"role": "user", "content": text}],
"max_tokens": 1024, "temperature": 0},
timeout=120,
)
except requests.RequestException as e:
last_err = f"{model}: ๋คํธ์ํฌ ์ค๋ฅ {e}"
time.sleep(5)
continue
if r.status_code == 200:
content = (r.json().get("choices") or [{}])[0].get("message", {}).get("content") or ""
content = re.sub(r"(?s)<think>.*?</think>", "", content).strip()
if content:
return content, model
last_err = f"{model}: ๋น ์๋ต"
break
body = r.text
last_err = f"{model}: HTTP {r.status_code} {body[:180].replace(chr(10), ' ')}"
if r.status_code == 402:
raise LLMUnavailable("HF ๋ฌด๋ฃ ํฌ๋ ๋ง ์์ง(402). " + body[:150])
|
@@ -1,32 +1,19 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
# litellm์ ์ฌ์๋ ๊ธฐ๋ฅ์ ํ์ํ tenacity๊ฐ ์์ผ๋ฉด ์์ ์ ์๋ ์ค์น
|
| 11 |
-
try:
|
| 12 |
-
import tenacity # noqa: F401
|
| 13 |
-
except ImportError:
|
| 14 |
-
import subprocess
|
| 15 |
-
import sys
|
| 16 |
-
|
| 17 |
-
subprocess.run([sys.executable, "-m", "pip", "install", "tenacity"], check=False)
|
| 18 |
|
| 19 |
import gradio as gr
|
| 20 |
import pandas as pd
|
| 21 |
import requests
|
| 22 |
-
from smolagents import (
|
| 23 |
-
CodeAgent,
|
| 24 |
-
DuckDuckGoSearchTool,
|
| 25 |
-
InferenceClientModel,
|
| 26 |
-
LiteLLMModel,
|
| 27 |
-
VisitWebpageTool,
|
| 28 |
-
tool,
|
| 29 |
-
)
|
| 30 |
|
| 31 |
# ZeroGPU ํ๋์จ์ด์์ ์์ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํ ๋๋ฏธ ํจ์ (CPU ํ๋์จ์ด์์๋ ๋ฌด์๋จ)
|
| 32 |
try:
|
|
@@ -38,61 +25,68 @@ try:
|
|
| 38 |
except ImportError:
|
| 39 |
pass
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
# ---------------------------------------------------------------------------
|
| 42 |
-
# ์ค์ (Space
|
| 43 |
# ---------------------------------------------------------------------------
|
| 44 |
-
|
|
|
|
| 45 |
FILES_DIR = "/tmp/task_files"
|
| 46 |
CACHE_PATH = "/tmp/answers_cache.json"
|
| 47 |
os.makedirs(FILES_DIR, exist_ok=True)
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
ANSWER_RULES = """You are a careful research and reasoning agent solving GAIA-style questions.
|
| 56 |
Rules:
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
|
| 70 |
# ---------------------------------------------------------------------------
|
| 71 |
-
#
|
| 72 |
# ---------------------------------------------------------------------------
|
| 73 |
-
_events = deque(maxlen=
|
| 74 |
-
_active: dict = {} # task_id -> ์์ ์๊ฐ
|
| 75 |
-
_state_lock = threading.Lock()
|
| 76 |
|
| 77 |
|
| 78 |
def log(msg: str):
|
| 79 |
line = f"[{time.strftime('%H:%M:%S')}] {msg}"
|
| 80 |
print(line, flush=True)
|
| 81 |
-
|
| 82 |
-
_events.append(line)
|
| 83 |
|
| 84 |
|
| 85 |
def recent_log() -> str:
|
| 86 |
-
|
| 87 |
-
return "\n".join(_events)
|
| 88 |
|
| 89 |
|
| 90 |
# ---------------------------------------------------------------------------
|
| 91 |
# ์บ์
|
| 92 |
# ---------------------------------------------------------------------------
|
| 93 |
-
_cache_lock = threading.Lock()
|
| 94 |
-
|
| 95 |
-
|
| 96 |
def load_cache() -> dict:
|
| 97 |
try:
|
| 98 |
with open(CACHE_PATH, "r", encoding="utf-8") as f:
|
|
@@ -101,224 +95,367 @@ def load_cache() -> dict:
|
|
| 101 |
return {}
|
| 102 |
|
| 103 |
|
| 104 |
-
def
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
json.dump(cache, f, ensure_ascii=False)
|
| 110 |
|
| 111 |
|
| 112 |
# ---------------------------------------------------------------------------
|
| 113 |
-
#
|
| 114 |
# ---------------------------------------------------------------------------
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
"""Reads a local file (txt, csv, json, py, md, xlsx) and returns its content as text.
|
| 118 |
|
| 119 |
-
Args:
|
| 120 |
-
path: Absolute or relative path of the file to read.
|
| 121 |
-
"""
|
| 122 |
-
try:
|
| 123 |
-
if not os.path.exists(path):
|
| 124 |
-
basename = os.path.basename(path)
|
| 125 |
-
matches = glob.glob(os.path.join(FILES_DIR, f"*{basename}"))
|
| 126 |
-
if matches:
|
| 127 |
-
path = matches[0]
|
| 128 |
-
lower = path.lower()
|
| 129 |
-
if lower.endswith((".xlsx", ".xls")):
|
| 130 |
-
sheets = pd.read_excel(path, sheet_name=None)
|
| 131 |
-
return "\n\n".join(
|
| 132 |
-
f"## Sheet: {n}\n{df.to_string(max_rows=200)}" for n, df in sheets.items()
|
| 133 |
-
)
|
| 134 |
-
if lower.endswith(".csv"):
|
| 135 |
-
return pd.read_csv(path).to_string(max_rows=200)
|
| 136 |
-
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
| 137 |
-
return f.read()[:20000]
|
| 138 |
-
except Exception as e: # noqa: BLE001
|
| 139 |
-
return f"Could not read file: {e}"
|
| 140 |
|
|
|
|
|
|
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
def build_model():
|
| 150 |
-
gemini_key = os.getenv("GEMINI_API_KEY")
|
| 151 |
-
if gemini_key:
|
| 152 |
-
return LiteLLMModel(
|
| 153 |
-
model_id=f"gemini/{gemini_model_id()}",
|
| 154 |
-
api_key=gemini_key,
|
| 155 |
-
timeout=MODEL_TIMEOUT, # LLM ํธ์ถ์ด ๋ฌดํ์ ๋ฉ์ถ์ง ์๋๋ก ์ ํ
|
| 156 |
-
num_retries=2,
|
| 157 |
-
)
|
| 158 |
-
openai_key = os.getenv("OPENAI_API_KEY")
|
| 159 |
-
if openai_key:
|
| 160 |
-
return LiteLLMModel(
|
| 161 |
-
model_id=f"openai/{os.getenv('OPENAI_MODEL', 'gpt-4o-mini')}",
|
| 162 |
-
api_key=openai_key,
|
| 163 |
-
timeout=MODEL_TIMEOUT,
|
| 164 |
-
num_retries=2,
|
| 165 |
-
)
|
| 166 |
-
return InferenceClientModel(
|
| 167 |
-
model_id=os.getenv("HF_MODEL", "Qwen/Qwen2.5-Coder-32B-Instruct"),
|
| 168 |
-
token=os.getenv("HF_TOKEN"),
|
| 169 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
|
| 185 |
-
def
|
| 186 |
-
|
| 187 |
-
text = re.sub(r"^\s*final answer\s*:\s*", "", text, flags=re.IGNORECASE)
|
| 188 |
-
return text.strip().rstrip(".").strip()
|
| 189 |
|
| 190 |
|
| 191 |
# ---------------------------------------------------------------------------
|
| 192 |
-
#
|
| 193 |
# ---------------------------------------------------------------------------
|
| 194 |
-
def
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
raise ValueError("์ง๋ฌธ ๋ชฉ๋ก์ด ๋น์ด ์์ต๋๋ค.")
|
| 200 |
-
return data
|
| 201 |
|
| 202 |
|
| 203 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
if not file_name:
|
| 205 |
return None
|
| 206 |
try:
|
| 207 |
-
|
| 208 |
-
|
| 209 |
path = os.path.join(FILES_DIR, f"{task_id}_{file_name}")
|
| 210 |
with open(path, "wb") as f:
|
| 211 |
-
f.write(
|
| 212 |
return path
|
| 213 |
except Exception as e: # noqa: BLE001
|
| 214 |
-
log(f"ํ์ผ ๋ค์ด๋ก๋ ์คํจ
|
| 215 |
return None
|
| 216 |
|
| 217 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
# ---------------------------------------------------------------------------
|
| 219 |
-
# ๋ฌธํญ ํ์ด
|
| 220 |
# ---------------------------------------------------------------------------
|
| 221 |
-
def
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
"""(๋ต๋ณ, ์ฑ๊ณต ์ฌ๋ถ, ๋น๊ณ )๋ฅผ ๋ฐํํฉ๋๋ค."""
|
| 231 |
-
task_id = item["task_id"]
|
| 232 |
-
short = task_id[:8]
|
| 233 |
-
cached = load_cache().get(task_id)
|
| 234 |
-
if cached:
|
| 235 |
-
log(f"์บ์ ์ฌ์ฉ {short} -> {cached}")
|
| 236 |
-
return cached, True, "cache"
|
| 237 |
-
|
| 238 |
-
box: dict = {}
|
| 239 |
-
|
| 240 |
-
def target():
|
| 241 |
-
try:
|
| 242 |
-
box["answer"] = solve_one(item, api_url)
|
| 243 |
-
except Exception as e: # noqa: BLE001
|
| 244 |
-
box["error"] = f"{type(e).__name__}: {e}"[:300]
|
| 245 |
-
|
| 246 |
-
log(f"์์ {short}")
|
| 247 |
-
with _state_lock:
|
| 248 |
-
_active[task_id] = time.time()
|
| 249 |
-
t = threading.Thread(target=target, daemon=True)
|
| 250 |
-
t.start()
|
| 251 |
-
t.join(QUESTION_TIMEOUT)
|
| 252 |
-
with _state_lock:
|
| 253 |
-
_active.pop(task_id, None)
|
| 254 |
-
|
| 255 |
-
if t.is_alive():
|
| 256 |
-
log(f"์๊ฐ ์ด๊ณผ {short} ({QUESTION_TIMEOUT}์ด)")
|
| 257 |
-
return "unknown", False, f"timeout {QUESTION_TIMEOUT}s"
|
| 258 |
-
if "error" in box:
|
| 259 |
-
log(f"์ค๋ฅ {short}: {box['error']}")
|
| 260 |
-
return "unknown", False, box["error"]
|
| 261 |
-
|
| 262 |
-
save_cache_entry(task_id, box["answer"])
|
| 263 |
-
log(f"์๋ฃ {short} -> {box['answer']}")
|
| 264 |
-
return box["answer"], True, ""
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
def iter_solve(questions: list[dict], api_url: str):
|
| 268 |
-
"""๋๋ ๋ฌธํญ์ (item, answer, ok, note)๋ก, ๋๊ธฐ ์ค์๋ ์ฃผ๊ธฐ์ ์ผ๋ก None์ ๋ด๋ณด๋
๋๋ค."""
|
| 269 |
-
valid = [q for q in questions if q.get("task_id") and q.get("question") is not None]
|
| 270 |
-
pool = ThreadPoolExecutor(max_workers=WORKERS)
|
| 271 |
try:
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
|
| 286 |
def make_df(rows: dict, order: list[str]) -> pd.DataFrame:
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
def status_text(label: str, done: int, total: int, start: float) -> str:
|
| 292 |
-
with _state_lock:
|
| 293 |
-
running = len(_active)
|
| 294 |
-
oldest = int(time.time() - min(_active.values())) if _active else 0
|
| 295 |
-
return (
|
| 296 |
-
f"{label}: {done}/{total} ์๋ฃ | ์คํ ์ค {running}๊ฐ "
|
| 297 |
-
f"(๊ฐ์ฅ ์ค๋๋ ๋ฌธํญ {oldest}๏ฟฝ๏ฟฝ๏ฟฝ) | ์ด ๊ฒฝ๊ณผ {int(time.time() - start)}์ด\n"
|
| 298 |
-
f"--- ์ต๊ทผ ๊ธฐ๋ก ---\n{recent_log()}"
|
| 299 |
)
|
| 300 |
|
| 301 |
|
| 302 |
-
def run_batch(questions: list[dict],
|
| 303 |
-
"""์งํ ์ํฉ์
|
| 304 |
order = [q["task_id"] for q in questions if q.get("task_id")]
|
| 305 |
rows: dict = {}
|
|
|
|
| 306 |
start = time.time()
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
|
| 324 |
# ---------------------------------------------------------------------------
|
|
@@ -332,144 +469,117 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
|
|
| 332 |
if not space_id:
|
| 333 |
yield "SPACE_ID๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. Space ์์์ ์คํํด์ผ ํฉ๋๋ค.", None
|
| 334 |
return
|
| 335 |
-
|
| 336 |
-
username = profile.username
|
| 337 |
-
api_url = DEFAULT_API_URL
|
| 338 |
-
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
|
| 339 |
-
|
| 340 |
-
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค... (์ต๋ 30์ด)", None
|
| 341 |
try:
|
| 342 |
-
questions = fetch_questions(
|
| 343 |
except Exception as e: # noqa: BLE001
|
| 344 |
yield f"์ง๋ฌธ ๊ฐ์ ธ์ค๊ธฐ ์ค๋ฅ: {e}", None
|
| 345 |
return
|
| 346 |
|
| 347 |
-
rows, order = yield from run_batch(questions,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
-
|
| 350 |
-
{"task_id": tid, "submitted_answer": rows[tid]["Submitted Answer"]}
|
| 351 |
-
for tid in order if tid in rows
|
| 352 |
-
]
|
| 353 |
failed = sum(1 for r in rows.values() if r["OK"] == "X")
|
| 354 |
-
yield f"ํ์ด ์๋ฃ (์คํจ
|
| 355 |
-
|
| 356 |
submission = {
|
| 357 |
-
"username": username.strip(),
|
| 358 |
-
"agent_code":
|
| 359 |
-
"answers":
|
| 360 |
}
|
| 361 |
try:
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
status = (
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
f"์ ์: {r.get('score', 'N/A')}% "
|
| 369 |
-
f"({r.get('correct_count', '?')}/{r.get('total_attempted', '?')} ์ ๋ต)\n"
|
| 370 |
-
f"๋ฉ์์ง: {r.get('message', '')}"
|
| 371 |
-
)
|
| 372 |
except requests.exceptions.HTTPError as e:
|
| 373 |
-
|
| 374 |
-
status = f"์ ์ถ ์คํจ: {detail}"
|
| 375 |
except Exception as e: # noqa: BLE001
|
| 376 |
status = f"์ ์ถ ์ค ์ค๋ฅ: {e}"
|
| 377 |
yield status, make_df(rows, order)
|
| 378 |
|
| 379 |
|
| 380 |
def run_test_only(n_questions: int):
|
| 381 |
-
|
| 382 |
-
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค... (์ต๋ 30์ด)", None
|
| 383 |
try:
|
| 384 |
-
questions = fetch_questions(
|
| 385 |
except Exception as e: # noqa: BLE001
|
| 386 |
yield f"์ค๋น ์ค ์ค๋ฅ: {e}", None
|
| 387 |
return
|
| 388 |
-
|
| 389 |
-
yield f"ํ
์คํธ ์๋ฃ (์ ์ถ ์ ํจ)\n--- ์ต๊ทผ ๊ธฐ๋ก ---\n{recent_log()}", make_df(rows, order)
|
| 390 |
-
|
| 391 |
|
| 392 |
-
def _call_with_timeout(fn, seconds: int):
|
| 393 |
-
box: dict = {}
|
| 394 |
|
| 395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
try:
|
| 397 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
except Exception as e: # noqa: BLE001
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
th = threading.Thread(target=target, daemon=True)
|
| 402 |
-
th.start()
|
| 403 |
-
th.join(seconds)
|
| 404 |
-
if th.is_alive():
|
| 405 |
-
return False, f"{seconds}์ด ์์ ์๋ต ์์"
|
| 406 |
-
if "e" in box:
|
| 407 |
-
return False, box["e"]
|
| 408 |
-
return True, str(box.get("r"))[:150]
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
def diagnose():
|
| 412 |
-
"""์ฑ์ ์๋ฒ / Gemini / ์น ๊ฒ์ ์ฐ๊ฒฐ์ ๊ฐ๊ฐ ์ ๊ฒํฉ๋๋ค."""
|
| 413 |
-
lines = [
|
| 414 |
-
f"์ค์ : ๋ชจ๋ธ={gemini_model_id()}, GEMINI_API_KEY={'์์' if os.getenv('GEMINI_API_KEY') else '์์'}, "
|
| 415 |
-
f"WORKERS={WORKERS}, MAX_STEPS={MAX_STEPS}, QUESTION_TIMEOUT={QUESTION_TIMEOUT}"
|
| 416 |
-
]
|
| 417 |
-
yield "\n".join(lines + ["", "1/3 ์ฑ์ ์๋ฒ ํ์ธ ์ค..."])
|
| 418 |
-
|
| 419 |
-
ok, msg = _call_with_timeout(lambda: len(fetch_questions(DEFAULT_API_URL)), 35)
|
| 420 |
-
lines.append(f"1) ์ฑ์ ์๋ฒ ์ง๋ฌธ ๋ชฉ๋ก: {'์ ์' if ok else '์คํจ'} - {msg}")
|
| 421 |
-
yield "\n".join(lines + ["", "2/3 Gemini ํธ์ถ ํ์ธ ์ค..."])
|
| 422 |
-
|
| 423 |
-
key = os.getenv("GEMINI_API_KEY")
|
| 424 |
-
if key:
|
| 425 |
-
def ping():
|
| 426 |
-
import litellm
|
| 427 |
-
r = litellm.completion(
|
| 428 |
-
model=f"gemini/{gemini_model_id()}",
|
| 429 |
-
api_key=key,
|
| 430 |
-
messages=[{"role": "user", "content": "Reply with the single word OK."}],
|
| 431 |
-
timeout=45,
|
| 432 |
-
)
|
| 433 |
-
return r.choices[0].message.content
|
| 434 |
-
|
| 435 |
-
ok, msg = _call_with_timeout(ping, 60)
|
| 436 |
-
lines.append(f"2) Gemini ํธ์ถ: {'์ ์' if ok else '์คํจ'} - {msg}")
|
| 437 |
else:
|
| 438 |
-
lines.append("
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
yield "\n".join(lines)
|
| 444 |
|
| 445 |
|
| 446 |
def clear_cache():
|
|
|
|
|
|
|
| 447 |
try:
|
| 448 |
os.remove(CACHE_PATH)
|
| 449 |
-
return "์บ์๋ฅผ
|
| 450 |
except FileNotFoundError:
|
| 451 |
-
return "์ญ์ ํ ์บ์๊ฐ ์์ต๋๋ค."
|
| 452 |
|
| 453 |
|
| 454 |
# ---------------------------------------------------------------------------
|
| 455 |
# UI
|
| 456 |
# ---------------------------------------------------------------------------
|
| 457 |
with gr.Blocks() as demo:
|
| 458 |
-
gr.Markdown("# Basic Agent Evaluation Runner (Gemini
|
| 459 |
gr.Markdown(
|
| 460 |
-
"""**
|
| 461 |
-
1.
|
| 462 |
-
2. ๋ก๊ทธ์ธ ํ 'ํ
์คํธ ์คํ'์ผ๋ก
|
| 463 |
3. ๋ฌธ์ ์์ผ๋ฉด 'Run Evaluation & Submit All Answers'๋ฅผ ๋๋ฆ
๋๋ค."""
|
| 464 |
)
|
| 465 |
gr.LoginButton()
|
| 466 |
-
|
| 467 |
with gr.Row():
|
| 468 |
diag_button = gr.Button("์ฐ๊ฒฐ ์ง๋จ")
|
| 469 |
test_n = gr.Number(value=2, label="ํ
์คํธํ ๋ฌธํญ ์ (์ ์ถ ์ ํจ)", precision=0)
|
| 470 |
test_button = gr.Button("ํ
์คํธ ์คํ (์ ์ถ ์ ํจ)")
|
| 471 |
cache_button = gr.Button("์บ์ ์ด๊ธฐํ")
|
| 472 |
-
|
| 473 |
run_button = gr.Button("Run Evaluation & Submit All Answers")
|
| 474 |
status_output = gr.Textbox(label="Run Status / Submission Result", lines=14, interactive=False)
|
| 475 |
results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
|
|
|
|
| 1 |
+
"""HF Agents Course ์ต์ข
๊ณผ์ - Gemini ๋๋ Hugging Face ์ ํํ ๊ฒฝ๋ ๋ฒ์
|
| 2 |
+
|
| 3 |
+
์ค๊ณ ์์น: ๋ฌธํญ๋น LLM ํธ์ถ 1ํ.
|
| 4 |
+
์น ๊ฒ์(ํธ์ถ ์์) -> ๊ฒ์ ๊ฒฐ๊ณผ + ์ฒจ๋ถ ํ์ผ์ ํ ๋ฒ์ Gemini์ ์ ๋ฌ -> ์ต์ข
๋ต๋ง ๋ฐ์
|
| 5 |
+
"""
|
| 6 |
+
import base64
|
| 7 |
import json
|
| 8 |
+
import mimetypes
|
| 9 |
import os
|
| 10 |
import re
|
|
|
|
| 11 |
import time
|
| 12 |
from collections import deque
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
import gradio as gr
|
| 15 |
import pandas as pd
|
| 16 |
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
# ZeroGPU ํ๋์จ์ด์์ ์์ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํ ๋๋ฏธ ํจ์ (CPU ํ๋์จ์ด์์๋ ๋ฌด์๋จ)
|
| 19 |
try:
|
|
|
|
| 25 |
except ImportError:
|
| 26 |
pass
|
| 27 |
|
| 28 |
+
try:
|
| 29 |
+
from ddgs import DDGS
|
| 30 |
+
except ImportError:
|
| 31 |
+
try:
|
| 32 |
+
from duckduckgo_search import DDGS
|
| 33 |
+
except ImportError:
|
| 34 |
+
DDGS = None
|
| 35 |
+
|
| 36 |
# ---------------------------------------------------------------------------
|
| 37 |
+
# ์ค์ (Space Variables/Secrets๋ก ์กฐ์ )
|
| 38 |
# ---------------------------------------------------------------------------
|
| 39 |
+
SCORING_API = "https://agents-course-unit4-scoring.hf.space"
|
| 40 |
+
GEMINI_API = "https://generativelanguage.googleapis.com/v1beta"
|
| 41 |
FILES_DIR = "/tmp/task_files"
|
| 42 |
CACHE_PATH = "/tmp/answers_cache.json"
|
| 43 |
os.makedirs(FILES_DIR, exist_ok=True)
|
| 44 |
|
| 45 |
+
PROVIDER = (os.getenv("PROVIDER", "") or ("gemini" if os.getenv("GEMINI_API_KEY") else "hf")).lower() # gemini | hf
|
| 46 |
+
MIN_INTERVAL = float(os.getenv("MIN_INTERVAL", "7" if PROVIDER == "gemini" else "2")) # LLM ํธ์ถ ๊ฐ ์ต์ ๊ฐ๊ฒฉ(์ด)
|
| 47 |
+
HF_ROUTER = "https://router.huggingface.co/v1"
|
| 48 |
+
HF_MODELS = [m.strip() for m in (os.getenv("HF_MODELS") or os.getenv("HF_MODEL") or "Qwen/Qwen2.5-72B-Instruct").split(",") if m.strip()]
|
| 49 |
+
MAX_RETRY = int(os.getenv("MAX_RETRY", "3")) # 429 ์ฌ์๋ ํ์
|
| 50 |
+
FETCH_PAGES = int(os.getenv("FETCH_PAGES", "2")) # ๋ณธ๋ฌธ์ ์ฝ์ ๊ฒ์ ๊ฒฐ๊ณผ ์
|
| 51 |
+
PAGE_CHARS = int(os.getenv("PAGE_CHARS", "5000")) # ํ์ด์ง๋น ์ต๋ ๊ธ์ ์
|
| 52 |
+
LLM_QUERY = os.getenv("LLM_QUERY", "0") == "1" # 1์ด๋ฉด ๊ฒ์์ด๋ Gemini๋ก ์์ฑ(ํธ์ถ 2๋ฐฐ)
|
| 53 |
+
GEMINI_MODELS = os.getenv("GEMINI_MODELS", "") # ์ ํ: ์ผํ๋ก ๊ตฌ๋ถํ ๋ชจ๋ธ ์ด๋ฆ ๋ชฉ๋ก
|
| 54 |
+
|
| 55 |
+
PROMPT = """You are answering a GAIA-style benchmark question. The answer is graded by EXACT MATCH.
|
| 56 |
|
|
|
|
| 57 |
Rules:
|
| 58 |
+
- Use the provided web context and attached file. If the context is insufficient, use your own knowledge and reason carefully.
|
| 59 |
+
- Give ONLY the answer value: no explanation, no trailing period, no units unless asked.
|
| 60 |
+
- Numbers: digits only, no thousands separators, no currency or percent symbols unless asked.
|
| 61 |
+
- Lists: comma-separated, in the order the question asks.
|
| 62 |
+
- Strings: no leading articles (a, an, the), no abbreviations unless asked.
|
| 63 |
+
- End your reply with one final line exactly in the form:
|
| 64 |
+
FINAL ANSWER: <answer>
|
| 65 |
+
|
| 66 |
+
Question:
|
| 67 |
+
{question}
|
| 68 |
+
|
| 69 |
+
{extra}"""
|
| 70 |
|
| 71 |
# ---------------------------------------------------------------------------
|
| 72 |
+
# ๋ก๊ทธ
|
| 73 |
# ---------------------------------------------------------------------------
|
| 74 |
+
_events = deque(maxlen=12)
|
|
|
|
|
|
|
| 75 |
|
| 76 |
|
| 77 |
def log(msg: str):
|
| 78 |
line = f"[{time.strftime('%H:%M:%S')}] {msg}"
|
| 79 |
print(line, flush=True)
|
| 80 |
+
_events.append(line)
|
|
|
|
| 81 |
|
| 82 |
|
| 83 |
def recent_log() -> str:
|
| 84 |
+
return "\n".join(_events)
|
|
|
|
| 85 |
|
| 86 |
|
| 87 |
# ---------------------------------------------------------------------------
|
| 88 |
# ์บ์
|
| 89 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
| 90 |
def load_cache() -> dict:
|
| 91 |
try:
|
| 92 |
with open(CACHE_PATH, "r", encoding="utf-8") as f:
|
|
|
|
| 95 |
return {}
|
| 96 |
|
| 97 |
|
| 98 |
+
def save_cache(task_id: str, answer: str):
|
| 99 |
+
cache = load_cache()
|
| 100 |
+
cache[task_id] = answer
|
| 101 |
+
with open(CACHE_PATH, "w", encoding="utf-8") as f:
|
| 102 |
+
json.dump(cache, f, ensure_ascii=False)
|
|
|
|
| 103 |
|
| 104 |
|
| 105 |
# ---------------------------------------------------------------------------
|
| 106 |
+
# Gemini REST ํธ์ถ (๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด requests๋ง ์ฌ์ฉ)
|
| 107 |
# ---------------------------------------------------------------------------
|
| 108 |
+
class LLMUnavailable(Exception):
|
| 109 |
+
pass
|
|
|
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
+
def api_key() -> str:
|
| 113 |
+
return os.getenv("GEMINI_API_KEY", "")
|
| 114 |
|
| 115 |
+
|
| 116 |
+
def list_models() -> list[str]:
|
| 117 |
+
r = requests.get(
|
| 118 |
+
f"{GEMINI_API}/models",
|
| 119 |
+
headers={"x-goog-api-key": api_key()},
|
| 120 |
+
params={"pageSize": 200},
|
| 121 |
+
timeout=30,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
)
|
| 123 |
+
r.raise_for_status()
|
| 124 |
+
names = []
|
| 125 |
+
for m in r.json().get("models", []):
|
| 126 |
+
if "generateContent" in m.get("supportedGenerationMethods", []):
|
| 127 |
+
names.append(m["name"].replace("models/", ""))
|
| 128 |
+
return names
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
_BAD = ("image", "tts", "audio", "live", "embed", "vision", "robotics", "computer", "imagen", "veo")
|
| 132 |
+
_candidates: list[str] = []
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def candidate_models() -> list[str]:
|
| 136 |
+
"""GEMINI_MODELS๊ฐ ์์ผ๋ฉด ๊ทธ ๋ชฉ๋ก, ์์ผ๋ฉด ํค๋ก ์ธ ์ ์๋ ๋ชจ๋ธ์์ ์๋ ์ ํ."""
|
| 137 |
+
global _candidates
|
| 138 |
+
if _candidates:
|
| 139 |
+
return _candidates
|
| 140 |
+
if GEMINI_MODELS.strip():
|
| 141 |
+
_candidates = [m.strip().replace("models/", "") for m in GEMINI_MODELS.split(",") if m.strip()]
|
| 142 |
+
return _candidates
|
| 143 |
+
names = [n for n in list_models() if n.startswith("gemini") and not any(b in n for b in _BAD)]
|
| 144 |
+
lite = [n for n in names if "flash-lite" in n]
|
| 145 |
+
flash = [n for n in names if "flash" in n and "flash-lite" not in n]
|
| 146 |
+
_candidates = (lite[:2] + flash[:2]) or names[:3]
|
| 147 |
+
log(f"์๋ ์ ํ๋ ๋ชจ๋ธ ํ๋ณด: {_candidates}")
|
| 148 |
+
return _candidates
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
_last_call = 0.0
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def throttle():
|
| 155 |
+
global _last_call
|
| 156 |
+
wait = MIN_INTERVAL - (time.time() - _last_call)
|
| 157 |
+
if wait > 0:
|
| 158 |
+
time.sleep(wait)
|
| 159 |
+
_last_call = time.time()
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def call_gemini(parts: list[dict]) -> tuple[str, str]:
|
| 163 |
+
"""(์๋ต ํ
์คํธ, ์ฌ์ฉํ ๋ชจ๋ธ)์ ๋ฐํ. ๋ชจ๋ ํ๋ณด๊ฐ ์คํจํ๋ฉด LLMUnavailable."""
|
| 164 |
+
if not api_key():
|
| 165 |
+
raise LLMUnavailable("GEMINI_API_KEY๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.")
|
| 166 |
+
last_err = ""
|
| 167 |
+
for model in candidate_models():
|
| 168 |
+
for attempt in range(MAX_RETRY + 1):
|
| 169 |
+
throttle()
|
| 170 |
+
try:
|
| 171 |
+
r = requests.post(
|
| 172 |
+
f"{GEMINI_API}/models/{model}:generateContent",
|
| 173 |
+
headers={"x-goog-api-key": api_key(), "Content-Type": "application/json"},
|
| 174 |
+
json={
|
| 175 |
+
"contents": [{"role": "user", "parts": parts}],
|
| 176 |
+
"generationConfig": {"temperature": 0, "maxOutputTokens": 2048},
|
| 177 |
+
},
|
| 178 |
+
timeout=120,
|
| 179 |
+
)
|
| 180 |
+
except requests.RequestException as e:
|
| 181 |
+
last_err = f"{model}: ๋คํธ์ํฌ ์ค๋ฅ {e}"
|
| 182 |
+
time.sleep(5)
|
| 183 |
+
continue
|
| 184 |
|
| 185 |
+
if r.status_code == 200:
|
| 186 |
+
cand = (r.json().get("candidates") or [{}])[0]
|
| 187 |
+
text = "".join(
|
| 188 |
+
p.get("text", "")
|
| 189 |
+
for p in cand.get("content", {}).get("parts", [])
|
| 190 |
+
if not p.get("thought")
|
| 191 |
+
)
|
| 192 |
+
if text.strip():
|
| 193 |
+
return text, model
|
| 194 |
+
last_err = f"{model}: ๋น ์๋ต({cand.get('finishReason')})"
|
| 195 |
+
break
|
| 196 |
+
|
| 197 |
+
body = r.text
|
| 198 |
+
last_err = f"{model}: HTTP {r.status_code} {body[:180].replace(chr(10), ' ')}"
|
| 199 |
+
if r.status_code == 429:
|
| 200 |
+
# ์ผ์ผ ํ๋ ์์ง ๋๋ ํ๋ 0์ด๋ฉด ์ฌ์๋ํด๋ ์์ฉ์์ผ๋ฏ๋ก ๋ค์ ๋ชจ๋ธ๋ก
|
| 201 |
+
if "PerDay" in body or "limit: 0" in body:
|
| 202 |
+
log(f"{model}: ์ผ์ผ ํ๋ ์์ง ๋๋ ํ๋ 0 -> ๋ค์ ๋ชจ๋ธ")
|
| 203 |
+
break
|
| 204 |
+
if attempt == MAX_RETRY:
|
| 205 |
+
break
|
| 206 |
+
m = re.search(r'"retryDelay":\s*"(\d+(?:\.\d+)?)s"', body)
|
| 207 |
+
wait = min(float(m.group(1)) + 1, 60) if m else 15 * (attempt + 1)
|
| 208 |
+
log(f"{model}: 429, {int(wait)}์ด ํ ์ฌ์๋ ({attempt + 1}/{MAX_RETRY})")
|
| 209 |
+
time.sleep(wait)
|
| 210 |
+
continue
|
| 211 |
+
if r.status_code >= 500:
|
| 212 |
+
time.sleep(5)
|
| 213 |
+
continue
|
| 214 |
+
break # 400, 403, 404 ๋ฑ์ ์ด ๋ชจ๋ธ ํฌ๊ธฐ
|
| 215 |
+
raise LLMUnavailable(last_err or "์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ธ์ด ์์ต๋๋ค.")
|
| 216 |
|
| 217 |
+
|
| 218 |
+
# ---------------------------------------------------------------------------
|
| 219 |
+
# Hugging Face Inference Providers (OpenAI ํธํ ๋ผ์ฐํฐ)
|
| 220 |
+
# ---------------------------------------------------------------------------
|
| 221 |
+
def hf_token() -> str:
|
| 222 |
+
return os.getenv("HF_TOKEN", "")
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def call_hf(parts: list[dict]) -> tuple[str, str]:
|
| 226 |
+
"""(์๋ต ํ
์คํธ, ์ฌ์ฉํ ๋ชจ๋ธ)์ ๋ฐํ. ํ
์คํธ ์ ์ฉ์ด๋ฏ๋ก ์ด๋ฏธ์ง/์ค๋์ค ํํธ๋ ์ ๋ฌํ์ง ๋ชปํฉ๋๋ค."""
|
| 227 |
+
if not hf_token():
|
| 228 |
+
raise LLMUnavailable("HF_TOKEN์ด ์ค์ ๋์ง ์์์ต๋๋ค.")
|
| 229 |
+
text = "\n".join(p["text"] for p in parts if "text" in p)
|
| 230 |
+
if any("inline_data" in p for p in parts):
|
| 231 |
+
text = ("NOTE: An image/audio attachment exists but you cannot view it; "
|
| 232 |
+
"answer from the other information.\n\n" + text)
|
| 233 |
+
last_err = ""
|
| 234 |
+
for model in HF_MODELS:
|
| 235 |
+
for attempt in range(MAX_RETRY + 1):
|
| 236 |
+
throttle()
|
| 237 |
+
try:
|
| 238 |
+
r = requests.post(
|
| 239 |
+
f"{HF_ROUTER}/chat/completions",
|
| 240 |
+
headers={"Authorization": f"Bearer {hf_token()}", "Content-Type": "application/json"},
|
| 241 |
+
json={"model": model, "messages": [{"role": "user", "content": text}],
|
| 242 |
+
"max_tokens": 1024, "temperature": 0},
|
| 243 |
+
timeout=120,
|
| 244 |
+
)
|
| 245 |
+
except requests.RequestException as e:
|
| 246 |
+
last_err = f"{model}: ๋คํธ์ํฌ ์ค๋ฅ {e}"
|
| 247 |
+
time.sleep(5)
|
| 248 |
+
continue
|
| 249 |
+
if r.status_code == 200:
|
| 250 |
+
content = (r.json().get("choices") or [{}])[0].get("message", {}).get("content") or ""
|
| 251 |
+
content = re.sub(r"(?s)<think>.*?</think>", "", content).strip()
|
| 252 |
+
if content:
|
| 253 |
+
return content, model
|
| 254 |
+
last_err = f"{model}: ๋น ์๋ต"
|
| 255 |
+
break
|
| 256 |
+
body = r.text
|
| 257 |
+
last_err = f"{model}: HTTP {r.status_code} {body[:180].replace(chr(10), ' ')}"
|
| 258 |
+
if r.status_code == 402:
|
| 259 |
+
raise LLMUnavailable("HF ๋ฌด๋ฃ ํฌ๋ ๋ง ์์ง(402). " + body[:150])
|
| 260 |
+
if r.status_code in (401, 403):
|
| 261 |
+
raise LLMUnavailable(f"HF_TOKEN ์ธ์ฆ/๊ถํ ์ค๋ฅ({r.status_code}). ํ ํฐ์ Inference Providers ๊ถํ์ ํ์ธํ์ธ์.")
|
| 262 |
+
if r.status_code == 429:
|
| 263 |
+
if attempt == MAX_RETRY:
|
| 264 |
+
break
|
| 265 |
+
wait = 15 * (attempt + 1)
|
| 266 |
+
log(f"{model}: 429, {wait}์ด ํ ์ฌ์๋ ({attempt + 1}/{MAX_RETRY})")
|
| 267 |
+
time.sleep(wait)
|
| 268 |
+
continue
|
| 269 |
+
if r.status_code >= 500:
|
| 270 |
+
time.sleep(5)
|
| 271 |
+
continue
|
| 272 |
+
break # 400, 404 ๋ฑ์ ์ด ๋ชจ๋ธ ํฌ๊ธฐ
|
| 273 |
+
raise LLMUnavailable(last_err or "์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ธ์ด ์์ต๋๋ค.")
|
| 274 |
|
| 275 |
|
| 276 |
+
def call_llm(parts: list[dict]) -> tuple[str, str]:
|
| 277 |
+
return call_hf(parts) if PROVIDER == "hf" else call_gemini(parts)
|
|
|
|
|
|
|
| 278 |
|
| 279 |
|
| 280 |
# ---------------------------------------------------------------------------
|
| 281 |
+
# ์น ๊ฒ์ + ํ์ด์ง ์ฝ๊ธฐ (Gemini ํธ์ถ ์์)
|
| 282 |
# ---------------------------------------------------------------------------
|
| 283 |
+
def html_to_text(html: str) -> str:
|
| 284 |
+
html = re.sub(r"(?is)<(script|style|noscript|svg|header|footer|nav)[^>]*>.*?</\1>", " ", html)
|
| 285 |
+
text = re.sub(r"(?s)<[^>]+>", " ", html)
|
| 286 |
+
text = re.sub(r" | ", " ", text)
|
| 287 |
+
return re.sub(r"\s+", " ", text).strip()
|
|
|
|
|
|
|
| 288 |
|
| 289 |
|
| 290 |
+
def web_context(query: str) -> str:
|
| 291 |
+
if DDGS is None:
|
| 292 |
+
return ""
|
| 293 |
+
try:
|
| 294 |
+
results = list(DDGS().text(query, max_results=6))
|
| 295 |
+
except Exception as e: # noqa: BLE001
|
| 296 |
+
log(f"๊ฒ์ ์คํจ: {type(e).__name__}")
|
| 297 |
+
return ""
|
| 298 |
+
chunks = []
|
| 299 |
+
for i, res in enumerate(results, start=1):
|
| 300 |
+
chunks.append(f"[{i}] {res.get('title', '')}\n{res.get('href', '')}\n{res.get('body', '')}")
|
| 301 |
+
for res in results[:FETCH_PAGES]:
|
| 302 |
+
url = res.get("href")
|
| 303 |
+
if not url or url.lower().endswith((".pdf", ".zip", ".mp4")):
|
| 304 |
+
continue
|
| 305 |
+
try:
|
| 306 |
+
page = requests.get(url, timeout=15, headers={"User-Agent": "Mozilla/5.0"})
|
| 307 |
+
if page.ok and "text/html" in page.headers.get("content-type", ""):
|
| 308 |
+
chunks.append(f"--- PAGE: {url} ---\n{html_to_text(page.text)[:PAGE_CHARS]}")
|
| 309 |
+
except requests.RequestException:
|
| 310 |
+
continue
|
| 311 |
+
return "\n\n".join(chunks)
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
# ---------------------------------------------------------------------------
|
| 315 |
+
# ์ฒจ๋ถ ํ์ผ
|
| 316 |
+
# ---------------------------------------------------------------------------
|
| 317 |
+
def download_file(task_id: str, file_name: str):
|
| 318 |
if not file_name:
|
| 319 |
return None
|
| 320 |
try:
|
| 321 |
+
r = requests.get(f"{SCORING_API}/files/{task_id}", timeout=60)
|
| 322 |
+
r.raise_for_status()
|
| 323 |
path = os.path.join(FILES_DIR, f"{task_id}_{file_name}")
|
| 324 |
with open(path, "wb") as f:
|
| 325 |
+
f.write(r.content)
|
| 326 |
return path
|
| 327 |
except Exception as e: # noqa: BLE001
|
| 328 |
+
log(f"ํ์ผ ๋ค์ด๋ก๋ ์คํจ: {e}")
|
| 329 |
return None
|
| 330 |
|
| 331 |
|
| 332 |
+
def file_to_parts(path: str) -> tuple[str, list[dict]]:
|
| 333 |
+
"""(ํ๋กฌํํธ์ ๋ถ์ผ ํ
์คํธ, Gemini์ ์ง์ ๋ฃ์ inline_data ํํธ๋ค)"""
|
| 334 |
+
lower = path.lower()
|
| 335 |
+
try:
|
| 336 |
+
if lower.endswith((".png", ".jpg", ".jpeg", ".webp", ".mp3", ".wav", ".m4a")):
|
| 337 |
+
if os.path.getsize(path) > 15 * 1024 * 1024:
|
| 338 |
+
return "(์ฒจ๋ถ ํ์ผ์ด ๋๋ฌด ์ปค์ ์๋ต๋จ)", []
|
| 339 |
+
mime = mimetypes.guess_type(path)[0] or "application/octet-stream"
|
| 340 |
+
if lower.endswith(".mp3"):
|
| 341 |
+
mime = "audio/mp3"
|
| 342 |
+
with open(path, "rb") as f:
|
| 343 |
+
data = base64.b64encode(f.read()).decode()
|
| 344 |
+
return "(์ฒจ๋ถ ํ์ผ์ ํจ๊ป ์ ๋ฌ๋จ)", [{"inline_data": {"mime_type": mime, "data": data}}]
|
| 345 |
+
if lower.endswith((".xlsx", ".xls")):
|
| 346 |
+
sheets = pd.read_excel(path, sheet_name=None)
|
| 347 |
+
return "\n\n".join(f"## Sheet {n}\n{df.to_string(max_rows=300)}" for n, df in sheets.items())[:30000], []
|
| 348 |
+
if lower.endswith(".csv"):
|
| 349 |
+
return pd.read_csv(path).to_string(max_rows=300)[:30000], []
|
| 350 |
+
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
| 351 |
+
return f.read()[:30000], []
|
| 352 |
+
except Exception as e: # noqa: BLE001
|
| 353 |
+
return f"(ํ์ผ ์ฝ๊ธฐ ์คํจ: {e})", []
|
| 354 |
+
|
| 355 |
+
|
| 356 |
# ---------------------------------------------------------------------------
|
| 357 |
+
# ํ ๋ฌธํญ ํ์ด
|
| 358 |
# ---------------------------------------------------------------------------
|
| 359 |
+
def parse_final(text: str) -> str:
|
| 360 |
+
m = re.findall(r"FINAL ANSWER:\s*(.+)", text, flags=re.IGNORECASE)
|
| 361 |
+
ans = m[-1] if m else (text.strip().splitlines() or [""])[-1]
|
| 362 |
+
return ans.strip().strip("`").strip().rstrip(".").strip()
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def make_search_query(question: str) -> str:
|
| 366 |
+
if not LLM_QUERY:
|
| 367 |
+
return question[:300]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
try:
|
| 369 |
+
text, _ = call_llm([{"text": "Write ONE concise web search query (max 12 words) that would help answer "
|
| 370 |
+
"this question. Reply with the query only.\n\n" + question}])
|
| 371 |
+
return text.strip().splitlines()[0][:200]
|
| 372 |
+
except LLMUnavailable:
|
| 373 |
+
return question[:300]
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def solve(item: dict) -> tuple[str, str]:
|
| 377 |
+
"""(๋ต๋ณ, ์ฌ์ฉํ ๋ชจ๋ธ)"""
|
| 378 |
+
question = item["question"]
|
| 379 |
+
extra_parts: list[dict] = []
|
| 380 |
+
extra_text = ""
|
| 381 |
+
|
| 382 |
+
path = download_file(item["task_id"], item.get("file_name", ""))
|
| 383 |
+
if path:
|
| 384 |
+
file_text, extra_parts = file_to_parts(path)
|
| 385 |
+
extra_text += f"ATTACHED FILE ({os.path.basename(path)}):\n{file_text}\n\n"
|
| 386 |
+
|
| 387 |
+
ctx = web_context(make_search_query(question))
|
| 388 |
+
if ctx:
|
| 389 |
+
extra_text += f"WEB CONTEXT (may be incomplete or irrelevant):\n{ctx}\n"
|
| 390 |
+
|
| 391 |
+
prompt = PROMPT.format(question=question, extra=extra_text[:60000])
|
| 392 |
+
text, model = call_llm([{"text": prompt}] + extra_parts)
|
| 393 |
+
return parse_final(text), model
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
# ---------------------------------------------------------------------------
|
| 397 |
+
# ์ฑ์ ์๋ฒ
|
| 398 |
+
# ---------------------------------------------------------------------------
|
| 399 |
+
def fetch_questions() -> list[dict]:
|
| 400 |
+
r = requests.get(f"{SCORING_API}/questions", timeout=30)
|
| 401 |
+
r.raise_for_status()
|
| 402 |
+
data = r.json()
|
| 403 |
+
if not data:
|
| 404 |
+
raise ValueError("์ง๋ฌธ ๋ชฉ๋ก์ด ๋น์ด ์์ต๋๋ค.")
|
| 405 |
+
return data
|
| 406 |
|
| 407 |
|
| 408 |
def make_df(rows: dict, order: list[str]) -> pd.DataFrame:
|
| 409 |
+
return pd.DataFrame(
|
| 410 |
+
[rows[t] for t in order if t in rows],
|
| 411 |
+
columns=["Task ID", "Question", "Submitted Answer", "OK", "Note"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
)
|
| 413 |
|
| 414 |
|
| 415 |
+
def run_batch(questions: list[dict], label: str):
|
| 416 |
+
"""๋ฌธํญ์ ์์๋๋ก ํ๋ฉฐ ์งํ ์ํฉ์ ๋ด๋ณด๋
๋๋ค. ๋ง์ง๋ง์ (rows, order, aborted)๋ฅผ ๋ฐํ."""
|
| 417 |
order = [q["task_id"] for q in questions if q.get("task_id")]
|
| 418 |
rows: dict = {}
|
| 419 |
+
cache = load_cache()
|
| 420 |
start = time.time()
|
| 421 |
+
fails = 0
|
| 422 |
+
aborted = False
|
| 423 |
+
log(f"{label} ์์: {len(order)}๋ฌธํญ (๋ฌธํญ๋น LLM ํธ์ถ {2 if LLM_QUERY else 1}ํ)")
|
| 424 |
+
|
| 425 |
+
for i, item in enumerate(questions, start=1):
|
| 426 |
+
tid = item.get("task_id")
|
| 427 |
+
if not tid or item.get("question") is None:
|
| 428 |
+
continue
|
| 429 |
+
head = f"{label}: {i - 1}/{len(order)} ์๋ฃ, {i}๋ฒ์งธ ๋ฌธํญ ์ฒ๋ฆฌ ์ค (๊ฒฝ๊ณผ {int(time.time() - start)}์ด)"
|
| 430 |
+
yield f"{head}\n--- ์ต๊ทผ ๊ธฐ๋ก ---\n{recent_log()}", make_df(rows, order)
|
| 431 |
+
|
| 432 |
+
if tid in cache:
|
| 433 |
+
answer, ok, note = cache[tid], True, "cache"
|
| 434 |
+
log(f"์บ์ ์ฌ์ฉ {tid[:8]}")
|
| 435 |
+
else:
|
| 436 |
+
try:
|
| 437 |
+
answer, model = solve(item)
|
| 438 |
+
save_cache(tid, answer)
|
| 439 |
+
ok, note = True, model
|
| 440 |
+
fails = 0
|
| 441 |
+
log(f"์๋ฃ {tid[:8]} -> {answer}")
|
| 442 |
+
except LLMUnavailable as e:
|
| 443 |
+
answer, ok, note = "unknown", False, str(e)[:250]
|
| 444 |
+
fails += 1
|
| 445 |
+
log(f"LLM ์คํจ {tid[:8]}: {note}")
|
| 446 |
+
except Exception as e: # noqa: BLE001
|
| 447 |
+
answer, ok, note = "unknown", False, f"{type(e).__name__}: {e}"[:250]
|
| 448 |
+
log(f"์ค๋ฅ {tid[:8]}: {note}")
|
| 449 |
+
|
| 450 |
+
rows[tid] = {"Task ID": tid, "Question": item["question"],
|
| 451 |
+
"Submitted Answer": answer, "OK": "O" if ok else "X", "Note": note}
|
| 452 |
+
if fails >= 2:
|
| 453 |
+
aborted = True
|
| 454 |
+
log("LLM์ด ์ฐ์ 2ํ ์คํจํ์ฌ ์ค๋จํฉ๋๋ค. (ํ๋/ํฌ๋ ๋ง ๋ฌธ์ ๊ฐ๋ฅ์ฑ)")
|
| 455 |
+
break
|
| 456 |
+
|
| 457 |
+
yield f"{label} ์ข
๋ฃ (๊ฒฝ๊ณผ {int(time.time() - start)}์ด)\n--- ์ต๊ทผ ๊ธฐ๋ก ---\n{recent_log()}", make_df(rows, order)
|
| 458 |
+
return rows, order, aborted
|
| 459 |
|
| 460 |
|
| 461 |
# ---------------------------------------------------------------------------
|
|
|
|
| 469 |
if not space_id:
|
| 470 |
yield "SPACE_ID๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. Space ์์์ ์คํํด์ผ ํฉ๋๋ค.", None
|
| 471 |
return
|
| 472 |
+
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค...", None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
try:
|
| 474 |
+
questions = fetch_questions()
|
| 475 |
except Exception as e: # noqa: BLE001
|
| 476 |
yield f"์ง๋ฌธ ๊ฐ์ ธ์ค๊ธฐ ์ค๋ฅ: {e}", None
|
| 477 |
return
|
| 478 |
|
| 479 |
+
rows, order, aborted = yield from run_batch(questions, "๋ณธ ์คํ")
|
| 480 |
+
if aborted:
|
| 481 |
+
yield ("LLM ํ๋/ํฌ๋ ๋ง ๋ฌธ์ ๋ก ์ค๋จํ์ผ๋ฏ๋ก ์ ์ถํ์ง ์์์ต๋๋ค.\n"
|
| 482 |
+
"ํผ ๋ต์ ์ ์ฅ๋์ด ์์ด, ํ๋๊ฐ ํ๋ณต๋ ๋ค ๋ค์ ๋๋ฅด๋ฉด ์ด์ด์ ์งํํฉ๋๋ค. "
|
| 483 |
+
"(Space๊ฐ ์ฌ์์๋๋ฉด ์ ์ฅ๋ ๋ต์ ์ฌ๋ผ์ง๋๋ค.)\n\n" + recent_log()), make_df(rows, order)
|
| 484 |
+
return
|
| 485 |
|
| 486 |
+
payload = [{"task_id": t, "submitted_answer": rows[t]["Submitted Answer"]} for t in order if t in rows]
|
|
|
|
|
|
|
|
|
|
| 487 |
failed = sum(1 for r in rows.values() if r["OK"] == "X")
|
| 488 |
+
yield f"ํ์ด ์๋ฃ (์คํจ {failed}๊ฐ). ์ ์ถ ์ค...", make_df(rows, order)
|
|
|
|
| 489 |
submission = {
|
| 490 |
+
"username": profile.username.strip(),
|
| 491 |
+
"agent_code": f"https://huggingface.co/spaces/{space_id}/tree/main",
|
| 492 |
+
"answers": payload,
|
| 493 |
}
|
| 494 |
try:
|
| 495 |
+
r = requests.post(f"{SCORING_API}/submit", json=submission, timeout=120)
|
| 496 |
+
r.raise_for_status()
|
| 497 |
+
res = r.json()
|
| 498 |
+
status = (f"์ ์ถ ์ฑ๊ณต!\n์ฌ์ฉ์: {res.get('username')}\n"
|
| 499 |
+
f"์ ์: {res.get('score', 'N/A')}% ({res.get('correct_count', '?')}/{res.get('total_attempted', '?')} ์ ๋ต)\n"
|
| 500 |
+
f"๋ฉ์์ง: {res.get('message', '')}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
except requests.exceptions.HTTPError as e:
|
| 502 |
+
status = f"์ ์ถ ์คํจ: {e.response.text[:500] if e.response is not None else e}"
|
|
|
|
| 503 |
except Exception as e: # noqa: BLE001
|
| 504 |
status = f"์ ์ถ ์ค ์ค๋ฅ: {e}"
|
| 505 |
yield status, make_df(rows, order)
|
| 506 |
|
| 507 |
|
| 508 |
def run_test_only(n_questions: int):
|
| 509 |
+
yield "์ง๋ฌธ์ ๊ฐ์ ธ์ค๋ ์ค...", None
|
|
|
|
| 510 |
try:
|
| 511 |
+
questions = fetch_questions()[: int(n_questions)]
|
| 512 |
except Exception as e: # noqa: BLE001
|
| 513 |
yield f"์ค๋น ์ค ์ค๋ฅ: {e}", None
|
| 514 |
return
|
| 515 |
+
yield from run_batch(questions, "ํ
์คํธ")
|
|
|
|
|
|
|
| 516 |
|
|
|
|
|
|
|
| 517 |
|
| 518 |
+
def diagnose():
|
| 519 |
+
lines = [f"PROVIDER={PROVIDER}, MIN_INTERVAL={MIN_INTERVAL}์ด, LLM_QUERY={LLM_QUERY}"]
|
| 520 |
+
yield "\n".join(lines + ["", "๋ชจ๋ธ ํ์ธ ์ค..."])
|
| 521 |
+
if PROVIDER == "hf":
|
| 522 |
+
lines.append(f"HF_TOKEN: {'์์' if hf_token() else '์์'}, ์ฌ์ฉํ ๋ชจ๋ธ: {HF_MODELS}")
|
| 523 |
try:
|
| 524 |
+
r = requests.get(f"{HF_ROUTER}/models", headers={"Authorization": f"Bearer {hf_token()}"}, timeout=30)
|
| 525 |
+
r.raise_for_status()
|
| 526 |
+
ids = [m.get("id") for m in r.json().get("data", [])]
|
| 527 |
+
check = ", ".join(f"{m}={'๋ชฉ๋ก์ ์์' if m in ids else '๋ชฉ๋ก์ ์์'}" for m in HF_MODELS)
|
| 528 |
+
lines.append(f"1) ๋ผ์ฐํฐ ์ ๊ณต ๋ชจ๋ธ {len(ids)}๊ฐ / {check}")
|
| 529 |
except Exception as e: # noqa: BLE001
|
| 530 |
+
lines.append(f"1) ๋ชจ๋ธ ๋ชฉ๋ก ์กฐํ ์คํจ(์งํ์ ๊ณ์): {type(e).__name__}: {str(e)[:150]}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
else:
|
| 532 |
+
lines.append(f"GEMINI_API_KEY: {'์์' if api_key() else '์์'}")
|
| 533 |
+
try:
|
| 534 |
+
names = [n for n in list_models() if n.startswith("gemini")][:25]
|
| 535 |
+
lines.append("1) ์ด ํค๋ก ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ธ(์ผ๋ถ): " + ", ".join(names))
|
| 536 |
+
lines.append(f" ์ฌ์ฉํ ํ๋ณด ์์: {candidate_models()}")
|
| 537 |
+
except Exception as e: # noqa: BLE001
|
| 538 |
+
lines.append(f"1) ๋ชจ๋ธ ๋ชฉ๋ก ์กฐํ ์คํจ: {type(e).__name__}: {str(e)[:200]}")
|
| 539 |
+
yield "\n".join(lines)
|
| 540 |
+
return
|
| 541 |
+
yield "\n".join(lines + ["", "LLM ํธ์ถ ํ
์คํธ ์ค..."])
|
| 542 |
+
try:
|
| 543 |
+
text, model = call_llm([{"text": "Reply with the single word OK."}])
|
| 544 |
+
lines.append(f"2) LLM ํธ์ถ: ์ ์ (๋ชจ๋ธ {model}, ์๋ต '{text.strip()[:30]}')")
|
| 545 |
+
except LLMUnavailable as e:
|
| 546 |
+
lines.append(f"2) LLM ํธ์ถ: ์คํจ - {str(e)[:300]}")
|
| 547 |
+
yield "\n".join(lines + ["", "์น ๊ฒ์ ํ
์คํธ ์ค..."])
|
| 548 |
+
try:
|
| 549 |
+
ctx = web_context("Hugging Face")
|
| 550 |
+
lines.append(f"3) ์น ๊ฒ์: {'์ ์' if ctx else '๊ฒฐ๊ณผ ์์'} ({len(ctx)}์)")
|
| 551 |
+
except Exception as e: # noqa: BLE001
|
| 552 |
+
lines.append(f"3) ์น ๊ฒ์: ์คํจ - {e}")
|
| 553 |
yield "\n".join(lines)
|
| 554 |
|
| 555 |
|
| 556 |
def clear_cache():
|
| 557 |
+
global _candidates
|
| 558 |
+
_candidates = []
|
| 559 |
try:
|
| 560 |
os.remove(CACHE_PATH)
|
| 561 |
+
return "์บ์์ ๋ชจ๋ธ ์ ํ ์ ๋ณด๋ฅผ ์ด๊ธฐํํ์ต๋๋ค."
|
| 562 |
except FileNotFoundError:
|
| 563 |
+
return "์ญ์ ํ ์บ์๊ฐ ์์ต๋๋ค. (๋ชจ๋ธ ์ ํ ์ ๋ณด๋ ์ด๊ธฐํ๋จ)"
|
| 564 |
|
| 565 |
|
| 566 |
# ---------------------------------------------------------------------------
|
| 567 |
# UI
|
| 568 |
# ---------------------------------------------------------------------------
|
| 569 |
with gr.Blocks() as demo:
|
| 570 |
+
gr.Markdown("# Basic Agent Evaluation Runner (Gemini / Hugging Face ์ ํํ ๊ฒฝ๋ ๋ฒ์ )")
|
| 571 |
gr.Markdown(
|
| 572 |
+
"""**์ฌ์ฉ ์์**
|
| 573 |
+
1. '์ฐ๊ฒฐ ์ง๋จ'์ผ๋ก ๋ชจ๋ธ๊ณผ ํธ์ถ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ํ์ธํฉ๋๋ค.
|
| 574 |
+
2. ๋ก๊ทธ์ธ ํ 'ํ
์คํธ ์คํ'(1~2๋ฌธํญ)์ผ๋ก ํ์ธํฉ๋๋ค. (์ ์ถ ์ ํจ)
|
| 575 |
3. ๋ฌธ์ ์์ผ๋ฉด 'Run Evaluation & Submit All Answers'๋ฅผ ๋๋ฆ
๋๋ค."""
|
| 576 |
)
|
| 577 |
gr.LoginButton()
|
|
|
|
| 578 |
with gr.Row():
|
| 579 |
diag_button = gr.Button("์ฐ๊ฒฐ ์ง๋จ")
|
| 580 |
test_n = gr.Number(value=2, label="ํ
์คํธํ ๋ฌธํญ ์ (์ ์ถ ์ ํจ)", precision=0)
|
| 581 |
test_button = gr.Button("ํ
์คํธ ์คํ (์ ์ถ ์ ํจ)")
|
| 582 |
cache_button = gr.Button("์บ์ ์ด๊ธฐํ")
|
|
|
|
| 583 |
run_button = gr.Button("Run Evaluation & Submit All Answers")
|
| 584 |
status_output = gr.Textbox(label="Run Status / Submission Result", lines=14, interactive=False)
|
| 585 |
results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
|