@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 built from the kotlinx.serialization descriptor at runtime (Kotlin), and the model’s output decodes directly into your type.
Define the structured type
- Swift (iOS / macOS)
- Kotlin (all platforms)
The
@Generatable macro analyzes your struct at compile time and synthesizes the jsonSchema() method. All stored properties must carry a @Guide description.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
- Swift (iOS / macOS)
- Kotlin (all platforms)
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.- Swift (iOS / macOS)
- Kotlin (all platforms)
JSONSchemaGenerator.getJSONSchema(for:) ships in the LeapSDKMacros product (no try needed; it’s non-throwing — it just forwards to the macro-synthesized Joke.jsonSchema()).Supported types
Composition types are supported as long as the leaf types are supported.| Schema type | Swift | Kotlin |
|---|---|---|
| String | String | String |
| Integer | Int, Int32, Int64 | Int, Long |
| Number | Double, Float | Float, Double |
| Boolean | Bool | Boolean |
| Enum | String with constrained enumValues | Kotlin enum class (plain name used as value) |
| Object | nested @Generatable struct | nested @Generatable data class |
| Array | [T] of any supported type | List<T> / MutableList<T> of supported type |
| Optional | Optional<T> (T?) | nullable T? |
Complex nested structures
- Swift (iOS / macOS)
- Kotlin (all platforms)
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.
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
Temperature0.1–0.3 typically improves adherence to the schema — high-temperature sampling adds variation that doesn’t help when you need parseable JSON. Use the per-model defaults the LFM model cards recommend (e.g. 0.1 for instruct/VL, 0.3 for LFM2 text) and lower from there if the model strays.
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.- Swift (iOS / macOS)
- Kotlin (all platforms)
How it works
- Compile/load time —
@Generatableproduces a JSON Schema for your type. (Swift: compile-time macro emitsjsonSchema(); Kotlin: built at runtime from thekotlinx.serializationdescriptor.) - Configuration — Swift
options.with(jsonSchema: T.jsonSchema())(orGenerationOptionsCompat.setResponseFormat(jsonSchema:)) / KotlinsetResponseFormatType<T>()installs the schema asjsonSchemaConstrainton the generation options. - 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
| Error | When 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 DecodingError | JSONDecoder 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.