Hanish's picture
Add demo app
00255a8 verified
Raw
History Blame Contribute Delete
4.55 kB
"""Palette-K-Midm live demo — 회의록 → 결정사항 추출 (PALETTE-BENCH-KO #1 model).
HF Space (ZeroGPU). Deploy: upload this folder as a Gradio Space, hardware = ZeroGPU.
"""
import json
import re
import gradio as gr
import spaces
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_ID = "imcapsule/palette-k-midm" # TODO: replace ORG on drop day
tok = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, torch_dtype=torch.bfloat16, device_map="auto"
)
PROMPT = """다음 회의록에서 결정사항을 추출하라.
규칙:
- 실제로 확정된 결정만 포함한다. 보류/검토중/논의만 된 항목은 제외한다.
- 각 결정에 담당자(owner)와 기한(due_date, YYYY-MM-DD)을 붙인다. 기한이 명시되지 않았으면 null.
- 상대 날짜("다음 주 금요일까지")는 회의 날짜 기준으로 환산한다.
- JSON만 출력한다: {{"decisions": [{{"decision": "...", "owner": "...", "due_date": "..."}}]}}
회의록:
{minutes}"""
EXAMPLE = """[주간 운영회의] 2026-08-17 (월) 14:00 · 참석: 김부장, 이과장, 박대리, 최주임
김부장: 3분기 예산안은 지난주 논의대로 확정합니다. 이과장이 이번 주 금요일까지 품의서 올려주세요.
이과장: 네. 그리고 신규 거래처 계약 건은 법무 검토가 아직이라 다음 회의로 넘기겠습니다.
박대리: 사무실 이전 견적 3곳 받았습니다. 비교표는 제가 정리 중입니다.
김부장: 비교표는 정리되는 대로 공유만 해주세요. 아, 그리고 전사 보안교육은 9월 5일로 확정. 최주임이 공지 담당.
최주임: 알겠습니다."""
def _extract_json(s: str):
start = s.find("{")
while start != -1:
depth, in_str, esc = 0, False, False
for i in range(start, len(s)):
c = s[i]
if in_str:
if esc:
esc = False
elif c == "\\":
esc = True
elif c == '"':
in_str = False
continue
if c == '"':
in_str = True
elif c == "{":
depth += 1
elif c == "}":
depth -= 1
if depth == 0:
try:
return json.loads(s[start : i + 1])
except Exception:
break
start = s.find("{", start + 1)
return None
@spaces.GPU(duration=60)
def extract(minutes: str):
if not minutes.strip():
return "회의록을 입력하세요.", ""
msgs = [{"role": "user", "content": PROMPT.format(minutes=minutes.strip())}]
ids = tok.apply_chat_template(
msgs, return_tensors="pt", add_generation_prompt=True
).to(model.device)
out = model.generate(ids, max_new_tokens=800, do_sample=False)
text = tok.decode(out[0][ids.shape[-1]:], skip_special_tokens=True)
parsed = _extract_json(text)
if parsed and "decisions" in parsed:
rows = "\n".join(
f"| {d.get('decision','')} | {d.get('owner','')} | {d.get('due_date') or '—'} |"
for d in parsed["decisions"]
)
table = "| 결정사항 | 담당자 | 기한 |\n|---|---|---|\n" + rows
return table, text
return "JSON 파싱 실패 — 원문 출력을 확인하세요.", text
with gr.Blocks(title="Palette-K-Midm · 회의록→결정 추출") as demo:
gr.Markdown(
"# Palette-K-Midm — 회의록에서 결정사항 추출\n"
"PALETTE-BENCH-KO **1위 (0.866)** 모델의 라이브 데모. "
"보류·논의중 항목 제외, 상대 날짜 환산, 기한 없으면 null — "
"전 모델 공통 난제 과제입니다.\n\n"
"_정직 고지: 1위 격차는 0.010(n=32)이며, 이 과제 자체는 최고 모델도 0.582 수준의 난제입니다._"
)
inp = gr.Textbox(lines=12, label="회의록", value=EXAMPLE)
btn = gr.Button("결정사항 추출", variant="primary")
out_table = gr.Markdown(label="추출 결과")
out_raw = gr.Textbox(lines=6, label="모델 원문 출력")
btn.click(extract, inputs=inp, outputs=[out_table, out_raw])
gr.Markdown(
"[벤치마크](https://github.com/palette-lab/palette-bench-ko) · "
"[모델](https://huggingface.co/imcapsule/palette-k-midm) · "
"[기술 보고서](https://pltt.xyz/paper) · Apache-2.0, base: KT Mi:dm-2.0 (MIT)"
)
demo.launch()