Chapters

Hide chapters

Apple Foundation Models

First Edition · iOS 26.5 · Swift 6.3 · Xcode 26.4

Section I: Apple Foundation Models

Section 1: 9 chapters
Show chapters Hide chapters

5. Guided Generation
Written by Bill Morefield

To this point in the book, you’ve produced a text response for each prompt in Foundation Explorer. Given that the app is by nature a chat-style app, a text response was a logical choice. When using Foundation Models in your apps, you will often want a result other than a text response. For this, Apple Foundation Models supports the generating parameter when calling either the LanguageModelSession respond(to:options:) or streamResponse(to:options:) methods. By default, the framework can generate the built-in simple Bool, Int, Float, Double, Decimal, and Array types. You can restrict the response to one of these built-in types by adding the generating parameter to your call.

Open the starter project for this chapter. You’ll see a new project that lets you select meal options. You will expand this app to use Foundation Models to build a dining menu in this chapter. The app lets the user select breakfast, lunch, dinner, or dessert. You can then select a cuisine type for the menu. The next step will be to select the menu ingredients, but the app doesn’t generate them yet.

Menu Generation App
Menu Generation App

You will also see a toolbar button that will allow you to view the current transcript of the session property of this view. It starts with a default LanguageModelSession, but you’ll later tie this session into the menu generation.

While there are times when producing a built-in type is helpful, the true power of this guided generation comes when you define your data structure and provide guidance on generating it. While generating data with LLMs has always been possible with the right prompts, this has typically required careful tuning to produce a format such as JSON and meticulous text parsing. The native inclusion of this ability may be the most important feature of Foundation Models compared to general-purpose LLMs.

Imagine a scenario where you want to provide a realistic menu in a game when the player enters a restaurant. You could enter a prompt in the Foundation Explorer app from earlier chapters, such as:

Create a lunch menu for a casual dining restaurant.

The result will be a plausible menu that reads like a wall of text. For a case where the user only needs to read the result, this works fine. But if you want to put this into a data structure, you need to parse the result. Traditionally, you’d do this by producing the information in JSON. You would need to refine your prompts to create a format that you can interpret as a structure. Instead, you will let Foundation Models do this work for you.

Menu created by asking Foundation Explorer app.
Menu created by asking Foundation Explorer app.

Before using guided generation, you must first define the data structures to fill with the generated information. Create a new file under the Models folder named CuisineIngredients.swift and replace the code with:

import SwiftUI
import FoundationModels

struct CuisineIngredients {
  let ingredients: [String]
}

This is a pretty simple structure that contains a single string array property called ingredients. Open FoodMenuView.swift. Add the following new method after generateCuisineList():

func generateIngredients() -> [String] {
  return ["salmon", "beef", "mushrooms", "salt"]
}

This is about as simple a list as you could create. You return four static string ingredients. Now, to call this method, find the Task modifier on the VStack that contains the view just before the navigationTitle("Menu Maker") modifier. Add the following code after the Task and before navigationTitle:

.onChange(of: cuisine) { _ , _ in
  selectedIngredients = []
  ingredientList = generateIngredients()
}

Whenever the user selects a different cuisine from the picker, this method clears the selected ingredients, then calls the new generateIngredients() method. Run the app and select a cuisine. You’ll see those four ingredients. Tap any ingredient, and you’ll see a check appear next to it. Tap an ingredient again to unselect it.

Ingredient Selection
Ingredient Selection

Now that you’ve explored the user interface, you’ll adapt the app to generate a list of ingredients for the selected cuisine.

Generating Custom Data Structures

Foundation Models provides two macros that let you assist the model and guide the generated data. The first is Generable(description:), which marks structures and enumerations for guided generation and provides context to the model. You will use it along with Guide(description:) on each property in these Generable types. The framework allows nesting Generable types to support complex data hierarchies.

Foundation Models require Generable(description:) for any type that you wish to create. To see this, add the following code above the definition of CuisineIngredients:

@Generable(description: "A list of ingredients common in a specific type of cuisine.")

