feat(study): Phase 4-A explanation_md 길이 cap + prompt 강화
운영 데이터에서 ready 박힌 풀이가 793/838/866자 — 권장 200~400 대비 큰 편.
1차 운영 후 결과 화면 가독성 + 토큰 사용량 통제 위해 prompt 강화 + 저장 전 cap.
Prompt (study_explanation_envelope.txt):
- explanation_md 권장 300~600자, 최대 900자 명시
- 핵심 개념 + 정답 근거 + 헷갈리는 1~2개 오답만 — 모든 오답 풀이 X
- explanation_md 안 줄바꿈 최소화 (parse_json fix 와 결합 — invalid escape 줄임)
- LaTeX 수식 자제 — \\circ/\\text/\\, 매크로 가능하면 평문 ('0°C', 'C')
- 출력은 raw JSON 한 객체만 — 코드 펜스/thinking/메타 X 강조
Worker (study_explanation_worker.py):
- _cap_explanation_md(text, max_chars=1200) 헬퍼 신규
· 1200자 이하 passthrough
· 초과 시 마지막 200자 안에서 \\n\\n / \\n / '. ' / '다.' / '요.' 경계 탐색
· 경계에서 자르기 + '…' (단어 중간 자르기 회피)
· 경계 못 찾으면 단순 자르기 + '…'
- save 전 cap 적용. ai_explanation_status='ready' 유지 (cap 됐다고 failed X)
- payload 에 운영 분석 metadata: explanation_len_original / _saved / capped 플래그
검증:
- tests/test_explanation_cap.py (6 케이스)
· short passthrough / exact at limit / paragraph boundary / sentence boundary
· no boundary fallback / empty input
- scripts/phase4_health.sql 섹션 8/9 추가
· ai_explanation 길이 p50/p95/max (study_questions.ready)
· cap 작동 빈도 (job.payload 의 explanation_capped/_original/_saved)
cap 1200 = 800 (4-B summary_md) 보다 여유 — 기사시험 풀이는 공식+오답+개념 묶이면
800 빡빡함. 운영 후 800~1000 으로 조정 검토.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""Phase 4-A 후속 — explanation_md 길이 cap 단위 테스트.
|
||||
|
||||
worker 의 _cap_explanation_md 헬퍼가 plan 의도대로 동작하는지 검증.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / "app"))
|
||||
|
||||
from workers.study_explanation_worker import ( # noqa: E402
|
||||
EXPLANATION_MAX_CHARS,
|
||||
_cap_explanation_md,
|
||||
)
|
||||
|
||||
|
||||
def test_cap_short_text_passthrough():
|
||||
"""1200자 이하는 변경 없이 통과."""
|
||||
text = "짧은 풀이." * 50 # ~300자
|
||||
assert _cap_explanation_md(text) == text
|
||||
|
||||
|
||||
def test_cap_exactly_at_limit():
|
||||
"""딱 1200자는 그대로."""
|
||||
text = "ㄱ" * EXPLANATION_MAX_CHARS
|
||||
assert len(_cap_explanation_md(text)) == EXPLANATION_MAX_CHARS
|
||||
|
||||
|
||||
def test_cap_long_text_with_paragraph_boundary():
|
||||
"""1500자 + 마지막 200자 안에 \\n\\n 있으면 거기서 자르기 + …"""
|
||||
head = "정답 풀이 본문. " * 60 # ~1200자
|
||||
boundary_pos = 1100
|
||||
text = head[:boundary_pos] + "\n\n" + "추가 단락 본문." * 50 # 1500자+
|
||||
assert len(text) > EXPLANATION_MAX_CHARS
|
||||
capped = _cap_explanation_md(text)
|
||||
assert len(capped) <= EXPLANATION_MAX_CHARS + 1 # + "…"
|
||||
assert capped.endswith("…")
|
||||
|
||||
|
||||
def test_cap_long_text_with_sentence_boundary():
|
||||
"""\\n\\n 이 없으면 마침표에서 자르기."""
|
||||
parts = ["문장 한 개가 약간 깁니다. " for _ in range(100)]
|
||||
text = "".join(parts) # 약 2400자
|
||||
assert len(text) > EXPLANATION_MAX_CHARS
|
||||
capped = _cap_explanation_md(text)
|
||||
assert len(capped) <= EXPLANATION_MAX_CHARS + 1
|
||||
assert capped.endswith("…")
|
||||
# 자른 위치가 마침표/공백 직후여야 — 단어 중간 X
|
||||
body_no_ellipsis = capped[:-1]
|
||||
assert body_no_ellipsis[-1] in (".", " ", "다", "요"), \
|
||||
f"last char before ellipsis: {body_no_ellipsis[-1]!r}"
|
||||
|
||||
|
||||
def test_cap_no_boundary_falls_back_to_plain_cut():
|
||||
"""경계 마커 (\\n, ., 다., 요.) 가 전혀 없으면 단순 자르기."""
|
||||
text = "ㅁ" * 2000 # 한글 한 글자만 반복, 경계 0
|
||||
capped = _cap_explanation_md(text)
|
||||
assert capped == "ㅁ" * EXPLANATION_MAX_CHARS + "…"
|
||||
assert len(capped) == EXPLANATION_MAX_CHARS + 1
|
||||
|
||||
|
||||
def test_cap_empty_input():
|
||||
assert _cap_explanation_md("") == ""
|
||||
assert _cap_explanation_md(None) is None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cap_short_text_passthrough()
|
||||
test_cap_exactly_at_limit()
|
||||
test_cap_long_text_with_paragraph_boundary()
|
||||
test_cap_long_text_with_sentence_boundary()
|
||||
test_cap_no_boundary_falls_back_to_plain_cut()
|
||||
test_cap_empty_input()
|
||||
print("OK")
|
||||
Reference in New Issue
Block a user