I tried text generation with the Foundation Models framework

I tried text generation with the Foundation Models framework

I verified the basic functionality of text generation using the Foundation Models framework. This article introduces implementation methods including simple generation with respond(to:), streaming display with streamResponse, persona configuration with instructions, and structured output with @Generable.
2026.06.16

This page has been translated by machine translation. View original

The other day, I published an article on image analysis using the multimodal capabilities of Foundation Models.

https://dev.classmethod.jp/articles/foundation-models-multimodal-image-analysis/

However, looking back, I realized I hadn't yet written an article covering the basic usage of text generation. I decided to write this article because I wanted to first cover the basics of text generation before moving on to multimodal.

In this article, I will introduce the basic text generation features of Foundation Models step by step. I hope it will be helpful for those who want to try similar experiments.

Testing Environment

  • MacBook Pro (16-inch, 2023), Apple M2 Pro
  • macOS Tahoe 26.5.1
  • Xcode 27.0 Beta
  • iPhone 17 Pro Simulator (iOS 27.0 Beta)
  • iPhone 16e physical device (iOS 27.0 Beta)

About Text Generation with Foundation Models

The Foundation Models framework is a framework that enables on-device inference on devices equipped with Apple Intelligence, which was introduced at WWDC25. Since no communication with external servers occurs, it can be utilized for privacy-conscious app development.

Lirioossa presented at "try! Swift Tokyo 2026" a method of replacing diary content with emoji using Foundation Models.

https://dev.classmethod.jp/articles/please-save-genmoji/#foundation-models

The main use cases for text generation include the following.

  • Text summarization, paraphrasing, and proofreading
  • In-app chat and question answering
  • Text classification and analysis of user input
  • Automatic generation of templated content

However, operation requires a device that supports Apple Intelligence. Please refer to the Apple official page for supported devices.

Implementation Steps

Step 1: Project Setup

Create a new iOS project in Xcode and use the FoundationModels framework. No additional SPM dependencies are required, as it is available as a system framework.

No special configuration is required in Info.plist.

First, add a simple screen that executes a process when a button is tapped and displays the result as text, in order to run the sample code. It is assumed that the processes explained from here on will be added in the action1() section.

import SwiftUI
import FoundationModels

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                Text(text)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
                Button("Run", action: action1)
            }
        }
    }

    func action1() {
        // Add Foundation Models processing here
    }
}

Step 2: Checking Model Availability and Creating a Session

Use SystemLanguageModel.default to get the device's default model. Make sure to check whether it is available using isAvailable before using it. All subsequent code will be added inside action1().

// Check whether the device supports Apple Intelligence
guard SystemLanguageModel.default.isAvailable else {
    text = "Apple Intelligence is not available"
    return
}
let session = LanguageModelSession()

Step 3: Basic Text Generation

Pass a prompt to session.respond(to:) and retrieve the generated text using response.content.

Task {
    do {
        let response = try await session.respond(to: "Please tell me the appeal of iOS app development in one sentence")
        text = response.content
        print(response.content)
    } catch {
        text = "Error: \(error.localizedDescription)"
        print("Error: \(error)\n\(String(reflecting: error))")
    }
}

To check for variation in output, I ran the same prompt five times. The number of seconds in parentheses is the processing time measured as the difference between Date() before and after execution.

Response Processing Time
The appeal of iOS app development is that you can enrich the user experience with a simple and intuitive interface. 3347.9 ms
The appeal of iOS app development is that you can improve the user experience with a simple and intuitive interface. 2953.1 ms
The appeal of iOS app development is that you can enrich the user experience with a simple and intuitive interface. 3385.4 ms
The appeal of iOS app development is that you can achieve intuitive design and smooth performance tailored to user needs. 2122.7 ms
The appeal of iOS app development is that you can leverage a simple and intuitive design along with a powerful ecosystem. 2097.5 ms

For the same prompt, both identical and different expressions were returned. It was confirmed that, similar to cloud LLMs, the behavior is probabilistic and not completely deterministic.

Full source code for Steps 1–3
import SwiftUI
import FoundationModels

