Skip to content

Structured Output

Generate type-safe Swift values directly from LLM responses using the @Generable macro.

Overview

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.

The @Generable Macro

Apply @Generable to a struct or enum to opt it into structured generation:

swift
@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 output
  • static var generationSchema: GenerationSchema — JSON schema describing the type
  • PartiallyGenerated nested type — For progressive streaming assembly
  • generatedContent property — Convert back to GeneratedContent

Using with Providers

Pass the schema in the generation config:

swift
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)  // 9

The @Guide Macro

Add descriptions and constraints to individual properties:

swift
@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 Constraints

@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:

swift
@Generable
struct ContactInfo {
    @Guide("Email address", /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
    let email: String
}

Supported Types

@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] where T: ConvertibleFromGeneratedContent
  • T? where T: ConvertibleFromGeneratedContent
  • Other @Generable types

GenerationSchema

GenerationSchema describes the JSON schema for a type. It's auto-generated by @Generable but can be built manually:

swift
// Auto-generated
let schema = MovieReview.generationSchema

// Convert to JSON for inspection
let json = schema.toJSONString()

DynamicGenerationSchema

Build schemas at runtime for dynamic use cases:

swift
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

GeneratedContent is the intermediate representation between raw JSON and typed Swift values. It's a self-describing tree type:

swift
// 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.jsonString

Incomplete JSON Recovery

During streaming, GeneratedContent can recover from incomplete JSON. This powers the PartiallyGenerated type for progressive UI updates:

swift
// 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 accessible

ConvertibleFromGeneratedContent

The 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:

swift
struct Color: ConvertibleFromGeneratedContent {
    let hex: String

    init(_ content: GeneratedContent) throws {
        self.hex = try content.value(forProperty: "hex")
    }
}

Enums with @Generable

Enums with string raw values work naturally:

swift
@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
}

Released under the MIT License.