model2vec 2.0.2
model2vec: ^2.0.2 copied to clipboard
On-device Model2Vec text embeddings for Dart & Flutter — a self-contained Rust core via FFI and Native Assets. Fast, local, static, minimal memory.
2.0.2 #
Fixes a build hook that could never be cached, so every build of a dependent package paid to rebuild this crate.
- The hook no longer declares its own build output as its input. It looked
for cargo's dep-info beside the artifact as
'$binaryPath.d'—libm2v_ffi.dylib.d. Cargo writes one dep-info per target, named after the target: a crate built as["staticlib", "cdylib"]produceslibm2v_ffi.a,libm2v_ffi.dyliband a singlelibm2v_ffi.d. The requested name is written on no platform, so the parse returned nothing every time and the fallback —output.dependencies.add(.directory(nativeDir))— was not a fallback but the only path ever taken.native/contains cargo's owntarget/, so the hook declared as its input a tree it rewrites on every run. Anything that writes there — a second consumer building the same crate — marks the hook dirty, and the runner reportsFile modified during build. Build must be rerun.and invokes it again. Measured on this repository againsthooks 2.1.0, a file written intotarget/without touchingsrc/: 1 re-run and 1.55 s before, 0 and 0.85 s after. The re-run is cheap only while cargo itself has nothing to do; when the re-run coincides with a real rebuild the cost is the rebuild, and in a dependent workspace that measured 68 s against 3.5 s settled, with the spawning tests of a paralleldart testtiming out. See dart-lang/native#1998 for the same shape reported againstnative_toolchain_c, and dart.dev/tools/hooks: dependencies are the inputs a hook reads, never the outputs it produces. - The dep-info parser no longer breaks on Windows paths. It split the file
on the first
':', which on Windows is the drive letter — soC:\out\m2v_ffi.dll: C:\src\lib.rsyielded\out\m2v_ffi.dllas a "dependency": a path that exists nowhere, with the hook still reporting success. That bug was dormant only because the filename above meant the parser was never reached, so fixing the filename alone would have shipped it. The separator is now the first": ". While there, the parser reads every artifact line rather than only the first, and honours cargo's\escape so a path containing a space stays one path. - When the dep-info really is missing, the fallback now names the crate's
actual inputs —
native/src/,Cargo.lock,rust-toolchain.tomland the manifest — instead of the directory that holds the build tree.
test/build_hook_test.dart covers both: an integration test reads the
dependencies the hook actually writes for the host, and unit tests pin the
Windows, multi-line and escaped-space cases that a run on one platform cannot
produce. No API change; this is a build-time fix only.
2.0.1 #
Fixes a build hook that broke every Flutter app depending on this package.
flutter runno longer fails with "Building native assets failed". The hook readinput.config.codewithout first askinginput.config.buildCodeAssets. That is fine fordart build, which only ever invokes hooks with code assets requested — but Flutter also runs them from its asset-bundling pass (buildCodeAssets: null), and with data assets behind a feature flag that pass arrives withbuild_asset_typesempty. Reading.codethere throwsStateError, the hook exits 255, and Flutter reports the whole app's native-asset build as failed, including the pass that would have built the library. The hook now returns early when no code assets are asked for.test/build_hook_test.dartpins the empty case, which no localdartcommand reaches on its own.
2.0.0 #
Major release reworking the FFI boundary and public surface for testability and correctness. This release is breaking — see migration below.
Breaking changes:
- Static API.
Model2Vecis now a stateless namespace of static methods.Model2Vec.instance, theModel2Vec(DynamicLibrary)constructor andModel2Vec.boot(...)were removed — the native library is resolved automatically through Native Assets (@Nativecode assets). ReplaceModel2Vec.instance.foo(...)withModel2Vec.foo(...). - Recommended models.
getRecommendedModels()(returningList<Map<String, dynamic>>) is replaced by the typed constantModel2Vec.recommendedModels(List<RecommendedModel>). - Typed errors.
Model2VecExceptionnow carries aModel2VecErrorKind kind; its constructor is(kind, message, [code])and thefromCodefactory is replaced byfromNative(code, message). Native failures surface the message produced by the Rust layer, each with an exhaustively-switchablekind. - Lifecycle naming. The
initEmbedder*methods are renamed toloadModel*, pairingloadModel⇄unloadModelover the model.initEmbedder,initEmbedderAdvanced,initEmbedderFromBytesand their async forms are removed.Model2VecUtils.similaritySearch/similaritySearchWithThresholdare removed in favour ofsimilaritySearchWithScores(read.index). - Batch signature.
generateBatchEmbeddingsno longer takesbatchSize(its signature is now(List<String> texts, {int maxLength})). The native layer batches internally;batchSizeremains only ongenerateEmbeddingStream, which still controls its per-batch size.
Improvements:
- Native memory safety. The
generate_*FFI functions now allocate their output inside the native call (returned as a pointer the caller frees), removing a dimension/model-switch race that could overflow the output buffer. Every native entry point is wrapped incatch_unwind, so a panic (including from a malformed model) surfaces as a typed error instead of undefined behaviour. - Windows ABI fix. FFI length parameters use
size_t(wasunsigned long, 32-bit on 64-bit Windows and mismatched against Rust'susize). - Streaming rework.
generateEmbeddingStreamis rebuilt on small, tested modules — a batching transformer, a transport-agnostic worker protocol, and a worker isolate. Worker errors cross the isolate boundary as typedModel2VecExceptions (kind + code preserved) rather than stringified errors.
New capabilities:
- Local vector index.
EmbeddingIndex— store embeddings by id, thensearchthe nearest by cosine similarity. Optional int8-quantized storage (~4x less memory) and binarytoBytes/fromBytespersistence. Turns the package into a local retrieval engine for RAG. - RAG pipeline helpers.
chunkText(overlapping character chunker),Model2VecUtils.similaritySearchWithScores(index + score), andModel2VecUtils.maximalMarginalRelevance(MMR reranking for diverse results). - Lifecycle & DX.
Model2Vec.isInitialized(non-throwing check),Model2Vec.unloadModel()(free the native model),Model2Vec.modelInfo(all metadata in oneModelInfo), andModel2VecUtils.dequantizeInt8(the inverse ofquantizeToInt8). - Load progress.
Model2Vec.loadModelWithProgress()loads on a background isolate and returns aStream<LoadProgress>reporting the HF weights download (bytesDownloaded/totalBytes/fraction) plus a coarseLoadPhase(resolving → downloading → parsing → done). A cached model or local path streams straight todone. - Parallel worker pool.
EmbeddingPoolfans batches across N worker isolates to embed concurrently across CPU cores.
Migration:
| 1.x | 2.0.0 |
|---|---|
Model2Vec.instance.generateEmbedding(t) |
Model2Vec.generateEmbedding(t) |
Model2Vec.boot(lib) / Model2Vec(lib) |
removed — resolution is automatic |
Model2Vec.instance.getRecommendedModels() |
Model2Vec.recommendedModels (typed) |
Model2Vec.instance.initEmbedder(path) |
Model2Vec.loadModel(path) |
Model2VecUtils.similaritySearch(q, c) |
similaritySearchWithScores(q, c).map((r) => r.index) |
catch (e) { e.code } |
still works; add e.kind for exhaustive handling |
1.2.0 #
- Lowered minimum Dart SDK requirement to
3.10.0to support a wider range of environments.
1.1.0 #
New Features:
-
getRecommendedModels()no longer calls FFI — now returns a hardcoded list of 7 models -
Removed
get_model_listfrom FFI bindings (Rust, Dart,.h) -
generateEmbedding()now acceptsmaxLengthparameter — signature changed -
generateBatchEmbeddings()now acceptsmaxLengthandbatchSizeparameters — signature changed -
Streaming API —
generateEmbeddingStream()for processing large datasets with batching and optional worker isolate -
Async API —
generateEmbeddingAsync()andgenerateBatchEmbeddingsAsync()withmaxLength/batchSizesupport -
Advanced init —
initEmbedderAdvanced()withhfToken,cacheDirectory,normalize,subfolder -
In-memory init —
initEmbedderFromBytes()for loading models from raw bytes -
boot()— manual initialization with a customDynamicLibrary -
isNormalized— getter for L2-normalization check -
medianTokenLength— getter for median token length -
maxLength— token truncation parameter forgenerateEmbedding() -
batchSize— internal batching control forgenerateBatchEmbeddings() -
Model2VecUtils— vector math:cosineSimilarity,dotProduct,euclideanDistance,similaritySearch,similaritySearchWithThreshold,cosineDistance,normalize,meanPooling,quantizeToInt8,toBase64,fromBase64,pairwiseSimilarity
Improvements:
- Streaming API Performance:
generateEmbeddingStream()now utilizes a single long-lived worker isolate instead of spawning one per batch, dramatically reducing IPC and memory overhead for large datasets. - Inter-Isolate Communication: Switched from
Map<String, dynamic>to Dart 3 Records for significantly faster and strictly typed isolate communication. - FFI Optimization:
generateEmbedding()in Rust rewritten to avoid array pointer allocations and correctly respectmax_length. - Refactored
quantizeToInt8()to use Dart's native.clamp(). - Added clear documentation for zero-vector handling in
cosineSimilarityandnormalize. - Added documentation warning about IPC overhead in
generateEmbeddingStreamfor CLI/Server applications. - Better error messages when loading the native library fails, explaining possible missing Rust builds.
- Cleaned up FFI bindings: removed dead
get_model_listsymbol from.hand bindings. generate_embeddingin Rust now returns-5on empty results instead of silently corrupting data.generate_batch_embeddings_advancedvalidates result count matches input count.- Benchmark updated to run all 5 models.
- README fully rewritten with API reference and accurate model dimensions.
1.0.0 #
- Initial version.