proofread-demo / runner.py
dev-strender's picture
feat: switch demo to the solar-mini4 pipeline (v44 = v41 + document chunking)
dd41633 verified
Raw
History Blame Contribute Delete
6.25 kB
"""ꡐ열 ꡬ성(νŒŒμ΄ν”„λΌμΈ Γ— ν”„λ‘¬ν”„νŠΈ Γ— λͺ¨λΈ)을 ν…μŠ€νŠΈ 1건에 μ‹€ν–‰ν•˜λŠ” ν”„λ‘œκ·Έλž˜λ§€ν‹± λŸ¬λ„ˆ.
ꡐ열 λ‘œμ§μ„ μž¬κ΅¬ν˜„ν•˜μ§€ μ•Šκ³  solar-eval 엔진을 κ·ΈλŒ€λ‘œ import ν•œλ‹€ β€” 데λͺ¨κ°€
λ³΄μ—¬μ£ΌλŠ” λ™μž‘μ΄ 평가 run κ³Ό λ°”μ΄νŠΈ λ‹¨μœ„λ‘œ κ°™μ•„μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€ (사본 λ‘œμ§μ€
λ°˜λ“œμ‹œ κ°ˆλΌμ§„λ‹€). λ‘œμ»¬μ—μ„œλŠ” uv μ›Œν¬μŠ€νŽ˜μ΄μŠ€μ˜ solar_eval 을, HF Space μ—μ„œλŠ”
`build_space.py` κ°€ λ²€λ”λ§ν•œ 사본을 μ“΄λ‹€ (sys.path 상 슀크립트 μ˜†μ΄ 먼저라
Space μ—μ„œλŠ” 벀더 사본이 이긴닀).
**ꡬ성은 (pipeline, prompt, model) ν•œ λ¬ΆμŒμ΄λ‹€** β€” λͺ¨λΈλ§Œ κ°ˆμ•„λΌμš°λŠ” μ„ νƒμ§€λŠ”
두지 μ•ŠλŠ”λ‹€. mini4 의 이득은 λͺ¨λΈ ꡐ체가 μ•„λ‹ˆλΌ νŒŒμ΄ν”„λΌμΈΒ·ν”„λ‘¬ν”„νŠΈ ꡬ쑰 λ³€κ²½μ—μ„œ
λ‚˜μ™”κ³ (step1 을 μƒμ„±μ—μ„œ νŒμ •μœΌλ‘œ, step2 λ₯Ό μ–΄νœ˜/λ¬Έλ²•μœΌλ‘œ λΆ„ν• , κ·œμΉ™ μΈ΅ 2개 μΆ”κ°€),
μ˜› combo ν”„λ‘¬ν”„νŠΈμ— mini4 만 κ½‚μœΌλ©΄ 2026-09-16 에 이관 λΆˆκ°€ νŒμ •μ„ 받은 κ·Έ 쑰합이
λœλ‹€. κ·Έλž˜μ„œ μ„Έ 좕을 `Config` ν•˜λ‚˜λ‘œ λ¬Άκ³ , 데λͺ¨λŠ” κ·Έ κ΅¬μ„±λ§Œ λ…ΈμΆœν•œλ‹€.
config μžμ‚°(νŒŒμ΄ν”„λΌμΈ yamlΒ·ν”„λ‘¬ν”„νŠΈΒ·μΉ˜ν™˜ μ‚¬μ „Β·ν™”μ΄νŠΈλ¦¬μŠ€νŠΈ)은
1. `./assets/` β€” HF Space λ ˆμ΄μ•„μ›ƒ (build_space.py κ°€ 쑰립)
2. `../03-evaluation/` β€” 레포 λ ˆμ΄μ•„μ›ƒ (둜컬 개발)
μˆœμ„œλ‘œ μ°ΎλŠ”λ‹€.
"""
from __future__ import annotations
import asyncio
import time
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
@dataclass(frozen=True)
class Config:
"""데λͺ¨κ°€ κ³ λ₯Ό 수 μžˆλŠ” ν•œ ꡬ성. `base` λŠ” pipeline 이 extends ν•˜λŠ” 파일(λ²ˆλ“€ λŒ€μƒ)."""
label: str
pipeline: str
prompt: str
model: str
note: str
base: str | None = None
#: 데λͺ¨μ— λ…ΈμΆœλ˜λŠ” ꡬ성. μ§€κΈˆμ€ ν•˜λ‚˜λΏμ΄μ§€λ§Œ tuple 둜 λ‘”λ‹€ β€” `build_space.required_assets`
#: κ°€ 이 λͺ©λ‘μ—μ„œ λ²ˆλ“€ μžμ‚°μ„ μœ λ„ν•˜λ―€λ‘œ, ꡬ성이 λŠ˜μ–΄λ„ 쑰립 μ½”λ“œλ₯Ό κ³ μΉ  ν•„μš”κ°€ μ—†λ‹€.
CONFIGS: tuple[Config, ...] = (
Config(
label="solar-mini4 νŒŒμ΄ν”„λΌμΈ",
pipeline="pipeline_dev_v44", # = v41 + document_chunking
prompt="prompt_dev_260917_vsplit",
model="solar-mini4-preview",
note=(
"FT ꡐ열 β†’ νŒμ •ν˜• step1(5KB) β†’ λ¬Έλ§₯ ꡐ정 μ–΄νœ˜/문법 2μŠ€ν… β†’ κ²°μ •λ‘  κ·œμΉ™ 6μΈ΅. "
"μž…λ ₯ 3.3k 토큰."
),
base="pipeline_dev_v40",
),
)
#: 라벨 β†’ Config
BY_LABEL: dict[str, Config] = {c.label: c for c in CONFIGS}
DEFAULT_LABEL = CONFIGS[0].label
# μ—”μ§„μ˜ StepCallback κ³Ό 같은 λͺ¨μ–‘: (event, index, total, step_name, doc_chunk)
StepCallback = Callable[[str, int, int, str, tuple[int, int] | None], None]
def find_config_dir(pipeline: str) -> Path:
"""κ·Έ νŒŒμ΄ν”„λΌμΈ yaml 을 μ‹€μ œλ‘œ κ°€μ§„ μžμ‚° 루트. Space λ²ˆλ“€μ΄ λ ˆν¬λ³΄λ‹€ λ¨Όμ €λ‹€."""
here = Path(__file__).resolve().parent
for cand in (here / "assets", here.parent / "03-evaluation"):
if (cand / "pipelines" / f"{pipeline}.yaml").is_file():
return cand
raise FileNotFoundError(
f"config μžμ‚°μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€ β€” {here}/assets λ˜λŠ” ../03-evaluation 에 "
f"pipelines/{pipeline}.yaml 이 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€ (build_space.py μ°Έμ‘°)."
)
def build_pipeline(label: str = DEFAULT_LABEL) -> Any:
"""ꡬ성 ν•˜λ‚˜μ˜ νŒŒμ΄ν”„λΌμΈ μΈμŠ€ν„΄μŠ€. μ•±μ—μ„œ κ΅¬μ„±λ³„λ‘œ 1회 λ§Œλ“€μ–΄ μž¬μ‚¬μš©ν•œλ‹€."""
import solar_eval.pipelines.steps # noqa: F401 β€” STEP_REGISTRY 등둝
from solar_eval.core.dataset_loader import DatasetLoader
from solar_eval.core.pipeline_compose import compose_pipeline
from solar_eval.models.prompt_version import load_step_prompts
from solar_eval.pipelines.registry import create_pipeline
cfg = BY_LABEL[label]
config_dir = find_config_dir(cfg.pipeline)
pipelines_dir = config_dir / "pipelines"
def load_base(name: str) -> dict[str, Any]:
return yaml.safe_load((pipelines_dir / f"{name}.yaml").read_text(encoding="utf-8"))
composed = compose_pipeline(load_base(cfg.pipeline), load_base=load_base)
prompts = load_step_prompts(config_dir / "prompts" / cfg.prompt)
return create_pipeline(
"multi_step",
input_fields=["original"],
pipeline_config=composed,
prompts=prompts,
dataset_loader=DatasetLoader(),
config_dir=config_dir,
)
def step_names(pipeline: Any) -> list[str]:
"""UI κ°€ μ§„ν–‰ ν‘œμ‹œλ₯Ό 미리 그릴 수 있게 μŠ€ν… 이름을 μˆœμ„œλŒ€λ‘œ."""
return [step.name for step in pipeline.steps]
async def _run_async(
pipeline: Any, text: str, cfg: Config, on_step: StepCallback | None
) -> dict[str, Any]:
from solar_eval.models.sample import EvalSample
from solar_eval.providers.upstage import UpstageProvider
sample = EvalSample(input={"original": text})
start = time.monotonic()
# 평가 run κ³Ό 같은 쑰건: temp 0.0, reasoning off (60초 μ§€μ—° μ˜ˆμ‚° μ œμ•½)
result = await pipeline.run(
sample,
prompts="",
provider=UpstageProvider(),
model=cfg.model,
temperature=0.0,
max_tokens=8000,
reasoning_effort=None,
on_step=on_step,
)
elapsed = time.monotonic() - start
return {
"output": result.output,
"step_outputs": result.artifacts.get("step_outputs", {}),
"usage": result.artifacts.get("usage", {}),
"elapsed_s": elapsed,
"pipeline_key": cfg.pipeline,
"prompt_key": cfg.prompt,
"model": cfg.model,
"config_label": cfg.label,
}
def run_proofread(
pipeline: Any,
text: str,
label: str = DEFAULT_LABEL,
on_step: StepCallback | None = None,
) -> dict[str, Any]:
"""ν…μŠ€νŠΈ 1건 ꡐ열. {output, step_outputs, usage, elapsed_s, pipeline_key, prompt_key, model, config_label}.
`pipeline` 은 같은 `label` 둜 `build_pipeline` ν•œ μΈμŠ€ν„΄μŠ€μ—¬μ•Ό ν•œλ‹€ β€” 앱이
κ΅¬μ„±λ³„λ‘œ μΊμ‹œν•œλ‹€. `on_step` 은 μŠ€ν… μ‹œμž‘/μ’…λ£Œμ™€ chunk μ§„ν–‰λ§ˆλ‹€ λΆˆλ¦°λ‹€(UI μ§„ν–‰ ν‘œμ‹œ).
"""
return asyncio.run(_run_async(pipeline, text, BY_LABEL[label], on_step))