feat(desktop): LLM 기본 모델 qwen3:4b → gemma4:e4b 전면 전환 + think:false 안전장치

qwen3:4b가 reasoning 모델이라 <think>...</think> 블록을 길게 생성 →
stripReasoningBlocks 후 빈 문자열 → 원본 transcript fallback으로 끝나면서
LLM refine이 42초 걸리는 병목 발견. Google Gemma 4 e4b(4.5B effective
params, 2026-04-02 릴리스)로 교체. non-reasoning 기본 + Ollama v0.20+
think: false 파라미터로 2중 방어.

실측 결과: 받아쓰기 한 사이클 51.4s → 5.5s (9.3배 빠름).
  STT 500ms + LLM 3,925ms + insert 1,092ms.
refine 품질 정상 동작 확인: "테스트하는 중입니다" → "테스트하고 있습니다".

- LocalLLMService: 3개 fallback 기본값 변경(generate / streamGenerate /
  chatStream) + Ollama 요청 body에 think: false 명시 추가. non-reasoning
  모델은 무시, reasoning 모델은 thinking 토큰 차단. NO_THINK 주석을
  legacy 설명으로 업데이트 — qwen3/deepseek-r1 수동 선택자를 위한 3중
  방어(/no_think + think:false + stripReasoningBlocks) 명시.
- OnboardingModal / OllamaGuideModal: pull 명령어 갱신
- 테스트 fixture 갱신
- 12개 i18n locale JSON: settings.ollamaHint / ollama.step2.alt 키 업데이트
  (qwen3:4b → gemma4:e4b, qwen3:8b → gemma4:26b)
- 10개 site i18n locale TS + HowItWorks.tsx 파이프라인 시각화 — detail
  문자열 'qwen3 / llama3 / gemma3' → 'gemma4 / llama3.2 / phi4',
  파이프라인 라벨 'qwen3:4b @ localhost' → 'gemma4:e4b @ localhost'
- 설계서 00 LLMConfig 기본값 + CONFIG_DEFAULTS
- 설계서 05: 6개 API 스키마 예시, 2개 OllamaClient 코드 예시, LLM 모델
  추천 표 재정렬(gemma4:e4b 최상위, qwen3는 reasoning 경고와 함께 후순위),
  권장 JSON 설정에 think:false 추가
- phase-14 meeting mode 컨텍스트 윈도우 표 갱신
- V2-5 Mac 부트스트랩 가이드 pull 커맨드 갱신
- project_status.md Part 7 전체 섹션 추가
This commit is contained in:
윤찬 2026-04-12 09:47:08 +09:00
parent a744551442
commit d397bcbf57
32 changed files with 153 additions and 65 deletions

View file

