Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.liquid.ai/llms.txt

Use this file to discover all available pages before exploring further.

Constrained generation forces the model to emit JSON matching a schema. Use the language’s native facility — Swift macros (@Generatable / @Guide) or Kotlin annotations (@Generatable / @Guide) — to define the structure, then set it on GenerationOptions. The schema is computed at compile time (Swift) or via reflection at load time (Kotlin), and the model’s output decodes directly into your type.

Define the structured type

The @Generatable macro analyzes your struct at compile time and synthesizes the jsonSchema() method. All stored properties must carry a @Guide description.
import LeapModelDownloader

@Generatable("A joke with metadata")
struct Joke: Codable {
    @Guide("The joke text")
    let text: String

    @Guide("The category of humor (pun, dad-joke, programming, etc.)")
    let category: String

    @Guide("Humor rating from 1-10")
    let rating: Int

    @Guide("Whether the joke is suitable for children")
    let kidFriendly: Bool
}
Requires Swift 5.9+ (Swift macros). The @Generatable / @Guide macros ship in the LeapSDKMacros SPM product — add it to your target alongside LeapModelDownloader (or LeapSDK) if you use constrained generation.

Apply the schema in GenerationOptions

var options = GenerationOptions(temperature: 0.3, minP: 0.15, repetitionPenalty: 1.05)
try options.setResponseFormat(type: Joke.self)

let message = ChatMessage(role: .user, content: [.text("Tell me a programming joke")])

for try await response in conversation.generateResponse(
    message: message,
    generationOptions: options
) {
    if case .complete(let completion) = onEnum(of: response) {
        let jsonText = completion.fullMessage.content.compactMap { part -> String? in
            if case let .text(t) = onEnum(of: part) { return t.text }
            return nil
        }.joined()
        guard let data = jsonText.data(using: .utf8) else { continue }
        let joke = try JSONDecoder().decode(Joke.self, from: data)
        print(joke)
    }
}

Embedding the schema in the prompt

Some models do better when the JSON Schema is also included in the prompt text. Both platforms expose a helper.
let schemaString = try JSONSchemaGenerator.getJSONSchema(for: Joke.self)
let message = ChatMessage(
    role: .user,
    content: [.text("Tell me a programming joke following this JSON Schema: \(schemaString)")]
)

Supported types

Composition types are supported as long as the leaf types are supported.
Schema typeSwiftKotlin
StringStringString
IntegerInt, Int32, Int64Int, Long
NumberDouble, FloatFloat, Double
BooleanBoolBoolean
EnumString with constrained enumValuesKotlin enum class (plain name used as value)
Objectnested @Generatable structnested @Generatable data class
Array[T] of any supported typeList<T> / MutableList<T> of supported type
OptionalOptional<T> (T?)nullable T?

Complex nested structures

@Generatable("A recipe with ingredients and instructions")
struct Recipe: Codable {
    @Guide("Name of the dish")
    let name: String

    @Guide("List of ingredients with quantities")
    let ingredients: [String]

    @Guide("Step-by-step cooking instructions")
    let instructions: [String]

    @Guide("Cooking time in minutes")
    let cookingTimeMinutes: Int

    @Guide("Number of servings this recipe makes")
    let servings: Int?

    @Guide("Nutritional information if available")
    let nutrition: NutritionInfo?
}

@Generatable("Nutritional information for a recipe")
struct NutritionInfo: Codable {
    @Guide("Calories per serving")
    let caloriesPerServing: Int

    @Guide("Protein in grams")
    let proteinGrams: Double

    @Guide("Carbohydrates in grams")
    let carbsGrams: Double
}

Best practices

Write descriptive @Guide annotations

The model uses them as natural-language hints about what each field should contain. Specific descriptions outperform generic ones.
✓ @Guide("The programming language name (e.g., Swift, Python, JavaScript)")
✗ @Guide("A string")

Keep structures focused

Smaller, single-responsibility types produce better output than sprawling structures with twenty fields. If a type starts mixing concerns (profile + preferences + history), split it.

Lower temperature for structured output

Temperature 0.3–0.5 typically improves adherence to the schema. The default 0.7 is biased toward conversational variation that doesn’t help when you need parseable JSON.

Validate the decoded output

Even with constrained generation, you should handle parse failures gracefully. The model’s output is constrained against the schema but not against business invariants.
private func parse<T: Codable>(_ jsonText: String, as type: T.Type) -> T? {
    guard let data = jsonText.data(using: .utf8) else { return nil }
    do {
        return try JSONDecoder().decode(type, from: data)
    } catch {
        print("Failed to decode response as \(type): \(error)")
        return nil
    }
}

How it works

  1. Compile/load time@Generatable produces a JSON Schema for your type. (Swift: compile-time macro; Kotlin: reflective build at load time.)
  2. ConfigurationGenerationOptions.setResponseFormat(type:) / setResponseFormatType(...) installs the schema as jsonSchemaConstraint on the generation options.
  3. Generation — the SDK constrains decoding so only tokens that produce schema-valid JSON are emitted. The model’s output is guaranteed to parse.

Error handling

ErrorWhen it happens
LeapGeneratableSchematizationException (Kotlin)The data class can’t be translated to JSON Schema (unsupported field type, missing primary-constructor declaration).
LeapGeneratableDeserializationException (Kotlin)The generated JSON can’t be deserialized into the target data class.
Swift DecodingErrorJSONDecoder rejects the generated payload — usually because the model output contains stray prose alongside the JSON; filter Complete.fullMessage.content for the .text fragments first.

Troubleshooting

“External macro implementation could not be found” (Swift) — clean the build folder, restart Xcode, verify Swift 5.9+. Generated JSON includes prose alongside the JSON object — common with low-quality prompts. Add “Reply with only the JSON object” to the user message, lower temperature, and filter .text content fragments before decoding. Model produces empty or trivial JSON — verify each @Guide description gives the model a concrete sense of what to fill in. Generic descriptions (“a string”) leave the model guessing.