createEmbeddingModel method
Future<EmbeddingModel>
createEmbeddingModel({
- String? modelPath,
- String? tokenizerPath,
- PreferredBackend? preferredBackend,
override
Creates and returns a new EmbeddingModel instance.
Modern API: If paths are not provided, uses the active embedding model set via
FlutterGemma.installEmbedder() or modelManager.setActiveModel().
Legacy API: Provide explicit paths for backward compatibility.
modelPath — path to the embedding model file (optional if active model set).
tokenizerPath — path to the tokenizer file (optional if active model set).
preferredBackend — backend preference (e.g., CPU, GPU).
Implementation
@override
Future<EmbeddingModel> createEmbeddingModel({
String? modelPath,
String? tokenizerPath,
PreferredBackend? preferredBackend,
}) async {
// Check if active embedding model changed
final currentActiveModel = _modelManager.activeEmbeddingModel;
if (_initEmbeddingCompleter != null &&
_initializedEmbeddingModel != null &&
_lastActiveEmbeddingModelName != null) {
final modelChanged = currentActiveModel == null ||
currentActiveModel.name != _lastActiveEmbeddingModelName;
if (modelChanged) {
await _initializedEmbeddingModel?.close();
_initEmbeddingCompleter = null;
_initializedEmbeddingModel = null;
_lastActiveEmbeddingModelName = null;
} else {
return _initEmbeddingCompleter!.future;
}
}
// Return existing if initialization in progress
if (_initEmbeddingCompleter case Completer<EmbeddingModel> completer) {
return completer.future;
}
final completer = _initEmbeddingCompleter = Completer<EmbeddingModel>();
try {
// Resolve model and tokenizer paths from active embedding model
if (modelPath == null || tokenizerPath == null) {
final activeModel = _modelManager.activeEmbeddingModel;
if (activeModel == null) {
throw StateError(
'No active embedding model set. '
'Use `FlutterGemma.installEmbedder()` first.',
);
}
final filePaths = await _modelManager.getModelFilePaths(activeModel);
if (filePaths == null || filePaths.isEmpty) {
throw StateError('Embedding model file paths not found');
}
modelPath ??= filePaths[PreferencesKeys.embeddingModelFile];
tokenizerPath ??= filePaths[PreferencesKeys.embeddingTokenizerFile];
}
if (modelPath == null) {
throw StateError('Embedding model path is required');
}
debugPrint('[FlutterGemmaDesktop] Loading embedding model: $modelPath');
// Load TFLite interpreter via dart:ffi
final numThreads =
preferredBackend == PreferredBackend.cpu ? 4 : 6;
final interpreter = TfLiteInterpreter.fromFile(
modelPath,
numThreads: numThreads,
);
debugPrint(
'[FlutterGemmaDesktop] Embedding model loaded: '
'seqLen=${interpreter.inputSequenceLength}, '
'dim=${interpreter.outputDimension}',
);
// Load tokenizer - auto-detect format by file extension
// BOS/EOS are added manually in DesktopEmbeddingModel with correct Gemma IDs
// (dart_sentencepiece_tokenizer defaults to bosId=1/eosId=2, but Gemma uses bosId=2/eosId=1)
if (tokenizerPath == null) {
interpreter.close();
throw StateError('Tokenizer path is required for desktop embeddings');
}
final SentencePieceTokenizer tokenizer;
try {
if (tokenizerPath.endsWith('.json')) {
tokenizer = await TokenizerJsonLoader.fromJsonFile(
tokenizerPath,
config: const SentencePieceConfig(),
);
} else {
tokenizer = await SentencePieceTokenizer.fromModelFile(
tokenizerPath,
config: const SentencePieceConfig(),
);
}
} catch (e) {
interpreter.close();
rethrow;
}
List<int> tokenize(String text) {
return tokenizer.encode(text).ids.toList();
}
final model = _initializedEmbeddingModel = DesktopEmbeddingModel(
interpreter: interpreter,
tokenize: tokenize,
onClose: () {
_initializedEmbeddingModel = null;
_initEmbeddingCompleter = null;
_lastActiveEmbeddingModelName = null;
},
);
_lastActiveEmbeddingModelName = currentActiveModel?.name;
completer.complete(model);
return model;
} catch (e, st) {
completer.completeError(e, st);
_initEmbeddingCompleter = null;
rethrow;
}
}