Generate type-safe Swift values directly from LLM responses using the @Generable macro.
Conduit's structured output system lets you define Swift types that models can populate directly. The @Generable macro synthesizes JSON schema, initializers, and partial types automatically. Combined with @Guide for property-level constraints, you get validated, typed responses from any provider.
Apply @Generable to a struct or enum to opt it into structured generation:
@Generable
struct MovieReview {
let title: String
let rating: Int
let summary: String
let pros: [String]
let cons: [String]
}The macro synthesizes:
init(_ generatedContent: GeneratedContent)— Initializer from LLM outputstatic var generationSchema: GenerationSchema— JSON schema describing the typePartiallyGeneratednested type — For progressive streaming assemblygeneratedContentproperty — Convert back toGeneratedContent
Pass the schema in the generation config:
let provider = AnthropicProvider(apiKey: "sk-ant-...")
let result = try await provider.generate(
messages: Messages {
Message.user("Review the movie Inception")
},
model: .claudeSonnet45,
config: .default.responseFormat(.jsonSchema(MovieReview.generationSchema))
)
let review = try MovieReview(GeneratedContent(json: result.text))
print(review.title) // "Inception"
print(review.rating) // 9Add descriptions and constraints to individual properties:
@Generable
struct RecipeRequest {
@Guide("Name of the recipe")
let name: String
@Guide("Number of servings", .range(1...12))
let servings: Int
@Guide("Cuisine type", .anyOf(["Italian", "Mexican", "Japanese", "Indian", "French"]))
let cuisine: String
@Guide("Preparation time in minutes", .range(5...180))
let prepTime: Int
@Guide("List of ingredients needed")
let ingredients: [String]
}@Guide supports constraints via GenerationGuide:
String constraints:
.anyOf(["option1", "option2"])— Restrict to specific values.pattern("^[A-Z]")— Regex pattern matching.constant("fixed-value")— Exact value
Numeric constraints:
.range(1...100)— Inclusive range.minimum(0)— Lower bound.maximum(1000)— Upper bound
Array constraints:
.count(5)— Exact element count.minimumCount(1)— Minimum elements.maximumCount(10)— Maximum elements.element(guide)— Constraint on each element
Regex patterns:
@Generable
struct ContactInfo {
@Guide("Email address", /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
let email: String
}@Generable works with:
- Structs with stored properties
- Enums with string raw values
- Nested @Generable types
- Optionals (
String?,Int?) - Arrays (
[String],[NestedType])
Property types that conform to ConvertibleFromGeneratedContent are supported:
String,Int,Double,Bool,Float[T]whereT: ConvertibleFromGeneratedContentT?whereT: ConvertibleFromGeneratedContent- Other
@Generabletypes
GenerationSchema describes the JSON schema for a type. It's auto-generated by @Generable but can be built manually:
// Auto-generated
let schema = MovieReview.generationSchema
// Convert to JSON for inspection
let json = schema.toJSONString()Build schemas at runtime for dynamic use cases:
let schema = DynamicGenerationSchema(
type: .object,
properties: [
"name": .init(type: .string, description: "User's name"),
"age": .init(type: .integer, description: "User's age"),
],
required: ["name", "age"]
)GeneratedContent is the intermediate representation between raw JSON and typed Swift values. It's a self-describing tree type:
// Parse from JSON
let content = try GeneratedContent(json: """
{"title": "Inception", "rating": 9}
""")
// Access values
let title: String = try content.value(forProperty: "title")
let rating: Int = try content.value(forProperty: "rating")
// Convert back to JSON
let json = content.jsonStringDuring streaming, GeneratedContent can recover from incomplete JSON. This powers the PartiallyGenerated type for progressive UI updates:
// Even incomplete JSON can be partially parsed
let partial = try GeneratedContent(json: """
{"title": "Inception", "rating": 9, "summary": "A mind-bending
""")
// partial.isComplete == false
// But title and rating are still accessibleThe ConvertibleFromGeneratedContent protocol defines how types are initialized from GeneratedContent. Standard library types (String, Int, Double, Bool, arrays, optionals) already conform. Implement it for custom types:
struct Color: ConvertibleFromGeneratedContent {
let hex: String
init(_ content: GeneratedContent) throws {
self.hex = try content.value(forProperty: "hex")
}
}Enums with string raw values work naturally:
@Generable
enum Sentiment: String {
case positive
case negative
case neutral
}
@Generable
struct Analysis {
@Guide("Overall sentiment of the text")
let sentiment: Sentiment
@Guide("Confidence score from 0 to 1")
let confidence: Double
}