Cactus for Flutter¶
Run AI models on-device with dart:ffi direct bindings for iOS, macOS, and Android.
Building¶
Build output:
| File | Platform |
|---|---|
libcactus.so |
Android (arm64-v8a) |
cactus-ios.xcframework |
iOS |
cactus-macos.xcframework |
macOS |
see the main README.md for how to use CLI & download weight
Integration¶
Android¶
- Copy
libcactus.sotoandroid/app/src/main/jniLibs/arm64-v8a/ - Copy
cactus.dartto yourlib/folder
iOS¶
- Copy
cactus-ios.xcframeworkto yourios/folder - Open
ios/Runner.xcworkspacein Xcode - Drag the xcframework into the project
- In Runner target > General > "Frameworks, Libraries, and Embedded Content", set to "Embed & Sign"
- Copy
cactus.dartto yourlib/folder
macOS¶
- Copy
cactus-macos.xcframeworkto yourmacos/folder - Open
macos/Runner.xcworkspacein Xcode - Drag the xcframework into the project
- In Runner target > General > "Frameworks, Libraries, and Embedded Content", set to "Embed & Sign"
- Copy
cactus.dartto yourlib/folder
Usage¶
Handles are typed as CactusModelT, CactusIndexT, and CactusStreamTranscribeT (all Pointer<Void> aliases). All functions are top-level.
Basic Completion¶
import 'cactus.dart';
import 'dart:convert';
final model = cactusInit('/path/to/model', null, false);
final messages = jsonEncode([{'role': 'user', 'content': 'What is the capital of France?'}]);
final resultJson = cactusComplete(model, messages, null, null, null);
final result = jsonDecode(resultJson);
print(result['response']);
cactusDestroy(model);
Completion with Options and Streaming¶
final options = jsonEncode({'max_tokens': 256, 'temperature': 0.7});
final tokens = <String>[];
final resultJson = cactusComplete(model, messages, options, null, (token, _) {
tokens.add(token);
stdout.write(token);
});
Audio Transcription¶
// From file
final result = cactusTranscribe(model, '/path/to/audio.wav', '', null, null, null);
// From PCM data (16 kHz mono)
final pcmData = Uint8List.fromList([...]);
final result = cactusTranscribe(model, null, null, null, null, pcmData);
Streaming Transcription¶
final stream = cactusStreamTranscribeStart(model, null);
final partial = cactusStreamTranscribeProcess(stream, audioChunk);
final final_ = cactusStreamTranscribeStop(stream);
Embeddings¶
final embedding = cactusEmbed(model, 'Hello, world!', true); // Float32List
final imageEmbedding = cactusImageEmbed(model, '/path/to/image.jpg');
final audioEmbedding = cactusAudioEmbed(model, '/path/to/audio.wav');
Tokenization¶
final tokens = cactusTokenize(model, 'Hello, world!'); // List<int>
final scores = cactusScoreWindow(model, tokens, 0, tokens.length, 512);
VAD¶
RAG¶
Vector Index¶
final index = cactusIndexInit('/path/to/index', 384);
cactusIndexAdd(
index,
[1, 2],
['Document 1', 'Document 2'],
[[0.1, 0.2], [0.3, 0.4]],
null,
);
final resultsJson = cactusIndexQuery(index, [0.1, 0.2], null);
// JSON: {"results":[{"id":1,"score":0.99,...},...]}
cactusIndexDelete(index, [2]);
cactusIndexCompact(index);
cactusIndexDestroy(index);
API Reference¶
All functions are top-level and mirror the C FFI directly. All functions throw Exception on failure.
Types¶
typedef CactusModelT = Pointer<Void>;
typedef CactusIndexT = Pointer<Void>;
typedef CactusStreamTranscribeT = Pointer<Void>;
Init / Lifecycle¶
CactusModelT cactusInit(String modelPath, String? corpusDir, bool cacheIndex)
void cactusDestroy(CactusModelT model)
void cactusReset(CactusModelT model)
void cactusStop(CactusModelT model)
String cactusGetLastError()
Completion¶
String cactusComplete(
CactusModelT model,
String messagesJson,
String? optionsJson,
String? toolsJson,
void Function(String token, int tokenId)? callback,
)
Transcription¶
String cactusTranscribe(
CactusModelT model,
String? audioPath,
String? prompt,
String? optionsJson,
void Function(String, int)? callback,
Uint8List? pcmData,
)
CactusStreamTranscribeT cactusStreamTranscribeStart(CactusModelT model, String? optionsJson)
String cactusStreamTranscribeProcess(CactusStreamTranscribeT stream, Uint8List pcmData)
String cactusStreamTranscribeStop(CactusStreamTranscribeT stream)
Embeddings¶
Float32List cactusEmbed(CactusModelT model, String text, bool normalize)
Float32List cactusImageEmbed(CactusModelT model, String imagePath)
Float32List cactusAudioEmbed(CactusModelT model, String audioPath)
Tokenization / Scoring¶
List<int> cactusTokenize(CactusModelT model, String text)
String cactusScoreWindow(CactusModelT model, List<int> tokens, int start, int end, int context)
VAD / RAG¶
String cactusVad(CactusModelT model, String? audioPath, String? optionsJson, Uint8List? pcmData)
String cactusRagQuery(CactusModelT model, String query, int topK)
Vector Index¶
CactusIndexT cactusIndexInit(String indexDir, int embeddingDim)
void cactusIndexDestroy(CactusIndexT index)
int cactusIndexAdd(CactusIndexT index, List<int> ids, List<String> documents, List<List<double>> embeddings, List<String>? metadatas)
int cactusIndexDelete(CactusIndexT index, List<int> ids)
String cactusIndexGet(CactusIndexT index, List<int> ids)
String cactusIndexQuery(CactusIndexT index, List<double> embedding, String? optionsJson)
int cactusIndexCompact(CactusIndexT index)
Telemetry¶
void cactusSetTelemetryEnvironment(String cacheDir)
void cactusSetAppId(String appId)
void cactusTelemetryFlush()
void cactusTelemetryShutdown()
All functions throw a standard Exception on failure.
Bundling Model Weights¶
Models must be accessible via file path at runtime.
Android¶
Copy from assets to internal storage on first launch:
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
Future<String> getModelPath() async {
final dir = await getApplicationDocumentsDirectory();
final modelFile = File('${dir.path}/model');
if (!await modelFile.exists()) {
final data = await rootBundle.load('assets/model');
await modelFile.writeAsBytes(data.buffer.asUint8List());
}
return modelFile.path;
}
iOS/macOS¶
Add model to bundle and access via path:
Requirements¶
- Flutter 3.0+
- Dart 2.17+
- iOS 14.0+ / macOS 13.0+
- Android API 24+ / arm64-v8a
See Also¶
- Cactus Engine API — Full C API reference underlying the Flutter bindings
- Cactus Index API — Vector database API for RAG applications
- Fine-tuning Guide — Deploy custom fine-tunes to mobile
- Swift SDK — Native Swift alternative for Apple platforms
- Kotlin/Android SDK — Native Kotlin alternative for Android