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 = modelManager as WebModelManager;
    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');
    }

    modelPath = activeModelPath;
    tokenizerPath = activeTokenizerPath;

    if (kDebugMode) {
      gemmaLog(
          'Using active embedding model: $modelPath, tokenizer: $tokenizerPath');
    }
  }

  // Check if model already exists with different parameters. The LiteRT.js
  // embedding runtime now lives in flutter_gemma_embeddings, so core can no
  // longer downcast to the package's WebEmbeddingModel to read its paths —
  // it compares against the last resolved paths it cached itself.
  if (_initializedEmbeddingModel != null) {
    final p = _lastEmbeddingPaths;
    final modelChanged = p == null ||
        p.modelPath != modelPath ||
        p.tokenizerPath != tokenizerPath;

    if (modelChanged) {
      if (kDebugMode) {
        gemmaLog(
            '[FlutterGemmaWeb] Embedding model paths changed, closing existing model');
      }
      await _initializedEmbeddingModel?.close();
      _initializedEmbeddingModel = null;
      _lastEmbeddingPaths = null;
    }
  }

  if (_initializedEmbeddingModel != null) {
    return _initializedEmbeddingModel!;
  }

  // The LiteRT.js embedding runtime moved to flutter_gemma_embeddings; core
  // resolves paths (preamble above) + owns the singleton lifecycle, then
  // dispatches construction through the EmbeddingRegistry. The backend reads
  // ONLY config.modelPath/config.tokenizerPath — it ignores the spec for path
  // resolution. Web selects by the sole registered backend (WebGPU LiteRT.js).
  final activeEmb = (modelManager as WebModelManager).activeEmbeddingModel;
  final EmbeddingBackendProvider? backend = activeEmb is EmbeddingModelSpec
      ? EmbeddingRegistry.instance.findFor(activeEmb)
      : (EmbeddingRegistry.instance.registered.isNotEmpty
          ? EmbeddingRegistry.instance.registered.first
          : null);
  if (backend == null) {
    throw StateError(
      'No embedding backend registered. Add flutter_gemma_embeddings to '
      'pubspec.yaml and pass it in embeddingBackends: of '
      'FlutterGemma.initialize(...). Registered backends: '
      '${EmbeddingRegistry.instance.registered.map((b) => b.name).join(", ")}.',
    );
  }
  // modelPath/tokenizerPath are non-null here (resolved in the preamble or
  // passed by the caller). preferredBackend is ignored on web (LiteRT.js uses
  // WebGPU when available); maxTokens is unused by embeddings.
  final embConfig = RuntimeConfig(
    maxTokens: 0,
    modelPath: modelPath,
    tokenizerPath: tokenizerPath,
    preferredBackend: preferredBackend,
  );
  // The backend's createModel(spec, config) requires a non-null spec but
  // resolves paths exclusively from config; synthesize one from the resolved
  // file paths when there's no active EmbeddingModelSpec.
  final model = await backend.createModel(
    activeEmb is EmbeddingModelSpec
        ? activeEmb
        : EmbeddingModelSpec(
            name: 'web-active-embedding',
            modelSource: ModelSource.file(modelPath),
            tokenizerSource: ModelSource.file(tokenizerPath),
          ),
    embConfig,
  );
  _initializedEmbeddingModel = model;
  _lastEmbeddingPaths = (modelPath: modelPath, tokenizerPath: tokenizerPath);
  model.addCloseListener(() {
    _initializedEmbeddingModel = null;
    _lastEmbeddingPaths = null;
  });
  return model;
}