The parameter to the Generable macro is a textual description of the data structure’s purpose. To provide more guidance for the properties of the structure, use Guide(description:). Add the following code before the ingredients property:

@Guide(description: "A list of individual food ingredients.")

With these defined, you can now use Foundation Models to generate the ingredient list. Go back to FoodMenuView.swift and replace generateIngredients() with:

func generateIngredients() async -> [String] {
  // 1
  guard cuisine != "N/A" else { return [] }
  isGenerating = true
  defer { isGenerating = false }
  
  // 2
  let ingredientPrompt = """
    Give me a list of ingredients used in \(cuisine) for \(selectedMeal).
    Do not repeat ingredients. Do not provide examples of ingredients.
    """
  let session = LanguageModelSession()
  
  // 3
  let response = try? await session.respond(to: ingredientPrompt, generating: CuisineIngredients.self)
  
  // 4
  if let response = response {
    return response.content.ingredients
  } else {
    return []
  }
}

Most of this should look familiar from the earlier chapters. You change the method to async as with most methods related to Foundation Models. Inside the method, you:

  1. You ensure the user has selected a cuisine, then set a property to show an indicator that the app is working. Even with asynchronous streaming responses, an indicator helps the user feel the app is more responsive.
  2. This prompt asks for a list of ingredients, and fills in the type of cuisine and meal from the values selected in the view. A simple prompt will suffice thanks to guided generation. Without it, you would need a lengthy prompt that specifies a format and provides examples to get useful results. Note that since the user selects cuisine and selectedMeal from a list of choices, you avoid many of the risks of user-generated content while still allowing users to make choices.
  3. The significant change to this method is adding the generating: CuisineIngredients.self parameter when calling respond(to:generating:includeSchemaInPrompt:options:). This tells Foundation Models to produce a structure of the CuisineIngredients you defined. Note that you must mark this type as Generable, which you did by adding the macro earlier.
  4. As before, when using the try? await pattern, you attempt to unwrap the returned value. If successful, you access the returned CuisineIngredients struct through response.content. Since you only need the string array with the ingredients, you return the generated ingredients property. If the unwrap failed, you return an empty array.

You need to make one more change since this method is now async. Find your call to generateIngredients() inside the view and change it to:

Task {
  ingredientList = []
  selectedIngredients = []
  await ingredientList = generateIngredients()
}

This change first wraps the code inside a Task. Since generating ingredients takes a few seconds, you clear the ingredients array first. After clearing the selected ingredients, you add an await call to the now asynchronous method.

Run the app, select a meal and cuisine from the menu. After a few seconds, an appropriate ingredient list will appear.

Generated French Dinner Ingredients in French
Generated French Dinner Ingredients in French

Depending on the combination you chose, you could see a large number of ingredients. It would be useful to narrow this list a bit. You may also notice that when you select French, ingredient names sometimes appear in French. Let’s adjust both of those. Go back to CuisineIngredients.swift and change the declaration of ingredients to:

@Guide(description: "An array of individual ingredients specified by their English name.", .count(10...15))
let ingredients: [String]

The description now specifies that ingredients should be in English, which should give you “chicken” instead of “poulet”. The .count(10...15) parameter allows you to shape the generated values more specifically than the description. You can apply the .count(10...15) parameter to @Guide to an array providing a closed range. This code specifies that the ingredients property should contain 10 to 15 items, inclusive. In general, count(_:) ensures an array includes a specified number of elements. You can specify these in addition to or instead of the description. This example applied both the description and count(_:) in one macro. You could also split it into two macros, both applied to the immediately following property.

Run the app to see your changes. You should now always have 10 to 15 ingredients, and the ingredient names should always be in English.

Adjust ingredients to ensure English and produce 10 to 15 ingredients.
Adjust ingredients to ensure English and produce 10 to 15 ingredients.

