FoundationModels: 기기 내 LLM (iOS 26)은(는) 무엇을 하나요?
FoundationModels 프레임워크를 사용하여 Apple의 기기 내 언어 모델을 앱에 통합하는 패턴입니다. 텍스트 생성, @Generable을 사용한 구조화된 출력, 사용자 정의 도구 호출, 스냅샷 스트리밍을 다루며, 모두 기기 내에서 실행되어 프라이버시와 오프라인 지원을 보장합니다.
활성화 시기
- Apple Intelligence를 사용하여 기기 내에서 AI 기반 기능 구축
- 클라우드 의존 없이 텍스트 생성 또는 요약
- 자연어 입력에서 구조화된 데이터 추출
- 도메인 특화 AI 작업을 위한 사용자 정의 도구 호출 구현
- 실시간 UI 업데이트를 위한 스트리밍 구조화된 응답
- 프라이버시를 보호하는 AI 필요 (기기 외부로 데이터 전송 없음)
핵심 패턴 — 가용성 확인
세션을 생성하기 전에 항상 모델 가용성을 확인합니다:
struct GenerativeView: View {
private var model = SystemLanguageModel.default
var body: some View {
switch model.availability {
case .available:
ContentView()
case .unavailable(.deviceNotEligible):
Text("기기가 Apple Intelligence를 지원하지 않습니다")
case .unavailable(.appleIntelligenceNotEnabled):
Text("설정에서 Apple Intelligence를 활성화해 주세요")
case .unavailable(.modelNotReady):
Text("모델이 다운로드 중이거나 준비되지 않았습니다")
case .unavailable(let other):
Text("모델을 사용할 수 없음: \(other)")
}
}
}
핵심 패턴 — 기본 세션
// 단일 턴: 매번 새 세션 생성
let session = LanguageModelSession()
let response = try await session.respond(to: "파리를 방문하기 좋은 달은 언제인가요?")
print(response.content)
// 다중 턴: 대화 맥락을 위해 세션 재사용
let session = LanguageModelSession(instructions: """
당신은 요리 도우미입니다.
재료를 바탕으로 레시피를 추천해 주세요.
간결하고 실용적인 추천을 해 주세요.
""")
let first = try await session.respond(to: "닭고기와 쌀이 있어요")
let followUp = try await session.respond(to: "채식 옵션은 어떤가요?")
instructions에 대한 핵심 사항:
- 모델의 역할 정의 ("당신은 멘토입니다")
- 수행할 작업 지정 ("캘린더 이벤트 추출을 도와주세요")
- 스타일 선호도 설정 ("가능한 한 간결하게 응답하세요")
- 안전 조치 추가 ("위험한 요청에는 '도와드릴 수 없습니다'라고 응답하세요")
핵심 패턴 — @Generable을 사용한 유도 생성
원시 문자열 대신 구조화된 Swift 타입을 생성합니다:
1. Generable 타입 정의
@Generable(description: "고양이에 대한 기본 프로필 정보")
struct CatProfile {
var name: String
@Guide(description: "고양이의 나이", .range(0...20))
var age: Int
@Guide(description: "고양이 성격에 대한 한 문장 프로필")
var profile: String
}
2. 구조화된 출력 요청
let response = try await session.respond(
to: "귀여운 구조 고양이를 생성해 주세요",
generating: CatProfile.self
)
// 구조화된 필드에 직접 접근
print("이름: \(response.content.name)")
print("나이: \(response.content.age)")
print("프로필: \(response.content.profile)")
지원되는 @Guide 제약 조건
.range(0...20)— 숫자 범위.count(3)— 배열 요소 개수description:— 생성을 위한 의미적 안내
핵심 패턴 — 도구 호출
모델이 도메인 특화 작업을 위해 사용자 정의 코드를 호출하도록 합니다:
1. 도구 정의
struct RecipeSearchTool: Tool {
let name = "recipe_search"
let description = "주어진 검색어와 일치하는 레시피를 검색하고 결과 목록을 반환합니다."
@Generable
struct Arguments {
var searchTerm: String
var numberOfResults: Int
}
func call(arguments: Arguments) async throws -> ToolOutput {
let recipes = await searchRecipes(
term: arguments.searchTerm,
limit: arguments.numberOfResults
)
return .string(recipes.map { "- \($0.name): \($0.description)" }.joined(separator: "\n"))
}
}
2. 도구와 함께 세션 생성
let session = LanguageModelSession(tools: [RecipeSearchTool()])
let response = try await session.respond(to: "파스타 레시피를 찾아 주세요")
3. 도구 오류 처리
do {
let answer = try await session.respond(to: "토마토 수프 레시피를 찾아 주세요.")
} catch let error as LanguageModelSession.ToolCallError {
print(error.tool.name)
if case .databaseIsEmpty = error.underlyingError as? RecipeSearchToolError {
// 특정 도구 오류 처리
}
}
핵심 패턴 — 스냅샷 스트리밍
PartiallyGenerated 타입을 사용하여 실시간 UI 업데이트를 위한 구조화된 응답을 스트리밍합니다:
@Generable
struct TripIdeas {
@Guide(description: "예정된 여행에 대한 아이디어")
var ideas: [String]
}
let stream = session.streamResponse(
to: "흥미로운 여행 아이디어는 무엇이 있나요?",
generating: TripIdeas.self
)
for try await partial in stream {
// partial: TripIdeas.PartiallyGenerated (모든 속성은 Optional)
print(partial)
}
SwiftUI 통합
@State private var partialResult: TripIdeas.PartiallyGenerated?
@State private var errorMessage: String?
var body: some View {
List {
ForEach(partialResult?.ideas ?? [], id: \.self) { idea in
Text(idea)
}
}
.overlay {
if let errorMessage { Text(errorMessage).foregroundStyle(.red) }
}
.task {
do {
let stream = session.streamResponse(to: prompt, generating: TripIdeas.self)
for try await partial in stream {
partialResult = partial
}
} catch {
errorMessage = error.localizedDescription
}
}
}
주요 설계 결정
| 결정 | 이유 |
|---|---|
| 기기 내 실행 | 프라이버시 — 데이터가 기기를 떠나지 않음; 오프라인에서 작동 |
| 4,096 토큰 제한 | 기기 내 모델 제약; 대용량 데이터는 여러 세션으로 분할 |
| 스냅샷 스트리밍 (델타 아님) | 구조화된 출력에 적합; 각 스냅샷은 완전한 부분 상태 |
@Generable 매크로 | 구조화된 생성을 위한 컴파일 타임 안전성; PartiallyGenerated 타입 자동 생성 |
| 세션당 단일 요청 | isResponding이 동시 요청을 방지; 필요 시 여러 세션 생성 |
response.content (.output 아님) | 올바른 API — 항상 .content 속성을 통해 결과에 접근 |
모범 사례
- 세션 생성 전 항상
model.availability확인 — 모든 사용 불가 사례 처리 instructions사용하여 모델 동작 안내 — 프롬프트보다 우선 순위 높음- 새 요청 전에
isResponding확인 — 세션은 한 번에 하나의 요청 처리 - 결과에 접근 시
response.content사용 —.output사용 금지 - 대용량 입력은 여러 청크로 분할 — 4,096 토큰 제한은 instructions + prompt + output 합계 적용
- 구조화된 출력에는
@Generable사용 — 원시 문자열 파싱보다 강력한 보장 GenerationOptions(temperature:)사용하여 창의성 조정 (값이 높을수록 더 창의적)- Instruments로 모니터링 — Xcode Instruments를 사용하여 요청 성능 프로파일링
피해야 할 안티 패턴
model.availability확인 없이 세션 생성- 4,096 토큰 컨텍스트 윈도우를 초과하는 입력 전송
- 단일 세션에 동시 요청 시도
- 응답 데이터 접근에
.content대신.output사용 @Generable구조화된 출력이 가능할 때 원시 문자열 응답 파싱- 단일 프롬프트에 복잡한 다단계 로직 구축 — 여러 개의 집중된 프롬프트로 분할
- 모델이 항상 사용 가능하다고 가정 — 기기 호환성 및 설정은 다양함
사용 시기
- 프라이버시가 중요한 앱을 위한 기기 내 텍스트 생성
- 사용자 입력(양식, 자연어 명령)에서 구조화된 데이터 추출
- 오프라인에서 작동해야 하는 AI 지원 기능
- 생성된 콘텐츠를 점진적으로 보여주는 스트리밍 UI
- 도구 호출을 통한 도메인 특화 AI 작업 (검색, 계산, 조회)