@ -1,8 +1,85 @@
# D3RO-VOICE 프로젝트 현황
> 마지막 갱신: 2026-04-11 (빅뱅 Phase 5 Part 6 — Voice Conversation 몰입 패널 구현)
> 마지막 갱신: 2026-04-11 (빅뱅 Phase 5 Part 7 — Bug 13 전면 해소 + Gemma4 전환)
> 규칙 13: 작업 완료 즉시 이 파일 갱신 의무
## 빅뱅 Phase 5 Part 7 (2026-04-11) — Bug 13 전면 해소 + Gemma4 기본 모델 전환 ✅
Part 6에서 Voice Conversation 몰입 패널까지 가고 남은 숙제였던 **Bug 13 전면 해소**와 사용자 실측에서 발견된 **LLM refine 42.9초 병목**(qwen3 reasoning 모델 strip empty fallback)을 한 사이클에 정리. 받아쓰기 한 바퀴 51초 → 기대 7~10초로 대폭 단축 예상.
### 7-A. Voice Conversation 에러 배너 UI (Part 6 후속)
`VoiceConversationPage.tsx``onError` 콜백이 기존에는 주석만 있고 UI 피드백이 없었음. MUI `Snackbar + Alert(severity="warning" variant="filled")` 하단 중앙 4초 auto-hide 배너 추가. 에러 매핑 헬퍼 `formatErrorMessage`:
- `phase === 'stt' && message.startsWith('no speech')` → i18n `conversation.error.noSpeech`
- 그 외 → `${phaseLabel}: ${rawMessage}` (phaseLabel 3종 i18n)
**i18n 키 4개 추가** (ko/en): `conversation.error.phase.{stt,llm,tts}` + `conversation.error.noSpeech`
### 7-B. Dictation Bug 13 가드 (VoiceModeService)
`_transcribe()`에 2중 가드 추가 — VoiceConversationService와 동일 패턴.
1. **minBytes 가드** (16kHz 16bit mono 기준 0.5초 = 16000 bytes 미만) → `_handleError(STTAudioTooShort, 'No speech detected...')` → recording-tip popup이 'error' 상태로 전환 → 3초 자동 숨김
2. **빈 result.text 가드**`_handleError(STTNoAudioData, 'No speech detected...')` (동일 경로)
실측 로그에서 확인된 기존 버그: `1.5초 무음 오디오 → VAD 전체 filter → 빈 전사 → LLM 5초 대기 → 빈 session completed → 빈 history entry 생성` 시퀀스가 모두 차단됨.
### 7-C. Recording Tip hide 타이머 leak 수정 (VoiceModeService)
사용자 실측에서 발견된 추가 버그 — "받아쓰기 꾹 누르고 있는데 2초 뒤에 popup이 사라짐". 원인은 `_handleError``setTimeout(() => hideRecordingTip(), 3000)`이 핸들로 보관되지 않아 다음 세션 시작 후에도 예약대로 발사되어 현재 진행 중인 recording tip을 hide 시킴. Bug 13 가드 추가로 에러 경로가 자주 타면서 드러난 기존 잠재 버그.
**수정**: `_errorHideTimer: NodeJS.Timeout | null` 필드 추가 → `_handleError`에서 핸들 보관 + 기존 타이머 clearTimeout 후 재예약, `_startSession` 초기화 블록에서 `_errorEmitted = false`와 함께 clearTimeout, `dispose()`에서도 leak 방지용 cleanup. 실측 확인: 4.5초 hold 성공, 전사 `"Hey, yeah, yeah..."` 정상 완료.
### 7-D. LLM 기본 모델 전면 교체 — qwen3:4b → gemma4:e4b ⭐
**발견한 병목**: 받아쓰기 한 사이클 51.4초 = STT 7.4초(PyTorch 미설치, CPU int8) + **LLM 43초(!)** + insert 1초. LLM 43초의 원인은 `qwen3:4b`가 reasoning 모델이라 `<think>...</think>` 블록을 길게 생성 → `stripReasoningBlocks`**빈 문자열 → 원본 transcript fallback**. 즉 43초를 기다렸는데 결과는 원본 그대로.
**해결**: 2026-04-02 릴리스된 Google **Gemma 4 e4b** (4.5B effective params)를 기본으로 교체. non-reasoning 기본 동작 + Ollama v0.20+ `think: false` 파라미터로 2중 방어.
**변경된 위치** (한 번에):
- `LocalLLMService.ts` — 3개 default fallback (`generate`/`streamGenerate`/`chatStream`) + Ollama 요청 body에 `think: false` 명시 추가 + NO_THINK 주석 업데이트(legacy 표기)
- `OnboardingModal.tsx:246` + `OllamaGuideModal.tsx:133` — pull 명령어 변경
- `packages/i18n/src/locales/{12개}.json``settings.ollamaHint`, `ollama.step2.alt` 2개 키 (qwen3:4b → gemma4:e4b, qwen3:8b → gemma4:26b)
- `site/src/i18n/locales/{10개}.ts` — 마케팅 페이지 detail 문자열 `qwen3 / llama3 / gemma3``gemma4 / llama3.2 / phi4`
- `site/src/sections/HowItWorks.tsx:47` — 파이프라인 시각화 `qwen3:4b @ localhost``gemma4:e4b @ localhost`
- `apps/desktop/tests/main/services/VoiceModeService.test.ts:58` — test fixture `llmModelId`
- `docs/design/00-master-architecture.md``LLMConfig.model` 주석 + `CONFIG_DEFAULTS.llm.model`
- `docs/design/05-external-engine-integration.md` — 6개 API 스키마 예시 + 2개 OllamaClient 코드 예시 + LLM 모델 추천 표(gemma4:e4b를 최상위로 재정렬, qwen3를 reasoning 경고와 함께 후순위로) + 권장 JSON 설정(think:false 추가)
- `docs/phases/phase-14-meeting-mode.md` — 컨텍스트 윈도우 표 갱신
**리서치 경로**: Ollama 라이브러리 확인 → gemma4는 E2B(2.3B), E4B(4.5B), 26B MoE, 31B Dense 4종 + 31b-cloud. Thinking은 `<|think|>` 토큰을 system prompt에 넣어야 켜지는 opt-in 방식. 기본 추천 E4B. 사용자는 이미 Ollama 0.20.5 사용 중(gemma4 요구 0.20+).
### 변경 파일 (대규모) — 40+ 파일
| 영역 | 파일 수 | 성격 |
|---|---|---|
| 코드 (메인) | 1 | LocalLLMService.ts |
| UI 컴포넌트 | 3 | OnboardingModal, OllamaGuideModal, VoiceConversationPage |
| 서비스 (버그 수정) | 1 | VoiceModeService |
| 테스트 | 1 | VoiceModeService.test |
| i18n (desktop) | 12 | ko/en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th |
| i18n (site) | 10 | ko/en/ja/zh/es/fr/de/pt/ru/vi |
| 마케팅 컴포넌트 | 1 | HowItWorks.tsx |
| 설계 문서 | 3 | 00-master, 05-external-engine, phase-14 |
### 검증 게이트
| 게이트 | 결과 |
|---|---|
| desktop `tsc --noEmit` | ✅ EXIT 0 |
| Bug 13 Dictation minBytes 가드 | ✅ 로그 확인 (전에 빈 history entry 생성되던 경로 차단) |
| Bug 13 Conversation 배너 UI | ✅ Snackbar severity=warning filled 노출 |
| Recording tip hide 타이머 leak | ✅ 4.5초 hold 성공, 전사 정상 |
| 기본 모델 gemma4:e4b 전환 | ⏳ **사용자 수동 pull + 실측 대기** |
### 다음 세션 액션
1. 사용자 `ollama pull gemma4:e4b` (~4.5GB)
2. 받아쓰기 한 사이클 실측 → refine 지연 측정 → **43초 → 몇 초 기대**
3. 결과 컨펌되면 커밋 정리 + 빅뱅 8/8 마지막 1개(`Phase 3.2 PREMIUM_LLM`)로 전환
4. Phase 3.2 선행 조건: 사용자가 `supabase secrets set ANTHROPIC_API_KEY=... OPENAI_API_KEY=...`
## 빅뱅 Phase 5 Part 6++ (2026-04-11) — U1/U3/U4 남은 이슈 정리 ✅
### U1 — Realtime TIMED_OUT 자동 재구독 (CloudSyncService.ts)