From 440e947d47198e47a549dfaf72d630c25e449f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=83=81=ED=98=B8=20Sangho=20Park?= Date: Wed, 12 Aug 2026 21:07:56 +0900 Subject: [PATCH] refine: glossary - reject empty keys, guard non-dict, schema field Reviewer feedback: (1) CLI --glossary rejects empty key/value so an empty pattern cannot corrupt text; (2) worker guards job.options glossary/post_correction with isinstance dict; (3) TranscribeOptions gains a dedicated glossary dict field; (4) rules.py documents the word-boundary/Hangul adjacency limitation. --- src/luke_scribe/api/schemas.py | 1 + src/luke_scribe/cli.py | 6 +++++- src/luke_scribe/jobqueue/worker.py | 6 ++++-- src/luke_scribe/postprocess/rules.py | 2 ++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/luke_scribe/api/schemas.py b/src/luke_scribe/api/schemas.py index 80a88b0..d70d2a6 100644 --- a/src/luke_scribe/api/schemas.py +++ b/src/luke_scribe/api/schemas.py @@ -23,6 +23,7 @@ class TranscribeOptions(BaseModel): hotwords: list[str] = Field(default_factory=list) vad: bool = True glossary_id: str | None = None + glossary: dict[str, str] | None = None # {오인식 패턴: 표준 표기} — 후처리 glossary post_correction: dict[str, Any] | None = None diarize: bool = False diff --git a/src/luke_scribe/cli.py b/src/luke_scribe/cli.py index c1fbc00..6488970 100644 --- a/src/luke_scribe/cli.py +++ b/src/luke_scribe/cli.py @@ -121,7 +121,11 @@ def transcribe( if "=" not in item: _fail(EXIT_INPUT, f"--glossary는 'KEY=VALUE' 형식이어야 합니다: {item}") key, _, value = item.partition("=") - glossary_dict[key.strip()] = value.strip() + key, value = key.strip(), value.strip() + if not key or not value: + # 빈 패턴(re.escape(''))은 모든 위치에 매칭돼 텍스트를 망가뜨린다 + _fail(EXIT_INPUT, f"--glossary 키/값이 비어 있으면 안 됩니다: {item}") + glossary_dict[key] = value try: pipeline = BatchPipeline(settings=settings, token=token) result = pipeline.run( diff --git a/src/luke_scribe/jobqueue/worker.py b/src/luke_scribe/jobqueue/worker.py index b3622b2..de915da 100644 --- a/src/luke_scribe/jobqueue/worker.py +++ b/src/luke_scribe/jobqueue/worker.py @@ -110,10 +110,12 @@ class Worker: if k in TranscriptionOptions.__slots__ } ) - # API 옵션의 glossary/post_correction(dict)을 후처리 glossary로 전달 - glossary = (job.options or {}).get("glossary") or (job.options or {}).get( + # API 옵션의 glossary/post_correction(dict)을 후처리 glossary로 전달. + # pydantic 스키마를 거치지 않은 직접 생성 Job은 비-dict일 수 있어 방어. + raw_glossary = (job.options or {}).get("glossary") or (job.options or {}).get( "post_correction" ) + glossary = raw_glossary if isinstance(raw_glossary, dict) else None result = pipeline.run(job, options, progress_cb=progress_cb, glossary=glossary) # 결과를 먼저 영속화한 뒤 상태 전이 (실패 시 FAILED로 전이 가능하게) self.store.write_result(job.id, result) diff --git a/src/luke_scribe/postprocess/rules.py b/src/luke_scribe/postprocess/rules.py index b936128..e1834fa 100644 --- a/src/luke_scribe/postprocess/rules.py +++ b/src/luke_scribe/postprocess/rules.py @@ -14,6 +14,8 @@ from ..results.models import Segment DEFAULT_RULES: list[tuple[re.Pattern, str]] = [ (re.compile(r"\bv ?l ?l ?m\b", re.IGNORECASE), "vLLM"), # 흔한 오인식: vLLM → BLM (GPU 실전에서 재현). 기술 STT 도메인 전제로 복원. + # 주의: \b 경계는 유니코드 \w 기준이라 'BLM은'(공백 없음)은 교정하지 않는다. + # faster-whisper 출력은 어절 단위 공백 분리("BLM 서버를")라 실제로는 충분하다. (re.compile(r"\bblm\b", re.IGNORECASE), "vLLM"), (re.compile(r"\bk ?u ?b ?e ?r ?n ?e ?t ?e ?s\b", re.IGNORECASE), "Kubernetes"), (re.compile(r"\bf ?a ?s ?t ?a ?p ?i\b", re.IGNORECASE), "FastAPI"),