There are several more common properties to add restrictions for generated data:

  • Arrays can also specify .maximumCount(_:), which specifies a maximum length of the array, and .minimumCount(_:), which specifies a minimum length for the array.
  • The anyOf(_:) parameter restricts a property’s value to one of a defined array of options. The format would resemble @Guide(.anyOf(["Apple", "Banana", "Grape", "Strawberry"])).
  • For String properties, you can specify the pattern(_:) parameter that ensures the string follows a specified regular expression.
  • The Int type allows you to specify minimum(_:) or maximum(_:) values or a range(_:) to constrain the value.

Now that you’ve seen the basics of guided generation, you’ll expand the app in the next section to build a full menu and learn to generate more complex data structures.

Guided Generation on Complex Structures

Open the Models folder. In addition to the CuisineIngredients.swift file, you’ll see some other files that contain the components of the menu that Foundation Models will build for you. The MealType enumeration defines the different meal types. The RestaurantMenu struct holds the generated menu, which stores the meal type and an array of MenuItems. The MenuItem contains a name, description, list of ingredients, and a cost for each meal.

You might think all you need to make the menu ready for guided generation is to add the Generable macro. Open RestaurantMenu.swift and note that it already imports FoundationModels. Add the macro above the RestaurantMenu struct declaration:

@Generable(description: "A menu of offerings for a restaurant for a single meal.")

If you attempt to build the app after this change, you will encounter several compilation errors. The errors all result from the requirement that any property inside a Generable struct must also be a Generable type. As mentioned earlier in the chapter, the basic Swift types already meet this requirement. This is why your earlier Array and String types in CuisineIngredients worked automatically. But both the type and menu properties are of a custom type, so you must also make them Generable. Open MealType.swift and add the following code above the definition of the MealType enumerable:

@Generable

Now open MenuItem.swift and add the following code above the definition of the MenuItem struct:

@Generable(description: "A single dish for a restaurant menu.")

Building the app now will no longer produce errors as the types inside the MenuItem struct already support Generable. You also see that you do not have to provide a description to the macro. In this case, Foundation Models will use the names of the properties and elements to produce appropriate content. The description parameter with MenuItem and RestaurantMenu provides the model with semantics and context for the data. Try to keep descriptions as short as possible, as long descriptions take up additional context size and increase latency.

Foundation Modals will generate the properties in the order you declare them within the Swift struct. This ordering can influence the model’s data production. In the MenuItem struct, the description property precedes the ingredients property. The model first generates the description, then provides a list of ingredients. It generates both after the first name property. Providing this order generates the name first, followed by a description that matches it. The model then generates ingredient lists that match the name and description of the menu item. Finally, the cost should reflect the components of the menu item. If you had started the struct with a property like cost, the cost would influence the others.

While the property names do give helpful information on what the struct should contain, you will often get better results by adding Guide(description:) on each property.

Update the MenuItem definition to:

@Generable(description: "A single dish for a restaurant menu.")
struct MenuItem {
  @Guide(description: "Name for this dish.")
  let name: String

  @Guide(description: "The description of this dish in a style appropriate for a restaurant menu.")
  let description: String

  @Guide(description: "The main ingredients for this dish.")
  let ingredients: [String]

  @Guide(description: "A cost for this dish in US dollars, which should be appropriate for the ingredients", )
  let cost: Decimal
}

This code describes each property. As with many aspects of LLMs, prompts are as much an art as a science. They clarify the purpose of each property and how they integrate.

You can also provide more specific guidance to the model using the @Guide macro. Go back to RestaurantMenu.swift and update the RestaurantMenu definition to:

@Generable(description: "A menu of offerings for a restaurant for a single meal.")
struct RestaurantMenu {
  let type: MealType

  @Guide(description: "A list of menu items, appropriate for the selected type of meal.", .count(4...8))
  let menu: [MenuItem]
}

That’s all the work needed to allow Foundation Models to generate a restaurant menu. To show the menu, open FoodMenuView.swift. Add the following new property to the top of the view:

@State private var menu: RestaurantMenu?

This state property will store the menu once the modal creates it. Since the app will reuse a session to create the menu, you will create a new shared session for each menu generation. Add the following new method after the generateIngredients() method:

