<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>THIS IS ELLIE</title>
    <link>https://hyerios.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 08:53:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Ellie Kim</managingEditor>
    <image>
      <title>THIS IS ELLIE</title>
      <url>https://tistory1.daumcdn.net/tistory/2944546/attach/24781de980c445f29182db031486d28e</url>
      <link>https://hyerios.tistory.com</link>
    </image>
    <item>
      <title>Foundation Models로 AI 스토리 생성 앱 만들기</title>
      <link>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-AI-%EC%8A%A4%ED%86%A0%EB%A6%AC-%EC%83%9D%EC%84%B1-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘도 어김없이 애플 Foundation Models를 활용해서 간단한 AI 스토리 생성 앱을 만들어보려고 합니다!&lt;br /&gt;이번에 만들어볼 앱은 사용자가 설정한 입력값을 기반으로 감성적인 이야기를 자동 생성하고, 생성된 이야기를 음성으로 들려주는 스토리북 앱이고 네트워크 없이 온디바이스에서 동작해요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 프로젝트를 시작하기 전에, Foundation Models을 사용하기 위한 &lt;a href=&quot;https://hyerios.tistory.com/entry/FoundationModels-%EC%82%AC%EC%9A%A9%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%A4%80%EB%B9%84%EC%82%AC%ED%95%AD&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;몇가지 요구사항&lt;/a&gt;이 충족되어야 합니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@State 프로퍼티 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768059241441&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@State private var situation: String = &quot;On an airplane&quot;
@State private var likes: String = &quot;bunnies, stars&quot;
@State private var minutes: Int = 3

