Stores
The store is the vector database: it holds one row per enrolled shot and answers “which stored vectors are nearest this one”.
public interface IFaceStore{ Task Add(Person person, CancellationToken ct = default); Task<IReadOnlyList<FaceMatch>> FindNearest(ReadOnlyMemory<float> embedding, int count, CancellationToken ct = default); Task<IReadOnlyList<Person>> GetAll(CancellationToken ct = default); Task<int> RemoveByPersonIdentifier(string personIdentifier, CancellationToken ct = default);}SQLite + sqlite-vec (the default)
Section titled “SQLite + sqlite-vec (the default)”dotnet add package Shiny.FaceIntelligence.DocumentDb.Sqliteface.UseSqliteStore(o =>{ o.ConnectionString = $"Data Source={Path.Combine(FileSystem.AppDataDirectory, "faces.db")}";});This is a convenience wrapper over UseDocumentDbStore that wires a vector-enabled SQLite provider through
Shiny.DocumentDb.Sqlite, backed by sqlite-vec (vec0) for approximate
nearest-neighbour search.
Any other DocumentDb provider
Section titled “Any other DocumentDb provider”Server-side, you probably want Postgres/pgvector, SQL Server, Cosmos or Mongo rather than SQLite. Use the provider-agnostic package and hand it a provider factory:
dotnet add package Shiny.FaceIntelligence.DocumentDbface.UseDocumentDbStore(sp => new PostgreSqlDatabaseProvider(connectionString));The vector dimension is read from the registered IFaceEmbedder, so it always matches the model — you never
declare it, and it cannot drift. See DocumentDb — Vector / ANN Search for what each
backend supports.
Your own store
Section titled “Your own store”Implement IFaceStore and register it directly:
face.UseStore(sp => new MyFaceStore(sp.GetRequiredService<IMyBackend>()));Two contract details worth knowing:
FindNearestreturns cosine distance,0= identical. If your backend reports similarity, convert — the manager compares directly againstMaxDistanceand a flipped convention will match everything or nothing.Person.Embeddingis[JsonIgnore]d. In the DocumentDb stores it lives only in the vector sidecar table and comes back empty fromGetAll(). Your implementation may do otherwise, but callers are told not to rely on reading it back.
Startup cost
Section titled “Startup cost”new DocumentStore(...) is built eagerly when IFaceStore resolves, and that is cheap on purpose: the
constructor only creates the connection object and the mapping metadata, all in memory. The store opens the
connection and loads the native vector extension itself, lazily, on the first operation. So the database
open and the vec0 load land inside the first enroll or recognize call — where your page is already catching
errors — not at app startup.