skills/apple-foundation-models/references/guided_generation.md

704 lines
16 KiB
Markdown

# Guided Generation
## Overview
Guided generation allows you to constrain the language model's output to conform to specific Swift types. Instead of receiving unstructured text, you get type-safe Swift objects that match your data model.
### Why Use Guided Generation?
**Problem with Raw Strings:**
When you perform a request, the model returns a raw string in its natural language format. Raw strings require you to manually parse the details you want, which is:
- Error-prone
- Requires regex or string parsing
- No type safety
- Fragile to format changes
**Solution: Guided Generation**
The framework provides guided generation, which gives **strong guarantees** that the response is in a format you expect. The framework uses **constrained sampling** when generating output, which defines the rules on what the model can generate.
**Constrained sampling prevents the model from generating anything invalid.** For example, if you define a Float property, the model can only generate valid floating-point numbers—it cannot generate letters or invalid characters.
### Simple Example: Primitive Types
```swift
// Without guided generation - need to parse
let prompt = "How many tablespoons are in a cup?"
let session = LanguageModelSession(model: .default)
let response = try await session.respond(to: prompt)
// response = "There are 16 tablespoons in a cup."
// Now you need to extract "16"...
// With guided generation - type-safe!
let value = try await session.respond(to: prompt, generating: Float.self)
// value = 16.0 (guaranteed Float)
```
## Generable Protocol
The `Generable` protocol marks types that the language model can generate.
```swift
protocol Generable: Decodable {
// No required methods - conformance indicates the type can be generated
}
```
Any `Decodable` type can conform to `Generable`:
```swift
struct Recipe: Generable {
var title: String
var ingredients: [String]
var steps: [String]
}
```
## Basic Usage
### Simple Struct Generation
```swift
struct BookSummary: Generable {
var title: String
var author: String
var mainThemes: [String]
var rating: Int
}
let session = try await model.session()
let prompt = Prompt(text: "Summarize '1984' by George Orwell")
let summary = try await session.respond(to: prompt, using: BookSummary.self)
print(summary.title) // "1984"
print(summary.author) // "George Orwell"
print(summary.mainThemes) // ["totalitarianism", "surveillance", "freedom"]
```
### Nested Structures
```swift
struct Address: Generable {
var street: String
var city: String
var state: String
var zipCode: String
}
struct Person: Generable {
var name: String
var age: Int
var address: Address
var hobbies: [String]
}
let prompt = Prompt(text: "Create a profile for a fictional software engineer in San Francisco")
let person = try await session.respond(to: prompt, using: Person.self)
print(person.address.city) // "San Francisco"
```
## Generation Schema
For more control, use `GenerationSchema`:
```swift
struct GenerationSchema<T: Generable> {
let type: T.Type
init(_ type: T.Type) {
self.type = type
}
}
```
### Basic Schema Usage
```swift
struct MovieReview: Generable {
var movieTitle: String
var rating: Int // 1-5
var pros: [String]
var cons: [String]
var recommendation: String
}
let schema = GenerationSchema(MovieReview.self)
let prompt = Prompt {
Instructions("Provide honest movie reviews")
"Review 'The Matrix'"
}
let review = try await session.respond(to: prompt, using: MovieReview.self)
```
## Dynamic Generation Schema
For runtime-defined schemas, use `DynamicGenerationSchema` to construct schemas programmatically at runtime:
### Creating Dynamic Schemas
```swift
// Array schema
let arraySchema = DynamicGenerationSchema(
arrayOf: elementSchema,
minimumElements: 1,
maximumElements: 10
)
// Object schema with properties
let objectSchema = DynamicGenerationSchema(
name: "Person",
description: "A person object",
properties: [
"name": stringSchema,
"age": integerSchema,
"skills": skillsArraySchema
]
)
// Any-of schema (enum-like)
let statusSchema = DynamicGenerationSchema(
name: "Status",
description: "Task status",
anyOf: [
.constant("pending"),
.constant("in_progress"),
.constant("completed")
]
)
// Reference schema
let referenceSchema = DynamicGenerationSchema(
referenceTo: "ExistingType"
)
// Schema from Generable type with guides
let guidedSchema = DynamicGenerationSchema(
type: MyType.self,
guides: [.minimum(0), .maximum(100)]
)
```
### Complete Dynamic Schema Example
```swift
// Define schemas at runtime based on user configuration
func createDynamicSchema(fieldTypes: [String: String]) -> DynamicGenerationSchema {
var properties: [String: DynamicGenerationSchema] = [:]
for (fieldName, fieldType) in fieldTypes {
switch fieldType {
case "string":
properties[fieldName] = DynamicGenerationSchema(
type: String.self,
guides: []
)
case "integer":
properties[fieldName] = DynamicGenerationSchema(
type: Int.self,
guides: [.minimum(0)]
)
case "array":
properties[fieldName] = DynamicGenerationSchema(
arrayOf: DynamicGenerationSchema(type: String.self, guides: []),
minimumElements: 1,
maximumElements: 10
)
default:
break
}
}
return DynamicGenerationSchema(
name: "DynamicObject",
description: "Runtime-defined object",
properties: properties
)
}
// Usage
let schema = createDynamicSchema(fieldTypes: [
"name": "string",
"age": "integer",
"tags": "array"
])
```
## Guide Macro
The `@Guide` macro provides fine-grained control over generation for individual properties:
```swift
struct WeatherForecast: Generable {
@Guide("City name, e.g., 'San Francisco'")
var location: String
@Guide("Temperature in Fahrenheit, between -50 and 150")
var temperature: Int
@Guide("One of: sunny, cloudy, rainy, snowy")
var condition: String
@Guide("Array of hourly forecasts for the next 24 hours")
var hourlyForecast: [HourlyForecast]
}
struct HourlyForecast: Generable {
@Guide("Hour in 24-hour format (0-23)")
var hour: Int
@Guide("Temperature in Fahrenheit")
var temp: Int
}
```
### Using @Guide for Constraints
```swift
struct Product: Generable {
@Guide("Product name, 2-50 characters")
var name: String
@Guide("Price in USD, positive decimal number")
var price: Double
@Guide("Exactly 5 tags, each 1 word")
var tags: [String]
@Guide("Description, 50-200 characters")
var description: String
@Guide("true if in stock, false otherwise")
var inStock: Bool
}
let prompt = Prompt(text: "Create a product listing for wireless headphones")
let product = try await session.respond(to: prompt, using: Product.self)
```
## GenerationGuide API
The `GenerationGuide` struct provides programmatic control over how values are generated, offering more precise constraints than simple @Guide descriptions.
### Pattern Matching
```swift
struct FormData: Generable {
@Guide("Email address", guides: .pattern("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z]{2,}"))
var email: String
@Guide("Phone number", guides: .pattern("\\d{3}-\\d{3}-\\d{4}"))
var phoneNumber: String
@Guide("Zip code", guides: .pattern("\\d{5}"))
var zipCode: String
}
```
### Constant and AnyOf
```swift
struct Configuration: Generable {
@Guide("Environment", guides: .anyOf(["development", "staging", "production"]))
var environment: String
@Guide("API version", guides: .constant("v2"))
var apiVersion: String
}
```
### Array Constraints
```swift
struct Survey: Generable {
@Guide("Exactly 5 questions", guides: .count(5))
var questions: [String]
@Guide("At least 3 tags", guides: .minimumCount(3))
var tags: [String]
@Guide("At most 10 categories", guides: .maximumCount(10))
var categories: [String]
@Guide("2-8 ratings", guides: [.minimumCount(2), .maximumCount(8)])
var ratings: [Int]
}
```
### Element Constraints
```swift
struct NumberList: Generable {
@Guide("Numbers between 1-100", guides: .element(.range(1...100)))
var numbers: [Int]
@Guide("Lowercase words", guides: .element(.pattern("[a-z]+")))
var words: [String]
}
```
### Range Constraints
```swift
struct Measurement: Generable {
@Guide("Temperature -50 to 150", guides: .range(-50...150))
var temperature: Int
@Guide("Percentage", guides: .range(0.0...100.0))
var percentage: Double
}
```
### Min/Max Constraints
```swift
struct Limits: Generable {
@Guide("Positive number", guides: .minimum(0))
var value: Int
@Guide("Score up to 100", guides: .maximum(100))
var score: Int
@Guide("Price between 10-1000", guides: [.minimum(10), .maximum(1000)])
var price: Double
}
```
### Combining Multiple Guides
```swift
struct ValidatedInput: Generable {
@Guide(
"Username",
guides: [
.pattern("[a-zA-Z0-9_]+"),
.minimumCount(3),
.maximumCount(20)
]
)
var username: String
@Guide(
"Age",
guides: [
.minimum(13),
.maximum(120)
]
)
var age: Int
@Guide(
"Skills",
guides: [
.minimumCount(1),
.maximumCount(10),
.element(.pattern("[A-Za-z]+"))
]
)
var skills: [String]
}
```
## Advanced Examples
### Event Planning
```swift
struct Event: Generable {
@Guide("Event title")
var title: String
@Guide("ISO 8601 date string")
var date: String
@Guide("Duration in minutes")
var duration: Int
@Guide("List of required attendees")
var attendees: [String]
@Guide("Structured agenda items")
var agenda: [AgendaItem]
}
struct AgendaItem: Generable {
@Guide("Topic title")
var topic: String
@Guide("Duration in minutes")
var duration: Int
@Guide("Presenter name")
var presenter: String
}
let prompt = Prompt {
Instructions("You are an event planning assistant")
"Plan a team retrospective meeting for next Friday with Alice, Bob, and Carol"
}
let event = try await session.respond(to: prompt, using: Event.self)
```
### Code Analysis
```swift
struct CodeReview: Generable {
@Guide("List of issues found")
var issues: [Issue]
@Guide("List of suggestions")
var suggestions: [String]
@Guide("Overall quality score 1-10")
var qualityScore: Int
@Guide("Summary of review findings")
var summary: String
}
struct Issue: Generable {
@Guide("Issue severity: low, medium, high, critical")
var severity: String
@Guide("Line number where issue occurs")
var lineNumber: Int
@Guide("Description of the issue")
var description: String
@Guide("Suggested fix")
var suggestedFix: String
}
let code = """
func processData(data: [String]) {
for item in data {
print(item)
}
}
"""
let prompt = Prompt {
Instructions("You are a code reviewer focused on Swift best practices")
"Review this code: \(code)"
}
let review = try await session.respond(to: prompt, using: CodeReview.self)
```
### Recipe Generator
```swift
struct Recipe: Generable {
@Guide("Recipe title")
var title: String
@Guide("Brief description, 1-2 sentences")
var description: String
@Guide("Preparation time in minutes")
var prepTime: Int
@Guide("Cooking time in minutes")
var cookTime: Int
@Guide("Number of servings")
var servings: Int
@Guide("List of ingredients with quantities")
var ingredients: [Ingredient]
@Guide("Ordered cooking steps")
var steps: [String]
@Guide("Difficulty: easy, medium, hard")
var difficulty: String
}
struct Ingredient: Generable {
@Guide("Ingredient name")
var name: String
@Guide("Amount needed")
var amount: String
@Guide("Unit of measurement")
var unit: String
}
let prompt = Prompt(text: "Create a simple pasta carbonara recipe")
let recipe = try await session.respond(to: prompt, using: Recipe.self)
print("\(recipe.title) - \(recipe.difficulty)")
print("Time: \(recipe.prepTime + recipe.cookTime) minutes")
for ingredient in recipe.ingredients {
print("- \(ingredient.amount) \(ingredient.unit) \(ingredient.name)")
}
```
## Enums and Constrained Values
```swift
enum Priority: String, Codable {
case low, medium, high, urgent
}
enum Status: String, Codable {
case todo, inProgress, completed, blocked
}
struct Task: Generable {
@Guide("Task title")
var title: String
@Guide("Detailed description")
var description: String
@Guide("One of: low, medium, high, urgent")
var priority: Priority
@Guide("One of: todo, inProgress, completed, blocked")
var status: Status
@Guide("Estimated hours to complete")
var estimatedHours: Double
@Guide("List of assigned people")
var assignees: [String]
}
let prompt = Prompt(text: "Create a high-priority task for implementing user authentication")
let task = try await session.respond(to: prompt, using: Task.self)
```
## Optional Properties
```swift
struct Article: Generable {
@Guide("Article title")
var title: String
@Guide("Author name")
var author: String
@Guide("Publication date in YYYY-MM-DD format, if available")
var publicationDate: String?
@Guide("Article content")
var content: String
@Guide("List of tags, if any")
var tags: [String]?
@Guide("Featured image URL, if available")
var imageURL: String?
}
```
## Best Practices
### 1. Use Descriptive Property Names
```swift
// Good
struct User: Generable {
var firstName: String
var lastName: String
var emailAddress: String
}
// Less clear
struct User: Generable {
var fn: String
var ln: String
var email: String
}
```
### 2. Provide Clear Guides
```swift
struct Appointment: Generable {
@Guide("Appointment date in ISO 8601 format (YYYY-MM-DDTHH:mm:ss)")
var dateTime: String
@Guide("Duration in minutes, between 15 and 240")
var duration: Int
}
```
### 3. Use Nested Types for Complex Data
```swift
struct Project: Generable {
var name: String
var milestones: [Milestone]
var team: Team
}
struct Milestone: Generable {
var title: String
var deadline: String
var deliverables: [String]
}
struct Team: Generable {
var lead: String
var members: [String]
}
```
### 4. Validate Generated Data
```swift
let session = try await model.session()
let prompt = Prompt(text: "Create a user profile")
let user = try await session.respond(to: prompt, using: User.self)
// Validate the generated data
guard !user.emailAddress.isEmpty,
user.emailAddress.contains("@") else {
throw ValidationError.invalidEmail
}
```
### 5. Combine with Instructions
```swift
let prompt = Prompt {
Instructions {
"Generate realistic test data"
"Use proper formatting for dates and emails"
"Ensure all required fields are populated"
}
"Create 3 sample user profiles for a social media app"
}
struct UserProfiles: Generable {
var users: [User]
}
let profiles = try await session.respond(to: prompt, using: UserProfiles.self)
```
## Error Handling
```swift
do {
let data = try await session.respond(to: prompt, using: MyStruct.self)
// Process generated data
} catch {
// Handle generation failures
// May occur if the model cannot conform to the schema
print("Failed to generate structured data: \(error)")
}
```
## Limitations
1. **Complexity**: Very complex nested structures may be challenging for the model
2. **Validation**: Generated data should be validated as the model may not perfectly follow constraints
3. **Token Limits**: Large schemas count toward session token limits
4. **Ambiguity**: Clear property names and guides lead to better results