Skip to main content

Complete example

Application.kt

Step by step

1. Initialize

The Android overload of initialize() takes a Context and installs the platform context (secure storage, model path resolution) before phase 1 starts.
You do not call AndroidPlatformContext.initialize() or CppBridgeModelPaths.setBaseDirectory(). Both are SDK-internal. initialize() is synchronous and returns in single-digit milliseconds. Phase 2 (authentication, model assignments, device registration) runs in the background and is awaited automatically on first feature use. For production, pass credentials:

Environment options

SDKEnvironment is the Wire-generated proto enum ai.runanywhere.proto.v1.SDKEnvironment. Values are fully qualified: SDKEnvironment.SDK_ENVIRONMENT_DEVELOPMENT, not SDKEnvironment.DEVELOPMENT. The only other value is SDK_ENVIRONMENT_UNSPECIFIED. There is no staging value.

2. Register backends

Order matters. Register llama.cpp and ONNX before initialize(): once initialization returns, a concurrent caller can reach loadModel() while only the platform backend is registered, and the request fails with “no provider could handle the request”. Register QHexRT after initialize(), because its DSP skel installer needs the application context the SDK holds.
register() is a suspend function on all three modules and takes no arguments. There is no priority parameter. Wrap the CPU backends in runCatching: a build that ships only some native libraries throws on the missing one, and an unguarded call aborts SDK setup. QHexRT.register() rejects itself on unsupported parts and logs instead of throwing.

3. Register models

registerModel() is a suspend function with three overloads. Single file:
Archive, for Sherpa speech models that ship as .tar.gz:
Multi-file, for VLMs that need a model GGUF plus an mmproj projector:
There is no registerMultiFileModel; the multi-file form is an overload of registerModel. Registration is idempotent. The registry merges on re-save and preserves runtime fields such as is_downloaded and per-file local paths, so a catalog can be re-seeded on every launch. Call RunAnywhere.refreshModelRegistry() afterwards so a local rescan picks up what is already on disk.

4. Download and load

downloadModelStream(model: RAModelInfo) returns Flow<DownloadProgress>. The suspend form is downloadModel(model, onProgress), which returns the terminal progress. Both take a ModelInfo, not a model id string. total_bytes is 0 until the planner has sized the artifact. loadModel() is one entry point for every modality. Category is inferred from the registry entry and is only needed to disambiguate a model registered under more than one:
loadModel() returns a ModelLoadResult with a success flag rather than throwing:

5. Run inference

generate() returns an LLMGenerationResult and transcribe() an STTOutput, so both need .text. Proto field names are snake_case throughout.

6. Stream

generateStream() emits LLMStreamEvent, not String, and is not a suspend function. See Streaming.

What’s next?

LLM chat

Multi-turn conversations

Streaming

Token streaming with Flows

Speech-to-text

Transcribe audio to text

Text-to-speech

Convert text to spoken audio