func createSession() {
  let instructions = """
    You are generating a simple, plausible restaurant menu for a restaurant in a game.
    The menu must match the given cuisine and meal type.
    Use at least ONE ingredient from the provided ingredient list, but you may include additional ingredients beyond the provided list.
    Avoid repeating the same primary ingredient across all dishes.
  """
  session = LanguageModelSession(instructions: instructions)
}

This method creates a new LanguageModelSession and provides it with instructions appropriate to the task the session will perform. Now add a new method after the createSession() method to create the menu:

// 1
func generateLunchMenu() async {
  isGenerating = true
  defer {
    isGenerating = false
  }

  // 2
  let prompt = """
    Create a menu for \(selectedMeal) at a \(cuisine)) restaurant.
    Each meal on the menu must include one of the following ingredients: \(selectedIngredients.joined(separator: ", "))

    Requirements:
    - Each dish must include at least ONE of the available ingredients.
    - Dishes should be appropriate for the cuisine and meal type.
    - Keep items simple, recognizable, and realistic (not overly complex or experimental).
    - Vary the primary ingredients across dishes when possible.
    - Prices should feel reasonable for a casual restaurant in USD.
    """
  // 3
  let response = try? await session.respond(to: prompt, generating: RestaurantMenu.self)
  // 4
  menu = response?.content
}

Here’s how this works:

  1. You mark the new method as async since it contains asynchronous code. You also use the familiar pattern to show an indicator while Foundation Models builds the menu.
  2. You create a prompt that provides the information about the response you want to make. Notice this specifies the type of restaurant and meal type, along with a list of possible ingredients. Changing the prompt will create menus for different meals or different restaurants.
  3. You get a response as before, now passing the generating property the value RestaurantMenu.self. This instructs the model to produce a RestaurantMenu. You use the try? await pattern to generate a nil response if anything goes wrong.
  4. This code sets the menu property you created to the generated Foundation Model response. If anything went wrong in step four, this will be nil. Otherwise, it should contain a menu of four to eight items as specified using the @Guide macro.

You need to run this code when the user taps the button. Find the empty button action that reads // Do Menu Generation and replace it with:

withAnimation {
  showControls = false
}
Task {
  createSession()
  await generateLunchMenu()
}

Note: The title of the button to generate the menu changes to include the selected meal type. For this chapter, it will be referred to as the Generate Menu button regardless of the cuisine selected.

When the user taps the Generate Menu button, the code first hides the controls to give more room on the view for the menu. It then creates a fresh session to generate this menu before calling the method to create the lunch menu. You wrap these calls inside a Task closure to handle the asynchronous task and ensure the session completes before attempting to generate a menu.

Finally, add the following code to show the menu after the Button and before the Spacer:

if let menu = menu {
  Text("\(menu.type.rawValue.capitalized) Menu")
    .font(.headline.bold())
  ForEach(menu.menu, id: \.name) { item in
    MenuItemView(menuItem: item)
    Divider()
  }
}

This code attempts to unwrap the menu property. When not nil, it displays the meal type. It then loops through each menu item and displays it using the MenuItemView view. The Divider view separates each menu item.

Run the app and tap Generate Menu. After a few seconds, you should see the generated menu. You can show the options again by tapping the Show Options button above the menu. Try a few examples to see how Foundation Models handles the requirements.

Complete generated menu.
Complete generated menu.

Behind the scenes, Foundation Models built this by generating JSON text. Open the transcript when menu generation completes to see this.

Transcript after generating menu.
Transcript after generating menu.

Guided generation took the JSON produced by the model and translated it into a structure for you. That’s a powerful and useful feature that saves you from having to extensively test prompts and write code to parse JSON and handle model failures and missing information.

You’ve learned how to create data structures with guided generation. This example waits until the full data structure exists before showing it to the user. As with text responses, you can also stream the response to improve the user experience. You’ll learn that in the next section.

Streaming Guided Generation

To use guided generation with streaming, the response begins with the same changes you made in Chapter Two to stream the text response. Replace the current call to respond(to:) in generateLunchMenu() after comment three with:

let streamedResponse = session.streamResponse(to: prompt, generating: RestaurantMenu.self)

