🌐 KO
πŸ†• iOS 26

πŸ€– μ˜¨λ””λ°”μ΄μŠ€ AI 챗봇 λ§Œλ“€κΈ°

Build privacy-preserving AI apps with Foundation Models. All processing happens on-device.

⭐ Difficulty: ⭐⭐⭐ ⏱️ Est. Time: 2h πŸ“‚ App Frameworks

✨ Foundation Models νŠΉμ§•

β€’ ν”„λΌμ΄λ²„μ‹œ 보호 (데이터가 κΈ°κΈ°λ₯Ό λ– λ‚˜μ§€ μ•ŠμŒ)
β€’ μ˜€ν”„λΌμΈ Supported (λ„€νŠΈμ›Œν¬ λΆˆν•„μš”)
β€’ Swift λ„€μ΄ν‹°λΈŒ async/await API

πŸ’¬ Basic Usage (LanguageModelSession)

import FoundationModels

// μ„Έμ…˜ 생성 (μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ μ„€μ •)
let session = LanguageModelSession(
    instructions: "μΉœμ ˆν•œ AI μ–΄μ‹œμŠ€ν„΄νŠΈμž…λ‹ˆλ‹€"
)

// 응닡 μš”μ²­
let response = try await session.respond(to: "μ•ˆλ…•ν•˜μ„Έμš”")
print(response.content)

πŸ”„ 슀트리밍 응닡

// 슀트리밍으둜 μ‹€μ‹œκ°„ 응닡 λ°›κΈ°
let stream = session.streamResponse(to: prompt)
for try await partial in stream {
    response = partial.outputSoFar  // μ§€κΈˆκΉŒμ§€ μƒμ„±λœ ν…μŠ€νŠΈ
}

πŸ”§ Tool Calling

@Tool
struct WeatherTool {
    static let description = "ν˜„μž¬ 날씨λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€"

    @Parameter(description: "λ„μ‹œ 이름")
    var city: String

    func execute() async -> String {
        return "\(city) 날씨: λ§‘μŒ, 23Β°C"
    }
}

πŸ’¬ SwiftUI 챗봇 κ΅¬ν˜„

import SwiftUI
import FoundationModels

struct ChatView: View {
    @State private var messages: [Message] = []
    @State private var inputText = ""
    @State private var isGenerating = false
    @State private var streamingText = ""

    // LanguageModelSession μ‚¬μš©
    @State private var session = LanguageModelSession(
        instructions: "μΉœμ ˆν•œ AI μ–΄μ‹œμŠ€ν„΄νŠΈμž…λ‹ˆλ‹€. ν•œκ΅­μ–΄λ‘œ λ‹΅λ³€ν•˜μ„Έμš”."
    )

    var body: some View {
        VStack {
            ScrollView {
                ForEach(messages) { message in
                    MessageBubble(message: message)
                }
                // 슀트리밍 쀑인 응닡 ν‘œμ‹œ
                if !streamingText.isEmpty {
                    MessageBubble(message: Message(role: .assistant, content: streamingText))
                }
            }

            HStack {
                TextField("λ©”μ‹œμ§€ μž…λ ₯", text: $inputText)
                    .textFieldStyle(.roundedBorder)

                Button("전솑") {
                    sendMessage()
                }
                .disabled(isGenerating || inputText.isEmpty)
            }
            .padding()
        }
    }

    func sendMessage() {
        let userMessage = Message(role: .user, content: inputText)
        messages.append(userMessage)
        let prompt = inputText
        inputText = ""
        isGenerating = true
        streamingText = ""

        Task {
            do {
                // 슀트리밍 응닡
                let stream = session.streamResponse(to: prompt)
                for try await partial in stream {
                    streamingText = partial.outputSoFar
                }
                // μ™„λ£Œ ν›„ λ©”μ‹œμ§€ 배열에 μΆ”κ°€
                messages.append(Message(role: .assistant, content: streamingText))
                streamingText = ""
            } catch {
                print("였λ₯˜: \(error)")
            }
            isGenerating = false
        }
    }
}

struct Message: Identifiable {
    let id = UUID()
    let role: MessageRole
    var content: String
}

enum MessageRole {
    case user, assistant
}

βš™οΈ λͺ¨λΈ μ„€μ • & Error Handling

do {
    // Temperature μ‘°μ • (0.0 = 일관성, 1.0 = μ°½μ˜μ„±)
    let result = try await model.generate(
        prompt: prompt,
        temperature: 0.7,
        maxTokens: 500
    )
} catch let error as FoundationModelsError {
    switch error {
    case .modelNotAvailable:
        print("λͺ¨λΈμ΄ λ‹€μš΄λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€")
    case .insufficientMemory:
        print("λ©”λͺ¨λ¦¬κ°€ λΆ€μ‘±ν•©λ‹ˆλ‹€")
    default:
        print("였λ₯˜ λ°œμƒ: \(error)")
    }
}

πŸ’‘ HIG Guidelines
When using AI features, clearly state "Processed on-device." Users care about privacy. Show progress while the AI model downloads on first use.

πŸ“¦ Learning Resources

πŸ“š
DocC Tutorial
πŸ’»
GitHub Project
🍎
Apple HIG Docs

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation 🎬 WWDC Sessions