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.

Latest version: v0.10.6 The LEAP SDK is a Kotlin Multiplatform library: the same ModelRunner / Conversation / MessageResponse API runs on every supported target. The code differs only in language (Swift vs. Kotlin) and packaging (SPM, Gradle, or Kotlin/Native plugin) β€” the call shapes are identical.
Migrating from 0.9.x? v0.10.0 unifies the SDK into a single Kotlin Multiplatform distribution published from Liquid4All/leap-sdk. The standalone Liquid4All/leap-ios repo is no longer the source-of-truth. See the SDK changelog for the transition story and drop-in replacements for legacy Leap.load(...) / LiquidEngine(...) call sites.

1. Prerequisites

  • Xcode 16.0+ with Swift 6.0.
  • iOS 17.0+ or macOS 15.0+ (Mac Catalyst 17.0+ also supported).
  • A physical iPhone or iPad with at least 3 GB RAM for best performance. The simulator works for development but runs models much slower.
v0.10.0 raises the minimum iOS deployment target from 15.0 to 17.0 and macOS from 12.0 to 15.0. Apps targeting older OSes need to pin to 0.9.x or bump their deployment target before upgrading.

2. Install the SDK

The Leap SDK ships exclusively through Swift Package Manager in v0.10.0. CocoaPods support has been removed.
  1. In Xcode choose File β†’ Add Package Dependencies.
  2. Enter https://github.com/Liquid4All/leap-sdk.git.
  3. Select the 0.10.6 release (or newer).
  4. Add the products you need to your app target.
The package vends five products. Most apps only need one or two:
ProductWhat it providesRe-exports
LeapSDKCore inference + conversation API. Use this when you only need foreground manifest loads via LeapDownloader.loadModel(...).β€”
LeapModelDownloaderThe Swift ModelDownloader class with URLSession-backed loadModel(...) / downloadModel(...) and background-session support. Re-exports every LeapSDK Kotlin type, so a single import LeapModelDownloader reaches Conversation, ModelRunner, ChatMessage, Leap, the convenience extensions, and so on.every LeapSDK type
LeapOpenAIClientOpenAI-compatible cloud chat clientβ€”
LeapUIVoice assistant widget (SwiftUI/AppKit/Compose)LeapSDK
LeapSDKMacros@Generatable / @Guide macrosswift-syntax
Pick exactly one of LeapSDK or LeapModelDownloader per target. LeapModelDownloader is the recommended choice β€” its ModelDownloader.loadModel(...) covers everything LeapDownloader.loadModel(...) does and adds background-friendly transfers, and LeapSDK’s types are re-exported through the same import LeapModelDownloader statement. Drop the LeapSDK dependency from any target that already pulls in LeapModelDownloader. Add LeapSDKMacros if you use @Generatable constrained generation. LeapOpenAIClient and LeapUI are independent opt-ins.
Dual-import build-time guard (v0.10.6+). LeapModelDownloader’s umbrella header carries a __has_include(<LeapSDK/LeapSDK.h>) && !defined(LEAP_DUAL_IMPORT_ALLOW) check that fires #error at preprocessing time if both LeapSDK and LeapModelDownloader are linked into the same target. The K/N export creates a distinct Swift type per Kotlin protocol per framework, so LeapSDK.Conversation and LeapModelDownloader.Conversation are different types and every protocol reference triggers β€œambiguous for type lookup.”Opt out only for the legitimate LeapUI + LeapModelDownloader combination (LeapUI transitively bundles LeapSDK and there’s no source-level workaround): add LEAP_DUAL_IMPORT_ALLOW=1 to OTHER_CFLAGS for the affected target, and qualify any ambiguous Swift type with the source module β€” e.g. LeapModelDownloader.Conversation β€” or stick to a single import per file.
Framework type (v0.10.6+). LeapModelDownloader.xcframework is now a dynamic framework (was static in 0.10.5) and bundles the three inference engine dylibs under Frameworks/. SPM applies Embed & Sign automatically; manual Xcode integrators must select β€œEmbed & Sign” on the framework instead of β€œDo Not Embed”.
For explicit pinning, declare each framework as a .binaryTarget in your Package.swift. The XCFramework assets live on the Liquid4All/leap-sdk v0.10.6 release page β€” copy the SHA-256 values from there.
The constrained-generation macros (@Generatable, @Guide) are Swift macros, not XCFrameworks β€” they ship as the LeapSDKMacros source target inside the SPM package and cannot be installed as a .binaryTarget. If you need them, use the standard SPM package URL above (or add the LeapSDKMacros source target separately on top of your binary targets).
.binaryTarget(
  name: "LeapSDK",
  url: "https://github.com/Liquid4All/leap-sdk/releases/download/v0.10.6/LeapSDK.xcframework.zip",
  checksum: "236fb6c897d25fc5804be64edc16a9ee73c26678d02e58dab4a1b77ab2e4898f"
),
.binaryTarget(
  name: "LeapModelDownloader",
  url: "https://github.com/Liquid4All/leap-sdk/releases/download/v0.10.6/LeapModelDownloader.xcframework.zip",
  checksum: "a2a57f9c932ef7005d42b33b69d7a67f0ffb65fb79dffa954be99a0225932a61"
),
.binaryTarget(
  name: "LeapOpenAIClient",
  url: "https://github.com/Liquid4All/leap-sdk/releases/download/v0.10.6/LeapOpenAIClient.xcframework.zip",
  checksum: "b661059af8bfb086931099f8fac9f54e957272d5d6bbc9dd36e3e154fddf8222"
),
.binaryTarget(
  name: "LeapUi",
  url: "https://github.com/Liquid4All/leap-sdk/releases/download/v0.10.6/LeapUi.xcframework.zip",
  checksum: "694f4b8a8d1a8cd9086ce718a9fc15f4e74c442541b983816fd0eef8cecc7875"
),
Note that the binary target name is LeapUi (lowercase i) β€” import LeapUi in Swift sources matches the binary-target module name, even though the SPM library product is LeapUI.

