fix: 포터블에 transformers 포함 + 실제 모델로 드러난 용어집 결함 수정

리뷰 지적(포터블에서 transformers 제외 -> NLLB 로드 실패)을 고치려고 실제
NLLB 를 받아 돌려봤고, 그 과정에서 용어집이 사실상 동작하지 않고 있었다는
것을 발견했다. 단위 테스트는 "모델이 자리표시자를 통과시킨다"는 틀린 전제
위에 서 있었다.

1) 포터블 패키징 (리뷰 지적)
   transformers 를 제외 목록에서 빼고 hiddenimports 에 넣었다. NLLB
   토크나이저가 AutoTokenizer 를 쓰기 때문이다. transformers 는 torch 가
   없으면 토크나이저 전용 모드로 뜨며 그게 우리 용도와 정확히 맞는다.
   torch 없는 환경에서 ctranslate2/transformers/faster-whisper import 와
   앱 기동을 검증하는 test_portable.py 를 추가했다.

2) 자리표시자 형식 (실측으로 발견)
   `⟦0⟧` 는 NLLB 가 괄호를 날려 생존률 0/3 이었다. 용어가 자막에서 그냥
   사라지고 있었다 ("Third party incoming" -> "0 들어오는"). 후보 8종을
   실제 모델로 비교해 `#0#` 로 교체 (3/3, 다중 4/5).

3) 소실 대비
   모델이 문장 일부를 누락하면 자리표시자도 사라진다. 그대로 복원하면
   용어가 증발하므로, 하나라도 없으면 보호 없이 재번역한다.

4) 서술어는 문장 전체일 때만 (whole_only)
   절/서술어를 문장 중간에서 치환하면 문법이 무너진다.
     before: "탄 필요해와 구급상자"
     after : "탄약과 구급상자가 필요합니다"
   해당 56개 항목을 whole_only 로 지정해 단독 발화일 때만 적용한다.
   ("Cover me!" -> "엄호해줘" 는 그대로 유지)

5) 조사 교정
   역어 받침이 달라 "자기장를" 이 남던 것을 fix_particles() 로 고친다.
   을/를, 이/가, 은/는, 과/와, (으)로 — 한글 코드에서 받침을 읽어 판정.

검증: pytest 189개 통과, ruff clean
      실제 NLLB-600M(torch 없이 CPU)로 번역 품질 직접 확인

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-09-23 01:20:42 +09:00
parent 71436843d8
commit bede94ee48
18 changed files with 370 additions and 64 deletions

View File

@@ -240,3 +240,39 @@ def test_unknown_speech_level_value_falls_back_to_polite(monkeypatch, tmp_path):
assert _run(monkeypatch, tmp_path, "무엇인가이상한값", "Enemy", "en") == (
"적이 왼쪽에서 옵니다"
)
# --- 조사 교정 --------------------------------------------------------------
@pytest.mark.parametrize(
("wrong", "right"),
[
("자기장를 봐요", "자기장을 봐요"),
("바론를 치자", "바론을 치자"),
("데드섹가 해킹했다", "데드섹이 해킹했다"),
("구급상자을 주세요", "구급상자를 주세요"),
("넥서스은 저기", "넥서스는 저기"),
("서울으로 간다", "서울로 간다"),
("칼으로 베다", "칼로 베다"),
("적으로 간다", "적으로 간다"),
],
)
def test_particles_are_corrected(wrong, right):
from livesub.models.speech_level import fix_particles
assert fix_particles(wrong) == right
def test_correct_particles_are_left_alone():
from livesub.models.speech_level import fix_particles
for text in ["적이 왼쪽에서 온다", "넥서스를 밀어", "스파이크를 설치했다", "바론을 치자"]:
assert fix_particles(text) == text
def test_particle_fix_ignores_non_hangul_and_empty():
from livesub.models.speech_level import fix_particles
assert fix_particles("") == ""
assert fix_particles("ctOS를 해킹") == "ctOS를 해킹"