Spaces:
Sleeping
Sleeping
File size: 11,322 Bytes
d7686af 2c74d58 d7686af 2c74d58 d7686af 2c74d58 d7686af 2c74d58 d7686af 2c74d58 cb9016e d7686af cb9016e d51e344 cb9016e d51e344 cb9016e d7686af 2c74d58 a5a00fb 2c74d58 7c1b981 2c74d58 cb9016e 2c74d58 d7686af 80d473a d7686af 80d473a d7686af 80d473a d7686af 2c74d58 d7686af 2c74d58 d7686af 2c74d58 d7686af 2c74d58 dd76518 d7686af 2c74d58 d7686af 2c74d58 dd76518 d7686af 2c74d58 d7686af 2c74d58 80d473a d7686af 2c74d58 80d473a d7686af 2c74d58 d7686af 80d473a d7686af 80d473a 2c74d58 d7686af 2c74d58 3c35862 2c74d58 d7686af 2c74d58 cb62a14 2c74d58 cb62a14 2c74d58 cb62a14 dd76518 2c74d58 d7686af 2c74d58 d7686af 2c74d58 d7686af 2c74d58 d7686af 2c74d58 d7686af cb9016e d7686af 2c74d58 d7686af 2c74d58 80d473a 2c74d58 d7686af 3c35862 2c74d58 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | """chartbench ν΄λ¨Ό μ΄λ°Έ: Qwen vs Opus blind A/B (4μΆ).
- μ μ: 곡μ λ§ν¬μ ?key= κ° ACCESS_KEY μ μΌμΉν΄μΌ νκ° κ°λ₯ (Spaceλ publicμ΄μ§λ§ ν€ μμΌλ©΄ μ§μ
λΆκ°)
- ν λΉ: λ―Έμλ£ μμ΄ν
μ€ μ΅κ·Ό 10λΆ λ΄ νμΈμκ² ν λΉλμ§ μμ κ²μ λλ€ μ§κΈ. 1λͺ
μ μΆ = κ·Έ μμ΄ν
μλ£.
- μ μ₯: μ μΆ 1건 = results repo μ JSON 1νμΌ (rater / ν λΉΒ·μ μΆ μκ° / μμμ΄ ν¬ν¨). μ¬μμ μ repo λ‘ μλ£ λͺ©λ‘ 볡μ.
- κ°μ μ΄λ¦ = κ°μ νκ°μλ‘ μ§κ³ (λΆμ μ μ΄λ¦ κΈ°μ€ dedup).
"""
import json, os, time, uuid, random, threading
from datetime import datetime, timezone, timedelta
from pathlib import Path
import gradio as gr
from huggingface_hub import HfApi
ACCESS_KEY = os.environ.get("ACCESS_KEY", "devkey")
RESULTS_REPO = os.environ.get("RESULTS_REPO", "")
HF_TOKEN = os.environ.get("HF_TOKEN", "")
RESERVE_TTL = 600
KST = timezone(timedelta(hours=9))
ROOT = Path(__file__).parent
ITEMS = [json.loads(l) for l in (ROOT/"items.jsonl").open()]
BY_ID = {it["item_id"]: it for it in ITEMS}
HINT = '<div class="axis-hint">{}</div>'
AXES = [
("query_following",
"**1. 쿼리 μꡬμ¬ν λ°μ** β μ΄λ μ°¨νΈκ° μ§λ¬Έμ΄ μꡬν λ°λ₯Ό λ μ λ°μνλμ?"),
("chart_quality",
"**2. κ°λ
μ±Β·μ°¨νΈ νμ§** β μ΄λ μ°¨νΈκ° λ μ½κΈ° μ’κ³ μ μ ν ννμΈκ°μ?"),
("data_fidelity",
"**3. Data λ°μλ** β μ΄λ μ°¨νΈκ° κΈ°λ° λ°μ΄ν°λ₯Ό λ μ νν λ°μνλμ?"
+ HINT.format("λ μ°¨νΈμ μμΉκ° μ μ¬ν΄ 보μ΄λ©΄ 'λΉμ·ν¨'μ λλ¬μ£ΌμΈμ. ν¬κ² λ€λ₯΄λ©΄ νλ¨ ν κΈμ μλ³Έ νλ₯Ό νμΈν΄ νλ¨νμκ³ , ν μμ²΄κ° μ΄μνκ±°λ λ λ€ μλͺ» λ°μνμΌλ©΄ 'λΉμ·ν¨'μ λλ¬μ£ΌμΈμ.")),
("overall",
"**4. μ λ°μ μ νΈ** β μ’
ν©μ μΌλ‘ μ΄λ μ°¨νΈλ₯Ό μ νΈνμλμ?"),
]
CHOICES = [("μ°¨νΈ A", "A"), ("μ°¨νΈ B", "B"), ("λΉμ·ν¨ / νλ¨ μ΄λ €μ", "Tie")]
GUIDE = """
### μλ΄
1. **μ΄λ¦μ μ
λ ₯νκ³ μμ**μ λλ₯΄λ©΄ μ νΈ νλ¨ λ¬Έμ κ° λ¬΄μμλ‘ μ μλ©λλ€. *(μ΅λͺ
μΌλ‘ μμ±νμ
λ λ©λλ€)*
2. κ° λ¬Έμ λ **μ§λ¬Έ(쿼리) 1κ° + μ°¨νΈ 2κ°(A/B)** λ‘ κ΅¬μ±λλ©°, μ°¨νΈμ κΈ°λ° λ°μ΄ν°(μλ³Έ ν)λ νλ¨μ μ ν μμ΅λλ€.
3. νλ¨ νλͺ©μ **4κ°μ§** β 쿼리 μꡬμ¬ν λ°μ Β· κ°λ
μ± Β· Data λ°μλ Β· μ λ°μ μ νΈ β κ°κ° **A / B / λΉμ·ν¨** μ€ μ νν©λλ€.
4. μ μΆνλ©΄ κ·Έ λ¬Έμ λ μλ£λκ³ λ€μ λ¬Έμ κ° λμ΅λλ€. **μΈλΉ 10κ° μ΄μ ν΄μ£Όμλ©΄ κ°μ¬νκ² μ΅λλ€.** *(λ°μμλ€λ©΄ λ μ κ² ν΄μ£Όμ
λ ν° λμμ΄ λ©λλ€!)* μ€κ°μ κ·Έλ₯ μ°½μ λ«μλ λ©λλ€.
5. κ°λ³κ² λ³΄κ³ **λκ»΄μ§λ λλ‘** μ ννμλ©΄ λ©λλ€. Data λ°μλλ νλ₯Ό μΌμΌμ΄ λμ‘°ν기보λ€, λ μ°¨νΈμ μμΉκ° ν¬κ² λ€λ₯Ό λλ§ νλ₯Ό μ°Έκ³ νλ μ λλ©΄ μΆ©λΆν©λλ€.
"""
CSS = """
.guide-box {font-size: 1.15rem; line-height: 1.75;}
.guide-box li {margin-bottom: 6px;}
.axis-hint {color: var(--body-text-color-subdued); font-size: 0.92em; margin-top: 2px;}
.query-card {font-size: 1.25rem; line-height: 1.55; background: var(--block-background-fill);
border: 1px solid var(--border-color-primary); border-left: 6px solid #4f7cff;
border-radius: 10px; padding: 14px 18px; margin: 4px 0 10px;}
.axis-block .wrap {gap: 4px;}
footer {display: none !important;}
"""
api = HfApi(token=HF_TOKEN) if HF_TOKEN else None
_lock = threading.Lock()
_completed: set[str] = set()
_reserved: dict[str, float] = {}
_rater_counts: dict[str, int] = {}
def _load_completed():
"""κ²°κ³Ό repo μ€λ
μ·μμ μλ£ μμ΄ν
κ³Ό νκ°μλ³ λμ μλ₯Ό 볡μνλ€."""
if not (api and RESULTS_REPO):
return
try:
from huggingface_hub import snapshot_download
local = snapshot_download(RESULTS_REPO, repo_type="dataset", token=HF_TOKEN,
allow_patterns=["results/*.json"])
for p in Path(local).glob("results/*.json"):
try:
rec = json.loads(p.read_text())
except Exception:
continue
if rec.get("item_id"):
_completed.add(rec["item_id"])
r = (rec.get("rater") or "").strip()
if r:
_rater_counts[r] = _rater_counts.get(r, 0) + 1
except Exception as e:
print("results repo load failed:", e)
_load_completed()
print(f"items={len(ITEMS)} completed={len(_completed)}")
def _now_iso() -> str:
return datetime.now(KST).isoformat(timespec="seconds")
def _pick_item() -> str | None:
now = time.time()
with _lock:
for k, t in list(_reserved.items()):
if now - t > RESERVE_TTL:
del _reserved[k]
pool = [it["item_id"] for it in ITEMS
if it["item_id"] not in _completed and it["item_id"] not in _reserved]
if not pool:
pool = [it["item_id"] for it in ITEMS if it["item_id"] not in _completed]
if not pool:
return None
item_id = random.choice(pool)
_reserved[item_id] = now
return item_id
def _save(rater: str, item_id: str, answers: dict, assigned_at: str, assigned_ts: float):
rec = {"rater": rater, "item_id": item_id, "answers": answers,
"assigned_at": assigned_at, "submitted_at": _now_iso(),
"elapsed_s": round(time.time() - assigned_ts, 1) if assigned_ts else None,
"id": uuid.uuid4().hex[:8]}
# μ μ₯μ΄ μ±κ³΅ν λ€μλ§ μλ£ μ²λ¦¬νλ€: μ
λ‘λ μ€ν¨ μ μμΈλ₯Ό μ¬λ € μ¬μ©μμκ²
# μ¬μ μΆμ μλ΄νκ³ , μμ΄ν
μ λ―Έμλ£λ‘ λ¨λλ€ (μλ΅ μ μ€ λ°©μ§).
if api and RESULTS_REPO:
num = item_id.split("_")[1]
api.upload_file(path_or_fileobj=json.dumps(rec, ensure_ascii=False).encode(),
path_in_repo=f"results/r_{num}_{rec['id']}.json",
repo_id=RESULTS_REPO, repo_type="dataset",
commit_message=f"rating {item_id} by {rater}")
else:
with (ROOT/"local_results.jsonl").open("a") as f:
f.write(json.dumps(rec, ensure_ascii=False)+"\n")
with _lock:
_completed.add(item_id)
_reserved.pop(item_id, None)
_rater_counts[rater] = _rater_counts.get(rater, 0) + 1
def _progress(my_count: int) -> str:
return f"μ 체 μ§ν **{len(_completed)} / {len(ITEMS)}** Β· λ΄κ° νκ°ν μ **{my_count}**"
def _render_item(item_id: str):
it = BY_ID[item_id]
return (f'<div class="query-card"><b>μ§λ¬Έ</b><br>{it["query"]}</div>',
str(ROOT/"images"/f"{item_id}_A.png"), str(ROOT/"images"/f"{item_id}_B.png"),
it["table_markdown"])
N_START_OUT = 12 # gate, panel, query, imgA, imgB, table, progress, rater, item, assigned_at, assigned_ts, my_count
def start(rater, request: gr.Request):
if (request.query_params.get("key") or "") != ACCESS_KEY:
raise gr.Error("μ κ·Ό ν€κ° μμ΅λλ€. 곡μ λ°μ λ§ν¬λ‘ μ μνμΈμ.")
rater = (rater or "").strip()
if not rater:
gr.Warning("μ΄λ¦(λλ μ΅λͺ
μ½λ)μ μ
λ ₯ν΄μ£ΌμΈμ.")
return tuple(gr.skip() for _ in range(N_START_OUT))
my_count = _rater_counts.get(rater, 0)
item_id = _pick_item()
if item_id is None:
gr.Info("λͺ¨λ λ¬Έμ νκ°κ° λλ¬μ΅λλ€. κ°μ¬ν©λλ€!")
return (gr.update(visible=True), gr.update(visible=False), "", None, None, "",
"π λͺ¨λ λ¬Έμ κ° μλ£λμμ΅λλ€!", rater, "", "", 0.0, my_count)
q, a, b, t = _render_item(item_id)
return (gr.update(visible=False), gr.update(visible=True), q, a, b, t,
_progress(my_count), rater, item_id, _now_iso(), time.time(), my_count)
N_SUB_OUT = 11 # query, imgA, imgB, table, progress, item, assigned_at, assigned_ts, my_count, + 4 radios -> actually computed below
def submit(rater, item_id, assigned_at, assigned_ts, my_count, c1, c2, c3, c4, request: gr.Request):
choices = (c1, c2, c3, c4)
n_out = 9 + len(AXES)
if (request.query_params.get("key") or "") != ACCESS_KEY:
raise gr.Error("μ κ·Ό ν€κ° μμ΅λλ€.")
if not item_id:
gr.Warning("ν λΉλ λ¬Έμ κ° μμ΅λλ€. μλ‘κ³ μΉ¨ ν λ€μ μμν΄μ£ΌμΈμ.")
return tuple(gr.skip() for _ in range(n_out))
missing = [f"{i+1}λ²" for i, c in enumerate(choices) if c is None]
if missing:
gr.Warning("λͺ¨λ νλͺ©μ μ νν΄μ£ΌμΈμ. λ―Έμ ν: " + ", ".join(missing))
return tuple(gr.skip() for _ in range(n_out))
try:
_save(rater, item_id, {k: c for (k, _), c in zip(AXES, choices)}, assigned_at, assigned_ts)
except Exception as e:
print("save failed:", type(e).__name__, e)
gr.Warning("μ μ₯μ μ€ν¨νμ΅λλ€ (λ€νΈμν¬ λ¬Έμ μΌ μ μμ΅λλ€). μ μ ν 'μ μΆνκ³ λ€μ'μ λ€μ λλ¬μ£ΌμΈμ β μ νμ μ μ§λ©λλ€.")
with _lock:
_reserved[item_id] = time.time() # μ΄ μ¬μ©μ λͺ«μΌλ‘ μμ½ μ°μ₯
return tuple(gr.skip() for _ in range(n_out))
my_count = (my_count or 0) + 1
nxt = _pick_item()
if nxt is None:
return ("π λͺ¨λ λ¬Έμ κ° μλ£λμμ΅λλ€. μ°Έμ¬ν΄μ£Όμ
μ κ°μ¬ν©λλ€!", None, None, "",
_progress(my_count), "", "", 0.0, my_count, *[None]*len(AXES))
q, a, b, t = _render_item(nxt)
return (q, a, b, t, _progress(my_count), nxt, _now_iso(), time.time(), my_count, *[None]*len(AXES))
with gr.Blocks(title="μ°¨νΈ μ νΈ νκ°") as demo:
gr.Markdown("# π μ°¨νΈ μμ± μ νΈ νκ° (Blind A/B)\nκ°μ μ§λ¬ΈΒ·κ°μ λ°μ΄ν°λ‘ λ μμ€ν
μ΄ λ§λ μ°¨νΈμ
λλ€. νλͺ©λ³λ‘ **λ λμ μͺ½**μ 골λΌμ£ΌμΈμ. A/B λ°°μΉλ λ¬Έμ λ§λ€ 무μμμ
λλ€.")
with gr.Group(visible=True) as gate:
rater_in = gr.Textbox(label="μ΄λ¦ (λλ μ΅λͺ
μ½λ)", placeholder="μ: λ°OO")
start_btn = gr.Button("νκ° μμ", variant="primary")
gr.Markdown(GUIDE, elem_classes="guide-box")
with gr.Group(visible=False) as panel:
prog = gr.Markdown()
qhtml = gr.HTML()
with gr.Row():
img_a = gr.Image(label="μ°¨νΈ A", type="filepath", height=440)
img_b = gr.Image(label="μ°¨νΈ B", type="filepath", height=440)
radios = []
for _, axis_md in AXES:
gr.Markdown(axis_md)
radios.append(gr.Radio(CHOICES, show_label=False, elem_classes="axis-block"))
sub_btn = gr.Button("μ μΆνκ³ λ€μ β", variant="primary", size="lg")
with gr.Accordion("μλ³Έ ν νΌμ³λ³΄κΈ° (Data λ°μλ μ°Έκ³ μ©)", open=False):
tbl = gr.Markdown()
rater_st = gr.State(""); item_st = gr.State(""); at_st = gr.State(""); ts_st = gr.State(0.0); cnt_st = gr.State(0)
start_btn.click(start, [rater_in],
[gate, panel, qhtml, img_a, img_b, tbl, prog, rater_st, item_st, at_st, ts_st, cnt_st])
sub_btn.click(submit, [rater_st, item_st, at_st, ts_st, cnt_st, *radios],
[qhtml, img_a, img_b, tbl, prog, item_st, at_st, ts_st, cnt_st, *radios])
if __name__ == "__main__":
demo.launch(css=CSS)
|