16 KiB
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
// 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.
protocol Generable: Decodable {
// No required methods - conformance indicates the type can be generated
}
Any Decodable type can conform to Generable:
struct Recipe: Generable {
var title: String
var ingredients: [String]
var steps: [String]
}
Basic Usage
Simple Struct Generation
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
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:
struct GenerationSchema<T: Generable> {
let type: T.Type
init(_ type: T.Type) {
self.type = type
}
}
Basic Schema Usage
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
// 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
// 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:
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
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
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
struct Configuration: Generable {
@Guide("Environment", guides: .anyOf(["development", "staging", "production"]))
var environment: String
@Guide("API version", guides: .constant("v2"))
var apiVersion: String
}
Array Constraints
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
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
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
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
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
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
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
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
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
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
// 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
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
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
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
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
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
- Complexity: Very complex nested structures may be challenging for the model
- Validation: Generated data should be validated as the model may not perfectly follow constraints
- Token Limits: Large schemas count toward session token limits
- Ambiguity: Clear property names and guides lead to better results