3. Load a model

The recommended path is manifest-based loading. On every platform, the platform downloader’s loadModel(...) downloads (if needed) and loads in one call β€” LeapModelDownloader.loadModel(...) on iOS / macOS / Android, LeapDownloader.loadModel(...) on JVM and Linux / Windows Kotlin/Native. All paths fetch from the LEAP Model Library on first use and load from cache thereafter.
import LeapModelDownloader
import Combine

@MainActor
final class ChatViewModel: ObservableObject {
    @Published var isLoading = false
    @Published var conversation: Conversation?

    private let modelsDir: String = {
        let caches = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.path
        return (caches as NSString).appendingPathComponent("leap_models")
    }()
    private lazy var downloader = ModelDownloader(
        config: LeapDownloaderConfig(saveDir: modelsDir)
        // For background transfers, pass:
        // sessionConfiguration: .background(withIdentifier: "com.myapp.leap.downloads")
    )

    private var modelRunner: ModelRunner?
    private var generationTask: Task<Void, Never>?

    func loadModel() async {
        isLoading = true
        defer { isLoading = false }
        do {
            let runner = try await downloader.loadModel(
                modelName: "LFM2-1.2B",
                quantizationType: "Q5_K_M"
            ) { fraction, _ in
                // fraction: Double (0...1) Β· bytesPerSecond: Int64
            }
            conversation = runner.createConversation(
                systemPrompt: "You are a helpful travel assistant."
            )
            self.modelRunner = runner
        } catch {
            print("Failed to load model: \(error)")
        }
    }
}
ModelDownloader.loadModel(...) runs the file transfer through URLSession (so it inherits background-session support when you pass a sessionConfiguration) and then loads the on-disk files in place β€” no need to pair the downloader with a separate loader. If you only need foreground transfers and cross-platform Swift/Kotlin code, LeapDownloader.loadModel(modelName:, quantizationType:) has the same shape minus the URLSession integration; it ships in the same LeapModelDownloader SPM product, so no extra import is needed. See Model Loading.

Loading a sideloaded GGUF

