f512d94c74
git-subtree-dir: clients/ds-app git-subtree-mainline:a24e3e6f22git-subtree-split:5206cf3b0c
144 lines
5.1 KiB
Swift
144 lines
5.1 KiB
Swift
// AIProvider.swift — S2 인터페이스 동결 (멀티디바이스 LLM 라우팅 패브릭)
|
|
//
|
|
// 이 파일이 앱(S3)이 코딩하는 "AI 호출 모양"을 동결한다.
|
|
// S3 는 이 프로토콜 + MockAIProvider 로 빌드하고, S2 가 실제 provider
|
|
// (OnDevice / LocalMLX / RemoteDS / Specialized)를 그 뒤에서 채운다.
|
|
//
|
|
// Foundation 외 의존 0 → 표준 출발선. 실제 구현(FoundationModels / MLX / URLSession)은
|
|
// Providers/ 의 각 스켈레톤에서 S2 가 결선.
|
|
|
|
import Foundation
|
|
|
|
// MARK: - Provider 식별 (디바이스 역할 = 인프라 패브릭과 1:1)
|
|
|
|
public enum AIProviderID: String, Codable, Sendable, CaseIterable {
|
|
/// 맥북·아이폰 온디바이스 (Apple Foundation Models). 즉답·오프라인·프라이버시.
|
|
case onDevice
|
|
/// 맥미니 메인 로컬 LLM 허브 (Gemma 4 26B, MLX :8801 / llm-router :8890). 무거운 생성.
|
|
case localMLX
|
|
/// 원격 DS 코퍼스 RAG (`GET /search/ask`). 전체 지식베이스 질의 — 온디바이스 불가 영역.
|
|
case remoteDS
|
|
/// GPU 특화 통로 (rerank / embed / vision / OCR). 온디맨드.
|
|
case specialized
|
|
}
|
|
|
|
// MARK: - 태스크 분류 (앱이 AI 에 요청하는 일의 종류 → 라우팅 기준)
|
|
|
|
public enum AITask: String, Codable, Sendable {
|
|
/// 선택 텍스트/문서 빠른 요약.
|
|
case quickSummarize
|
|
/// 메모 정리·제목 생성·태그 제안.
|
|
case memoAssist
|
|
/// 현재 문서/선택에 대한 질문 (로컬 컨텍스트, 코퍼스 불필요).
|
|
case askSelection
|
|
/// 전체 지식베이스 RAG 질의 (코퍼스 필요 → 원격 DS).
|
|
case corpusAsk
|
|
/// 문서 분류 (도메인/카테고리 제안).
|
|
case classify
|
|
/// 이미지/스캔 PDF 이해 (비전).
|
|
case vision
|
|
}
|
|
|
|
public enum AIConfidence: String, Codable, Sendable {
|
|
case high, medium, low
|
|
}
|
|
|
|
public enum AIFinishReason: String, Codable, Sendable {
|
|
case completed // 정상 완료
|
|
case refused // 모델이 답변 거부 (근거 부족 등)
|
|
case timeout // 생성 지연으로 생략
|
|
case unavailable // provider 사용 불가 (503 계열)
|
|
case noEvidence // (corpusAsk) 관련 근거 없음
|
|
}
|
|
|
|
// MARK: - 요청 / 응답
|
|
|
|
public struct AICompletionRequest: Sendable {
|
|
public var task: AITask
|
|
public var prompt: String
|
|
/// 선택 텍스트·문서 본문 등 로컬 컨텍스트 (corpusAsk 는 보통 nil — 코퍼스가 컨텍스트).
|
|
public var context: String?
|
|
public var systemPrompt: String?
|
|
public var maxTokens: Int?
|
|
/// 사용자가 명시적으로 고른 provider. 지정 시 **자동 fallback 금지**(명시 opt-in).
|
|
public var explicitProvider: AIProviderID?
|
|
|
|
public init(
|
|
task: AITask,
|
|
prompt: String,
|
|
context: String? = nil,
|
|
systemPrompt: String? = nil,
|
|
maxTokens: Int? = nil,
|
|
explicitProvider: AIProviderID? = nil
|
|
) {
|
|
self.task = task
|
|
self.prompt = prompt
|
|
self.context = context
|
|
self.systemPrompt = systemPrompt
|
|
self.maxTokens = maxTokens
|
|
self.explicitProvider = explicitProvider
|
|
}
|
|
}
|
|
|
|
/// corpusAsk 응답의 근거 — DS `Citation`(CONTRACT.md §4)에서 매핑.
|
|
public struct AICitation: Codable, Sendable, Identifiable {
|
|
public var id: Int { n }
|
|
public var n: Int
|
|
public var docId: Int
|
|
public var title: String?
|
|
public var sectionTitle: String?
|
|
public var spanText: String
|
|
|
|
public init(n: Int, docId: Int, title: String?, sectionTitle: String?, spanText: String) {
|
|
self.n = n
|
|
self.docId = docId
|
|
self.title = title
|
|
self.sectionTitle = sectionTitle
|
|
self.spanText = spanText
|
|
}
|
|
}
|
|
|
|
public struct AICompletionResponse: Sendable {
|
|
public var text: String
|
|
public var providerUsed: AIProviderID
|
|
public var finishReason: AIFinishReason
|
|
public var citations: [AICitation]
|
|
public var confidence: AIConfidence?
|
|
public var latencyMs: Double?
|
|
/// 라우팅 가시성 — rule-based fallback 으로 1차 선택이 빗나갔을 때 채워짐(silent 금지).
|
|
public var routingNote: String?
|
|
|
|
public init(
|
|
text: String,
|
|
providerUsed: AIProviderID,
|
|
finishReason: AIFinishReason = .completed,
|
|
citations: [AICitation] = [],
|
|
confidence: AIConfidence? = nil,
|
|
latencyMs: Double? = nil,
|
|
routingNote: String? = nil
|
|
) {
|
|
self.text = text
|
|
self.providerUsed = providerUsed
|
|
self.finishReason = finishReason
|
|
self.citations = citations
|
|
self.confidence = confidence
|
|
self.latencyMs = latencyMs
|
|
self.routingNote = routingNote
|
|
}
|
|
}
|
|
|
|
// MARK: - 프로토콜 (S2 가 구현, S3 가 소비)
|
|
|
|
public protocol AIProvider: Sendable {
|
|
var id: AIProviderID { get }
|
|
/// 능력 프로브 (온디바이스 Apple Intelligence 가용? 맥미니 도달 가능? 등).
|
|
var isAvailable: Bool { get async }
|
|
func complete(_ request: AICompletionRequest) async throws -> AICompletionResponse
|
|
}
|
|
|
|
public enum AIProviderError: Error, Sendable {
|
|
case notImplemented(AIProviderID)
|
|
case unavailable(AIProviderID)
|
|
case backendError(AIProviderID, status: Int, reason: String?)
|
|
}
|