Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI

on:
push:
branches: [main]
pull_request:

# A newer push to the same branch/PR cancels an in-flight run.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
# Static checks only — no Rust build, no model download. Fast feedback.
analyze:
name: Format & analyze
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
# `stable` resolves to the latest Dart stable; native assets (this
# package's FFI build hook) work flag-free on 3.12+.
with:
sdk: stable
- run: dart pub get
- name: Check formatting
run: dart format --output=none --set-exit-if-changed .
- name: Analyze
run: dart analyze --fatal-infos

# Everything except the model-downloading tests. Needs the Rust build (the
# native asset), but never hits the network for a model.
test:
name: Unit tests
runs-on: ubuntu-latest
env:
# Override native/rust-toolchain.toml, whose `targets` list is for
# cross-compiled release builds (Android/iOS/Windows/…). CI only needs the
# host target, and this env var makes cargo ignore the toml entirely.
RUSTUP_TOOLCHAIN: "1.95.0"
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
with:
sdk: stable
- name: Install Rust ${{ env.RUSTUP_TOOLCHAIN }} (host target + clippy)
# --profile minimal omits clippy, so request the component explicitly.
run: rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal --component clippy
- name: Cache cargo registry + build
uses: Swatinem/rust-cache@v2
with:
workspaces: native
- name: Clippy (native)
run: cargo clippy --manifest-path native/Cargo.toml --all-targets -- -D warnings
- run: dart pub get
- name: Run unit tests (no model download)
run: dart test -x integration

# The model-dependent tests. The Hugging Face cache is restored between runs,
# so the ~40 MB download happens once and is reused (keyed on the model ids in
# test/support.dart).
integration:
name: Integration tests
runs-on: ubuntu-latest
env:
RUSTUP_TOOLCHAIN: "1.95.0"
steps:
- uses: actions/checkout@v4
- uses: dart-lang/setup-dart@v1
with:
sdk: stable
- name: Install Rust ${{ env.RUSTUP_TOOLCHAIN }} (host target only)
run: rustup toolchain install "$RUSTUP_TOOLCHAIN" --profile minimal
- name: Cache cargo registry + build
uses: Swatinem/rust-cache@v2
with:
workspaces: native
- name: Cache Hugging Face models
uses: actions/cache@v4
with:
path: ~/.cache/huggingface
key: hf-models-${{ hashFiles('test/support.dart') }}
restore-keys: hf-models-
- run: dart pub get
- name: Run integration tests (model download cached across runs)
run: dart test -t integration
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ doc/api/
.flutter-plugins-dependencies

native/target
docs
CONTEXT.md
76 changes: 76 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,80 @@
<!-- markdownlint-disable-file MD025 -->
# 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.** `Model2Vec` is now a stateless namespace of static methods.
`Model2Vec.instance`, the `Model2Vec(DynamicLibrary)` constructor and
`Model2Vec.boot(...)` were removed — the native library is resolved
automatically through Native Assets (`@Native` code assets). Replace
`Model2Vec.instance.foo(...)` with `Model2Vec.foo(...)`.
- **Recommended models.** `getRecommendedModels()` (returning
`List<Map<String, dynamic>>`) is replaced by the typed constant
`Model2Vec.recommendedModels` (`List<RecommendedModel>`).
- **Typed errors.** `Model2VecException` now carries a `Model2VecErrorKind kind`;
its constructor is `(kind, message, [code])` and the `fromCode` factory is
replaced by `fromNative(code, message)`. Native failures surface the message
produced by the Rust layer, each with an exhaustively-switchable `kind`.
- **Lifecycle naming.** The `initEmbedder*` methods are renamed to `loadModel*`,
pairing `loadModel` ⇄ `unloadModel` over the model. `initEmbedder`,
`initEmbedderAdvanced`, `initEmbedderFromBytes` and their async forms are
removed. `Model2VecUtils.similaritySearch` /`similaritySearchWithThreshold`
are removed in favour of `similaritySearchWithScores` (read `.index`).
- **Batch signature.** `generateBatchEmbeddings` no longer takes `batchSize`
(its signature is now `(List<String> texts, {int maxLength})`). The native
layer batches internally; `batchSize` remains only on
`generateEmbeddingStream`, 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 in `catch_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` (was `unsigned long`,
32-bit on 64-bit Windows and mismatched against Rust's `usize`).
- **Streaming rework.** `generateEmbeddingStream` is rebuilt on small, tested
modules — a batching transformer, a transport-agnostic worker protocol, and a
worker isolate. Worker errors cross the isolate boundary as typed
`Model2VecException`s (kind + code preserved) rather than stringified errors.

**New capabilities:**

- **Local vector index.** `EmbeddingIndex` — store embeddings by id, then
`search` the nearest by cosine similarity. Optional int8-quantized storage
(~4x less memory) and binary `toBytes`/`fromBytes` persistence. Turns the
package into a local retrieval engine for RAG.
- **RAG pipeline helpers.** `chunkText` (overlapping character chunker),
`Model2VecUtils.similaritySearchWithScores` (index + score), and
`Model2VecUtils.maximalMarginalRelevance` (MMR reranking for diverse results).
- **Lifecycle & DX.** `Model2Vec.isInitialized` (non-throwing check),
`Model2Vec.unloadModel()` (free the native model), `Model2Vec.modelInfo`
(all metadata in one `ModelInfo`), and `Model2VecUtils.dequantizeInt8`
(the inverse of `quantizeToInt8`).
- **Load progress.** `Model2Vec.loadModelWithProgress()` loads on a background
isolate and returns a `Stream<LoadProgress>` reporting the HF weights download
(`bytesDownloaded` / `totalBytes` / `fraction`) plus a coarse `LoadPhase`
(resolving → downloading → parsing → done). A cached model or local path
streams straight to `done`.
- **Parallel worker pool.** `EmbeddingPool` fans 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.0` to support a wider range of environments.
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 Andrii
Copyright (c) 2026 pro100Andrey

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading
Loading