struct ContentView: View {
    @State private var text: String = ""

    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                Text(text)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding()
                Button("Run", action: action1)
            }
        }
    }

    func action1() {
        // Check whether the device supports Apple Intelligence
        guard SystemLanguageModel.default.isAvailable else {
            text = "Apple Intelligence is not available"
            return
        }
        let session = LanguageModelSession()

        Task {
            do {
                let response = try await session.respond(to: "Please tell me the appeal of iOS app development in one sentence")
                text = response.content
                print(response.content)
            } catch {
                text = "Error: \(error.localizedDescription)"
                print("Error: \(error)\n\(String(reflecting: error))")
            }
        }
    }
}

Step 4: Real-Time Display with Streaming

While respond(to:) returns the full text after generation is complete, using streamResponse(to:) allows you to receive the text sequentially as it is being generated. This is especially effective for improving UX during long text generation.

Add action2() to ContentView and switch the button action from action1 to action2 to verify.

func action2() {
    guard SystemLanguageModel.default.isAvailable else {
        text = "Apple Intelligence is not available"
        return
    }
    let session = LanguageModelSession()

    Task {
        do {
            text = ""
            let stream = session.streamResponse(to: "Please list 3 benefits of learning Swift")
            for try await partial in stream {
                text = partial.content
            }
        } catch {
            text = "Error: \(error.localizedDescription)"
            print("Error: \(error)\n\(String(reflecting: error))")
        }
    }
}

Since partial.content is the cumulative value of the text generated so far, real-time display can be achieved simply by overwriting with text =.

Step 5: Setting a System Prompt with instructions

Using LanguageModelSession(instructions:), you can configure a character setting equivalent to a system prompt in cloud LLMs. By changing instructions, you can control the tone and granularity of responses to the same prompt.

Add action3() to ContentView to verify the behavior. First, the result of asking a question without instructions.

// Without instructions
let session = LanguageModelSession()
let r1 = try await session.respond(to: "What is AutoLayout? Please explain in 2-3 sentences.")
// → AutoLayout is a framework for automatically adjusting the position of UI elements in iOS and macOS app development.
//    This allows you to achieve designs that adapt to different device sizes and OS versions.
//    It saves the effort of manually adjusting layouts in code.

Next, the result of asking the same question with instructions set.

// With instructions
let session = LanguageModelSession(
    instructions: "You are an iOS development expert. Answer concisely using technical terminology."
)
let r2 = try await session.respond(to: "What is AutoLayout? Please explain in 2-3 sentences.")
// → AutoLayout is a framework for automatically adjusting the layout of UI elements.
//    It is used in Swift and Objective-C to minimize device dependency.
//    It uses Constraints to control the distance and placement between elements.

Both had a 3-sentence structure with similar length, but the response with instructions included technical terms such as "Constraints," "device dependency," and "Swift and Objective-C." While not as dramatic a change as with cloud LLMs, differences in vocabulary level were confirmed.

Here I introduced an example of setting a "iOS development expert" persona, but roleplay chat apps can also be implemented with the same feature. For example, setting instructions to a samurai from the Warring States period will return responses in a tone consistent with that character.

let session = LanguageModelSession(
    instructions: "You are a samurai living in the Warring States period. You are permitted to speak directly to your lord. Answer questions concisely."
)
let response = try await session.respond(to: "What is AutoLayout? Please explain in 2-3 sentences.")
// → I, as a samurai of the Warring States period, have no such knowledge. However, if you wish to know,
//    it is a design technology used for smartphones and computers.

The full picture of action3() is as follows.

func action3() {
    guard SystemLanguageModel.default.isAvailable else {
        text = "Apple Intelligence is not available"
        return
    }
    let session = LanguageModelSession(
        instructions: "You are an iOS development expert. Answer concisely using technical terminology."
    )

    Task {
        do {
            let response = try await session.respond(to: "What is AutoLayout? Please explain in 2-3 sentences.")
            text = response.content
        } catch {
            text = "Error: \(error.localizedDescription)"
            print("Error: \(error)\n\(String(reflecting: error))")
        }
    }
}

