fix: Seed-X 프롬프트 형식과 티어별 용어집 전략 수정

4티어(Seed-X-PPO-7B) 번역 경로가 모델 카드 요구사항을 어기고 있었다.

- 프롬프트 끝의 `<ko>` 등 대상 언어 태그가 빠져 있었다. PPO 학습에 쓰인
  신호라 없으면 번역 품질이 흔들린다.
- Seed-X 는 chat template 없는 번역 전용 completion 모델인데 "구어체로
  자연스럽게" 같은 지시문 래퍼를 씌우고 있었다. 학습 분포를 벗어난다.
- 그 결과 MTSpec.supports_prompt_glossary=True 가 사실과 달랐다.
  Seed-X 는 용어집 지시문을 이해하지 못하므로 플레이스홀더 치환을 써야 한다.

수정
- PromptStyle(NONE/SEEDX/INSTRUCT) 도입, supports_prompt_glossary 를
  prompt_style 에서 파생시켜 둘이 어긋날 수 없게 함
- build_seedx_prompt() 로 모델 카드 형식을 분리 (지시문 주입 불가)
- INSTRUCT 경로는 chat template 사용, Qwen3 thinking 모드는 끔
- LANGUAGES 에 seedx 태그 명시
- finetune_mt.py 가 티어의 prompt_style 을 따라가게 해 학습/추론 프롬프트 일치
- AWQ Int4 로드 실패 시 autoawq 설치 안내를 담은 오류 메시지

검증: pytest 52개 통과 (프롬프트 회귀 테스트 13개 추가), ruff clean

근거: https://huggingface.co/ByteDance-Seed/Seed-X-PPO-7B

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-09-21 10:56:49 +09:00
parent 1a87ec6677
commit 5596f905d1
8 changed files with 181 additions and 21 deletions

82
tests/test_prompts.py Normal file
View File

@@ -0,0 +1,82 @@
"""번역 프롬프트 형식 회귀 테스트.
Seed-X 는 chat template 없는 번역 전용 completion 모델이라 모델 카드가 정한
형식을 한 글자도 벗어나면 안 된다. 특히 끝의 `<언어코드>` 태그는 PPO 학습에
쓰인 것이라 빠지면 품질이 무너진다.
"""
from __future__ import annotations
import pytest
from hearo.constants import LANGUAGE_CODES, LANGUAGES
from hearo.models.glossary import Glossary, GlossaryEntry
from hearo.models.tiers import TIERS, MTBackend, PromptStyle, get_tier
from hearo.models.translator import build_seedx_prompt
def test_seedx_prompt_matches_model_card_exactly():
"""모델 카드 예시: "Translate the following English sentence into Chinese:\\nMay the force be with you <zh>" """
assert build_seedx_prompt("May the force be with you", "en", "zh") == (
"Translate the following English sentence into Chinese:\n"
"May the force be with you <zh>"
)
def test_seedx_prompt_always_ends_with_target_language_tag():
for target in LANGUAGE_CODES:
prompt = build_seedx_prompt("hello", "en", target)
assert prompt.endswith(f" <{target}>"), f"{target} 태그 누락"
def test_seedx_prompt_has_no_extra_instructions():
"""지시문을 끼워 넣으면 Seed-X 의 학습 분포를 벗어난다."""
prompt = build_seedx_prompt("fall back now", "en", "ko")
lowered = prompt.lower()
for forbidden in ("casual", "output only", "glossary", "game or broadcast", "용어"):
assert forbidden not in lowered
assert prompt.count("\n") == 1 # 지시문 한 줄 + 본문 한 줄
@pytest.mark.parametrize("code", LANGUAGE_CODES)
def test_every_language_has_a_seedx_tag(code):
assert LANGUAGES[code]["seedx"], f"{code} 에 seedx 태그가 없습니다"
def test_seedx_tier_does_not_use_prompt_glossary():
"""Seed-X 는 지시문을 못 알아들으므로 프롬프트 용어집을 쓰면 안 된다."""
precision = get_tier("precision")
assert precision.mt.prompt_style is PromptStyle.SEEDX
assert precision.mt.supports_prompt_glossary is False
def test_instruct_tier_uses_prompt_glossary():
ultimate = get_tier("ultimate")
assert ultimate.mt.prompt_style is PromptStyle.INSTRUCT
assert ultimate.mt.supports_prompt_glossary is True
def test_seq2seq_tiers_never_use_prompt_glossary():
for tier in TIERS.values():
if tier.mt.backend is MTBackend.CTRANSLATE2:
assert tier.mt.prompt_style is PromptStyle.NONE
assert tier.mt.supports_prompt_glossary is False
def test_every_llm_tier_declares_a_prompt_style():
for tier in TIERS.values():
if tier.mt.backend is MTBackend.TRANSFORMERS:
assert tier.mt.prompt_style is not PromptStyle.NONE, tier.key
def test_glossary_survives_seedx_placeholder_path():
"""Seed-X 경로에서 용어집이 프롬프트가 아니라 치환으로 동작하는지."""
g = Glossary([GlossaryEntry("Nexus", {"ko": "넥서스"})])
protected, repl = g.protect("push to the Nexus", "ko")
prompt = build_seedx_prompt(protected, "en", "ko")
assert "Nexus" not in prompt # 모델이 건드릴 수 없게 가려짐
assert prompt.endswith(" <ko>")
# 모델이 플레이스홀더를 그대로 통과시켰다고 가정
assert Glossary.restore("⟦0⟧로 밀어", repl) == "넥서스로 밀어"