@State private var storyText: String = &quot;&quot;
@State private var isGenerating = false
@State private var errorText: String?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;situation - 이야기의 상황&lt;br /&gt;likes - 아이가 좋아하는 요소&lt;br /&gt;minutes - 스토리 길이 (분)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;storyText - 생성된 스토리 결과 텍스트&lt;br /&gt;isGenerating - 생성 중 로딩 상태&lt;br /&gt;errorText - 에러 메세지 표시용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;body&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768059379075&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var body: some View {
    NavigationStack {
        Form {
            Section(&quot;Story Settings&quot;) {
                TextField(&quot;Situation&quot;, text: $situation)
                TextField(&quot;Likes&quot;, text: $likes)
                
                Picker(&quot;Length&quot;, selection: $minutes) {
                    Text(&quot;1 min&quot;).tag(1)
                    Text(&quot;3 min&quot;).tag(3)
                    Text(&quot;5 min&quot;).tag(5)
                    Text(&quot;8 min&quot;).tag(8)
                    Text(&quot;12 min&quot;).tag(12)
                }
            }
            
            Section {
                Button {
                    Task { await generateAndSpeak() }
                } label: {
                    HStack {
                        Spacer()
                        if isGenerating { ProgressView() }
                        Text(isGenerating ? &quot;Generating...&quot; : &quot;Generate &amp;amp; Play&quot;)
                        Spacer()
                    }
                }
                .disabled(isGenerating || !storyService.isAvailable)
                
                Button(&quot;Stop&quot;) {
                    speech.stop()
                }
            }
            
            if let errorText {
                Section(&quot;Error&quot;) { Text(errorText) }
            }
            
            Section(&quot;Story&quot;) {
                Text(storyText.isEmpty ? &quot;No story yet.&quot; : storyText)
                    .textSelection(.enabled)
            }
        }
        .navigationTitle(&quot;StoryBook&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;body는 사용자가 이야기를 설정하고, AI가 생성한 스토리를 플레이 할 수 있는 전체 UI 흐름을 담당하고 있어요&lt;br /&gt;코드는 크게 설정 입력, 버튼 액션, 결과/에러 표시의 구조로 나누어져있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;generateAndSpeak()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768060506593&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MainActor
private func generateAndSpeak() async {
    isGenerating = true
    errorText = nil
    storyText = &quot;&quot;
    
    do {
        let story = try await storyService.generateStory(
            situation: situation,
            minutes: minutes,
            likes: likes
        )
        storyText = story
        speech.speak(story)
    } catch {
        errorText = String(describing: error)
    }
    
    isGenerating = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 탭하면 비동기 함수가 호출돼서 AI 스토리 생성 + TTS 재생이 차례로 실행되며&lt;br /&gt;progressView()가 나타나며 현재 생성중인 상태를 표시해줘요&lt;br /&gt;@MainActor를 사용해 UI 상태 업데이트를 메인 스레드에서 안전하게 업데이트해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SpeechService&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768060991561&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import AVFoundation
internal import Combine

@MainActor
final class SpeechService: NSObject, ObservableObject {
    private let synthesizer = AVSpeechSynthesizer()
    
    override init() {
        super.init()
        synthesizer.delegate = self
    }
    
    func speak(_ text: String) {
        stop()
        
        let utterance = AVSpeechUtterance(string: text)
        utterance.voice = AVSpeechSynthesisVoice(language: &quot;en-US&quot;)
        utterance.rate = 0.45
        utterance.pitchMultiplier = 1.0
        utterance.preUtteranceDelay = 0.2
        utterance.postUtteranceDelay = 0.2
        
        synthesizer.speak(utterance)
    }
    
    func stop() {
        synthesizer.stopSpeaking(at: .immediate)
    }
}

extension SpeechService: AVSpeechSynthesizerDelegate {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpeechService는 AVSpeechSynthesizer를 이용해 텍스트를 음성으로 변환해줘요 (TTS)&lt;br /&gt;아이를 위한 이야기이기 때문에 말하는 속도는 조금 느리게, 톤은 최대한 안정적으로 설정했어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;StoryService&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768061086390&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation
import FoundationModels
internal import Combine

@MainActor
final class StoryService: ObservableObject {
    @Published var isAvailable: Bool = false
    @Published var statusMessage: String = &quot;&quot;
    
    private let session: LanguageModelSession
    
    init() {
        let model = SystemLanguageModel.default
        switch model.availability {
        case .available:
            isAvailable = true
            statusMessage = &quot;모델 사용 가능&quot;
        case .unavailable(let reason):
            isAvailable = false
            statusMessage = &quot;모델 사용 불가: \(String(describing: reason))&quot;
        }
        
        self.session = LanguageModelSession {
            &quot;&quot;&quot;
            You write calming, safe bedtime-style stories for young children.
            Rules:
            - No scary, violent, or threatening content.
            - No medical or drug advice.
            - Use short, simple sentences.
            - Keep a warm, reassuring tone.
            - End with a gentle, calming closing.
            &quot;&quot;&quot;
        }
    }
    
    func generateStory(
        situation: String,
        minutes: Int,
        likes: String
    ) async throws -&amp;gt; String {
        let target = targetWordCount(forMinutes: minutes)
        let prompt = &quot;&quot;&quot;
        Situation: \(situation)
        Child likes: \(likes)
        Length: about \(target) words.
        
        Write the story in 4-6 short paragraphs.
        &quot;&quot;&quot;
        
        let response = try await session.respond(to: prompt)
        return response.content
    }
    
    private func targetWordCount(forMinutes minutes: Int) -&amp;gt; String {
        switch minutes {
        case 1: return &quot;120-180&quot;
        case 3: return &quot;350-500&quot;
        case 5: return &quot;650-900&quot;
        case 8: return &quot;1000-1300&quot;
        case 12: return &quot;1500-1900&quot;
        default: return &quot;350-500&quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StoryService는 Foundation Models를 이용해 실제 스토리를 생성하는 핵심 로직을 담당해요&lt;br /&gt;isAvailable을 통해 현재 디바이스에서 Foundation Models 사용 가능 여부를 판단하고&lt;br /&gt;statusMessage를 통해 사용 가능/불가 상태를 UI에 전달하기 위한 메세지를 담아줍니다&lt;br /&gt;시스템 프롬프트를 통해 Role을 명확히 지정해주고 사용자 입력 기반 프롬프트를 구성해줘요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 요롷게 됩니다 ㅎㅎㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ScreenRecording_01-02-202613-55-56_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBI9nC/dJMcabv8Vcr/Wd0Iyk4mmciVqsZ3OgTmNk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBI9nC/dJMcabv8Vcr/Wd0Iyk4mmciVqsZ3OgTmNk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBI9nC/dJMcabv8Vcr/Wd0Iyk4mmciVqsZ3OgTmNk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bBI9nC/dJMcabv8Vcr/Wd0Iyk4mmciVqsZ3OgTmNk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;1733&quot; data-filename=&quot;ScreenRecording_01-02-202613-55-56_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 여기서 확인할 수 이써요!&lt;br /&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/CozyTales&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/CozyTales&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768062165929&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;FoundationModelsPlayground/CozyTales at main &amp;middot; kimhyeri/FoundationModelsPlayground&quot; data-og-description=&quot;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/CozyTales&quot; data-og-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/CozyTales&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnCUtR/hyZRg9eU5A/tttFo0xUtHbbTuU2Tpfvp0/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/e9q3C/hyZQGUlSpg/lwKSJ9rDHvWYKxMbO7P8Qk/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185&quot;&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/CozyTales&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/CozyTales&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnCUtR/hyZRg9eU5A/tttFo0xUtHbbTuU2Tpfvp0/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/e9q3C/hyZQGUlSpg/lwKSJ9rDHvWYKxMbO7P8Qk/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;FoundationModelsPlayground/CozyTales at main &amp;middot; kimhyeri/FoundationModelsPlayground&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>Apple</category>
      <category>Apple Foundation Models</category>
      <category>Apple 온디바이스</category>
      <category>foundationmodels</category>
      <category>llm</category>
      <category>ondevice</category>
      <category>스토리북</category>
      <category>온디바이스</category>
      <category>온디바이스 llm</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/327</guid>
      <comments>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-AI-%EC%8A%A4%ED%86%A0%EB%A6%AC-%EC%83%9D%EC%84%B1-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry327comment</comments>
      <pubDate>Sun, 11 Jan 2026 01:28:32 +0900</pubDate>
    </item>
    <item>
      <title>Foundation Models로 Workout Plan Generator만들기</title>
      <link>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-Workout-Plan-Generator%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 :)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 애플 Foundation Models을 활용해서 간단한 Workout Plan Generator를 만들어보려고 해요&lt;br /&gt;이 간단한 앱의 기능은 사용자가 목표/운동 시간/장비/레벨을 선택하면, 온디바이스 모델이 맞춤형 운동 루틴을 생성해 주는 구조에요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*프로젝트 시작하기 전에, Foundation Models을 사용하기 위한 &lt;a href=&quot;https://hyerios.tistory.com/entry/FoundationModels-%EC%82%AC%EC%9A%A9%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%A4%80%EB%B9%84%EC%82%AC%ED%95%AD&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;몇 가지 요구사항&lt;/a&gt;이 충족되어야 합니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@State 프로퍼티 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765515110835&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@State private var planText: String = &quot;No workout plan yet.\nTap 'Generate Plan' to begin!&quot;
@State private var isLoading: Bool = false
@State private var errorMessage: String?
@State private var goal: String = &quot;Fat loss&quot;
@State private var minutes: Int = 30
@State private var equipment: String = &quot;Bodyweight&quot;
@State private var level: String = &quot;Beginner&quot;

private let workoutAI = WorkoutAI()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;planText는 안내 메세지를 담고 있고&lt;br /&gt;isLoading은 로딩 상태 관리를 위한 변수이고&lt;br /&gt;errorMessage는 에러 발생 시 UI에 표시할 텍스트를 담기 위한 변수이고&lt;br /&gt;goal/minutes/equiment/level은 피커로 선택된 사용자 입력 값을 담기 위한 변수이고&lt;br /&gt;workoutAI는 실질적으로 Foundtaion Models 호출을 담당하는 객체에요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;body&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765516208793&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var body: some View {
    NavigationStack {
        VStack(spacing: 20) {
            inputSection
            
            ScrollView {
                Text(planText)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
                    .background(Color(.secondarySystemBackground))
                    .clipShape(RoundedRectangle(cornerRadius: 16))
            }
            
            if let errorMessage {
                Text(errorMessage)
                    .font(.footnote)
                    .foregroundStyle(.red)
            }
            
            Button {
                Task { await generatePlan() }
            } label: {
                if isLoading {
                    ProgressView()
                        .tint(.white)
                        .frame(maxWidth: .infinity)
                        .padding()
                } else {
                    Text(&quot;Generate Plan&quot;)
                        .frame(maxWidth: .infinity)
                        .padding()
                }
            }
            .font(.headline)
            .background(isLoading ? Color.gray : Color.blue)
            .foregroundStyle(.white)
            .clipShape(RoundedRectangle(cornerRadius: 16))
            .disabled(isLoading)
        }
        .padding()
        .navigationTitle(&quot;AI Workout&quot;)
        .navigationBarTitleDisplayMode(.inline)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 화면은 다음과 같은 구조로 구성되어 있어요&lt;br /&gt;1. Workout 옵션 입력 영역&lt;br /&gt;2. 모델이 생성한 결과 영역&lt;br /&gt;3. 에러 메세지&lt;br /&gt;4. Generate Plan버튼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 입력 영역이에요&lt;/p&gt;
&lt;pre id=&quot;code_1765516476314&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private var inputSection: some View {
    VStack(alignment: .leading, spacing: 16) {
        Text(&quot;Workout Preferences&quot;)
            .font(.headline)
        
        VStack(alignment: .leading) {
            Text(&quot;Goal&quot;)
                .font(.caption)
                .foregroundStyle(.secondary)
            Picker(&quot;Goal&quot;, selection: $goal) {
                Text(&quot;Fat loss&quot;).tag(&quot;Fat loss&quot;)
                Text(&quot;Muscle gain&quot;).tag(&quot;Muscle gain&quot;)
                Text(&quot;Maintenance&quot;).tag(&quot;Maintenance&quot;)
            }
            .pickerStyle(.segmented)
        }
        
        VStack(alignment: .leading) {
            Text(&quot;Workout Duration (minutes)&quot;)
                .font(.caption)
                .foregroundStyle(.secondary)
            Picker(&quot;Minutes&quot;, selection: $minutes) {
                Text(&quot;10&quot;).tag(10)
                Text(&quot;20&quot;).tag(20)
                Text(&quot;30&quot;).tag(30)
                Text(&quot;45&quot;).tag(45)
                Text(&quot;60&quot;).tag(60)
            }
            .pickerStyle(.segmented)
        }
        
        VStack(alignment: .leading) {
            Text(&quot;Equipment&quot;)
                .font(.caption)
                .foregroundStyle(.secondary)
            Picker(&quot;Equipment&quot;, selection: $equipment) {
                Text(&quot;Bodyweight&quot;).tag(&quot;Bodyweight&quot;)
                Text(&quot;Dumbbells&quot;).tag(&quot;Dumbbells&quot;)
                Text(&quot;Gym equipment&quot;).tag(&quot;Gym equipment&quot;)
            }
            .pickerStyle(.segmented)
        }
        
        VStack(alignment: .leading) {
            Text(&quot;Experience Level&quot;)
                .font(.caption)
                .foregroundStyle(.secondary)
            Picker(&quot;Level&quot;, selection: $level) {
                Text(&quot;Beginner&quot;).tag(&quot;Beginner&quot;)
                Text(&quot;Intermediate&quot;).tag(&quot;Intermediate&quot;)
                Text(&quot;Advanced&quot;).tag(&quot;Advanced&quot;)
            }
            .pickerStyle(.segmented)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피커에는 목표, 운동시간, 장비, 난이도를 입력할 수 있도록 했어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 generatePlan함수&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1765516499611&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func generatePlan() async {
    errorMessage = nil
    isLoading = true
    
    do {
        let text = try await workoutAI.generatePlan(
            goal: goal,
            minutes: minutes,
            equipment: equipment,
            level: level
        )
        await MainActor.run {
            self.planText = text
        }
    } catch {
        await MainActor.run {
            self.errorMessage = &quot;Error generating plan: \(error.localizedDescription)&quot;
        }
    }
    
    await MainActor.run {
        self.isLoading = false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적인 흐름은 아래와 같아요&lt;br /&gt;1. 에러메세지를 초기화&lt;br /&gt;2. 로딩 시작&lt;br /&gt;3. WorkoutAI.generatePlan()호출&lt;br /&gt;4. LLM 응답을 planText에 반영&lt;br /&gt;5. 에러 발생 시 메세지 출력&lt;br /&gt;6. 로딩 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WorkoutAI&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765516712772&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct WorkoutAI {
    private let session: LanguageModelSession
    
    init() {
        let instructions = &quot;&quot;&quot;
        You are a friendly and safe fitness coach.
        Generate short, safe home workout routines in English.
        Avoid medical advice. Keep it simple and beginner-friendly.
        Output should be plain text with bullet points.
        &quot;&quot;&quot;
        
        self.session = LanguageModelSession(instructions: instructions)
    }
    
    func generatePlan(
        goal: String,
        minutes: Int,
        equipment: String,
        level: String
    ) async throws -&amp;gt; String {
        let prompt = &quot;&quot;&quot;
        User information:
        - Goal: \(goal)
        - Workout duration: \(minutes) minutes
        - Experience level: \(level)
        - Available equipment: \(equipment)
        
        Please create a full-body workout routine in English that matches the information above.
        
        Format:
        - A short one-line summary at the top
        - Then bullet-point exercises (e.g., &amp;bull; Squats &amp;ndash; 3 sets &amp;times; 12 reps)
        - A short safety or caution note as the last line
        
        Keep it concise, around 8&amp;ndash;12 lines in total.
        &quot;&quot;&quot;
        
        let response = try await session.respond(to: prompt)
        return response.content
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 LanguageModelSession을 생성해주고&lt;br /&gt;instructions로 너는 운동 코치 역할이야!!!라는걸 알려주고&lt;br /&gt;prompt로 사용자가 입력한 (goal/minuates/equipment/level)을 전달하고&lt;br /&gt;마지막으로 모델이 생성한 content를 반환해줘요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은!!&lt;br /&gt;instruction은 모델의 역할을 정의해주고, prompt는 사용자 입력을 전달해 모델이 더 높은 품질의 결과를 생성할 수 있도록 해줘요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 요렇게에 ㅎㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ScreenRecording_11-28-202500-07-08_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b38lIl/dJMcaiaMGRB/KKHH6kTbVCX1dFp2AeDjUk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b38lIl/dJMcaiaMGRB/KKHH6kTbVCX1dFp2AeDjUk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b38lIl/dJMcaiaMGRB/KKHH6kTbVCX1dFp2AeDjUk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b38lIl/dJMcaiaMGRB/KKHH6kTbVCX1dFp2AeDjUk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;650&quot; data-filename=&quot;ScreenRecording_11-28-202500-07-08_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 여기서 확인할 수 있어요!&lt;br /&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/WorkoutGuide&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/WorkoutGuide&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1765517855818&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;FoundationModelsPlayground/WorkoutGuide at main &amp;middot; kimhyeri/FoundationModelsPlayground&quot; data-og-description=&quot;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/WorkoutGuide&quot; data-og-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/WorkoutGuide&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/9UiTT/hyZPyO1bX1/G396iPEBYVkctmlL5dt03K/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/Lsjxo/hyZODxsHFm/cbayXIIGA5wiQcYTfnlIs0/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185&quot;&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/WorkoutGuide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground/tree/main/WorkoutGuide&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/9UiTT/hyZPyO1bX1/G396iPEBYVkctmlL5dt03K/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/Lsjxo/hyZODxsHFm/cbayXIIGA5wiQcYTfnlIs0/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;FoundationModelsPlayground/WorkoutGuide at main &amp;middot; kimhyeri/FoundationModelsPlayground&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>Apple</category>
      <category>Apple Foundation Models</category>
      <category>foundation models</category>
      <category>llm</category>
      <category>ondevice</category>
      <category>애플 온디바이스</category>
      <category>온디바이스</category>
      <category>온디바이스LLM</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/326</guid>
      <comments>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-Workout-Plan-Generator%EB%A7%8C%EB%93%A4%EA%B8%B0#entry326comment</comments>
      <pubDate>Fri, 12 Dec 2025 14:43:09 +0900</pubDate>
    </item>
    <item>
      <title>Foundation Models로 Recipe Generator 만들기</title>
      <link>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-Recipe-Generator-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 애플 Foundation Models을 활용해서 &lt;b&gt;streaming output &lt;/b&gt;방식으로&amp;nbsp;간단한 &lt;b&gt;Recipe Generator&lt;/b&gt;을 만들어볼게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그전에 &lt;b&gt;streaming output&lt;/b&gt;이 뭐냐??&lt;br /&gt;&lt;b&gt;non-streaming output(batch output)&lt;/b&gt;은 모델이 전체 응답을 다 생성한 후 한 번에 결과를 보내주는 방식이고&lt;br /&gt;&lt;b&gt;streaming output&lt;/b&gt;은 모델이 응답을 생성하면서 결과를 계속해서 보내주는 방식이에요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apple의 Foundation Models에서는&lt;br /&gt;streaming 방식을 활용할 수 있도록 &lt;a href=&quot;https://developer.apple.com/documentation/foundationmodels/generable/partiallygenerated&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;PartiallyGenerated&lt;/b&gt;&lt;/a&gt;를 제공해줘요&lt;br /&gt;Partially Generated - 이름만 봐도 알 수 있듯이 한번에 응답을 보내주는게 아니라 &lt;br /&gt;응답을 만들어 낼때마다 보내주기 때문에 UI를 부분적으로 업데이트할 수 있어요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 바로 PartiallyGenerated를 활용해서 간단한 레시피 생성하는 앱을 만들어 볼게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Recipe&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754575961115&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Generable(description: &quot;A simple cooking recipe&quot;)
struct Recipe {
    let title: String
    @Guide(description: &quot;List of required ingredients&quot;)
    let ingredients: [String]
    @Guide(description: &quot;Summary of cooking instructions&quot;)
    let steps: String
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Recipe 구조체를 생성해줍니다&lt;br /&gt;@Generable, @Guide매크로를 활용해 모델이 해당 타입의 인스턴스를 자동으로 생성할 수 있게 했고&lt;br /&gt;각각 description을 추가해 자연어로 설명을 추가해줘요&lt;br /&gt;(for more detail: &lt;a href=&quot;https://hyerios.tistory.com/323&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@Generable, @Guide&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ContentView&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@State Properties&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754577696019&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@State private var session = LanguageModelSession()
@State private var recipe: Recipe.PartiallyGenerated?
@State private var userInput: String = &quot;&quot;
@State private var isLoading: Bool = false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;session - LanguageModelSession 인스턴스&lt;br /&gt;recipe - 스트리밍으로 생성되는 레시피 결과 저장&lt;br /&gt;userInput - 유저가 입력하는 텍스트 필드 값 보관&lt;br /&gt;isLoading - 로딩 상태 표시를 위함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NavigationStack + VStack으로 구성된 body&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754577718575&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var body: some View {
    NavigationStack {
        VStack(alignment: .leading, spacing: 16) {
            HStack {
                TextField(&quot;What would you like to cook?&quot;, text: $userInput)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .disableAutocorrection(true)
                
                Button(&quot;Generate&quot;) {
                    UIApplication.shared.endEditing()
                    Task {
                        await generateRecipe()
                    }
                }
                .disabled(userInput.isEmpty)
            }
            
            ScrollView {
                if let recipe {
                    VStack(alignment: .leading) {
                        if let title = recipe.title {
                            Text(title)
                                .font(.title)
                                .bold()
                        }
                        
                        if let ingredients = recipe.ingredients {
                            Text(&quot;Ingredients:&quot;)
                                .font(.headline)
                            ForEach(ingredients, id: \.self) { item in
                                Text(&quot;- \(item)&quot;)
                            }
                        }
                        
                        if let steps = recipe.steps {
                            Text(&quot;Instructions:&quot;)
                                .font(.headline)
                            Text(steps)
                        }
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
                } else if isLoading {
                    VStack {
                        Spacer()
                        ProgressView(&quot;Generating recipe&quot;)
                            .padding()
                        Spacer()
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
            }
        }
        .padding()
        .navigationTitle(&quot;AI Recipe Generator&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 관련 디테일은 생략하고, 간단한 흐름만 살펴보면&lt;br /&gt;유저가 인풋 필드에 값을 입력한 후 Generate버튼을 누르면 키보드를 내려주고 generateRecipe()를 호출해 레시피 생성을 시작해줘요&lt;br /&gt;generateRecipe()는 비동기 함수이기 때문에 await를 사용해 호출해 줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;generateRecipe()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754577735491&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func generateRecipe() async {
    isLoading = true
    recipe = nil
    do {
        let stream = session.streamResponse(generating: Recipe.self) {
            &quot;Give me a recipe for \(userInput)&quot;
        }
        for try await partial in stream {
            self.recipe = partial
        }
    } catch {
        print(&quot;Recipe generation error: \(error.localizedDescription)&quot;)
    }
    isLoading = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;generateRecipe()가 호출되면 isLoading을 true로 변경해 ProgressView를 보여줘요&lt;br /&gt;그리고 세션에서 streamResponse(generating: Recipe.self)를 호출해 스트리밍 생성을 시작해요&lt;br /&gt;유저 입력을 프롬프트로 전달하고 모델이 응답을 계속해서 보내줄 때 마다 self.recipe = partial로 업데이트를 해줍니다&lt;br /&gt;self.recipe가 업데이트될 때마다 UI도 업데이트가 됩니당&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 전체 코드를 실행한 결과에요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ScreenRecording_08-08-202508-16-30_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Czx2C/btsPJCbwBvw/4kySjkIWfHzrBcuKJDMAiK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Czx2C/btsPJCbwBvw/4kySjkIWfHzrBcuKJDMAiK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Czx2C/btsPJCbwBvw/4kySjkIWfHzrBcuKJDMAiK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Czx2C/btsPJCbwBvw/4kySjkIWfHzrBcuKJDMAiK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;650&quot; data-filename=&quot;ScreenRecording_08-08-202508-16-30_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 전체 코드는 여기 올려뒀어요!&lt;br /&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kimhyeri/FoundationModelsPlayground&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754609953053&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - kimhyeri/FoundationModelsPlayground: A collection of examples using Apple&amp;rsquo;s FoundationModels framework&quot; data-og-description=&quot;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; data-og-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bzBVMP/hyZuzaAkpv/j7LQqeMVpKyed5teRZPi11/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/hj1ug/hyZviTL4Zp/m9TXkOnbAcaTVa67ktI9x1/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185&quot;&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bzBVMP/hyZuzaAkpv/j7LQqeMVpKyed5teRZPi11/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/hj1ug/hyZviTL4Zp/m9TXkOnbAcaTVa67ktI9x1/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - kimhyeri/FoundationModelsPlayground: A collection of examples using Apple&amp;rsquo;s FoundationModels framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>Apple Foundation Models</category>
      <category>foundationmodels</category>
      <category>On Device LLM</category>
      <category>Partially Generated</category>
      <category>streaming output</category>
      <category>애플 Foundation Models</category>
      <category>애플 온디바이스</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/325</guid>
      <comments>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-Recipe-Generator-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry325comment</comments>
      <pubDate>Fri, 8 Aug 2025 22:06:14 +0900</pubDate>
    </item>
    <item>
      <title>Apple Foundation Models - Tool calling</title>
      <link>https://hyerios.tistory.com/entry/FoundationModels-Tool-calling</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tool Calling이란? &lt;/b&gt;&lt;br /&gt;Foundation Models에 외부 도구를 알려주고, 모델이 이를 직접 호출할 수 있도록 해주는 기능이에요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 Tool calling이 필요한가?&lt;/b&gt;&lt;br /&gt;일반적인 언어 모델은 학습된 시점의 지식에 한정되어 있어요&lt;br /&gt;하지만 Tool calling을 활용하면 실시간 정보도 가져올 수 있고, 날씨, 지도, 캘린더 등 디바이스 기능을 활용할 수 있어요&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;protocol Tool&lt;/b&gt;&lt;br /&gt;Tool은 프로토콜을 살펴보면 Sendable프로토콜을 준수하고 있어요&lt;br /&gt;이유는 Foundation Models 프레임워크가 Tool을 concurrency 환경에서 실행하기 때문이에요&lt;/p&gt;
&lt;pre id=&quot;code_1754490838548&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;protocol Tool&amp;lt;Arguments, Output&amp;gt; : Sendable&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;따라서 Tool은 스레드-세이프하게 사용되도록 보장되어야 해요&lt;/u&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;애플 문서에 있는 예제를 조금 바꿔서 테스트해볼게요&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FindContacts&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754488685620&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FindContacts: Tool {
    let name = &quot;findContacts&quot;
    let description = &quot;Find a specific number of contacts&quot;

    @Generable
    struct Arguments {
        @Guide(description: &quot;The number of contacts to get&quot;, .range(1...10))
        let count: Int
    }

    func call(arguments: Arguments) async throws -&amp;gt; ToolOutput {
        let contacts: [CNContact] = [
            mockContact(given: &quot;Ellie&quot;, family: &quot;Kim&quot;),
            mockContact(given: &quot;Ellie&quot;, family: &quot;Lee&quot;),
            mockContact(given: &quot;Ellie&quot;, family: &quot;Choi&quot;)
        ]
        let count = min(arguments.count, contacts.count)
        let names = contacts.prefix(count).map { &quot;\($0.givenName) \($0.familyName)&quot; }

        return ToolOutput(names.joined(separator: &quot;, &quot;))
    }

    private func mockContact(given: String, family: String) -&amp;gt; CNContact {
        let contact = CNMutableContact()
        contact.givenName = given
        contact.familyName = family
        return contact.copy() as! CNContact
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tool프로토콜을 준수하는 구조체 FindContacts를 만들어줘요&lt;br /&gt;Tool이 받을 입력 값인 Arguments를 정의해 줄게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Generable 매크로를 사용하면, 모델이 해당 타입의 인스턴스를 자동으로 생성할 수 있어요&lt;br /&gt;또한, @Guide 매크로를 활용해 count에 대한 설명도 추가했어요 &lt;br /&gt;(&lt;a href=&quot;https://hyerios.tistory.com/323&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@Generable, @Guide 잘 모르겠으면 참고&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;call(arguments: Arguments) async throws -&amp;gt; ToolOutput는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Tool이 호출될 때 실행되는 비동기 함수예요&lt;/span&gt;&lt;br /&gt;이 예제에서는 테스트 용으로 mockContact 3개 추가해 놨는데&lt;br /&gt;실제 구현에서는 Contact 권한을 요청하고, 실제 디바이스의 연락처 정보를 불러오는 로직으로 대체하면 돼요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sendPrompt()&lt;/b&gt;&lt;br /&gt;모델 세션을 생성할 때 사용할 Tool로 FindContacts를 같이 넣어줘요&lt;br /&gt;이렇게 모델이 응답할 때 이 Tool을 쓸 수 있다는 걸 알려주는 거예요&lt;/p&gt;
&lt;pre id=&quot;code_1754488708067&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func sendPrompt() async {
    let session = LanguageModelSession(tools: [FindContacts()])
    let prompt = &quot;Can you give me 2 contacts?&quot;
    
    do {
        let result = try await session.respond(to: prompt)
        print(result.content)
    } catch {
        return
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프롬프트를 전달하면, 모델은 FindContacts Tool을 활용해 아래와 같이 Ellie Kim, Ellie Lee를 생성해줘요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-08-06 at 11.19.23 PM.png&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;23&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diLaJi/btsPIePiJIs/aVEOGdzJOd4kDzA5RsmcnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diLaJi/btsPIePiJIs/aVEOGdzJOd4kDzA5RsmcnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diLaJi/btsPIePiJIs/aVEOGdzJOd4kDzA5RsmcnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiLaJi%2FbtsPIePiJIs%2FaVEOGdzJOd4kDzA5RsmcnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;23&quot; data-filename=&quot;Screenshot 2025-08-06 at 11.19.23 PM.png&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;23&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체적인 Flow&lt;/b&gt;&lt;br /&gt;1. FindContacts Tool을 사용할 수 있도록 모델 세션을 생성&lt;br /&gt;2. Can you give me 2 contacts?라는 prompt 전달&lt;br /&gt;3. 모델은 이 요청을 확인하고 Tool을 사용할지 말지 판단&lt;br /&gt;4. 모델이 Tool호출이 필요하다고 판단되면 FindContacts.call 함수 실행 &lt;br /&gt;5. 반환된 결과를 바탕으로 응답 생성&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;resource:&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundationmodels/expanding-generation-with-tool-calling&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/foundationmodels/expanding-generation-with-tool-calling&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundationmodels/tool&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/documentation/foundationmodels/tool&lt;/a&gt; &lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2025/259/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2025/259/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>Apple OnDevice</category>
      <category>foundation models</category>
      <category>Foundation Models프레임워크</category>
      <category>foundationmodels</category>
      <category>tool</category>
      <category>tool calling</category>
      <category>애플 Foundation Models</category>
      <category>애플 온디바이스</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/324</guid>
      <comments>https://hyerios.tistory.com/entry/FoundationModels-Tool-calling#entry324comment</comments>
      <pubDate>Thu, 7 Aug 2025 00:12:02 +0900</pubDate>
    </item>
    <item>
      <title>Apple FoundationModels - @Generable, @Guide</title>
      <link>https://hyerios.tistory.com/entry/FoundationModels-Generable-Guide</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스위프트에서 &lt;b&gt;@Generable&lt;/b&gt; 매크로를 구조체 structure나 열거형 enum에 적용하면, &lt;br /&gt;Foundation Model이 프롬프트를 해석해 해당 타입의 인스턴스를 생성할 수 있어요&lt;br /&gt;또한 &lt;b&gt;@Guide&lt;/b&gt; 매크로를 통해 프로터피에 대한 자연어 설명을 추가할 수 있는데요, &lt;br /&gt;이를 통해서 모델이 어떤 값을 생성해야 하는지 힌트를 얻고, 생성 결과를 컨트롤할 수 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 만들었던, &lt;a href=&quot;https://hyerios.tistory.com/322&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Grammar Correction&lt;/a&gt;에 correctSentence()코드를 조금 수정해 볼게요&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754322135837&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func correctSentence() async {
    isLoading = true
    correctionResult = &quot;&quot;
    errorMessage = nil

    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

    do {
        let instructions = &quot;You are a helpful and knowledgeable English tutor. When a student provides a sentence, check it for grammar mistakes. If there are any, explain what is wrong and provide the corrected sentence. If the sentence is already correct, confirm that it is correct and explain why.&quot;
        let session = LanguageModelSession(instructions: instructions)

        let prompt = &quot;&quot;&quot;
        Please check the following sentence for any grammar mistakes. If there are mistakes, explain them and provide the corrected version. If the sentence is already correct, explain why it is correct.
        \&quot;\(userInput.trimmingCharacters(in: .whitespacesAndNewlines))\&quot;
        &quot;&quot;&quot;
        
        let response = try await session.respond(to: prompt)
        correctionResult = response.content
    } catch {
        errorMessage = &quot;Failed to get response: \(error.localizedDescription)&quot;
    }

    isLoading = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 crrectionResult에 response.content를 바로 넣어줬다면,&lt;br /&gt;CorrectedSentence구조체를 생성한 후 해당 타입으로 반환받을 수 있게 바꿔볼게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CorrectedSentence구조체에 @Generable매크로를 추가해 줍니다&lt;br /&gt;(+ @Generable을 사용하려면 안에 있는 프로퍼티의 타입 또한 @Generable 이여야 해요)&lt;br /&gt;@Guide매크로를 통해 모델이 어떤 값을 생성해야 할지 description을 작성해 줍니다&lt;/p&gt;
&lt;pre id=&quot;code_1754322417663&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Generable
struct CorrectedSentence {
    @Guide(description: &quot;The grammatically correct version of the sentence.&quot;)
    var corrected: String
    @Guide(description: &quot;Indicates whether the original sentence was grammatically correct.&quot;)
    var isCorrect: Bool
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 correctSentence함수안에 session.respond도 CorrectedSentence를 리턴할 수 있게 변경해 줍니다&lt;/p&gt;
&lt;pre id=&quot;code_1754322630203&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func correctSentence() async {
     isLoading = true
     correctionResult = &quot;&quot;
     errorMessage = nil

     UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

     do {
         let instructions = &quot;You are a helpful and knowledgeable English tutor. When a student provides a sentence, check it for grammar mistakes. If there are any, explain what is wrong and provide the corrected sentence. If the sentence is already correct, confirm that it is correct and explain why.&quot;
         let session = LanguageModelSession(instructions: instructions)

         let prompt = &quot;&quot;&quot;
         Please check the following sentence for any grammar mistakes. If there are mistakes, explain them and provide the corrected version. If the sentence is already correct, explain why it is correct.
         \&quot;\(userInput.trimmingCharacters(in: .whitespacesAndNewlines))\&quot;
         &quot;&quot;&quot;
         
         let response = try await session.respond(
             to: prompt,
             generating: CorrectedSentence.self
         )
         correctionResult = response.content.corrected
         print(&quot;isCorrect \(response.content.isCorrect)&quot;)
     } catch {
         errorMessage = &quot;Failed to get response: \(error.localizedDescription)&quot;
     }

     isLoading = false
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트해보니,&lt;br /&gt;틀린 문장을 입력하면 corrected에 수정된 문장과 isCorrect가 false로 리턴되고&lt;br /&gt;올바른 문장을 입력하면 corrected에 해당 문장과 isCorrect가 true가 리턴되네요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebEhfe/btsPHNCNFDg/5GnpVUFgscTYCGkTJzfD9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebEhfe/btsPHNCNFDg/5GnpVUFgscTYCGkTJzfD9K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot; data-filename=&quot;IMG_2364.PNG&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebEhfe/btsPHNCNFDg/5GnpVUFgscTYCGkTJzfD9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebEhfe%2FbtsPHNCNFDg%2F5GnpVUFgscTYCGkTJzfD9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/48Vai/btsPHjPJdIS/vKIDcPwdrd0ecXZLp3q310/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/48Vai/btsPHjPJdIS/vKIDcPwdrd0ecXZLp3q310/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot; data-filename=&quot;IMG_2363.PNG&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/48Vai/btsPHjPJdIS/vKIDcPwdrd0ecXZLp3q310/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F48Vai%2FbtsPHjPJdIS%2FvKIDcPwdrd0ecXZLp3q310%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1179&quot; height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-08-05 at 1.00.44 AM.png&quot; data-origin-width=&quot;148&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cT2MVm/btsPF6QQh0c/pkOKk2GSQ0kqYufvOCJys0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cT2MVm/btsPF6QQh0c/pkOKk2GSQ0kqYufvOCJys0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cT2MVm/btsPF6QQh0c/pkOKk2GSQ0kqYufvOCJys0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcT2MVm%2FbtsPF6QQh0c%2FpkOKk2GSQ0kqYufvOCJys0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;148&quot; height=&quot;37&quot; data-filename=&quot;Screenshot 2025-08-05 at 1.00.44 AM.png&quot; data-origin-width=&quot;148&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Generable&lt;/b&gt;을 사용하면 모델의 출력을 직접 파싱하지 않아도 되기 때문에 훨씬 편리하네요! &lt;br /&gt;결론 = 편하다, 좋다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resources:&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2025/259/&quot;&gt;https://developer.apple.com/videos/play/wwdc2025/259/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://developer.apple.com/documentation/foundationmodels/generable&quot;&gt;https://developer.apple.com/documentation/foundationmodels/generable&lt;/a&gt; &lt;a href=&quot;https://developer.apple.com/documentation/foundationmodels/generating-swift-data-structures-with-guided-generation&quot;&gt;https://developer.apple.com/documentation/foundationmodels/generating-swift-data-structures-with-guided-generation&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>@Generable</category>
      <category>@Guide</category>
      <category>foundationmodels</category>
      <category>FoundationModels Apple</category>
      <category>애플 OnDevice</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/323</guid>
      <comments>https://hyerios.tistory.com/entry/FoundationModels-Generable-Guide#entry323comment</comments>
      <pubDate>Tue, 5 Aug 2025 01:04:57 +0900</pubDate>
    </item>
    <item>
      <title>애플 Foundation Models로 Grammar Correction 만들기</title>
      <link>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-Grammar-Correction-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 애플 Foundation Models 프레임워크를 활용해서 간단한 Grammar Correction(문법 교정하는거?)을 만들어보려고 해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하기 전에, Foundation Models를 사용하기 위한 &lt;a href=&quot;https://hyerios.tistory.com/317&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;u&gt;몇 가지 요구사항&lt;/u&gt;&lt;/a&gt;이 충족되어야 하는데&lt;br /&gt;이 글을 읽기 전에 내 환경에서 사용 가능한지 먼저 확인해 보시는 걸 추천드립니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@State 프로퍼티 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1753621833582&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@State private var userInput: String = &quot;&quot;
@State private var correctionResult: String = &quot;&quot;
@State private var isLoading = false
@State private var errorMessage: String?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 4개의 @State변수를 사용하고 있어요&lt;br /&gt;userInput: 사용자가 입력한 문자를 담기 위한 변수&lt;br /&gt;correctionResult: 응답 결과를 담을 문자열 변수&lt;br /&gt;isLoading: 요청 중을 나타내는 플래그, progressView 제어&lt;br /&gt;errorMessage: 에러 발생시 에러 메세지를 위한 변수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;body&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1753621976176&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var body: some View {
    VStack(spacing: 16) {
        Text(&quot;  Enter a sentence you'd like to correct&quot;)
            .font(.headline)
            .frame(maxWidth: .infinity, alignment: .leading)
        
        TextEditor(text: $userInput)
            .frame(height: 120)
            .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray.opacity(0.4)))
            .padding(.horizontal, 4)
        
        HStack {
            Button(action: {
                userInput = &quot;&quot;
                correctionResult = &quot;&quot;
                errorMessage = nil
            }) {
                Label(&quot;Clear&quot;, systemImage: &quot;xmark.circle&quot;)
            }
            .buttonStyle(.bordered)
            
            Spacer()
            
            Button(action: {
                Task {
                    await correctSentence()
                }
            }) {
                HStack {
                    if isLoading {
                        ProgressView()
                    } else {
                        Image(systemName: &quot;wand.and.stars&quot;)
                        Text(&quot;Correct Grammar&quot;)
                    }
                }
            }
            .disabled(userInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isLoading)
            .buttonStyle(.borderedProminent)
        }
        
        if let errorMessage = errorMessage {
            Text(errorMessage)
                .foregroundColor(.red)
        }
        
        if !correctionResult.isEmpty {
            VStack(alignment: .leading, spacing: 8) {
                Text(&quot;Correction &amp;amp; Explanation&quot;)
                    .font(.headline)
                ScrollView {
                    Text(correctionResult)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
                .frame(minHeight: 100)
            }
            .padding()
            .background(Color(.systemGray6))
            .cornerRadius(12)
        }
        
        Spacer()
    }
    .padding()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;body는 크게&lt;br /&gt;타이틀, 입력 필드, 버튼 영역, 결과 출력부분으로 구성되어 있어요&lt;br /&gt;그냥 UI니까 여기는 패스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;correctSentence()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1753622530078&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func correctSentence() async {
    isLoading = true
    correctionResult = &quot;&quot;
    errorMessage = nil

    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

    do {
        let instructions = &quot;You are a helpful and knowledgeable English tutor. When a student provides a sentence, check it for grammar mistakes. If there are any, explain what is wrong and provide the corrected sentence. If the sentence is already correct, confirm that it is correct and explain why.&quot;
        let session = LanguageModelSession(instructions: instructions)

        let prompt = &quot;&quot;&quot;
        Please check the following sentence for any grammar mistakes. If there are mistakes, explain them and provide the corrected version. If the sentence is already correct, explain why it is correct.
        \&quot;\(userInput.trimmingCharacters(in: .whitespacesAndNewlines))\&quot;
        &quot;&quot;&quot;
        
        let response = try await session.respond(to: prompt)
        correctionResult = response.content
    } catch {
        errorMessage = &quot;Failed to get response: \(error.localizedDescription)&quot;
    }

    isLoading = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Correct Grammar 버튼을 탭하면 비동기 함수인 correctSentence()가 호출되는데요&lt;br /&gt;먼저, isLoading을 true로 설정하고 이전에 저장된 correctionResult와 errorMessage를 초기화시켜줍니다&lt;br /&gt;그리고 키보드를 내려주기 위해서 resignFirstResponder()도 호출해 줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, LanguageModelSession을 생성하고 instructions을 설정하는데요,&lt;br /&gt;instruction은 대충 &lt;br /&gt;너는 친절하고 똑똑한 영어 튜터이고 문법 오류가 있는지 확인해 주고 &lt;br /&gt;오류가 있다면 왜 잘못되었는지와 올바른 문장을 제시해 주고, &lt;br /&gt;문법 오류가 없다면 그 문장이 맞다는 것을 확인해 줘 뭐 이런 내용을 담았고&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용자 입력을 포함한&lt;span&gt; 구체적인&amp;nbsp;&lt;/span&gt;&lt;/span&gt;prompt도 비슷하게 작성해 줬어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 prompt를 LLM에 전달하고 응답을 기다려요&lt;br /&gt;응답이 오면, 그 내용을 correctionResult에 반영해 화면에 표시되게 해줘요&lt;br /&gt;만약 에러가 발생하면 catch블록이 실행되어 errorMessage에 오류 내용을 저장하게 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여튼 결과를 표시하거나 오류 메세지를 출력한 후 로딩 상태를 false로 변경해 줍니다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;+ 왜 instruction이랑 prompt랑 비슷한데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;분리해놨냐?&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;instruction이랑&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;prompt가 비슷해 보이지만, 역할이 조금 달라요&lt;br /&gt;instruction는 모델의 역할이나 동작 방식을 정의하는데 좀 더 포커스를 두고&lt;br /&gt;prompt는 실제 유저의 질문 또는 입력을 반영해서 구체적인 요청을 전달해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;WWDC25 비디오에 보니까 둘을 분리하는 것이 더 좋은 아웃풋을 낸다고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;하더라구요?&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 코드를 조합하면 아래와 같이 간단한 Grammar Correction을 만들어 볼 수 있어요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ScreenRecording_07-24-202520-27-37_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbsK2Z/btsPAXM3H8P/Me3ZyTCtXl1mRiiLROEy20/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbsK2Z/btsPAXM3H8P/Me3ZyTCtXl1mRiiLROEy20/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbsK2Z/btsPAXM3H8P/Me3ZyTCtXl1mRiiLROEy20/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bbsK2Z/btsPAXM3H8P/Me3ZyTCtXl1mRiiLROEy20/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;650&quot; data-filename=&quot;ScreenRecording_07-24-202520-27-37_1-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 여기에 올려뒀어요!&lt;br /&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kimhyeri/FoundationModelsPlayground&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753623522395&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - kimhyeri/FoundationModelsPlayground: A collection of examples using Apple&amp;rsquo;s FoundationModels framework&quot; data-og-description=&quot;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; data-og-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bi2vmM/hyZq0lbeN4/LdaaN4lPyhullGhRh2eTTk/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/FYgYG/hyZqWwkgrU/ZPkqyYNY21LI76HASeKo90/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185&quot;&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bi2vmM/hyZq0lbeN4/LdaaN4lPyhullGhRh2eTTk/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/FYgYG/hyZqWwkgrU/ZPkqyYNY21LI76HASeKo90/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - kimhyeri/FoundationModelsPlayground: A collection of examples using Apple&amp;rsquo;s FoundationModels framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>Apple On-device LLM</category>
      <category>foundation models</category>
      <category>foundationmodels</category>
      <category>Grammar Correction</category>
      <category>애플 온디바이스 모델</category>
      <category>온디바이스 llm</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/322</guid>
      <comments>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-Grammar-Correction-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry322comment</comments>
      <pubDate>Sun, 27 Jul 2025 23:06:20 +0900</pubDate>
    </item>
    <item>
      <title>애플 Foundation Models로 온디바이스 챗봇 만들기</title>
      <link>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-%EC%98%A8%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4-%EC%B1%97%EB%B4%87-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요 :)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플에서 제공하는 온디바이스 LLM인 Foundation Models를 이용해서 간단한 챗봇을 만들어 볼게요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하기 전에 , Foundation Models를 사용하기 위한 &lt;a title=&quot;몇 가지 요구사항&quot; href=&quot;https://hyerios.tistory.com/317&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;몇 가지 요구사항&lt;/a&gt;이 있는데요&lt;br /&gt;이 글을 읽기 전에 내 환경에서 사용 가능한지 먼저 확인해 보시는 걸 추천드립니다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ChatView&lt;br /&gt;&lt;/b&gt;먼저 뷰에서 사용할 세 가지 프로퍼티를 선언해줬어요&lt;/p&gt;
&lt;pre id=&quot;code_1753264959102&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@StateObject private var viewModel = ChatViewModel()
@FocusState private var isInputFocused: Bool
@State private var lastMessageID: UUID?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewModel은 채팅 메세지와 입력 상태 등을 관리하기 위한 뷰모델 인스턴스이고&lt;br /&gt;isInputFocused는 텍스트 필드에 포커스가 되어 있는지 확인하기 위한 프로퍼티이고&lt;br /&gt;lastMessageID는 마지막 메세지의 ID를 저장하는 변수예요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 body&lt;/p&gt;
&lt;pre id=&quot;code_1753265072121&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var body: some View {
    VStack {
        chatMessagesView
        Divider()
        inputView
    }
    .onTapGesture {
        isInputFocused = false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chatMessageView는 채팅 메세지 목록을 보여주는 뷰고&lt;br /&gt;Divider()을 추가해 메세지 목록이랑 입력창이랑 구분해 줬어요&lt;br /&gt;inputView는 텍스트 필드와 전송 버튼이 있는 뷰예요&lt;br /&gt;그리고 VStack에 onTapGesture등록해서 화면을 탭하면 키보드가 사라지도록 했어요&lt;br /&gt;&lt;br /&gt;다음으로는 chatMessageView&lt;/p&gt;
&lt;pre id=&quot;code_1753266673155&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private var chatMessagesView: some View {
    ScrollViewReader { proxy in
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 12) {
                ForEach(viewModel.messages) { msg in
                    MessageBubble(message: msg)
                }
                if viewModel.isLoading {
                    ProgressView()
                        .frame(maxWidth: .infinity)
                }
            }
            .padding()
        }
        .onChange(of: viewModel.messages) { oldMessages, newMessages in
            if let lastID = newMessages.last?.id {
                lastMessageID = lastID
                withAnimation {
                    proxy.scrollTo(lastID, anchor: .bottom)
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScrollView + LazyVstack 조합으로 채팅 메세지를 세로로 나열해줘요&lt;br /&gt;(LazyVStack을 사용하면 많은 메세지가 쌓여도 필요할 때만 뷰를 렌더링 해서 효율적이에요)&lt;br /&gt;MessageBubble 각각의 메세지를 보여주는 뷰고&lt;br /&gt;PregressView는 메세지 생성 중일 때 로딩 인디케이터를 표시하기 위한 뷰예요&lt;br /&gt;onChange를 통해 새로운 메세지가 추가되면 마지막 메세지로 스크롤되도록 했어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 inputView&lt;/p&gt;
&lt;pre id=&quot;code_1753266796012&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private var inputView: some View {
    HStack {
        TextField(&quot;Enter your message&quot;, text: $viewModel.currentInput)
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .focused($isInputFocused)
            .submitLabel(.send)
            .onSubmit {
                sendMessage()
            }

        Button(action: sendMessage) {
            Text(&quot;Send&quot;)
        }
        .disabled(viewModel.isLoading || viewModel.currentInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
    }
    .padding()
    .background(Color(UIColor.systemBackground))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메세지를 입력할 수 있게 TextField를 생성했어요&lt;br /&gt;텍스트 필드는 키보드 포커스를 감지하고 키보드에서 '보내기'를 보내는 경우 sendMessage()가 호출되도록 했어요&lt;br /&gt;또한 Send 버튼을 추가해서 버튼이 눌렸을 때에도 sendMessage()가 불릴 수 있게 했어요 &lt;br /&gt;단, 입력이 비었거나 로딩중이면 버튼이 비활성화 돼요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으론 sendMessage()&lt;/p&gt;
&lt;pre id=&quot;code_1753266977571&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func sendMessage() {
    Task {
        isInputFocused = true
        await viewModel.send()
        if let lastID = viewModel.messages.last?.id {
            lastMessageID = lastID
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 입력된 메세지를 전송을 하면 sendMessage()가 호출되어요&lt;br /&gt;이 함수에서 viewModel.send()를 호출시켜 챗봇의 응답을 처리하도록 하고 마지막 메세지를 갱신해줘요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ ChatView 전체 코드&lt;/p&gt;
&lt;pre id=&quot;code_1753264865925&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI
import FoundationModels
import Combine

struct ChatView: View {
    @StateObject private var viewModel = ChatViewModel()
    @FocusState private var isInputFocused: Bool
    @State private var lastMessageID: UUID?

    var body: some View {
        VStack {
            chatMessagesView
            Divider()
            inputView
        }
        .onTapGesture {
            isInputFocused = false
        }
    }

    private var chatMessagesView: some View {
        ScrollViewReader { proxy in
            ScrollView {
                LazyVStack(alignment: .leading, spacing: 12) {
                    ForEach(viewModel.messages) { msg in
                        MessageBubble(message: msg)
                    }
                    if viewModel.isLoading {
                        ProgressView()
                            .frame(maxWidth: .infinity)
                    }
                }
                .padding()
            }
            .onChange(of: viewModel.messages) { oldMessages, newMessages in
                if let lastID = newMessages.last?.id {
                    lastMessageID = lastID
                    withAnimation {
                        proxy.scrollTo(lastID, anchor: .bottom)
                    }
                }
            }
        }
    }

    private var inputView: some View {
        HStack {
            TextField(&quot;Enter your message&quot;, text: $viewModel.currentInput)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .focused($isInputFocused)
                .submitLabel(.send)
                .onSubmit {
                    sendMessage()
                }

            Button(action: sendMessage) {
                Text(&quot;Send&quot;)
            }
            .disabled(viewModel.isLoading || viewModel.currentInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
        }
        .padding()
        .background(Color(UIColor.systemBackground))
    }

    private func sendMessage() {
        Task {
            isInputFocused = true
            await viewModel.send()
            if let lastID = viewModel.messages.last?.id {
                lastMessageID = lastID
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MessageBubble&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1753267046195&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

struct MessageBubble: View {
    let message: Message
    
    var body: some View {
        HStack {
            if message.role.isUser {
                Spacer()
            }

            Text(message.content)
                .padding(12)
                .background(message.role.bubbleColor)
                .cornerRadius(12)

            if message.role.isChatBot {
                Spacer()
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MessageBubble은 채팅 메세지를 위한 뷰예요&lt;br /&gt;사용자 메세지와 챗봇 메세지를 구분해서 좌우 정렬하고 role에 따라 배경을 다르게 표시해 줍니다&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Message&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1753267123354&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

struct Message: Identifiable, Equatable {
    let id = UUID()
    let content: String
    let role: Role
    
    enum Role {
        case user, chatbot
    }
    
    static func == (lhs: Message, rhs: Message) -&amp;gt; Bool {
        return lhs.id == rhs.id &amp;amp;&amp;amp; lhs.content == rhs.content &amp;amp;&amp;amp; lhs.role == rhs.role
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id는 메세지를 구분할 수 있도록 UUID를 생성해서 넣어줬어요&lt;br /&gt;content는 메세지 내용을 위한 것이고 role은 해당 메세지가 내 메세지인지 chatBot 메세지인지 구분하기 위한 enum프로퍼티예요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 MessageBubble에서 사용자 메세지와 챗봇 메세지를 구분해서 각각 다른 색을 적용할건데 사용하기 쉽게 extension을 만들어줍니다&lt;/p&gt;
&lt;pre id=&quot;code_1753267150744&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Message.Role {
    var bubbleColor: Color {
        switch self {
        case .user:
            return Color.blue.opacity(0.9)
        case .chatbot:
            return Color.gray.opacity(0.15)
        }
    }
    
    var isUser: Bool {
        return self == .user
    }
    
    var isChatBot: Bool {
        return self == .chatbot
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ViewModel&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1753268022647&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Published var messages: [Message] = []
@Published var currentInput: String = &quot;&quot;
@Published var isLoading: Bool = false

private var session: LanguageModelSession?
private var conversationHistory: [String] = []
private let conversationHistoryLimit: Int = 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;messages 현재까지 주고받은 메세지를 담는 배열이고&lt;br /&gt;currentInput은 입력창에 현재 입력된 텍스트를 담고&lt;br /&gt;isLoading은 응답을 기다리는 중인지 여부를 확인하기 위한 프로퍼티예요&lt;br /&gt;session 챗봇 대화를 처리하는 세션 객체이고&lt;br /&gt;conversationHistory 과거 대화 내용을 문자열 배열로 저장하기 위한 프로퍼티이고&lt;br /&gt;converstaionHistoryLimit은 대화 히스토리의 최대 저장 개수를 나타내는 프로퍼티예요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기화&lt;/p&gt;
&lt;pre id=&quot;code_1753268082646&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;init() {
    let instructions = &quot;&quot;&quot;
    You are a chatbot with a great sense of humor.  
    Engage the user with friendly and lighthearted conversation, occasionally adding humorous remarks.  
    Avoid excessive jokes and keep the conversation flowing naturally.
    Respond in a concise manner, typically with 1-3 sentences.
    &quot;&quot;&quot;
    self.session = LanguageModelSession(instructions: instructions)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션을 초기화할 때, 챗봇이 유머 감각을 갖고 동작하도록 instructions를 설정했어요&lt;br /&gt;너무 길지 않게 1-3 문장의 답변을 리턴할 수 있게 설정했어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 send함수&lt;/p&gt;
&lt;pre id=&quot;code_1753268118466&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func send() async {
    guard !currentInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
    
    let userMessage = currentInput
    currentInput = &quot;&quot;
    
    messages.append(Message(content: userMessage, role: .user))
    isLoading = true
    
    do {
        let response = try await sendToAI(text: userMessage)
        messages.append(Message(content: response, role: .chatbot))
    } catch {
        messages.append(Message(content: &quot;Error: \(error.localizedDescription)&quot;, role: .chatbot))
    }
    
    isLoading = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메세지를 입력하고 전송했을 때 실행되는 함수예요&lt;br /&gt;공백은 무시되도록 했고 currentInput을 &quot;&quot;로 설정해 입력창을 초기화해줘요 &lt;br /&gt;그리고 사용자가 입력한 메세지를 messages배열에 추가해 화면에 표시되게 해줘요&lt;br /&gt;로딩 상태를 활성화해주고 sendToAI()를 호출해줘요&lt;br /&gt;sendToAI에서 받은 응답을 role = chatbot으로 설정해 주고 message에 추가해 줍니다&lt;br /&gt;그리고 로깅 상태를 비활성화해 줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sendToAI(text:)&lt;/p&gt;
&lt;pre id=&quot;code_1753269223397&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func sendToAI(text: String) async throws -&amp;gt; String {
    guard let session = session else {
        throw NSError()
    }
    
    conversationHistory.append(&quot;User: \(text)&quot;)
    
    let prompt = &quot;&quot;&quot;
    Previous conversation:
    \(conversationHistory.joined(separator: &quot;\n&quot;))
    
    Chatbot: Let me respond to the user's latest message in a friendly and slightly humorous way.
    
    Response:
    &quot;&quot;&quot;
    
    let response = try await session.respond(to: prompt)
    
    conversationHistory.append(&quot;Chatbot: \(response.content)&quot;)
    
    if conversationHistory.count &amp;gt; conversationHistoryLimit {
        conversationHistory = Array(conversationHistory.suffix(conversationHistoryLimit))
    }
    
    return response.content
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conversationHistory에 유저의 입력값을 추가해 주고&amp;nbsp;&lt;br /&gt;최근 대화 히스토리를 포함한 프롬프트를 생성해줘요&lt;br /&gt;이 프롬프트를 session.respond(to: prompt에 전달해 응답을 요청하고 챗봇의 응답을 conversationHistory에 추가해줘요&lt;br /&gt;대화 히스토리 개수는 최대 10개까지 유지되도록 했고 응답받은 reponse.content는 리턴하도록 했어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ ChatViewModel 전체 코드&lt;/p&gt;
&lt;pre id=&quot;code_1753267334245&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation
import FoundationModels
import Combine

@MainActor
class ChatViewModel: ObservableObject {
    @Published var messages: [Message] = []
    @Published var currentInput: String = &quot;&quot;
    @Published var isLoading: Bool = false
    
    private var session: LanguageModelSession?
    private var conversationHistory: [String] = []
    private let conversationHistoryLimit: Int = 10
    
    init() {
        let instructions = &quot;&quot;&quot;
        You are a chatbot with a great sense of humor.  
        Engage the user with friendly and lighthearted conversation, occasionally adding humorous remarks.  
        Avoid excessive jokes and keep the conversation flowing naturally.
        Respond in a concise manner, typically with 1-3 sentences.
        &quot;&quot;&quot;
        self.session = LanguageModelSession(instructions: instructions)
    }

    func send() async {
        guard !currentInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
        
        let userMessage = currentInput
        currentInput = &quot;&quot;
        
        messages.append(Message(content: userMessage, role: .user))
        isLoading = true
        
        do {
            let response = try await sendToAI(text: userMessage)
            messages.append(Message(content: response, role: .chatbot))
        } catch {
            messages.append(Message(content: &quot;Error: \(error.localizedDescription)&quot;, role: .chatbot))
        }
        
        isLoading = false
    }

    private func sendToAI(text: String) async throws -&amp;gt; String {
        guard let session = session else {
            throw NSError()
        }
        
        conversationHistory.append(&quot;User: \(text)&quot;)
        
        let prompt = &quot;&quot;&quot;
        Previous conversation:
        \(conversationHistory.joined(separator: &quot;\n&quot;))
        
        Chatbot: Let me respond to the user's latest message in a friendly and slightly humorous way.
        
        Response:
        &quot;&quot;&quot;
        
        let response = try await session.respond(to: prompt)
        
        conversationHistory.append(&quot;Chatbot: \(response.content)&quot;)
        
        if conversationHistory.count &amp;gt; conversationHistoryLimit {
            conversationHistory = Array(conversationHistory.suffix(conversationHistoryLimit))
        }
        
        return response.content
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지!&lt;br /&gt;이 모든 코드를 조합하면 아래와 같이 간단한 챗봇을 만들어 볼 수 있어요&lt;br /&gt;instructions, prompt를 다르게 변경해서 원하는 종류의 챗봇을 직접 만들어봐도 좋을 것 같아요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chatBot.gif&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;850&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blWZ38/btsPuM6XMuq/kavvRYG4P5eL7Cks8pT2tK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blWZ38/btsPuM6XMuq/kavvRYG4P5eL7Cks8pT2tK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blWZ38/btsPuM6XMuq/kavvRYG4P5eL7Cks8pT2tK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/blWZ38/btsPuM6XMuq/kavvRYG4P5eL7Cks8pT2tK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;850&quot; data-filename=&quot;chatBot.gif&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;850&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 여기에 올려뒀어요  &lt;br /&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot;&gt;https://github.com/kimhyeri/FoundationModelsPlayground&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1753268530122&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - kimhyeri/FoundationModelsPlayground: A collection of examples using Apple&amp;rsquo;s FoundationModels framework&quot; data-og-description=&quot;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; data-og-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cM4pDt/hyZng3LSMu/Y0aA49MlSZUoYehNKVEa91/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/bBA6CA/hyZngvW69P/fmtJ3s68wFAvXfdBcACei1/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185&quot;&gt;&lt;a href=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kimhyeri/FoundationModelsPlayground&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cM4pDt/hyZng3LSMu/Y0aA49MlSZUoYehNKVEa91/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185,https://scrap.kakaocdn.net/dn/bBA6CA/hyZngvW69P/fmtJ3s68wFAvXfdBcACei1/img.png?width=1200&amp;amp;height=600&amp;amp;face=967_116_1030_185');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - kimhyeri/FoundationModelsPlayground: A collection of examples using Apple&amp;rsquo;s FoundationModels framework&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A collection of examples using Apple&amp;rsquo;s FoundationModels framework - kimhyeri/FoundationModelsPlayground&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>Apple OnDevice LLM</category>
      <category>foundationmodels</category>
      <category>OnDevice ChatBot</category>
      <category>온디바이스 llm</category>
      <category>챗봇</category>
      <category>챗봇 만들기</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/321</guid>
      <comments>https://hyerios.tistory.com/entry/Foundation-Models%EB%A1%9C-%EC%98%A8%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4-%EC%B1%97%EB%B4%87-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry321comment</comments>
      <pubDate>Wed, 23 Jul 2025 23:48:00 +0900</pubDate>
    </item>
    <item>
      <title>Foundation Models - Design for on-device LLM (2)</title>
      <link>https://hyerios.tistory.com/entry/Foundation-Models-Design-for-on-device-LLM-2</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프롬프트 작성 모범 사례&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.51.07 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PZhvu/btsPr6oMrVU/JetT1a0GHZiAma29phyLpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PZhvu/btsPr6oMrVU/JetT1a0GHZiAma29phyLpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PZhvu/btsPr6oMrVU/JetT1a0GHZiAma29phyLpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPZhvu%2FbtsPr6oMrVU%2FJetT1a0GHZiAma29phyLpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.51.07 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 생성하는 콘텐츠의 양은 프롬프트로 직접 조절할 수 있어요&lt;br /&gt;(예: In a single paragraph, In three sentences 등)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.51.38 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8gZI6/btsPrKe9QZq/aV1hQKrOzm1yglCgUSMuhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8gZI6/btsPrKe9QZq/aV1hQKrOzm1yglCgUSMuhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8gZI6/btsPrKe9QZq/aV1hQKrOzm1yglCgUSMuhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8gZI6%2FbtsPrKe9QZq%2FaV1hQKrOzm1yglCgUSMuhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.51.38 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트에 역할을 명시함으로써 스타일과 어투를 조절할 수 있어요&lt;br /&gt;이와 같이 모델은 프롬프트에 따라 다양한 역할을 수행할 수 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Prompting best practices&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.52.25 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhLQEa/btsPrbxu8LE/hpdbpt8hI7G8SqL8gHfCw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhLQEa/btsPrbxu8LE/hpdbpt8hI7G8SqL8gHfCw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhLQEa/btsPrbxu8LE/hpdbpt8hI7G8SqL8gHfCw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhLQEa%2FbtsPrbxu8LE%2Fhpdbpt8hI7G8SqL8gHfCw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.52.25 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트는 명확한 명령어처럼 작성하세요&lt;br /&gt;모델은 하나의 구체적인 작업을 수행할 때 가장 잘 동작해요&lt;br /&gt;또, 원하는 출력 예시를 4개 미만으로 직접 프롬프트 안에 작성하면 효과를 높일 수 있어요&lt;br /&gt;해야 할 일을 강조할 때는 대문자로 MUST, 하지 말아야 할 일을 강조할 때는 대문자로 DO NOT 이런 식으로 단호한 어조로 사용해 보세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Xcode #Playground 활용하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 10.20.37 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V4M7d/btsPsadAaxA/e8K6zhHpdBtWSntaPDPISK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V4M7d/btsPsadAaxA/e8K6zhHpdBtWSntaPDPISK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V4M7d/btsPsadAaxA/e8K6zhHpdBtWSntaPDPISK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV4M7d%2FbtsPsadAaxA%2Fe8K6zhHpdBtWSntaPDPISK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 10.20.37 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트 실험을 해보고 싶다면 Xcode에서 지원하는 Playground메크로를 활용해 보세요&lt;br /&gt;스유 프리뷰처럼 오른쪽 캔버스에 즉시 응답을 확인할 수 있어요&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prompt와 instructions의 차이&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.54.12 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K6B2x/btsPpxhAU9e/FUBPltrULMrxB7CWpJL001/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K6B2x/btsPpxhAU9e/FUBPltrULMrxB7CWpJL001/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K6B2x/btsPpxhAU9e/FUBPltrULMrxB7CWpJL001/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK6B2x%2FbtsPpxhAU9e%2FFUBPltrULMrxB7CWpJL001%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.54.12 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instructions은 FoundationModels프레임워크에서 제공하는 또 다른 프롬프트 형식인데,&amp;nbsp;&lt;br /&gt;모델이 지속적으로 어떤 방식으로 응답할지 지정하는 역할을 해요&lt;br /&gt;Instructions을 추가하면, 모델은 먼저 이 지시를 인지한 후에 프롬프트를 받아요 (우선 적용)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;AI safety&lt;/b&gt;&amp;nbsp;&lt;br /&gt;프롬프트는 사용자로부터 받을 수 있는데&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;예상치 못한 입력이나 악의적인 입력이 들어올 수 있어서 이를 대비해 안전을 고려해야 해요&lt;br /&gt;프레임워크에는 Apple이 사전 학습시킨 가드레일이 내장되어 있고 이는 입력 출력 모두에 적용되어요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.58.05 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z9P0l/btsPrfNttcN/wMsHkbVI5TozbYAQCFPf51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z9P0l/btsPrfNttcN/wMsHkbVI5TozbYAQCFPf51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z9P0l/btsPrfNttcN/wMsHkbVI5TozbYAQCFPf51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz9P0l%2FbtsPrfNttcN%2FwMsHkbVI5TozbYAQCFPf51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.58.05 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트, instructions 같은 입력이 안전한지 감지해 차단하고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.58.23 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKrU1S/btsPqUv0VME/1pCnekjEqVUpo6OG12PIik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKrU1S/btsPqUv0VME/1pCnekjEqVUpo6OG12PIik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKrU1S/btsPqUv0VME/1pCnekjEqVUpo6OG12PIik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKrU1S%2FbtsPqUv0VME%2F1pCnekjEqVUpo6OG12PIik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.58.23 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 부적절한 출력을 하려 해도 가드레일이 이를 차단해요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.58.42 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxmN2L/btsPqQtBYBV/4jGUS1zmbFfcCWLWyeqfwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxmN2L/btsPqQtBYBV/4jGUS1zmbFfcCWLWyeqfwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxmN2L/btsPqQtBYBV/4jGUS1zmbFfcCWLWyeqfwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxmN2L%2FbtsPqQtBYBV%2F4jGUS1zmbFfcCWLWyeqfwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 9.58.42 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 위와 같은 에러를 감지할 수 있어요&lt;br /&gt;사용자 입력으로 인한 기능이면 적절한 피드백을 보여주고, 앱이 알아서 수행하는 기능이라면 무시할 수도 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Evaluate and test&lt;/b&gt;&lt;br /&gt;앱에서 생성되는 콘텐츠와 품질과 안전성을 보장하려면 테스트가 필수예요&lt;br /&gt;주요 사용 사례를 다룰 수 있는 프롬프트와 문제를 유발하는 프롬프트 모두를 수집해서 데이터셋을 구성해서 테스트하세요&lt;br /&gt;소규모는 수동 점검, 대규모는 자동화 툴 + LLM 기반 평가 도입 기능이 필요하며 비정상 시나리오도 테스트가 필요해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;체크리스트&lt;/b&gt;&lt;br /&gt;1. 가드레일 에러 발생 시 핸들링하는 UI를 만들 것&lt;br /&gt;2. Instructions에 안전 지침 포함할 것&lt;br /&gt;3. 사용자 입력을 안전하게 조합한 프롬프트를 설계할 것&lt;br /&gt;4. 생성된 콘텐츠가 사용자에게 미치는 영향 고려할 것 &lt;br /&gt;5. 테스트 및 평가 자동화 도구 구축할 것&lt;br /&gt;6. 문제 발견 시 Apple에 리포트할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resource: &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2025/248/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.apple.com/videos/play/wwdc2025/248/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>design for on-device llm</category>
      <category>foundationmodels</category>
      <category>ondevice llm</category>
      <category>애플 llm</category>
      <category>온디바이스</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/320</guid>
      <comments>https://hyerios.tistory.com/entry/Foundation-Models-Design-for-on-device-LLM-2#entry320comment</comments>
      <pubDate>Mon, 21 Jul 2025 22:50:25 +0900</pubDate>
    </item>
    <item>
      <title>Foundation Models - Design for on-device LLM (1)</title>
      <link>https://hyerios.tistory.com/entry/Foundation-Models-Design-for-on-device-LLM</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;prompt 프롬프트란?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.11.51 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdITUQ/btsPqGSdyGI/ai2v6Krcz9ualFQZXNKKx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdITUQ/btsPqGSdyGI/ai2v6Krcz9ualFQZXNKKx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdITUQ/btsPqGSdyGI/ai2v6Krcz9ualFQZXNKKx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdITUQ%2FbtsPqGSdyGI%2Fai2v6Krcz9ualFQZXNKKx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1898&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.11.51 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성형 AI 모델에 입력하는 텍스트를 의미해요&lt;br /&gt;이 텍스트는 모델의 응답을 유도하는 역할을 하며, 자연어로 즉 동료에게 메시지 보내듯 작성하면 돼요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.12.19 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTlJOJ/btsPqHp3E05/t2MeR4iafvQegbm5s1dCE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTlJOJ/btsPqHp3E05/t2MeR4iafvQegbm5s1dCE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTlJOJ/btsPqHp3E05/t2MeR4iafvQegbm5s1dCE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTlJOJ%2FbtsPqHp3E05%2Ft2MeR4iafvQegbm5s1dCE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.12.19 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트 예시: Generate a bedtime story about a fox&lt;br /&gt;(예시에선 영어로 작성했지만, Apple Intelligence가 지원하는 언어 암거나 가능해요)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.26.10 AM.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coda9N/btsPp7vIcRp/KMBFop1KuZAZPUp6OozFPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coda9N/btsPp7vIcRp/KMBFop1KuZAZPUp6OozFPK/img.png&quot; data-alt=&quot;https://www.apple.com/kr/newsroom/2025/02/apple-intelligence-expands-to-more-languages-and-regions-in-april/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coda9N/btsPp7vIcRp/KMBFop1KuZAZPUp6OozFPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcoda9N%2FbtsPp7vIcRp%2FKMBFop1KuZAZPUp6OozFPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1188&quot; height=&quot;718&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.26.10 AM.png&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.apple.com/kr/newsroom/2025/02/apple-intelligence-expands-to-more-languages-and-regions-in-april/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어도 공식적으로 지원하므로, 한국어도 사용할 수 있어요&lt;br /&gt;여튼, 이 프롬프트를 Language Model Session에 입력하면 모델은 이에 관련된 동화를 만들어내요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;온디바이스 LLM 작동 방식&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;애플의 Foundation Models프레임워크에서는,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;프롬프트가 온디바이스 LLM로 전달되어요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.12.38 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmjPHg/btsPrfNnUCg/O69Ks0A2sVJePCNnA4hpb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmjPHg/btsPrfNnUCg/O69Ks0A2sVJePCNnA4hpb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmjPHg/btsPrfNnUCg/O69Ks0A2sVJePCNnA4hpb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmjPHg%2FbtsPrfNnUCg%2FO69Ks0A2sVJePCNnA4hpb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.12.38 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 텍스트를 이해하고 생성할 수 있는 모델이고&lt;br /&gt;이는 general purpose 모델로 iOS, iPadOS, macOS, visionOS 같은 운영체제에 내장되어 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;온디바이스 LLM을 위한 디자인 전략&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.14.05 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dh3Z2a/btsPpx9RLKA/Q1LEYk6JGZfeNXknSyFm31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dh3Z2a/btsPpx9RLKA/Q1LEYk6JGZfeNXknSyFm31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dh3Z2a/btsPpx9RLKA/Q1LEYk6JGZfeNXknSyFm31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdh3Z2a%2FbtsPpx9RLKA%2FQ1LEYk6JGZfeNXknSyFm31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.14.05 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모델은 사용자의 기기 내에서 작동하며, 아래와 같은 언어 작업에 사용할 수 있어요&lt;br /&gt;(요약, 분류, 멀티 턴 대화, 텍스트 작성, 텍스트 수정, 텍스트에서 태그 추출하기)&lt;br /&gt;하지만 이 모델은 모바일 기기에 적합하도록 최적화 및 압축된 버전이에요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모델 사이즈&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.14.27 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HD8V3/btsPqGEFWuf/1RyS1apw0Kdo0t1Ll1Oyr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HD8V3/btsPqGEFWuf/1RyS1apw0Kdo0t1Ll1Oyr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HD8V3/btsPqGEFWuf/1RyS1apw0Kdo0t1Ll1Oyr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHD8V3%2FbtsPqGEFWuf%2F1RyS1apw0Kdo0t1Ll1Oyr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.14.27 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플이 사용하는 모델은 약 3B크기로 여전히 강력한 머신러닝 모델이긴 하지만,&amp;nbsp;&lt;br /&gt;서버 베이스 LLM과 비교해서는 매우 작아요&amp;nbsp;&lt;br /&gt;이런 서버 LLM들은 수백억 - 수천억 개의 파라미터를 가지고 있어요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크기에 따른 제약사항&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.34.10 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/va7ko/btsPrpIXFPU/R0qFTcDyBKOlfrh3OfMCs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/va7ko/btsPrpIXFPU/R0qFTcDyBKOlfrh3OfMCs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/va7ko/btsPrpIXFPU/R0qFTcDyBKOlfrh3OfMCs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fva7ko%2FbtsPrpIXFPU%2FR0qFTcDyBKOlfrh3OfMCs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.34.10 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기 차이 때문에, 서버 기반 LLM에서 잘 작동하던 작업이 온디바이스 모델에서는 작동하지 않을 수도 있어요&lt;br /&gt;복잡한 추론이 필요한 작업에서 결과가 만족스럽지 않다면, 프롬프트를 더 단순한 단계들로 나누는 것을 시도해 보세요&lt;br /&gt;수학 관련 작업의 경우, 계산기를 대신하도록 모델에 맡기는 것은 피하세요&lt;br /&gt;수학은 AI가 아닌 일반 코드로 처리하는 것이 훨씬 안정적이에요&lt;br /&gt;또한, 이 시스템은 코드 생성에 최적화되어 있지 않기 때문에, 코드 작성 작업도 피하는 것이 좋아요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.15.55 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vWfGI/btsPrpvov7G/QKJE4ZtxxFk3znZJ6uloaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vWfGI/btsPrpvov7G/QKJE4ZtxxFk3znZJ6uloaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vWfGI/btsPrpvov7G/QKJE4ZtxxFk3znZJ6uloaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWfGI%2FbtsPrpvov7G%2FQKJE4ZtxxFk3znZJ6uloaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.15.55 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 크기 때문에 world events에 대한 지식도 제한적이에요&lt;br /&gt;예를 들어, 모델이 학습한 이후에 발생한 최근 사건에 대해서는 알지 못해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Hallucination 환각&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.18.06 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfo1Vt/btsPpyVbZJ1/15f8CT0A9cqy52c1Tjmzy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfo1Vt/btsPpyVbZJ1/15f8CT0A9cqy52c1Tjmzy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfo1Vt/btsPpyVbZJ1/15f8CT0A9cqy52c1Tjmzy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfo1Vt%2FbtsPpyVbZJ1%2F15f8CT0A9cqy52c1Tjmzy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;1964&quot; data-filename=&quot;Screenshot 2025-07-21 at 12.18.06 AM.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hallucination이 뭐냐?&lt;br /&gt;모델이 모르는 정보를 요청받았을 때, 완전히 사실이 아닌 내용을 지어낼 수 있는데 이것을 Hallucination이라고 불러요&lt;br /&gt;이 때문에 사람들에게 잘못된 정보를 줄 수 있어요&lt;br /&gt;따라서 시스템 언어 모델을 사실에 의존하는 용도로 사용하지 마세요&lt;br /&gt;만약 정확한 사실을 생성하고 싶다면, 검증된 정보를 프롬프트에 직접 포함시켜 주고&lt;br /&gt;새로운 프롬프트를 작성할 때마다, 모델이 출력한 결과를 반드시 검증하세요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resource: &lt;a title=&quot;Explore prompt design &amp;amp; safety for on-device foundation models&quot; href=&quot;https://developer.apple.com/videos/play/wwdc2025/248/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/videos/play/wwdc2025/248/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>foundationmodels</category>
      <category>llm</category>
      <category>on-device</category>
      <category>wwdc25</category>
      <category>온디바이스</category>
      <category>온디바이스 llm</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/319</guid>
      <comments>https://hyerios.tistory.com/entry/Foundation-Models-Design-for-on-device-LLM#entry319comment</comments>
      <pubDate>Mon, 21 Jul 2025 00:58:20 +0900</pubDate>
    </item>
    <item>
      <title>Apple Foundation Models 사용해보기</title>
      <link>https://hyerios.tistory.com/entry/FoundationModels-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 :)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;애플이 Xcode 26부터 지원해 주는 &lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: center;&quot; href=&quot;https://developer.apple.com/documentation/Xcode/running-code-snippets-using-the-playground-macro&quot;&gt;playground 매크로&lt;/a&gt;덕에,&lt;br /&gt;FoundationModels 프레임워크를 테스트하기가 너무 편하더라구요&lt;br /&gt;await, try, throws 등과 같은 비동기/동기 코드도 지원하고&lt;br /&gt;전체 앱 빌드 없이도 코드 스니펫만 빠르게 실행할 수 있어서요&lt;br /&gt;이 playground 매크로를 활용해서 FoundationModels을 테스트해보려고 해요&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;FoundationModels프레임워크를 사용해 온디바이스 LLM을 실행하고,&lt;br /&gt;프롬프트에 대한 응답을 생성하는 아주 간단한 예제를 실행해봤어요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-07-18 at 2.11.02 AM.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KV0ob/btsPmyOcXDB/pxyuZIZGU2zHLoKUrxCLs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KV0ob/btsPmyOcXDB/pxyuZIZGU2zHLoKUrxCLs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KV0ob/btsPmyOcXDB/pxyuZIZGU2zHLoKUrxCLs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKV0ob%2FbtsPmyOcXDB%2FpxyuZIZGU2zHLoKUrxCLs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-filename=&quot;Screenshot 2025-07-18 at 2.11.02 AM.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;LanguageModelSession은 애플의 FoundationModels프레임워크에서 사용하는 객체로,&lt;br /&gt;온디바이스에서 LLM을 실행하고 세션 단위로 제어할 수 있게 해주는 클래스예요&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;프롬프트인 List the top 5 fastest-growing stock markets in 2025를 모델에 전달해 볼게요&lt;br /&gt;모델이 해당 요청에 대해 텍스트 응답을 생성하고 그 결과를 출력하도록 했어요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;와 근데 11.4s 실화?&lt;br /&gt;온디바이스 LLM인데?&lt;br /&gt;실시간 인터랙션용으로는 아직은 조금 느릴 수도 있겠다 싶어요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 2025년 기준으로 가장 빠르게 성장 중인 주식시장 5곳을 알려줘라고 했는데&lt;br /&gt;실제 주식거래소나 증시 시장 이름이 아닌 국가를 알려주고 있네요 ㅎㅎ;&lt;br /&gt;모델 응답 내용이 조금 아쉽긴 하지만 이런 점도 점차 개선될 거라 기대해야죠&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 애플은 앱 업데이트 없이도 OS나 프레임워크 레벨에서 모델을 갱신할 수 있는 구조를 제공하고 있어서&lt;br /&gt;유저는 항상 최신의 모델을 쓸 수 있어 이 부분은 사용자 입장에서 큰 장점이라고 생각 들어요&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;아직 베타라 그런지 속도와 모델 응답 부분에서 아쉬움이 남지만,&lt;br /&gt;그래도 점차 나아지겠죠 (기대 중)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;결론 : 애플 더 힘내줘&lt;/p&gt;</description>
      <category>Tech/On-Device</category>
      <category>foundationmodels</category>
      <category>llm</category>
      <category>ondevice</category>
      <category>온디바이스</category>
      <author>Ellie Kim</author>
      <guid isPermaLink="true">https://hyerios.tistory.com/318</guid>
      <comments>https://hyerios.tistory.com/entry/FoundationModels-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0#entry318comment</comments>
      <pubDate>Fri, 18 Jul 2025 02:55:57 +0900</pubDate>
    </item>
  </channel>
</rss>