load static method

Future<OnnxEmbeddingModel> load({
  1. ModelSpec? spec,
  2. String? cacheDir,
  3. String? modelPath,
  4. Tokenizer? tokenizer,
  5. DownloadProgress? onProgress,
})

Loads an embedding model and returns an OnnxEmbeddingModel.

Either modelPath or cacheDir must be supplied. Calling load without either throws ArgumentError synchronously — there is no default asset path or bundled model. Use cacheDir to download the model on demand (preferred), or modelPath to load from an explicit filesystem path. See ModelCatalog and ModelDownloader.

Download-on-demand path (preferred)

When cacheDir is provided, ModelDownloader is invoked to ensure the model files are present and checksummed before opening the ORT session. Files already in the cache are reused without downloading. Pass spec to select a specific catalog model; if omitted, ModelCatalog.defaultModelId ('bge-small-en-v1.5') is used.

onProgress is forwarded to ModelDownloader.ensure and receives incremental download progress. It is not called when files are cached.

final model = await OnnxEmbeddingModel.load(
  cacheDir: '/path/to/cache',
  onProgress: (received, total) => print('$received / $total'),
);

Explicit-path

When modelPath is provided, the file at that path is loaded directly (no download, no checksum). The model identity is set to ModelCatalog.defaultModelId unless spec is also supplied.

The second asset file must be named vocab.txt, beside modelPath, regardless of tokenizerFamily. This explicit-path mode is a lower-level escape hatch than the cacheDir path below and does not branch on tokenizer family for the asset filename — so loading an 'xlmr'-family model this way still requires its tokenizer.json to be named/placed as vocab.txt beside modelPath. The cacheDir path handles per-family asset naming correctly and is the recommended way to load an XLM-R-family model.

tokenizer overrides the word-segmentation step inside BertTokenizer. Defaults to RegExpTokenizer. Supply IcuTokenizer() from package:betto_icu for superior Unicode coverage. Ignored for models whose tokenizerFamily is 'xlmr' (word segmentation there is handled entirely by XlmRobertaTokenizer's SentencePiece/Unigram pipeline, which has no equivalent seam).

Tokenizer family selection

The concrete ModelTokenizer implementation is chosen from spec.meta['tokenizerFamily']: 'bert' loads BertTokenizer, 'xlmr' loads XlmRobertaTokenizer. This key must be present and recognised — an absent or unrecognised value throws ArgumentError rather than silently defaulting, so a future third tokenizer family can't be misresolved by accident.

Throws ArgumentError if neither modelPath nor cacheDir is supplied, or if spec.meta['tokenizerFamily'] is missing or unrecognised. Throws UnsupportedError if the model file does not exist on disk. Throws Exception if the ORT library cannot be loaded or the model is corrupt.

Implementation

static Future<OnnxEmbeddingModel> load({
  ModelSpec? spec,
  String? cacheDir,
  String? modelPath,
  Tokenizer? tokenizer,
  DownloadProgress? onProgress,
}) async {
  // Guard: at least one of modelPath or cacheDir must be supplied.
  // This is a required-argument check: throw synchronously before any I/O
  // so callers get a fast, clear failure rather than a confusing downstream
  // error. The bundled LFS asset path has been removed — download-on-demand
  // (cacheDir) or an explicit modelPath is the only supported mechanism.
  if (modelPath == null && cacheDir == null) {
    throw ArgumentError(
      'Either modelPath or cacheDir must be supplied. '
      'Pass an explicit modelPath, or pass cacheDir (with an optional spec) '
      'to download the model on demand. '
      'See ModelCatalog and ModelDownloader.',
    );
  }

  // Resolve the model spec. When no spec is given, use the default model.
  // When a raw modelPath is supplied without a spec, we still need an id for
  // model identity tracking — use the default catalog ID.
  final resolvedSpec =
      spec ?? ModelCatalog.lookup(ModelCatalog.defaultModelId);

  // Validate tokenizerFamily synchronously, before any I/O — same
  // fail-fast rationale as the modelPath/cacheDir guard above. This is a
  // pure, fast check (no file or network access), so it is fully covered
  // by unit tests rather than requiring live model assets.
  final tokenizerFamily = _tokenizerFamily(resolvedSpec);

  final String resolvedModelPath;
  final String resolvedVocabPath;

  if (modelPath != null) {
    // Explicit path — bypass catalog and downloader. The second asset file
    // is named 'vocab.txt' regardless of tokenizer family for this path —
    // an explicit modelPath is a lower-level escape hatch than the
    // catalog/downloader path below, so it keeps the original, simpler
    // convention rather than branching on tokenizerFamily too.
    resolvedModelPath = modelPath;
    resolvedVocabPath = p.join(p.dirname(modelPath), 'vocab.txt');
  } else {
    // cacheDir != null is guaranteed by the guard above.
    // Download-on-demand path: let ModelDownloader ensure the files are
    // present and their checksums match before opening the ORT session.
    // The ModelCatalog allowlist gates which models may be downloaded.
    final downloader = ModelDownloader(allowlist: ModelCatalog());
    final resolved = await downloader.ensure(
      resolvedSpec,
      cacheDir: cacheDir!,
      onProgress: onProgress,
    );
    // File names in ResolvedModel.filePaths match the keys in ModelSpec.files:
    // 'onnx' → absolute path to the .onnx model, 'vocab' → the tokenizer
    // asset (vocab.txt for BERT-family models, tokenizer.json for
    // XLM-R-family models — the key name is a generic "second asset" slot,
    // not tied to any one file format).
    resolvedModelPath = resolved.filePaths['onnx']!;
    resolvedVocabPath = resolved.filePaths['vocab']!;
  }

  _assertFileExists(resolvedModelPath, 'model file');
  _assertFileExists(resolvedVocabPath, 'tokenizer asset');

  // coverage:ignore-start
  // The lines below require a live ORT native library and model assets.
  // They are tested through integration tests when model assets are present.
  final runtime = await OnnxRuntime.load();
  final session = runtime.createSessionFromFile(resolvedModelPath);
  // tokenizerFamily was already validated above (fail-fast, before I/O) —
  // this switch only selects which concrete loader to invoke.
  final ModelTokenizer tok = tokenizerFamily == 'xlmr'
      ? await XlmRobertaTokenizer.load(resolvedVocabPath)
      : await BertTokenizer.load(resolvedVocabPath, tokenizer: tokenizer);
  return OnnxEmbeddingModel._(runtime, session, tok, resolvedSpec);
  // coverage:ignore-end
}