Files
life-helper/docs/adr/0003-on-device-llm-gemma.md
joungmin d31b17f3e8 [Architect] #215 ADR-0003 + design spec for Gemma frame suggest
- ADR-0003: on-device LLM Gemma 4 E2B Q4_0 + flutter_gemma 도입 결정.
  5개 대안(클라우드/정적확장/Llama/E4B/APK번들) 기각 사유 명시.
- docs/design/215-gemma-frame-suggest/: 설계서 게이트 통과 산출물.
  README.md (12 섹션 전부 + AC10 + OQ6 + 함수 15개) +
  fn-suggest_frame.md (suggestFrame/buildFewShotPrompt/parseFrameCandidates) +
  fn-model_lifecycle.md (LlmService/GemmaLlmService/ModelLifecycle).
- graceful degradation 전면: AI 실패 시 throw 없이 빈 리스트 + 수동 입력 유지.
- LlmService 추상화로 도메인 ↔ flutter_gemma 경계 분리 (테스트 가능성).

Refs #215
2026-06-12 11:16:15 +09:00

106 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0003: On-device LLM 도입 — Gemma 4 E2B Q4_0 + `flutter_gemma`
> **상태**: Accepted
> **날짜**: 2026-06-12 · **결정자**: [AI] Architect (사용자 Planner 채택안 수용) · **관련 이슈**: #215
> **관련 ADR**: 없음 (신규 결정)
## 맥락 (Context)
life-helper Phase 1 MVP(#204) 위에 사용자 입력 자동화 기능을 얹는다. 입력 마찰점은
1) L0/L1 raw text → L2/L3 프레임 재작성, 2) 자유서술 → 구조화 변환이다.
기존 `FramePattern` 30개 정적 카탈로그는 keyword exact-match 기반이라
사용자 입력 다양성(synonym, 한국어 구어체, 도메인 변형)을 흡수하지 못한다.
선택지는:
- **A. 클라우드 LLM API** (OpenAI / Claude / Gemini) — 품질 ↑, 비용·프라이버시·오프라인 X.
- **B. On-device LLM** (Gemma 4 / Llama / Phi 등) — 프라이버시·오프라인 ↑, 품질·기기 부담.
- **C. 도입 X, 정적 카탈로그 확장** — 안전하지만 동적 입력 흡수 한계.
Planner(#215)가 5개 의사결정으로 **B 채택안 + Gemma 4 E2B + 런타임 다운로드 + opt-in**
방향을 확정했다. 본 ADR은 그 선택을 되돌리기 어려운 기술 결정으로 고정한다.
## 결정 (Decision)
life-helper에 **on-device LLM**을 도입한다. 모델은 **Gemma 4 E2B (Q4_0 양자화)** 단일.
런타임 통합은 **`flutter_gemma`** 패키지(LiteRT-LM Android backend) 사용.
모델 파일은 **opt-in 토글 후 런타임에 백그라운드 다운로드**, opt-out 시 즉시 삭제.
모든 추론은 **on-device 전용**이며 어떠한 형태의 클라우드 fallback도 두지 않는다.
함수 호출(function calling) 기능을 사용해 LLM 응답을 **구조화된 JSON**으로 받는다.
프롬프트는 정적 하드코딩이 아닌 **SoT 카탈로그(`FramePattern` / `Protocol`)에서
런타임에 few-shot 예시를 동적 추출**해 조립한다.
## 근거 (Rationale)
- **프라이버시 = SoT 원칙과 일치**: life-helper는 개인 습관·중독·실패 기록을 다룬다.
사용자 자유서술이 외부로 떠나면 안 된다. on-device 전용은 비타협 요구.
- **오프라인 동작**: 사용자는 새벽·이동 중·산행 등 네트워크 약한 환경에서도 체크인한다.
클라우드 의존은 R8(≤ 60초 체크인) 보장 불가.
- **Gemma 4 E2B Q4_0 ≈ 1.5GB RAM**: 6GB RAM mid-range Android 까지 커버.
E4B(5GB)는 high-end 한정. v1은 E2B 단일로 시작해 hardware fragmentation 단순화.
- **`flutter_gemma`의 function calling**: LLM 응답을 자유 텍스트가 아닌 구조화 JSON으로
강제 → 파싱 신뢰도 ↑, 도메인 모델(`FrameCandidate`) 직결.
- **SoT few-shot 동적 추출**: 정적 prompt 하드코딩은 SoT(`huberman-protocols.md` /
`FramePattern` 카탈로그) 변경 시 동기화 부담 발생. 런타임 조립은 SoT 1회 갱신으로 전파.
- **런타임 다운로드**: APK +1.5GB는 첫 진입 장벽 과대. Play Store 제한(150MB)도 위반.
WiFi 권장 + 일시정지/재개 + 진행률 UX로 흡수.
- **Opt-in 기본 OFF**: AI 기능을 "추가 비용(스토리지·배터리)을 감내한 사용자만"이
켠다는 명시적 동의로, R6 사용자 통제권 원칙과 일치.
## 결과 (Consequences)
- **긍정**:
- 클라우드 비용·레이트리밋·약관 변경 리스크 0.
- 사용자 자유서술이 절대 단말 밖으로 나가지 않음 → 신뢰 자산.
- 오프라인 100% 동작.
- `flutter_gemma`가 Android(LiteRT-LM) + iOS 둘 다 지원 → Phase 2 iOS 진입 시 재사용.
- Function calling 응답이 구조화 → 도메인 모델 직접 매핑, 파싱 코드 최소.
- SoT 카탈로그 갱신이 LLM 출력 품질에 즉시 반영 (정적 prompt 동기화 부담 X).
- **부정 / 비용**:
- 모델 파일 1.5GB 다운로드 — UX 부담. 일시정지/재개 + 진행률 + WiFi 권장 필수.
- 첫 추론 latency 13초 (모델 로드 cold start). 이후 0.52초/응답.
- Pixel 5 이하·RAM 4GB 이하 기기는 로드 실패 가능 → 사전 사양 체크 필요.
- 배터리 0.51% / 추론 추정. 빈도 제한(throttle) 정책 필요.
- `flutter_gemma` 패키지 semver 변경 위험 — v1은 latest version 고정 후 pubspec.lock 동결.
- 한국어 품질 미검증 — Architect 단계 prototype 통과가 게이트 조건.
- **후속 작업**:
- Architect: 설계서 `docs/design/215-gemma-frame-suggest/` 작성 (본 ADR과 한 쌍).
- Architect: 한국어 prompt 프로토타입 통과 확인 (Open Question).
- Developer: `flutter_gemma` 의존성 추가, `LlmService` 인터페이스 + `GemmaLlmService` 구현.
- QA: 한국어 출력 품질 평가 corpus 30 케이스, 저사양 기기(RAM 4/6/8GB) 회귀.
- Documenter: 사용자 가이드에 "AI 도움" 섹션 추가 (오프라인·프라이버시 강조).
## 검토한 대안 (Alternatives Considered)
- **A. 클라우드 LLM API (OpenAI / Claude / Gemini)**
- 기각 사유: (1) 사용자 자유서술 외부 전송 → SoT 프라이버시 원칙 위반.
(2) 오프라인 미지원 → R8(≤ 60초 체크인) 보장 불가.
(3) API 비용·레이트리밋·약관 변경 리스크.
(4) Vendor lock-in.
- **C. 도입 X, 정적 카탈로그 확장 (FramePattern 30 → 100 → 300...)**
- 기각 사유: (1) 동적 입력(synonym, 구어체) 흡수 한계는 카탈로그 크기로 해결 불가.
(2) 한국어 변형 폭이 너무 넓어 enumeration 비현실적.
(3) Architect/Developer 유지보수 부담이 LLM 도입보다 큼 (역설).
- **D. Llama 3.2 1B / Phi-3.5 Mini**
- 기각 사유: (1) Gemma 4 E2B가 한국어 성능 더 좋다는 Google 자체 벤치 + 한국 커뮤니티 보고.
(2) `flutter_gemma`가 first-class 지원 — Llama용 동급 Flutter 패키지 부재.
(3) Google이 LiteRT-LM Android 최적화에 직접 투자.
- **E. E4B (4B parameter)**
- 기각 사유: 5GB RAM 요구 → high-end Pixel 8+ / Galaxy S24+ 한정.
Phase 2-A는 mid-range까지 커버가 우선. E4B는 v1.1 이상에서 토글 옵션으로 추가.
- **F. APK 번들 모델**
- 기각 사유: (1) Play Store APK 제한 150MB 위반. AAB로 회피 시도 가능하나 download size 절감 효과 미미.
(2) AI 미사용자에게도 1.5GB 강제 → 옵트인 원칙 위반.
## 추적성
- 설계서: `docs/design/215-gemma-frame-suggest/README.md` + `fn-*.md`.
- 의존: #204 Phase 1 MVP의 `FramePattern` 30 시드 + `validate_frame_level` 함수.
- 후속 결정 가능성: ADR-0004 (E4B 토글), ADR-0005 (시나리오 #2~#6 함수 확장).