This changed code will stream the model’s response. To handle the stream, replace all the code after comment four with:

do {
  for try await partialResponse in streamedResponse {
    menu = partialResponse.content
  }
} catch {
  print(error.localizedDescription)
}

You will see an error after this change: “Cannot assign value of type ‘RestaurantMenu.PartiallyGenerated’ to type ‘RestaurantMenu’. Xcode produces this error because a streamed response is not of the same type as the full response generated by respond(to:). When streaming the response, every property must be optional because the model may not have generated it yet. This requires a few code changes to handle these optionals. First, find the menu property and change it to:

@State private var menu: RestaurantMenu.PartiallyGenerated?

@Generable automatically produces a PartiallyGenerated type that matches the original type, RestaurantMenu in this case, except it makes every property optional. Since all the properties of RestaurantMenu.PartiallyGenerated are now optional, you must change any uses of the PartiallyGenerated values to unwrap or otherwise handle the optional type. Change the if let code you added earlier, before the Spacer(), to:

if let menu = menu {
  if let type = menu.type {
    Text("\(type.rawValue.capitalized) Menu")
      .font(.headline.bold())
  }
  if let menuitems = menu.menu {
    ForEach(menuitems, id: \.name) { item in
      MenuItemView(menuItem: item)
      Divider()
    }
  }
}

You now must attempt to unwrap the menu and type properties before using them. Next, you must update the MenuItemView view to also manage these optional types. Open MenuItemView.swift and change the declaration of the menuItem property to:

var menuItem: MenuItem.PartiallyGenerated

This will let you pass it the MenuItem.PartiallyGenerated. Now change the body of the view to:

VStack {
  HStack {
    Text(menuItem.name ?? "")
      .font(.headline.bold())
    Spacer()
    if let cost = menuItem.cost {
      Text(cost, format: .currency(code: "USD"))
        .font(.headline)
    }
  }
  .font(.title3)
  Text(menuItem.description ?? "")
    .frame(maxWidth: .infinity, alignment: .leading)
    .padding(.leading, 15.0)
    .font(.subheadline)
    .foregroundStyle(.secondary)
  if let ingredients = menuItem.ingredients {
    Text(ingredients.joined(separator: " • "))
      .font(.caption)
      .foregroundStyle(.tertiary)
  }
}
.padding(.vertical, 10)

For the string properties name and description, you use the nil-coalescing operator to provide an empty string in the case where the property doesn’t exist. For the cost and ingredients properties, you attempt to unwrap them and only display information when the unwrapping succeeds.

One final change. In the preview, change the call to the view to:

MenuItemView(
  menuItem: item.asPartiallyGenerated()
)

The asPartiallyGenerated() method converts any Generable object to its PartiallyGenerated equivalent.

Run the app and select the meal and cuisine of your choice. Then select a few ingredients and tap Generate Menu. You will now see that, instead of the final menu appearing all at once, it will appear in pieces as the modal generates it. While watching this, you should again observe the importance of property order, as properties defined first appear before those defined later.

As discussed in Chapter Two, showing information as soon as the model generates it improves the user’s perception of the response time. It provides immediate feedback, making the process feel shorter. No longer do you wait for a menu. You watch the menu assemble.

Menu streaming as it is generated.
Menu streaming as it is generated.

Handling these partially generated types is all you need to stream guided generation. This works well when your object is clearly defined when developing the app, but what do you do when you won’t know the structure until runtime? In the next section, you’ll learn how to use dynamic guided generation, which lets you define the data structure at runtime.

Dynamic Guided Generation

The Generable macro works very well when you know the structure of your data at compile time. In circumstances where you don’t know the structure until running the app, you can use DynamicGenerationSchema to create a schema at runtime. This produces a result similar to what you’ve done. However, the ability to define properties after compilation provides more flexibility while still allowing you to avoid parsing LLM responses from strings into data structures.

One way to expand the menu generation you’ve created is to add the ability to specify a special dish that will attempt to use as many of the selected ingredients as possible. If you knew the ingredients in advance, you could specify them at compile time using the .anyOf(_:) parameter on an array. Since the app generates them, and the user can select any combination, you will instead create a dynamically generated schema for the special of the day, based on a menu made from one of a set of specified ingredients.

