SQL-Eval / app.py
Jun-Min Lee
init
c28406d
Raw
History Blame Contribute Delete
7.67 kB
import random
import datetime
import gradio as gr
from utils.filesys_utils import json_load
from utils.common import list_completed_keys, upload_label
ERROR_CATEGORIES = [
"λŒ€μƒκ΅°(μ½”ν˜ΈνŠΈ) 해석 였λ₯˜",
"μ§€ν‘œ/계산식 해석 였λ₯˜",
"SQL ꡬ문/둜직 였λ₯˜",
"κ²°κ³Όβ†’μ„€λͺ… 뢈일치",
"기타",
]
MAX_TURNS = 15 # actual max across the dataset is 13; a little headroom
SESSIONS = json_load("data/sessions.json")
SESSION_BY_KEY = {f"{s['pid']}_{s['sid']}": s for s in SESSIONS}
# In-memory only, shared across all concurrent users of this single process.
# Best-effort de-duplication of who's currently working on what; the durable
# "is this session actually done" truth lives in the GitHub labels/ dir.
CLAIMED: set = set()
def render_dialogue_md(sess: dict) -> str:
role_label = {"user": "πŸ§‘ μ‚¬μš©μž", "assistant": "πŸ€– μ—μ΄μ „νŠΈ"}
judgable_set = set(sess["judgable_turns"])
lines = [f"### μ°Έκ°€μž `{sess['pid']}` / μ„Έμ…˜ `{sess['sid']}` (μ „λž΅: {sess['strategy']})", ""]
for i, t in enumerate(sess["dialogue"]):
marker = " **β¬… 라벨링 λŒ€μƒ (μ•„λž˜μ—μ„œ νŒλ‹¨)**" if i in judgable_set else ""
lines.append(f"**#{i} [{t['type']}] {role_label[t['role']]}**{marker}")
lines.append(t["content"])
lines.append("")
return "\n".join(lines)
def render_turn_detail_md(sess: dict, idx: int) -> str:
t = sess["dialogue"][idx]
lines = [f"#### νŒλ‹¨ λŒ€μƒ: ν„΄ #{idx} ({t['type']})"]
for j, s in enumerate(t.get("sql", [])):
lines.append(f"**쿼리 {j + 1}:**")
lines.append(f"```sql\n{s['query']}\n```")
lines.append(f"**μ‹€ν–‰ κ²°κ³Ό:** `{s['result']}`")
return "\n".join(lines)
def _reset_turn_updates():
"""One (visible=False, empty text, cleared radio, cleared checkboxes,
cleared textbox) group per pre-allocated turn slot."""
updates = []
for _ in range(MAX_TURNS):
updates += [gr.update(visible=False), "", gr.update(value=None), gr.update(value=[]), gr.update(value="")]
return updates
def get_new_session():
completed = list_completed_keys()
available = [k for k in SESSION_BY_KEY if k not in completed and k not in CLAIMED]
if not available:
return (
[None, ""]
+ _reset_turn_updates()
+ [gr.update(visible=False), "πŸŽ‰ λͺ¨λ“  μ„Έμ…˜μ΄ λΌλ²¨λ§λ˜μ—ˆμŠ΅λ‹ˆλ‹€! κ°μ‚¬ν•©λ‹ˆλ‹€."]
)
key = random.choice(available)
CLAIMED.add(key)
sess = SESSION_BY_KEY[key]
judgable = sess["judgable_turns"]
updates = []
for i in range(MAX_TURNS):
if i < len(judgable):
updates += [
gr.update(visible=True),
render_turn_detail_md(sess, judgable[i]),
gr.update(value=None),
gr.update(value=[]),
gr.update(value=""),
]
else:
updates += [gr.update(visible=False), "", gr.update(value=None), gr.update(value=[]), gr.update(value="")]
status = f"μ„Έμ…˜ `{key}` 배정됨 -- {len(judgable)}개 턴을 λΌλ²¨λ§ν•΄μ£Όμ„Έμš”."
return [key, render_dialogue_md(sess)] + updates + [gr.update(visible=True), status]
def submit_labels(key, *flat):
# session_key_state is a gr.State -- it needs its actual (unchanged) value
# passed through, not a bare gr.update(), which is only meaningful for
# ordinary display/interactive components.
def unchanged(msg):
return [key, gr.update()] + [gr.update()] * (MAX_TURNS * 5) + [gr.update(), msg]
if not key:
return unchanged("λ¨Όμ € 'μƒˆ μ„Έμ…˜ λ°›κΈ°'λ₯Ό λˆŒλŸ¬μ£Όμ„Έμš”.")
sess = SESSION_BY_KEY.get(key)
if sess is None:
return unchanged("μ„Έμ…˜ 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.")
judgable = sess["judgable_turns"]
n = len(judgable)
verdicts = list(flat[0::3])[:n]
cats_list = list(flat[1::3])[:n]
reasons = list(flat[2::3])[:n]
missing = [i + 1 for i, v in enumerate(verdicts) if not v]
if missing:
msg = f"❌ 아직 νŒλ‹¨ν•˜μ§€ μ•Šμ€ ν•­λͺ©μ΄ μžˆμŠ΅λ‹ˆλ‹€: #{', '.join(map(str, missing))}. λͺ¨λ‘ μ²΄ν¬ν•΄μ£Όμ„Έμš”."
return unchanged(msg)
turn_labels = [
{"turn_index": idx, "verdict": v, "error_categories": c or [], "reason": r or ""}
for idx, v, c, r in zip(judgable, verdicts, cats_list, reasons)
]
payload = {
"pid": sess["pid"],
"sid": sess["sid"],
"strategy": sess.get("strategy"),
"source": sess.get("source"),
"labeled_at": datetime.datetime.now().isoformat(),
"turns": turn_labels,
}
ok, msg = upload_label(key, payload)
if not ok:
return unchanged(msg)
CLAIMED.discard(key)
# auto-advance to the next session on success
next_state = get_new_session()
next_state[-1] = f"{msg} λ‹€μŒ μ„Έμ…˜μ„ λ°°μ •ν–ˆμŠ΅λ‹ˆλ‹€.\n\n{next_state[-1]}"
return next_state
css = """
.dialog-box {
max-height: 500px;
overflow-y: auto;
border: 1px solid var(--border-color-primary);
border-radius: 8px;
padding: 12px;
}
"""
with gr.Blocks(title="SQL Result Evaluation", css=css) as demo:
gr.Markdown("# 🩺 SQL κ²°κ³Ό 적합성 라벨링")
gr.Markdown(
"μ•„λž˜ 전체 λŒ€ν™”λ₯Ό λκΉŒμ§€ 읽고, **'라벨링 λŒ€μƒ'**으둜 ν‘œμ‹œλœ 각 μ—μ΄μ „νŠΈ 응닡이 "
"μ‹€μ œλ‘œ μ‹€ν–‰λœ SQLκ³Ό κ·Έ κ²°κ³Όλ₯Ό λ°”νƒ•μœΌλ‘œ μ‚¬μš©μžμ˜ μ΅œμ’… μ˜λ„"
"(쀑간에 λͺ…ν™•ν™”(clarification)κ°€ μžˆμ—ˆλ‹€λ©΄ κ·Έκ²ƒκΉŒμ§€ λ°˜μ˜ν•œ μ˜λ„)에 λ§žλŠ” 닡을 ν–ˆλŠ”μ§€ νŒλ‹¨ν•΄μ£Όμ„Έμš”."
)
gr.Markdown(
"* ν•œ μ„Έμ…˜μ—λŠ” 라벨링 λŒ€μƒμ΄ μ—¬λŸ¬ 개 μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€ -- μ„Έμ…˜μ˜ λͺ¨λ“  λŒ€μƒμ„ μ±„μ›Œμ•Ό μ œμΆœλ©λ‹ˆλ‹€.\n"
"* μ œμΆœν•˜λ©΄ κ·Έ μ„Έμ…˜μ€ μ™„λ£Œ μ²˜λ¦¬λ˜μ–΄ λ‹€λ₯Έ μ‚¬λžŒμ—κ²Œ λ‹€μ‹œ λ°°μ •λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.\n"
"* 'ν‹€λ¦Ό'을 μ„ νƒν•œ 경우 원인 μΉ΄ν…Œκ³ λ¦¬λ₯Ό μ²΄ν¬ν•˜κ³ , κ°€λŠ₯ν•˜λ©΄ 자유 μ„œμˆ λ‘œ μ΄μœ λ„ λ‚¨κ²¨μ£Όμ„Έμš”."
)
session_key_state = gr.State(None)
btn_new = gr.Button("🎲 μƒˆ μ„Έμ…˜ λ°›κΈ°")
status_msg = gr.Markdown("")
dialogue_md = gr.Markdown(elem_classes="dialog-box")
turn_groups, detail_mds, verdict_radios, category_checks, reason_boxes = [], [], [], [], []
for i in range(MAX_TURNS):
with gr.Group(visible=False) as grp:
detail = gr.Markdown()
verdict = gr.Radio(["맞음", "ν‹€λ¦Ό"], label=f"νŒλ‹¨ #{i + 1}: 이 응닡은 μ‚¬μš©μžμ˜ μ΅œμ’… μ˜λ„μ— λ§žλ‚˜μš”?")
cats = gr.CheckboxGroup(ERROR_CATEGORIES, label="였λ₯˜ μœ ν˜• (틀렸을 λ•Œλ§Œ ν•΄λ‹Ήν•˜λŠ” ν•­λͺ© 체크)")
reason = gr.Textbox(label="자유 μ„œμˆ  이유", lines=2)
turn_groups.append(grp)
detail_mds.append(detail)
verdict_radios.append(verdict)
category_checks.append(cats)
reason_boxes.append(reason)
with gr.Row(visible=False) as submit_row:
submit_btn = gr.Button("πŸ“€ μ œμΆœν•˜κΈ°")
all_outputs = [session_key_state, dialogue_md]
for grp, detail, verdict, cats, reason in zip(turn_groups, detail_mds, verdict_radios, category_checks, reason_boxes):
all_outputs += [grp, detail, verdict, cats, reason]
all_outputs += [submit_row, status_msg]
btn_new.click(fn=get_new_session, inputs=[], outputs=all_outputs)
submit_inputs = [session_key_state]
for verdict, cats, reason in zip(verdict_radios, category_checks, reason_boxes):
submit_inputs += [verdict, cats, reason]
submit_btn.click(fn=submit_labels, inputs=submit_inputs, outputs=all_outputs)
if __name__ == "__main__":
demo.launch()