Recursive AI
LibraryC#AvailablePreview

oneindex

An embedded search engine for .NET: full-text, vector and hybrid search in one in-process library, written in C# with no native code and no server.

dotnet add package RecursiveAI.OneIndex --prerelease

A quick look

Mark an ordinary class, upsert, commit, and search with what the user typed. A source generator writes the mapping at build time, so there is no reflection and it runs under NativeAOT.

Getting started
Program.csC#
using var index = SearchIndex.Open<Invoice>("data/search");
index.Upsert(invoices);
index.Commit();   // atomic and durable
 
// words, "phrases", -exclusions; typos forgiven
var hits = index.Search("overdue \"acme corp\" -draft")
    .Where(x => x.Tags.Contains("invoice"))
    .Take(10);
What it does

What it does

Full-text search

BM25F ranking, phrases, prefix, fuzzy and regex queries, typo tolerance and as-you-type, synonyms, “did you mean”, Snowball stemmers for 34 languages, and Asian segmentation in an add-on package.

Full-text search in C#

Vector search

HNSW and exact search, int8, int4 and sign-bit compressed copies with exact rerank, filters that steer the graph walk, learned sparse vectors and multi-vector MaxSim.

Vector search in .NET

Hybrid and rerank

Keyword and vector lists fused in one ranking, then a cross-encoder over the best hits. The embedder and reranker are bundled and run in your process; text is embedded at upsert and cached.

Hybrid search with reranking

Filters, facets and sorting

LINQ Where translated to index filters, filter strings, facets with value search, aggregations, distinct, geo, sorting with search-after, function scores and ranking rules.

Facets and distinct

EF Core

SaveChanges logs each change in the same transaction, and the index is updated from the database. One Where runs partly in the index and partly in the database. Verified on SQLite and PostgreSQL 16; SQL Server and MySQL have not been run. ExecuteUpdate, ExecuteDelete and raw SQL need ReindexAsync.

Search EF Core entities

Folders, with citations

PDF, Markdown, HTML, text and archives cut into passages; every hit cites its file, page and exact character range. OCR for scans in an add-on. A command-line tool does the same.

Search a folder with citations

In the browser

Memory and OPFS storage, and a static index any file server can host, read over plain HTTP. Heavier than Pagefind, in the README’s words: “a first query costs about 0.65 MB, or 0.74 MB with the manifest (Pagefind: 0.32 MB) and a later text query about 194 KB in 48 small requests (Pagefind: 46 KB in 4)”.

Static index

Query surfaces

User text that never throws, an opt-in strict syntax with positioned errors, versioned query JSON, and SearchTool.For<T>(), which offers a search to an LLM as a tool with a schema.

User text

From Lucene.NET

A call-by-call mapping from Lucene.NET 4.8, analysis equivalences and a worked example. OneIndex can’t open a Lucene index: reindex from your source of truth.

Migrate from Lucene.NET
How it measures

Relevance and speed

The project’s own measurements at the release, with their conditions in the relevance and benchmark pages.

  • Relevance, BM25. nDCG@10 on BEIR test sets, OneIndex at its defaults against Lucene.NET 4.8.0-beta00018’s flat layout on the same runs: SciFact 0.6918 against 0.6789, ArguAna 0.4347 against 0.4118, NQ 0.3452 against 0.3058, SCIDOCS 0.1544 against 0.1509. Summing per-field scores in Lucene.NET does better on SCIDOCS (0.1587).
  • Relevance, hybrid. BM25 with the bundled bge-small-en-v1.5 at the default fusion averages 0.5168 over SciFact, ArguAna, SCIDOCS and NQ, against 0.5058 for the vector arm alone and 0.4049 for BM25, and is above both arms on every set. The defaults were tuned on the first three; NQ was held out.
  • Text speed against Lucene.NET. 302,567 Wikipedia abstracts, matched analysis and commits, 17 rows from indexing and commit to phrase, fuzzy, facet, sort and function-score queries. Each ratio is OneIndex’s time over Lucene.NET’s, so below 1 is faster: geometric mean 0.62 on x64 (AMD Ryzen 9 7950X3D, Linux) and 0.41 on arm64 (Apple M2, macOS; 0.57 without the Stats and Sort rows, where Lucene.NET’s arm64 times are outliers). The slowest rows: Phrase 1.17 and FunctionScore 1.14, both on x64. The index is larger, 60.6 MB against 45.4 MB, because each document’s source is stored.
  • Vector speed against Hnsw.Net 0.1.4. At matched recall@10 (0.90, 0.95 and 0.99, or the highest both reach), 384 and 768 dimensions. Each ratio is OneIndex’s queries per second over Hnsw.Net’s, so above 1 is faster: geometric mean 1.19 on x64 and 1.15 on arm64; the lowest row is 1.04 (768 dimensions at 0.99, arm64).
  • Native engines are listed for information. In the project’s words, “Tantivy is 2-3x faster on plain term and phrase queries.”
Durability

What a commit promises

A commit that returns survives a crash or kill -9 of your process, a reader sees one whole commit, and every file is checksummed, so damage fails the open with an error naming the file instead of returning wrong results.

  • How it is tested. The project’s own testing: fault injection at every storage operation (about 890,000 schedules on x64 and arm64) and more than 7,000 process kills during writes, merges, schema changes and multi-process reading, with no failed open and no lost commit.
  • Power cuts. From the README: “On a power cut the index may roll back to an earlier commit, but it is never corrupt.” Power cuts were not exercised on real hardware.
  • Windows. The full test suite runs on Windows on every change; the kill and fault-injection campaigns run on Linux and macOS only.
  • Conditions. A local file system that honors fsync and atomic rename; network and FUSE file systems are not supported. One writer per index directory.
Promises, conditions and every test

Status and packages

Go further

OneIndex is unaffiliated with other products named OneIndex, OneSearch or similar.