Open FoodMenuView and add the following property to hold a list of user-provided ingredients:

@State private var specialIngredients = [String]()

Find the Task inside the onChange(of:initial:_:) method where you call generateIngredients(). Add the following code before that method call:

specialIngredients = []

This will clear this array when the user changes cuisine, as you cleared selectedIngredients.

Open MenuOptionsView.swift and add a new property to the end of the list:

@Binding var specialIngredients: [String]

This will let you pass in the array from the main view to this view. Update the preview at the bottom of the file to account for the new property. Add the preview state:

@Previewable @State var specialIngredients: [String] = []

This makes a default state available to the preview. Then, use it in the preview’s call to MenuOptionsView:

  MenuOptionsView(
    /* Existing code */
    specialIngredients: $specialIngredients
  )

Now add the following code before the Generate Menu button in FoodMenuView.swift:

Text("Special Ingredients")
  .font(.subheadline)
  .foregroundStyle(.secondary)
  .textCase(.uppercase)
if !ingredientList.isEmpty {
  MultiSelectView(
    options: ingredientList,
    selections: $specialIngredients,
    maxSelect: 3
  )
}
Divider()

This displays a second ingredient selection that allows the user to choose up to three ingredients.

Go back to FoodMenuView.swift and find the MenuOptionsView. Change it to reference the new specialIngredients property:

MenuOptionsView(
  mealtimes: mealtimes,
  selectedMeal: $selectedMeal,
  cuisineList: cuisineList,
  cuisine: $cuisine,
  ingredientList: ingredientList,
  selectedIngredients: $selectedIngredients,
  specialIngredients: $specialIngredients
)

Now that the user can select both the standard and special ingredients, it’s time to dynamically generate the special menu item. Add a new method after generateLunchMenu:

func generateMenuSpecial() async {
  isGenerating = true
  defer {
    isGenerating = false
  }

  // 1
  let specialMealSchema = DynamicGenerationSchema(
    name: "specialmenuitem",
    // 2
    properties: [
      // 3
      DynamicGenerationSchema.Property(
        name: "ingredients",
        // 4
        schema: DynamicGenerationSchema(
          name: "ingredients",
          anyOf: specialIngredients
        )
      ),
      // 5
      DynamicGenerationSchema.Property(
        name: "name",
        schema: DynamicGenerationSchema(type: String.self)
      ),
      DynamicGenerationSchema.Property(
        name: "description",
        schema: DynamicGenerationSchema(type: String.self)
      ),
      DynamicGenerationSchema.Property(
        name: "price",
        schema: DynamicGenerationSchema(type: Decimal.self)
      )
    ]
  )
}

Here’s how this code works:

  1. To create a dynamic schema, you first create a DynamicGenerationSchema object and give it a name.
  2. You then define the properties of this schema. This is the equivalent of the properties of the struct that you created when using the @Generable macro.
  3. The first property you define is the ingredients. Recall that guided generation fills in properties in the order you specify them. Since you want the ingredient to define the menu item, you specify it first.
  4. The schema parameter of the DynamicGenerationSchema call states the data type of the property. In this case, you make another call to DynamicGenerationSchema with the same name and provide the anyOf parameter, passing in the array of strings computed as ingredientArray. The result will select one of the ingredients provided at runtime. You cannot do this using the @Generable macro.
  5. The remaining parameters should look familiar. You provide the name for each and set the schema using the DynamicGenerationSchema initializer with the type property, specifying the appropriate simple type for each.

This DynamicGenerationSchema defines the same structure as earlier, but with the ingredients selected from a list provided by the user at runtime from the view. This is the equivalent of specifying the .anyOf(_:) parameter, except you do it at run time and not compile time.

Now add the following code to the end of generateMenuSpecial():

