f512d94c74
git-subtree-dir: clients/ds-app git-subtree-mainline:a24e3e6f22git-subtree-split:5206cf3b0c
83 lines
4.0 KiB
Swift
83 lines
4.0 KiB
Swift
// Composition.swift — S2 → S3 통합 진입점 (소비모델 b) + 엔드포인트 단일소스 config.
|
|
//
|
|
// INTEGRATION (소비모델 b): 앱(S3)은 이 패키지의 `AIFabric` product 에 **로컬 SwiftPM 의존**으로 붙고,
|
|
// Sources/AI 를 앱 타깃에 직접 포함하지 않는다(소스 이중소유/중복심볼 방지). S3 는 makeDefaultRouter(...)
|
|
// 하나로 실 라우터를 와이어링하고 MockAIProvider 를 대체한다. 구체 DSAskClient(HTTP) = S3 소유.
|
|
//
|
|
// 엔드포인트 단일소스(S2-Fa): raw URL 산재 금지 — 부주의한 편집의 침묵 엔드포인트 swap 방지
|
|
// (2026-05-17 Hermes incident 선례). env override → 검증된 기본값. ([[feedback_hermes_config_single_source_envvar]])
|
|
import Foundation
|
|
import os
|
|
|
|
public struct AIProviderConfiguration: Sendable {
|
|
/// 맥미니 llm-router base (trailing slash 없는 base; 경로는 provider 가 append).
|
|
public var localMLXBaseURL: URL
|
|
/// llm-router 모델 별칭(라이브 확정 2026-06-05: 'mac-mini-default' → gemma-4-26b resolve).
|
|
public var localMLXModel: String
|
|
/// DS API base — S3 의 DSAskClient 가 사용. 공인 https://document.hyungi.net/api · 내부 http://100.110.63.63:8000/api.
|
|
/// 주의: DS `/search/ask` 는 **trailing slash 필수**(경로 결합 시 S3 client 가 보장).
|
|
public var dsBaseURL: URL
|
|
public var requestTimeout: TimeInterval
|
|
public var probeTimeout: TimeInterval
|
|
|
|
public init(
|
|
localMLXBaseURL: URL,
|
|
localMLXModel: String = "mac-mini-default",
|
|
dsBaseURL: URL,
|
|
requestTimeout: TimeInterval = 60,
|
|
probeTimeout: TimeInterval = 2
|
|
) {
|
|
self.localMLXBaseURL = localMLXBaseURL
|
|
self.localMLXModel = localMLXModel
|
|
self.dsBaseURL = dsBaseURL
|
|
self.requestTimeout = requestTimeout
|
|
self.probeTimeout = probeTimeout
|
|
}
|
|
|
|
/// 환경변수 override → 검증된 기본값(단일 source). 키 부재 시 기본값.
|
|
public static func resolved(
|
|
environment: [String: String] = ProcessInfo.processInfo.environment
|
|
) -> AIProviderConfiguration {
|
|
let localMLX = environment["AIFABRIC_LOCALMLX_URL"].flatMap(URL.init(string:))
|
|
?? URL(string: "http://100.76.254.116:8890")!
|
|
let model = environment["AIFABRIC_LOCALMLX_MODEL"] ?? "mac-mini-default"
|
|
let ds = environment["AIFABRIC_DS_URL"].flatMap(URL.init(string:))
|
|
?? URL(string: "https://document.hyungi.net/api")!
|
|
return AIProviderConfiguration(localMLXBaseURL: localMLX, localMLXModel: model, dsBaseURL: ds)
|
|
}
|
|
}
|
|
|
|
/// 기본 OSLog 라우팅 훅 — 폴백/스킵을 가시화(silent 금지). S3 도 참조 가능(public).
|
|
public enum AIFabricLog {
|
|
static let router = Logger(subsystem: "ds-app.AIFabric", category: "AIRouter")
|
|
public static let routerHook: @Sendable (String) -> Void = { msg in
|
|
router.info("\(msg, privacy: .public)")
|
|
}
|
|
}
|
|
|
|
/// S3 → S2 단일 진입점. 4 provider 전부 등록(vision 체인 가시 폴백 보장) + 기본 정책 + log 훅.
|
|
/// - client: S3 가 주입하는 구체 DS ask client(HTTP).
|
|
/// - config: 엔드포인트 단일소스(기본 = env override → 검증 기본값).
|
|
/// - session: LocalMLX URLSession(기본 .shared; 테스트는 mock 주입).
|
|
public func makeDefaultRouter(
|
|
client: DSAskClient,
|
|
config: AIProviderConfiguration = .resolved(),
|
|
session: URLSession = .shared,
|
|
policy: AIRoutingPolicy = .default,
|
|
log: @escaping @Sendable (String) -> Void = AIFabricLog.routerHook
|
|
) -> AIRouter {
|
|
let providers: [AIProviderID: any AIProvider] = [
|
|
.remoteDS: RemoteDSProvider(client: client),
|
|
.localMLX: LocalMLXProvider(
|
|
baseURL: config.localMLXBaseURL,
|
|
model: config.localMLXModel,
|
|
session: session,
|
|
requestTimeout: config.requestTimeout,
|
|
probeTimeout: config.probeTimeout
|
|
),
|
|
.onDevice: OnDeviceProvider(),
|
|
.specialized: SpecializedProvider(), // scaffold(불가) — vision 폴백 가시화
|
|
]
|
|
return AIRouter(providers: providers, policy: policy, log: log)
|
|
}
|