flutter_edge_ai_sqlite 2.0.0
flutter_edge_ai_sqlite: ^2.0.0 copied to clipboard
SQLite vector search (sqlite-vec) provider for flutter_edge_ai_rag across native platforms and web.
flutter_edge_ai_sqlite #
Renamed from
flutter_gemma_rag_sqlite. Version 2.0.0 also moves RAG orchestration and contracts intoflutter_edge_ai_rag: replace the old dependency/imports, addflutter_edge_ai_rag, and registerSqliteVectorStoreProvider()inFlutterEdgeAiRag. Existing profile-less stores require verified, explicit adoption or re-indexing. See the migration guide.
First-class SQLite vector-store provider for
flutter_edge_ai_rag.
KNN runs inside SQLite via sqlite-vec
(vec0 virtual table) — no Dart brute-force, no in-memory index.
Register SqliteVectorStoreProvider once; it selects the implementation:
- Native (Android/iOS/macOS/Linux/Windows):
SqliteVectorStore—package:sqlite3(dart:ffi) + the per-platformvec0loadable extension. - Web:
WebSqliteVectorStore—package:sqlite3/wasm.dartdriving a customsqlite3.wasmwithsqlite-vec/vec0statically linked.
Both arms speak the same vec0 SQL dialect, so KNN and Filter behave
identically across all six platforms. A vec0 table declares an id TEXT PRIMARY KEY, so KNN returns the document id directly — no JOIN, no rowid bridge.
Teach your AI assistant this package #
dart run skills@ get --all
Installs the agent skills from the Flutter Edge AI package graph. The
flutter-edge-ai-rag skill covers embedding models, both vector stores, and
metadata filters.
Usage #
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_edge_ai_rag/flutter_edge_ai_rag.dart';
import 'package:flutter_edge_ai_sqlite/flutter_edge_ai_sqlite.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
final rag = FlutterEdgeAiRag(
providers: [const SqliteVectorStoreProvider()],
);
// An absolute file path on native, a plain name on Web (path_provider has no
// Web implementation, so it is only called on native).
final databasePath = kIsWeb
? 'knowledge.db'
: p.join((await getApplicationDocumentsDirectory()).path, 'knowledge.db');
final index = await rag.open(
spec: VectorStoreSpec(providerId: 'sqlite', location: databasePath),
embeddingProfile: EmbeddingProfile(
id: 'my-embedder-v1',
dimension: 768,
),
);
await index.addVector(
id: 'doc-1',
content: 'Flutter Edge AI runs on-device.',
embedding: documentVector,
);
final hits = await index.searchVector(embedding: queryVector);
await index.dispose();
For text RAG with the active core embedder, replace embeddingProfile with a
stable activeEmbedderProfileId and use addText / searchText. For a custom
embedder, pass embedder: to open(); neither path makes the RAG registry a
singleton. Dispose the index before FlutterEdgeAi.dispose() or before
disposing the borrowed custom embedder.
The provider removes the application-level kIsWeb branch between the native
and web stores; only the location differs. Use a stable
profile ID that versions the weights, tokenizer, pooling, normalization, and
document/query prefix contract. The profile is stored beside the vectors and
prevents a location from being reopened with an incompatible embedder.
Databases created before 2.0.0 contain vectors but no stored profile. Open a known
legacy database with allowLegacyProfileAdoption: true only after verifying
the exact embedder that created it; the RAG layer checks its dimension before
persisting the first profile. Leave the flag false for unverified data.
RagIndex.searchText / searchVector return cosine similarity in
RetrievalResult.similarity (1 = identical, higher = better), sorted
descending, filtered by threshold — the same contract as the qdrant store
(vec0 returns distance; the store converts 1 - distance at the boundary).
index.flush() is a no-op on native: the connection
autocommits, so a statement that returned is on disk. On web it drains the
IndexedDB storage and waits for it. sqlite3 3.4.0 through 3.5.2 returned
early over a write batch already in flight (upstream
sqlite3.dart#408), which
is why this package requires 3.6.0 and, with it, Flutter 3.47; close() drains
on every version. When neither OPFS nor IndexedDB is available
the store runs in memory, and flush() throws VectorStoreException.
On web, one location may be open by only one WebSqliteVectorStore at a
time. The store holds an exclusive Web Lock
for its complete lifetime, so another tab, worker, or store instance fails
immediately with an actionable VectorStoreException instead of opening a
second IndexedDB/OPFS snapshot. Current Chrome, Edge, Firefox, and Safari
releases expose Web Locks. The API is feature-detected: a runtime or insecure
context without navigator.locks fails closed with VectorStoreException
before opening OPFS or IndexedDB, because it cannot safely coordinate another
tab or worker. Always call index.dispose() or store.close() before reopening
that location. The in-memory VFS remains a last resort only when Web Locks are
available but persistent browser storage is not; it cannot bind a durable
embedding profile, so FlutterEdgeAiRag.open() rejects it.
Declared-column filters #
vec0 filters KNN only on declared, typed metadata columns (not arbitrary
JSON). Declare the filterable fields in VectorStoreSpec.filterSchema; the store
promotes those fields out of each document's metadata JSON into real columns and
translates Filter (must/should/mustNot) into a vec0 WHERE:
final index = await rag.open(
spec: VectorStoreSpec(
providerId: 'sqlite',
location: databasePath,
filterSchema: FilterSchema(fields: [
FilterField(name: 'lang', type: FilterFieldType.string),
FilterField(name: 'year', type: FilterFieldType.number),
FilterField(name: 'archived', type: FilterFieldType.bool),
]),
),
embeddingProfile: EmbeddingProfile(id: 'my-embedder-v1', dimension: 768),
);
// later, at query time:
final hits = await index.searchVector(
embedding: queryVec,
topK: 10,
filter: Filter(
must: [FieldRange(key: 'year', gte: 2000)],
mustNot: [FieldEquals(key: 'archived', value: true)],
),
);
FilterField.name must match ^[A-Za-z][A-Za-z0-9_]*$, and must not be a name
vec0 already declares: id, embedding, content, metadata, and the hidden
distance and k. Otherwise rag.open() throws an ArgumentError (from the
store's configure()) — at open, not at the first write, which is when the
table is really built.
The name becomes a real vec0 column, and sqlite-vec's DDL grammar accepts no
quoted identifier form ("doc-type", [doc-type] and `doc-type` all
fail), so a name outside that set is unrepresentable rather than merely
unescaped. qdrant accepts most of these names, so a schema written for it may be
refused here — this set is the portable one.
Filtering on an undeclared key is a safe no-op (never throws). With no
filterSchema, the store ignores filters entirely — identical to filter: null.
Supported operators: =, !=, >, >=, <, <=, BETWEEN, IN
(FieldEquals, FieldRange, FieldMatchAny); max 16 declared columns.
Upgrading from 1.0.x #
1.1.0 does not read an index written by 1.0.x. The switch to in-SQLite
vec0 KNN moved the data from a plain documents table into a vec_documents
virtual table. Nothing errors on upgrade: initialize() succeeds, getStats()
reports 0 documents, searchSimilar() returns no hits, and the old rows sit
untouched in documents. This was not called out when 1.1.0 shipped.
Your data is intact and needs no re-embedding — 1.0.x stored the vector as a
Float32 BLOB alongside the id, content and metadata. The one-time copy (verify
the embedder that produced the old vectors, bind its profile, re-add the rows,
then drop documents) is in the
migration guide. There is no built-in
migration call — this is a one-time fix for an upgrade that has already
happened.
Setup #
Native needs no setup — the vec0 loadable extension is fetched per platform
by this package's Native Assets hook (hook/build.dart), SHA256-verified, and
loaded automatically before any database is opened.
New in 1.3.0: that fetch is real. Until 1.2.0 the loadables were committed
into the package, so every install carried all seven platforms' binaries to use
one of them. They now come from this repository's native-sqlite-vec-v* GitHub
Release, which means the first build of each platform needs github.com
reachable; the library is cached under ~/.cache/flutter_gemma/native/
(~/Library/Caches/… on macOS, %LOCALAPPDATA%\… on Windows) and later builds
do not go out again. flutter_edge_ai_litertlm has always worked this way. If you
build in an air-gapped environment, pre-populate that cache directory.
Web ships the custom sqlite3.wasm (with sqlite-vec linked in) as the
package web asset web/rag/sqlite3.wasm. Copy it into your app's web root so it
sits next to index.html at rag/sqlite3.wasm — that's the URL
WasmSqlite3.loadFromUrl fetches. After flutter pub get, the package
directory is the rootUri printed under its name in your app's
.dart_tool/package_config.json:
grep -A1 '"name": "flutter_edge_ai_sqlite"' .dart_tool/package_config.json
# <pkg> = that rootUri without file:// (a relative one is relative to .dart_tool/)
mkdir -p web/rag
cp <pkg>/web/rag/sqlite3.wasm web/rag/sqlite3.wasm
OPFS persistence and SharedArrayBuffer require your web server to send the
cross-origin isolation headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
There is no CDN <script>, no wa-sqlite worker, and no index.html wiring
anymore.