fix(output): 한·영 깨진 토큰("덩ey") 결정론 감지 + 1회 수리 패스 (v2.2.230)

소형 로컬 모델이 한국어 단어 중간에 영문 토큰을 섞는 디코딩 사고
("덩어리"→"덩ey", "결과적으로"→"결ently"). 프롬프트 출력 위생 규칙으로는
못 막음 — 지시 불이행이 아니라 토큰 붕괴라서. 사후 보정으로 해결:

- hangulHygiene.ts: 고정밀 감지 패턴(한글 음절+영문 소문자 2+ 연속) —
  "API를"/"Code의"(영문+조사)·"플랜B"(한글+대문자)는 정상 표기로 미감지,
  코드 블록 제외. 감지 시 LLM 수리 1회 (깨진 토큰만 복원, 내용 변경 금지).
- 수리 검증 게이트: 길이 ±35% 이내 + 깨진 토큰 감소 — 미통과 시 원문 유지
  (수리가 더 망치는 것 방지). 실패 전 과정 로그 (관측성 원칙).
- 적용 경로: 채팅 답변(스트림 후·확정 전) + /wikify 산출물(영구 자산이라
  더 중요 — "🩹 표기 오류 N건 교정" 표시).

테스트 11건 (감지 정밀도·검증 게이트·실패 안전).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 13:50:26 +09:00
parent bfb0d23a2f
commit 1208050557
6 changed files with 158 additions and 3 deletions
+11
View File
@@ -445,6 +445,17 @@ async function wikifyOne(url: string, userContent: string, view: Webview | undef
report = await callLmSynthesis(buildWikifyPrompt(data, userContent, canonicalFormat), wikiSystem);
if (!report) throw new Error('LLM 응답이 비어 있습니다.');
report = report.replace(/\[\[([^\[\]]+?)\](?!\])/g, '[[$1]]');
// 한·영 깨진 토큰("덩ey" 류) 감지 시 1회 수리 — 위키 문서는 영구 자산이라
// 채팅보다 더 중요. 검증 미통과면 원문 유지.
try {
const { findBrokenHangulTokens, repairBrokenHangul } = await import('../../agent/hangulHygiene');
const broken = findBrokenHangulTokens(report);
if (broken.length > 0) {
chunk(view, ` 🩹 표기 오류 ${broken.length}건 교정…`);
const fixed = await repairBrokenHangul(report, broken, (system, user) => callLmSynthesis(user, system));
if (fixed) report = fixed;
}
} catch { /* 수리 실패 시 원문 유지 */ }
chunk(view, ` ✓ (${Math.round((Date.now() - synthT0) / 1000)}s)\n\n`);
} catch (e: any) {
const reason = `LLM 합성 실패: ${e?.message || String(e)}`;