Structured data
Typed Codable output, streamed partials, and the Schema DSL.
Covers Examples/Features/06-GenerateObject.swift, 07-StreamObject.swift, and
18-Schema.swift. You end up with model output you can hand straight to
your views.
Define the type and its schema
struct Recipe: Codable {
var name: String
var ingredients: [String]
var steps: [String]
}
let recipeSchema: JSONValue = [
"type": "object",
"properties": [
"name": ["type": "string"],
"ingredients": ["type": "array", "items": ["type": "string"]],
"steps": ["type": "array", "items": ["type": "string"]]
],
"required": ["name", "ingredients", "steps"]
]Generate the object
let result = try await generateObject(
model: OpenAIModel("gpt-5.6-sol"),
of: Recipe.self,
schema: recipeSchema,
prompt: "A simple lasagna recipe."
)
print(result.object.name, "with", result.object.steps.count, "steps")If the model produces JSON that doesn't match, the call throws. Your
views only ever see a valid Recipe.
Stream it for the UI
Partial JSON gets repaired into usable snapshots as it arrives:
let result = streamObject(
model: model,
schema: recipeSchema,
prompt: "A simple lasagna recipe."
)
var latest: JSONValue = .null
for try await partial in result.partialObjectStream {
latest = partial
print("so far:", partial["name"]?.stringValue ?? "...")
}
let recipe = try latest.decode(Recipe.self)Trade the raw schema for the DSL
Schema combinators build the JSON Schema and validate the output at
runtime before decoding:
let typedRecipeSchema = Schema.object([
"name": .string(description: "Recipe name"),
"steps": .array(of: .string(), minItems: 1),
"servings": .integer(minimum: 1).optional()
])The same schemas type your tools, and arguments get validated before the closure runs:
let serve = Tool(
name: "serve",
description: "Serve a number of portions",
parameters: Schema.object(["servings": .integer(minimum: 1)])
) { args in
.string("served \(args["servings"]?.intValue ?? 0)")
}Final code
import AI
struct Recipe: Codable {
var name: String
var ingredients: [String]
var steps: [String]
}
let recipeSchema = Schema.object([
"name": .string(description: "Recipe name"),
"ingredients": .array(of: .string(), minItems: 1),
"steps": .array(of: .string(), minItems: 1)
])
func generateRecipe() async throws -> Recipe {
let result = try await generateObject(
model: OpenAIModel("gpt-5.6-sol"),
of: Recipe.self,
schema: recipeSchema,
prompt: "A simple lasagna recipe."
)
return result.object
}
func streamRecipe(into render: @escaping (JSONValue) -> Void) async throws -> Recipe {
let result = streamObject(
model: OpenAIModel("gpt-5.6-sol"),
schema: recipeSchema,
prompt: "A simple lasagna recipe."
)
var latest: JSONValue = .null
for try await partial in result.partialObjectStream {
latest = partial
render(partial)
}
return try latest.decode(Recipe.self)
}Enums and unions compose when the shapes get richer:
let event = Schema.object([
"kind": .enum(["meeting", "reminder"]),
"when": .string(format: "date-time"),
"attendees": .array(of: .object([
"name": .string(),
"id": .anyOf([.integer(), .string()])
])).optional()
])