// 1
let schema = try? GenerationSchema(root: specialMealSchema, dependencies: [])
// 2
guard let schema = schema else { return }
// 3
let specialPrompt = """
  Create a special dish for \(selectedMeal) at a \(cuisine)) restaurant.

  Requirements:
  - Each dish must include at least ONE of the available ingredients.
  - The dishes should be appropriate for the cuisine and meal type.
  - This is the place to try more unique and authentic meals.
  - Prices may be a bit more expensive than expected at a casual restaurant in USD.
"""
let response = try? await session.respond(to: specialPrompt, schema: schema)

Most of this code should be familiar at this point:

  1. You first convert the dynamic schema to a GenerationSchema by calling GenerationSchema and passing your DynamicGenerationSchema as the root parameter.
  2. When you try to create a generation schema, it can throw an error if there are conflicting property names, undefined references, or duplicate types. If any of those occur, then the schema variable will be nil. You attempt to unwrap schema, and if that fails, you return from the method.
  3. The code then uses the already created session on the view. You provide a prompt and get a response from Foundation Models, passing in the unwrapped schema from step one to the schema parameter. The response will be of type GeneratedContent accessible via the content property.

Finish the method with the following code:

let name = try? response?.content.value(String.self, forProperty: "name")
let ingredients = try? response?.content.value(String.self, forProperty: "ingredients")
let description = try? response?.content.value(String.self, forProperty: "description")
let price = try? response?.content.value(Decimal.self, forProperty: "price")
let specialItem = MenuItem(
  name: name ?? "",
  description: description ?? "",
  ingredients: ingredients == nil ? [] : [ingredients!],
  cost: price ?? 0.0
)

special = specialItem

To get each property in the generated content, you call the value(_:forProperty:) method on the GeneratedContent. Note that this uses the try? pattern to return nil if anything goes wrong. You pass the expected type to value(_:forProperty:) along with the name of the property as you defined when creating the schema. The type should match the type specified when creating the schema.

You then create a MenuItem named specialItem from these properties, using the nil-coalescing operator to provide values if a property is nil. The method then assigns the generated data to a property named special. To add this state property, add the following after the menu property:

@State var special: MenuItem?

Now add the following code to call the method from the Button action after await generateLunchMenu():

await generateMenuSpecial()

To finish up the view, add the following code after the Button view and before the attempt to unwrap the menu property to display the special when available:

if let special = special {
  VStack {
    Text("Today's Special")
      .font(.title2)
    MenuItemView(
      menuItem: special.asPartiallyGenerated()
    )
  }
  .featuredCard()
  .padding(.bottom, 8)
}

This attempts to unwrap the special state property. If successful, it will show the special item using the MenuItemView view along with the asPartiallyGenerated() method to convert the MenuItem to the partially generated version expected by the view. The view also includes the featuredCard modifier to help the special visually stand out against the rest of the menu.

Run the app and generate a lunch menu. The regular menu will be generated as before. A few seconds after that, you will see the special menu item generated.

Including the Dynamically Generated Menu Item
Including the Dynamically Generated Menu Item

Challenge

The app uses an asynchronous response to generate the dynamic content. Update the app to stream the response. As a hint, create a new view to handle the GeneratedContent view. See the challenge project for one solution.

Conclusion

In this chapter, you’ve explored the rich offering of guided generation capabilities in Apple’s Foundation Models framework, starting with simple types and producing basic structured data. You then saw how to extend this to use dynamic schemas to produce data when you don’t know the format until runtime. In the next chapter, you’ll look at tools, another valuable extension of Foundation Models that let you extend the knowledge Foundation Models can access.

Key Concepts

  • Guided generation eliminates the need for error-prone text parsing while maintaining full type safety.
  • Swift built-in types already include support for guided generation.
  • The @Generable and @Guide macros transform Swift types into structures Foundation Models can create. Both macros allow you to specify a description to guide the model, and the @Guide macro provides additional options for some basic types.
  • Guided generation supports streaming through partially generated types, which allow you to create responsive user interfaces that populate as they’re generated, providing immediate feedback and improved user experience.
  • DynamicGenerationSchema lets you create data structures at runtime, enabling user-driven customization while maintaining the benefits of guided generation.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.