When you already have a model file on disk β€” shipped as an app asset, adb push-ed for development, or downloaded by your own pipeline β€” use loadSimpleModel(model: ModelSource(...)) to skip the LEAP Model Library lookup entirely.
let runner = try await downloader.loadSimpleModel(
  model: ModelSource(
    modelPath: bundledURL.path,
    modelName: "LFM2-1.2B-Instruct",
    quantizationId: "Q4_K_M"
  )
)
For a vision-capable model, pass mmprojPath. For an audio-capable model, pass audioDecoderPath (and optionally audioTokenizerPath).

4. Stream a response

Both platforms expose the same streaming shape: an async sequence of MessageResponse values, each handled with an exhaustive switch.
func send(_ text: String) {
    guard let conversation else { return }
    generationTask?.cancel()
    let userMessage = ChatMessage(role: .user, content: [.text(text)])
    generationTask = Task { [weak self] in
        do {
            for try await response in conversation.generateResponse(
                message: userMessage,
                generationOptions: GenerationOptions(temperature: 0.3, minP: 0.15, repetitionPenalty: 1.05)
            ) {
                self?.handle(response)
            }
        } catch {
            print("Generation failed: \(error)")
        }
    }
}

@MainActor
private func handle(_ response: MessageResponse) {
    // `onEnum(of:)` (v0.10.0+) gives exhaustive switching without a `default`.
    switch onEnum(of: response) {
    case .chunk(let chunk):
        print(chunk.text, terminator: "")
    case .reasoningChunk(let reasoning):
        print("Reasoning:", reasoning.reasoning)
    case .audioSample(let audio):
        audioRenderer.enqueue(audio.samples, sampleRate: Int(audio.sampleRate))
    case .functionCalls(let payload):
        handleFunctionCalls(payload.functionCalls)
    case .complete(let completion):
        if let stats = completion.stats {
            print("Finished with \(stats.totalTokens) tokens")
        }
        let text = completion.fullMessage.content.compactMap { part -> String? in
            if case let .text(t) = onEnum(of: part) { return t.text }
            return nil
        }.joined()
        print("Final:", text)
    }
}
Cancel the in-flight task (Swift) or coroutine job (Kotlin) to interrupt generation early.

5. Send images and audio (optional)

If the loaded model is multimodal (and its companion files were detected), you can attach a non-text part β€” an image, a WAV blob, or raw PCM samples β€” alongside the text in a ChatMessage.
Multimodality is model-specific. Most multimodal models we ship are text + one other modality: text + vision (the VLM family) or text + audio (the audio family) β€” not both in the same checkpoint. Send .image(...) parts only to a vision-capable model, and .audio(...) / .fromFloatSamples(...) parts only to an audio-capable model. Mixing modalities a model wasn’t trained on will either fail to load the companion file or produce nonsense. Check the model’s Hugging Face card before wiring up a non-text input path.
// Text + image (vision-capable model)
let imageMessage = ChatMessage(
  role: .user,
  content: [.text("Describe what you see."), .image(jpegData)]
)

// Text + WAV audio (audio-capable model)
let wavMessage = ChatMessage(
  role: .user,
  content: [.text("Transcribe and summarize this clip."), .audio(wavData)]
)

// Text + raw PCM samples (audio-capable model)
let pcmMessage = ChatMessage(
  role: .user,
  content: [
    .text("Give feedback on my pronunciation."),
    ChatMessageContent.fromFloatSamples(samples, sampleRate: 16000)
  ]
)

6. Next steps

Model Loading

Full LeapModelDownloader / LeapDownloader reference, loadSimpleModel, KV cache reuse, and runtime options.

Conversation & Generation

Conversation, ModelRunner, MessageResponse, and GenerationOptions.

Function Calling

Tool use with Hermes and Pythonic parsers.

Constrained Generation

Structured JSON output via @Generatable macros.

Voice Assistant Widget

Drop-in Compose voice orb for iOS, macOS, Android, and JVM Desktop.

Desktop & Native Platforms

JVM Desktop, Linux native (Kotlin/Native), Windows MinGW, and macOS deep-dive.
See LeapSDK-Examples for complete sample apps.