Step 6: Structured Output with @Generable

By attaching the @Generable macro to a Swift struct, you can receive the model's output as an instance of that type. The framework converts the type information into a JSON schema and passes it to the model.

Here, an example of parsing an app review text and organizing it by category is shown. The @Guide macro is for conveying the meaning of a property to the model in natural language; it is not required but can be used to improve output quality. The description of @Guide is written in English following the official documentation samples.

AppReviewAnalysis is defined at the top level of the file (outside of ContentView).

@Generable
struct AppReviewAnalysis {
    @Guide(description: "Overall sentiment: positive, negative, or neutral")
    var sentiment: String
    @Guide(description: "Key positive points mentioned in the review")
    var positivePoints: [String]
    @Guide(description: "Issues or complaints mentioned in the review")
    var issues: [String]
}

Add action4() to ContentView to verify the behavior.

func action4() {
    guard SystemLanguageModel.default.isAvailable else {
        text = "Apple Intelligence is not available"
        return
    }
    let session = LanguageModelSession()

    Task {
        let reviewText = """
            It starts up fast and is easy to use. I also like the design.
            However, there were too many notifications and it was hard to find the settings.
            """
        do {
            let response = try await session.respond(
                generating: AppReviewAnalysis.self
            ) {
                reviewText
            }
            print(response.content.sentiment)       // → positive
            print(response.content.positivePoints)  // → ["Starts up fast", "Easy to use", "Good design"]
            print(response.content.issues)          // → ["Too many notifications and hard to find settings"]
        } catch {
            text = "Error: \(error.localizedDescription)"
            print("Error: \(error)\n\(String(reflecting: error))")
        }
    }
}

The following analysis results were obtained. Even when run multiple times, sentiment and issues matched exactly, with only minor variation in the phrasing of positivePoints.

positive
["Starts up fast", "Easy to use", "Design is to my liking"]
["Too many notifications and hard to find settings"]

The output is more stable compared to free-form text generation (Step 3). This is due to the mechanism by which @Generable converts type information into a JSON schema and uses Guided Generation to constrain the model's output within the type's constraints.

Passing a negative review with the same code changes the content of sentiment and positivePoints.

let reviewText = """
    It's completely useless. It crashes every time I launch it,
    and the data I entered disappears. I hope they improve it.
    """
// → negative
// → []
// → ["Crashes every time it is launched", "Entered data disappears"]

positivePoints became an empty array, and crashes and data loss were listed in issues. It was confirmed that the contents of the structured data switch according to the tone of the input text.

Since unstructured text can be extracted as Swift types, it is easy to incorporate into subsequent processing. Note that @Generable type information consumes the context window. The more properties there are and the longer the @Guide descriptions, the more it consumes, so it is advisable to omit unnecessary properties.

Step 7: Multi-Turn Conversation

LanguageModelSession automatically retains the conversation history within the session. By continuing to send requests to the same session, conversations that carry over context can be realized.

Add action5() to ContentView to verify the behavior.

func action5() {
    guard SystemLanguageModel.default.isAvailable else {
        text = "Apple Intelligence is not available"
        return
    }
    let session = LanguageModelSession()

    Task {
        do {
            // First question
            let r1 = try await session.respond(to: "Please tell me the difference between SwiftUI and UIKit")
            print(r1.content)

            // Second question: ask while carrying over the previous context
            let r2 = try await session.respond(to: "So, which should I choose for a new project?")
            print(r2.content)
            text = r2.content
        } catch {
            text = "Error: \(error.localizedDescription)"
            print("Error: \(error)\n\(String(reflecting: error))")
        }
    }
}

The first output was as follows. A comparison covering four items — developer experience, flexibility, dependencies, and learning curve — was returned.

The main differences between SwiftUI and UIKit are as follows.

1. **Developer Experience**
   ...(abbreviated below)

The second output was as follows.

For a new project, I recommend choosing **SwiftUI**. SwiftUI improves development efficiency and enables intuitive UI design. However, if you need complex features or customization, it is also worth considering UIKit. Please choose based on the scale and needs of your project.

