# DS App AI 라우팅 계약 (S2 인터페이스 동결) — v0.1 S1(데이터 fixture)이 앱의 **데이터 모양**을 동결했듯, 이 문서 + `Sources/AI/`는 앱의 **AI 호출 모양**을 동결한다. S3 는 `AIProvider` 프로토콜 + `MockAIProvider` 로 AI 흐름을 끝까지 그리고, S2 가 실 provider 를 뒤에서 채운다. - **Frozen**: 2026-06-04 · **typecheck PASS** (swift 6 strict concurrency) · **router 스모크 PASS** - **위치**: `Sources/AI/{AIProvider,AIRouter,MockAIProvider}.swift` + `Sources/AI/Providers/*.swift` - **파일 경계**: 이 `AI/` 디렉토리 = **S2 소유**(앱 나머지 = S3). 같은 Xcode repo 디렉토리 단위 분담 → 충돌 0. ## 1. Provider 티어 (= 디바이스 역할 표와 1:1) | `AIProviderID` | 노드 | 용도 | 구현 경로 | |---|---|---|---| | `.onDevice` | 맥북·아이폰 | 즉답·오프라인·프라이버시 | Apple **FoundationModels** (`SystemLanguageModel`/`LanguageModelSession`) | | `.localMLX` | 맥미니 허브 | 무거운 로컬 생성 | Gemma 4 26B — llm-router `:8890` / MLX `:8801` (OpenAI 호환) | | `.remoteDS` | GPU(원격) | **코퍼스 RAG** | `GET /search/ask?backend=` (CONTRACT.md §4) | | `.specialized` | GPU | rerank·embed·**vision**·OCR | 특화 모델 통로(온디맨드) | ## 2. 태스크 → 티어 정책 (`AIRoutingPolicy.default`) | `AITask` | 선호 체인 | 근거 | |---|---|---| | `quickSummarize` | onDevice → localMLX | 빠르고 사적 | | `memoAssist` | onDevice → localMLX | 짧은 보조 | | `askSelection` | onDevice → localMLX → remoteDS | 로컬 컨텍스트, 부족 시 승급 | | `corpusAsk` | **remoteDS only** | 코퍼스 필요 — 온디바이스로 폴백 불가 | | `classify` | localMLX → remoteDS → onDevice | 분류 품질 우선 | | `vision` | specialized → onDevice | GPU VLM 우선 | ## 3. 3단 fallback 규칙 (feedback_task_routing_hybrid + no_silent_fallback) 1. **explicit > rule > error.** 2. **명시 opt-in**(`request.explicitProvider`) → 그 provider 만. 불가 시 **에러**(`explicitProviderUnavailable`) — 자동 다른 티어 호출 **금지**. 3. **미지정** → 태스크 선호 체인 순회. 불가/실패는 다음으로 넘기되 **`routingNote`로 가시화**(silent skip 금지) + `log` 훅. 4. 전부 불가 → `noProviderAvailable`. 스모크 검증: ``` quickSummarize → onDevice corpusAsk → remoteDS (citations=1) vision → specialized classify → localMLX explicit onDevice 불가 → 에러(자동 fallback X) rule fallback: onDevice 불가 → localMLX note="fallback from onDevice → localMLX" ``` ## 4. S1 계약과의 다리 (`RemoteDSProvider`) `corpusAsk` 만 `.remoteDS` 책임. 매핑(고정): ``` GET /search/ask?q=&backend= → AskResponse (CONTRACT.md §4) AskResponse.ai_answer → AICompletionResponse.text AskResponse.citations[] → AICitation[] (n, doc_id, title, section_title, span_text) AskResponse.synthesis_status → AIFinishReason (completed/timeout/no_evidence/backend_unavailable/…) AskResponse.confidence → AIConfidence AskResponse.backend_used → routingNote (어느 LLM 이 응답했는지) ``` `backend` 매핑: `nil`→`mac-mini-default` · `.localMLX`→`gemma-macmini` · (M5 Max Qwen 경로)→`qwen-macbook` · cloud→`claude-cloud`(503, 별 PR). ## 5. S2 가 다음에 채울 것 (인터페이스는 고정) - `OnDeviceProvider`: `import FoundationModels` 가용성 프로브 + `LanguageModelSession` 호출. - `LocalMLXProvider`: 맥미니 OpenAI 호환 호출(messages system/user 분리 call-shape 고정). - `RemoteDSProvider`: S3 의 DS API client 주입 → 위 §4 매핑 결선. - `SpecializedProvider`: GPU 비전/특화 통로(필요 태스크만). ## 동시 출발선 (S1 + S2 둘 다 동결 완료) - **S3** = `MockAIProvider` 주입 + S1 `fixtures/` 디코딩 → 실 백엔드·실 LLM 대기 0 으로 앱 전체 빌드. - **S1** = `[S1-ADD]` 구현 + 응답 shape 유지. - **S2** = 위 §5 provider 결선. 인터페이스(`AIProvider`/`AIRouter`) 변경 시만 합의.