Skip to main content

Vector stores

A vector store holds documents alongside their embedding vectors and supports similarity search. LogicGrid ships three implementations and an interface so you can plug in your own.

IVectorStore contract​

public interface IVectorStore
{
Task UpsertAsync(VectorDocument document, CancellationToken ct = default);
Task UpsertBatchAsync(IList<VectorDocument> documents, CancellationToken ct = default);

Task<IList<VectorSearchResult>> SearchAsync(
float[] queryVector,
int topK = 5,
float minimumScore = 0.0f,
CancellationToken ct = default);

Task DeleteAsync(string id, CancellationToken ct = default);
Task ClearAsync(CancellationToken ct = default);
Task<int> CountAsync(CancellationToken ct = default);
}

VectorDocument contains the following fields { Id, Text, Vector, Metadata }.

  • Id — your unique key for the document. Used for upsert and delete.
  • Text — the original text the vector was generated from. Returned with every search hit so you can feed it into a prompt.
  • Vector — the embedding (a float[]). Length must match the store's configured dimensions.
  • Metadata — an optional IDictionary<string, string> of arbitrary key/value tags you attach to the document at upsert time. It's not part of the similarity calculation; it's the extra context you'll want at retrieval time. Typical uses:
    • provenance — source, url, file, page, chunk_index
    • scope / filtering — tenant, user_id, language, version
    • freshness — created_at, ingested_at
    • classification — doc_type, category, tags Empty {} if you pass null at construction.

VectorSearchResult returns { Document, Score } where Document is the same VectorDocument you originally upserted (Id, Text, Vector, Metadata). Score is the cosine similarity in the range [-1.0, 1.0] (higher = more similar).

InMemoryVectorStore​

Fast, ephemeral, perfect for tests and small corpuses. Loses everything on process exit.

using LogicGrid.Memory.VectorStores;

var store = new InMemoryVectorStore();

await store.UpsertAsync(new VectorDocument(
id: "doc-1",
text: "The sky is blue because of Rayleigh scattering.",
vector: await embedder.EmbedAsync("…")));

var hits = await store.SearchAsync(queryVector, topK: 5);
foreach (var hit in hits)
Console.WriteLine($"{hit.Score:F3} {hit.Document.Text}");

A solid default whenever the corpus fits in process memory and you don't need persistence — unit tests, prototypes, single-run scripts, or short-lived semantic search over an admin's recent messages.

QdrantVectorStore​

Production-grade, persistent, distributed. Connects to a Qdrant instance over its HTTP API. The collection is created on first upsert if it doesn't already exist.

public QdrantVectorStore(
string collectionName,
int dimensions,
string baseUrl = "http://localhost:6333",
string? apiKey = null)
var store = new QdrantVectorStore(
collectionName: "my-docs",
dimensions: 768, // must match embedder
baseUrl: "http://localhost:6333");

await store.UpsertAsync(new VectorDocument(
id: "doc-1",
text: "…",
vector: await embedder.EmbedAsync("…")));

Run Qdrant in Docker for local dev:

docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

HybridVectorStore​

Wraps any IVectorStore and adds an in-memory BM25 keyword index. Hybrid searches combine dense (vector) and sparse (BM25) results through Reciprocal Rank Fusion. Drop-in replacement for IVectorStore plus a HybridSearchAsync method for the fused mode.

var dense = new InMemoryVectorStore();
var hybrid = new HybridVectorStore(dense, new HybridSearchOptions
{
RrfK = 60,
Bm25K1 = 1.5,
Bm25B = 0.75,
});

await hybrid.UpsertAsync(/* … */);

IList<HybridSearchResult> hits = await hybrid.HybridSearchAsync(
query: "rerank fusion",
queryVector: await embedder.EmbedAsync("rerank fusion"),
topK: 5);

See Hybrid search for the full documentation.

Choosing the right store​

FactorInMemoryQdrantHybrid (wraps either)
PersistenceNoYesInherits from inner store
Scale<100k docsMillionsSame as inner
SetupNoneDocker / managed serviceSame as inner
Latency<1 ms5–30 msInner + tiny BM25 overhead
Filtering by metadataYesYes (richer)Inner
Search modesDenseDenseDense, BM25, hybrid (RRF)
Best forsemantic search over an admin's recent messages, Tests, prototypesProduction, large corporaWhen exact-keyword matches matter

Building your own​

Implement IVectorStore. The contract is small — six methods. Any backend that supports K-NN search over dense vectors works:

  • Pinecone — wrap their REST API.
  • Weaviate — they expose a similar shape.
  • Postgres + pgvector — straightforward with Npgsql and the <-> operator.
  • Elasticsearch with dense_vector — for teams already on Elastic.

Where next​