In response to the second question starting with "So, ~", a reply recommending SwiftUI was returned based on the comparison content from the first question. It was confirmed that the context within the session was carried over. However, creating a new session resets the context.

Also, combining with the character settings via instructions introduced in Step 5 makes it easy to implement a roleplay chat app where the conversation continues with a consistent persona.

Verification

Confirm in advance that Apple Intelligence is enabled on the physical device.

  1. Go to Settings → "Apple Intelligence & Siri" → Turn on "Apple Intelligence"
  2. Confirm that the language and region are set to a supported language such as English (US)
  3. Wait until the model download is complete

Once the above preparations are in place, tapping the button will return a response after a few seconds. Since processing is done on-device, no communication with external networks occurs.

Notes

Context Window

The context window is smaller compared to cloud LLMs. Errors may occur if you continue a long conversation or pass a large amount of text at once. Since @Generable type definitions also consume context, it is advisable to keep properties to the necessary minimum.

Japanese Prompts

Japanese prompts often return Japanese responses, but language specification is not guaranteed. If you want responses in Japanese, it is best to explicitly state "Please answer in Japanese."

Troubleshooting

LanguageModelError occurs

The following type of error may occur.

Error Domain=FoundationModels.LanguageModelError

The main causes and remedies are as follows.

Cause Remedy
Apple Intelligence is disabled Turn on Apple Intelligence from Settings
Model download not complete Wait for the download to complete and retry
Context window exceeded Shorten the prompt or conversation history

SensitiveContentAnalysisML error occurs in the Simulator

When running in the Simulator, the following error may occur.

End sanitizeText with error: Error Domain=com.apple.SensitiveContentAnalysisML Code=15
  └─ SafetyGuardrailTextSanitizerBackend: Resource (Local Model Asset) unavailable error.
     └─ GenerativeError Code=1020000 "Resource (Local Model Asset) unavailable error."

Error Domain=FoundationModels.LanguageModelError Code=-1
  "The operation couldn't be completed. (com.apple.SensitiveContentAnalysisML error 15.)"

This error can occur even when isAvailable returns true. While isAvailable only checks the readiness of the main language model, based on the behavior inferred from the error log, all text generation goes through a sub-model for safety filtering (SafetyGuardrailTextSanitizerBackend), and this error appears to occur when that model asset cannot be found.

Since the model assets for the Simulator use those of the host Mac, if the versions of Xcode, iOS Simulator, and macOS do not match, some components may be missing.

This was resolved by testing on a physical device.

Summary

I verified the basic text generation features using the Foundation Models framework.

  • Simple text generation with respond(to:)
  • Real-time display with streamResponse(to:)
  • System prompt configuration with LanguageModelSession(instructions:)
  • Structured output with @Generable + @Guide
  • Multi-turn conversation within the same session

As an overall impression after actually trying these out, while there are constraints compared to cloud LLMs such as not being able to select a model and having a smaller context window, I found it attractive that this many features are available without external communication.

Additionally, at WWDC26, AFM 3 Core Advanced, a 20B parameter on-device model, and AFM 3 Cloud Pro for cloud inference were also announced. All the verification in this article was done with the 3B model, but even so it was capable of practical responses, and I felt a sufficient sense of potential. I look forward to the day when I can try the 20B model through the Foundation Models API.

As a next step, for those who want to try the multimodal feature that allows both text and images as input, please also refer to the following article.

https://dev.classmethod.jp/articles/foundation-models-multimodal-image-analysis/

References

Job Listings: Classmethod is Hiring iOS Engineers

The Starbucks Digital Technology Division is looking for engineers capable of iOS app development. We look forward to applications from those who would like to work with us while sharing information about new Xcode and iOS features on the misc-ios channel!

https://careers.classmethod.jp/requirements/sbj-nativeapp-ios/

We are also hiring iOS/Android engineers in other areas. Let's talk about mobile app development together!

https://careers.classmethod.jp/requirements/category/development/


AI白書2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

AI白書2026

無料でダウンロードする

Share this article