createEmbeddingModel method

  1. @override
Future<EmbeddingModel> createEmbeddingModel({
  1. String? modelPath,
  2. String? tokenizerPath,
  3. 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 {
  // Modern API: Use active embedding model if paths not provided
  if (modelPath == null || tokenizerPath == null) {
    final manager = _unifiedManager;
    final activeModel = manager.activeEmbeddingModel;

    // No active embedding model - user must set one first
    if (activeModel == null) {
      throw StateError(
          'No active embedding model set. Use `FlutterGemma.installEmbedder()` or `modelManager.setActiveModel()` to set a model first');
    }

    // Get the actual model file paths through unified system
    final modelFilePaths = await manager.getModelFilePaths(activeModel);
    if (modelFilePaths == null || modelFilePaths.isEmpty) {
      throw StateError(
          'Embedding model file paths not found. Use the `modelManager` to load the model first');
    }

    // Extract model and tokenizer paths from spec
    final activeModelPath = modelFilePaths[PreferencesKeys.embeddingModelFile];
    final activeTokenizerPath = modelFilePaths[PreferencesKeys.embeddingTokenizerFile];

    if (activeModelPath == null || activeTokenizerPath == null) {
      throw StateError('Could not find model or tokenizer path in active embedding model');
    }

    // Check if singleton exists and matches the active model
    if (_initEmbeddingCompleter != null &&
        _initializedEmbeddingModel != null &&
        _lastActiveEmbeddingSpec != null) {
      final currentSpec = _lastActiveEmbeddingSpec!;
      final requestedSpec = activeModel as EmbeddingModelSpec;

      if (currentSpec.name != requestedSpec.name) {
        // Active model changed - close old model and create new one
        debugPrint(
            '⚠️  Active embedding model changed: ${currentSpec.name} → ${requestedSpec.name}');
        debugPrint('🔄 Closing old embedding model and creating new one...');
        await _initializedEmbeddingModel?.close();
        // onClose callback will reset _initializedEmbeddingModel and _initEmbeddingCompleter
        _lastActiveEmbeddingSpec = null;
      } else {
        // Same model - return existing singleton
        debugPrint('ℹ️  Reusing existing embedding model instance for ${requestedSpec.name}');
        return _initEmbeddingCompleter!.future;
      }
    }

    modelPath = activeModelPath;
    tokenizerPath = activeTokenizerPath;

    debugPrint('Using active embedding model: $modelPath, tokenizer: $tokenizerPath');
  } else {
    // Legacy API with explicit paths - check if singleton exists
    if (_initEmbeddingCompleter case Completer<EmbeddingModel> completer) {
      debugPrint('ℹ️  Reusing existing embedding model instance (Legacy API)');
      return completer.future;
    }
  }

  final completer = _initEmbeddingCompleter = Completer<EmbeddingModel>();

  // Verify the active model is still installed (for Modern API path)
  final manager = _unifiedManager;
  final activeModel = manager.activeEmbeddingModel;

  if (activeModel != null) {
    final isModelInstalled = await manager.isModelInstalled(activeModel);
    if (!isModelInstalled) {
      completer.completeError(
        Exception(
            'Active embedding model is no longer installed. Use the `modelManager` to load the model first'),
      );
      return completer.future;
    }
  }

  try {
    await _platformService.createEmbeddingModel(
      modelPath: modelPath,
      tokenizerPath: tokenizerPath,
      preferredBackend: preferredBackend,
    );

    final model = _initializedEmbeddingModel = MobileEmbeddingModel(
      onClose: () {
        _initializedEmbeddingModel = null;
        _initEmbeddingCompleter = null;
        _lastActiveEmbeddingSpec = null;
      },
    );

    // Save the spec that was used to create this model (Modern API path only)
    if (activeModel != null) {
      _lastActiveEmbeddingSpec = activeModel as EmbeddingModelSpec;
    }

    completer.complete(model);
    return model;
  } catch (e, st) {
    completer.completeError(e, st);
    Error.throwWithStackTrace(e, st);
  }
}