Mark a float[] property [Vector(From = nameof(Text))] and OneIndex embeds the text when you Upsert, with a bundled English model that runs in your process. Then embed the question with the same model and call SearchVector: you get the documents nearest in meaning, even when they share no words with it.
dotnet new console -o VectorSearch && cd VectorSearch dotnet add package RecursiveAI.OneIndex --version 0.4.0-preview.2 dotnet add package RecursiveAI.OneIndex.Models --version 0.4.0-preview.2
RecursiveAI.OneIndex.Models carries the model weights inside the package (a 134 MB download from NuGet, once). Nothing is fetched at run time.
Put this in Program.cs and run dotnet run:
using OneIndex; using OneIndex.Models; using OneIndex.Vectors; var embedder = BgeSmallEmbedder.UseAsDefault(); // bge-small-en-v1.5, 384 dimensions using var index = SearchIndex.Open<Note>("notes-index"); index.Upsert( [ new() { Id = "a", Topic = "hardware", Text = "Overheating notebook? Clean the fan and vents." }, new() { Id = "b", Topic = "hardware", Text = "A swollen battery must be replaced at once." }, new() { Id = "c", Topic = "cooking", Text = "Let bread dough rest overnight in the fridge." }, new() { Id = "d", Topic = "software", Text = "Disable startup programs so Windows boots faster." }, new() { Id = "e", Topic = "software", Text = "Update graphics drivers to fix screen flicker." }, new() { Id = "f", Topic = "travel", Text = "Pack a universal plug adapter for trips abroad." }, ]); // each Text is embedded here index.Commit(); float[] question = embedder.Embed("my laptop gets too hot", EmbeddingRole.Query); foreach (var hit in index.SearchVector(n => n.Embedding, question, neighbors: 2)) Console.WriteLine($"{hit.Id} {hit.Score:F3} {hit.Document.Text}"); Console.WriteLine(); question = embedder.Embed("PC takes ages to turn on", EmbeddingRole.Query); var search = index.SearchVector(n => n.Embedding, question, neighbors: 2) .Where(n => n.Topic == "software"); // filter before neighbors foreach (var hit in search) Console.WriteLine($"{hit.Id} {hit.Score:F3} {hit.Document.Text}"); public class Note { [SearchId] public string Id { get; set; } = ""; [FullText] public string Text { get; set; } = ""; [Keyword, Filterable] public string Topic { get; set; } = ""; [Vector(From = nameof(Text))] public float[]? Embedding { get; set; } }
a 0.873 Overheating notebook? Clean the fan and vents. e 0.788 Update graphics drivers to fix screen flicker. d 0.835 Disable startup programs so Windows boots faster. e 0.809 Update graphics drivers to fix screen flicker.
BgeSmallEmbedder.UseAsDefault()loads the bundled model and makes it the embedder new indexes open with. Nothing registers itself: without this line (or an embedder passed toSearchIndex.Open<T>(path, embedder)), no text is embedded.[Vector(From = nameof(Text))]embedsTextatUpsertwith the document role. The length (384) comes from the model.- The question is embedded with
EmbeddingRole.Query, which adds the instruction the model expects for queries. SearchVector(member, vector, neighbors)returns the nearest documents by cosine similarity, highest score first.Where(...)picks the neighbors among documents that pass, so a filtered search still fills its page.- Each document stores its embedding beside a hash of the model and the text, so re-upserting unchanged text, reopening and merging never embed again.
Limits
- A preview (0.4.0-preview.2): APIs and file formats may still change between previews.
- The bundled embedder is English.
RecursiveAI.OneIndex.Models.Multilingualhas multilingual-e5-small (around 100 languages; its int8 weights are 120 MB). - Embedding runs on the CPU in the thread that calls
Upsert, so a large first load takes time. The model reads at most its input limit and cuts longer text; split long documents into passages. - Small collections are searched exactly; larger ones use an HNSW graph, which is approximate (recall below 1.0 at a given setting).
- Model licenses: bge-small-en-v1.5 is MIT. The weights are the model authors’ and are distributed under that license; the training data’s terms are not conferred on them.
Getting started · Vectors · Automatic embedding · Models · All recipes