| """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" |
|
|
| 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() |
|
|