Build RAG apps in C#.
Not Python.

The first production-ready RAG framework purpose-built for .NET 9. Build enterprise knowledge bases — all in idiomatic C# with full async support.

Get started
Apache-2.0 licensed .NET 9 native Zero-dep core
Program.cs
// Configure, ingest, and query — in C#
services.AddNetIndex(config => config
    .UseOllamaEmbedding()
    .UseSqliteVectorStore("Data Source=vectors.db")
    .Build());

await pipeline.IngestAsync(new PdfDocument("./docs/manual.pdf"));

await foreach (var result in pipeline.QueryAsync("How do I configure chunking?"))
    Console.WriteLine(result.Chunk.Text);
Built on a foundation .NET teams already trust
2.4k
GitHub stars
42
Contributors
18
Provider integrations
.NET 9
Native target
NuGet
v1.0 on NuGet.org
NetIndex.Core · NetIndex.Storage.Sqlite · NetIndex.Storage.Pgvector · NetIndex.Providers.Ollama · NetIndex.Providers.OpenAI · NetIndex.Providers.AzureOpenAI · NetIndex.Ingestion.Pdf · NetIndex.Ingestion.Docx · NetIndex.Ingestion.Markdown · NetIndex.AspNetCore · NetIndex.Core.Abstractions · NetIndex.Storage.InMemory · NetIndex.Benchmarks NetIndex.Core · NetIndex.Storage.Sqlite · NetIndex.Storage.Pgvector · NetIndex.Providers.Ollama · NetIndex.Providers.OpenAI · NetIndex.Providers.AzureOpenAI · NetIndex.Ingestion.Pdf · NetIndex.Ingestion.Docx · NetIndex.Ingestion.Markdown · NetIndex.AspNetCore · NetIndex.Core.Abstractions · NetIndex.Storage.InMemory · NetIndex.Benchmarks
// Quickstart

Working locally in under 60 seconds.

One command scaffolds a complete RAG app with Ollama + SQLite. No API keys. No Docker. Runs on your machine immediately.

1
Install the template
dotnet new install NetIndex.Templates
2
Scaffold your app
dotnet new netindex --name MyRagApp

Creates Program.cs, appsettings.json, and a sample ingest/query/generate API — pre-wired for local Ollama + SQLite.

3
Run it
dotnet run

Full RAG pipeline live at localhost:5000. POST a document, GET a query, stream a generated answer.

Terminal
$ dotnet new install NetIndex.Templates
Template netindex installed successfully
$ dotnet new netindex --name MyRagApp
Created MyRagApp/
Configured: Ollama embedding + SQLite vector store
Endpoints: POST /ingest · GET /query · GET /generate
$ cd MyRagApp && dotnet run
NetIndex pipeline ready
Listening on http://localhost:5000
$
// Core

A framework, not a wrapper.

NetIndex is a full data framework — ingestion, chunking, embedding, retrieval, generation — designed from the ground up for the .NET runtime, not ported from Python.

Idiomatic C#

Built from the ground up for .NET — not a Python port. IAsyncEnumerable<T> streaming, CancellationToken on every async method, Microsoft.Extensions.Options with IValidateOptions<T>, and System.Diagnostics.Activity for tracing.

Provider-agnostic

Ollama, OpenAI, Azure OpenAI — swap embedding or LLM providers with one line. Same IEmbeddingGenerator and IChatClient contracts. Your pipeline code never changes.

Storage that scales

InMemory for tests, SQLite (sqlite-vec) for single-server, pgvector with IVFFlat/HNSW for production. Same IVectorStore contract. All backends pass the shared VectorStoreContractSuite.

Three chunking strategies

FixedSize, Recursive (two-stage), and Semantic — all via IChunkingStrategy. Semantic chunking embeds candidate sentence groups and splits where cosine similarity drops below 0.7.

Security by default

DenyAllTenantResolver rejects every pipeline operation until you wire a real ITenantResolver. Authorization runs before every Ingest, Query, and Generate call — no exceptions.

Build-time validation

Build() spins up a temporary DI scope with ValidateOnBuild and validates embedding dimension consistency. Wire a 1536-dim OpenAI embedder to a 768-dim store → NetIndexConfigurationException at startup, not silent corruption at runtime.

// Comparison

The only .NET-native choice.

LlamaIndex and LangChain are excellent — for Python. NetIndex gives .NET teams the same primitives without leaving the ecosystem or running a sidecar.

Capability Build it yourself LlamaIndex / Python NetIndex .NET 9
.NET 9 / C# native Partial
Microsoft DI + IValidateOptions Manual
IAsyncEnumerable streaming Manual
Build-time dimension validation
Deny-all security by default
OpenTelemetry built-in Manual Plugin
Swap provider in one line Partial
Azure OpenAI + DefaultAzureCredential Manual Plugin
Shared backend contract test suite
Apache-2.0 license
// Architecture

A pipeline of typed contracts.

Every stage is an interface. Substitute a chunker, an embedder, or a vector store without touching the rest of your code.

01 / INGEST
Document loaders
PDF, DOCX, Markdown, Tesseract OCR. Configurable size limits, metadata extraction, batch directory loading.
IDocumentLoader<TFormat>
02 / CHUNK + EMBED
Split + vectorize
Fixed-size, recursive, or semantic chunking. Batched embedding generation.
IChunkingStrategy
03 / QUERY
Semantic search
Top-K vector similarity search. Optional cross-encoder reranking via IDocumentReranker. Tenant-authorized.
IVectorStore
04 / GENERATE
LLM answer
Streaming grounded answers with source citations via IChatClient.
IChatClient
Storage backends
InMemory SQLite (sqlite-vec) pgvector
// In-depth

Walk the pipeline, line by line.

Each stage is a typed contract. Click through to see the actual code you'd write.

Step 1 · Ingest documents IDocumentLoader<TFormat> → Document
// Load PDF, DOCX, or Markdown — same interface
var loader = new PdfDocumentLoader(new PdfDocumentLoaderOptions
{
    MaxInputSizeBytes = 50 * 1024 * 1024
});

var doc = await loader.LoadAsync("./report.pdf", ct);
Console.WriteLine($"Loaded {doc.Metadata["page_count"]} pages");

// Or batch-load a directory
await foreach (var d in loader.LoadDirectoryAsync("./docs", recursive: true))
    await pipeline.IngestAsync(d, ct);
// Provider matrix

Same code. Any model, any store.

The contract is the surface. Swap implementations via the fluent builder — your pipeline code never changes.

Embedding provider
Startup.cs
// OpenAI — needs an API key from environment
services.AddNetIndex(builder => builder
    .UseOpenAIEmbedding("sk-...", "text-embedding-3-small")
    .UseSqliteVectorStore("Data Source=vectors.db")
    .Build());
Vector store
Startup.cs
// InMemory — perfect for tests & quickstarts
services.AddNetIndex(builder => builder
    .UseOllamaEmbedding("http://localhost:11434", "nomic-embed-text")
    .UseInMemoryVectorStore()
    .Build());
End-to-end · ingest → query → generate
Program.cs
using NetIndex;
using NetIndex.Ingestion.Pdf;

// 1. Configure the pipeline
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddNetIndex(config => config
    .UseOpenAIEmbedding("sk-...", "text-embedding-3-small")
    .UsePgvector(connectionString)
    .UseDocumentLoader<PdfFormat>(o => o.MaxInputSizeBytes = 100 * 1024 * 1024)
    .UseFixedSizeChunking(o => { o.ChunkSize = 512; o.ChunkOverlap = 50; })
    .Build());

var app = builder.Build();
var pipeline = app.Services.GetRequiredService<INetIndexPipeline>();

// 2. Ingest a document
await pipeline.IngestAsync(new PdfDocument("./manual.pdf"));

// 3. Query with streaming
await foreach (var result in pipeline.QueryAsync("How do I configure chunking?"))
    Console.WriteLine($"[{result.Score:F2}] {result.Chunk.Text}");

// 4. Generate a grounded answer
await foreach (var token in pipeline.GenerateAsync("Summarize the key features"))
    Console.Write(token.Text);
// Quality

Designed to catch your mistakes.

Typed exceptions that tell you exactly what went wrong. A shared contract suite every storage backend must pass. Fail-fast validation before a single request is served.

Exception hierarchy
NetIndexException tree
NetIndexException // base — never swallowed
├── NetIndexConfigurationException// Build-time: dimension mismatch, invalid options
├── NetIndexAuthorizationException// Null tenant, resolution failure
├── NetIndexProviderException// .IsTransient — retry vs fatal distinction
├── NetIndexStorageException// Upsert / query failures
└── NetIndexOcrNotInstalledException
    // Clear Tesseract install guidance
NetIndexConfigurationException at startup
Embedding dimension mismatch: store expects 768, provider returns 1536. Full re-index required when switching providers.
PropertyName · Dimensions
Expected · 768
Actual · 1536
VectorStoreContractSuite — 10/10 required
Empty store — query returns zero results
Upsert and query — score > 0.9 on exact match
Multiple items — scores are strictly descending
Exact match — float-level embedding verification
No match — orthogonal vector returns empty
Idempotent upsert — same chunk ID overwrites silently
Cancellation — throws OperationCanceledException
Dimension mismatch — wrong-dim vector throws
Dimension consistency — store rejects mismatched dims
Reset — previously stored results are gone
// Ecosystem

Pluggable everything, out of the box.

Every stage of the RAG pipeline is an interface. Swap implementations without touching your application code.

Embedding & LLM Providers
3 · stable
Ollamastable
OpenAIstable
Azure OpenAIstable
Vector Stores
3 · stable
InMemorystable
SQLite (sqlite-vec)stable
pgvectorstable
Document Loaders
4 · stable
PDF (iTextSharp)stable
DOCX (Open XML)stable
Markdownstable
Tesseract OCRpreview
Chunking Strategies
3 · stable
FixedSizeChunkingStrategystable
RecursiveChunkingStrategystable
SemanticChunkingStrategystable
// Security

Deny-all by default. Authorize everything.

Every pipeline operation — Ingest, Query, Generate — begins with a mandatory authorization check. The default resolver rejects all requests until you explicitly configure one.

01

ITenantResolver

Every pipeline call runs AuthorizeAsync() first. Return a tenant ID to proceed; return null or throw to deny with a typed NetIndexAuthorizationException.

02

DenyAllTenantResolver

The default resolver. Refuses every request until you wire a real implementation. No accidental data exposure in dev, no forgotten security configuration in production.

03

Bring your own auth

Implement ITenantResolver to integrate with ASP.NET Core Identity, Azure AD / Entra ID, JWT claims, API keys, or any header-based scheme.

Default — blocks everything
DenyAllTenantResolver.cs
public sealed class DenyAllTenantResolver : ITenantResolver
{
    public Task<string?> ResolveTenantIdAsync(CancellationToken ct)
        => Task.FromResult<string?>(null); // Always denies
}
Custom — integrates with your auth
Program.cs
// Replace with your own ITenantResolver
services.AddNetIndex(builder => builder
    .UseOpenAIEmbedding("sk-...", "text-embedding-3-small")
    .UsePgvector(connectionString)
    .Build());

// Reads tenantId from JWT claims
services.AddSingleton<ITenantResolver, JwtTenantResolver>();
Authorization flow for every operation
Request arrives
ITenantResolver.ResolveTenantIdAsync()
nullNetIndexAuthorizationException
or
tenantId → pipeline proceeds
// Observability

Full trace visibility, zero configuration.

Every pipeline stage emits structured OpenTelemetry spans automatically. One line to wire up any OTLP exporter — Jaeger, Zipkin, Azure Monitor, or Console.

Sample trace · netindex.generate
netindex.generate
340ms
netindex.query
136ms
netindex.embed
68ms
netindex.store.query
61ms
netindex.generate (LLM)
197ms
All span names
netindex.ingest netindex.chunk netindex.embed netindex.store.upsert netindex.query netindex.store.query netindex.generate
Program.cs
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource("NetIndex")
        .AddConsoleExporter()
        .AddAspNetCoreInstrumentation());
// Open core

Apache-2.0 core. Optional enterprise extensions.

The framework is free forever. Enterprise add-ons are available for teams that need RBAC, advanced telemetry, and managed hosting.

Open source NetIndex Core

Everything you need to build, run, and ship a RAG system on .NET. No feature flags, no telemetry-by-default, no rug-pulls.

  • Pipeline orchestration + all contracts (zero external deps)
  • All provider adapters (Ollama, OpenAI, Azure OpenAI)
  • All storage backends (InMemory, SQLite, pgvector)
  • PDF, DOCX, Markdown loaders + Tesseract OCR
  • Three chunking strategies + deny-all security
  • OpenTelemetry tracing, ASP.NET Core integration, 80+ tests
Commercial NetIndex Enterprise

RBAC, advanced telemetry, and managed hosting for teams shipping LLM features in production. Stays compatible with the open core.

  • Document/chunk-level RBAC with ASP.NET Core Identity + AD/Entra ID
  • Prompt-answer lineage + hallucination detection dashboard
  • Managed hosting with ChatGPT-style web portal
  • Compliance, policy controls, audit retention, export
  • SLA-backed priority support
// Benchmarks

Numbers, when we've earned them.

We're still finalizing methodology. Rather than ship marketing numbers, we're publishing the harness first — then the results.

Pending · methodology in progress

A reproducible harness, then numbers.

We're benchmarking against a fixed corpus (FineWeb-edu sample) and a public eval set across three retrieval strategies and three storage backends. The harness is open source and reproducible — when results land, the methodology will be public, too.

  • Pipeline ingestion throughput (documents/sec)
  • Chunking throughput (characters/sec per strategy)
  • Embedding generation (embeddings/sec, reproducible in-memory fake)
  • P50 / P95 / P99 retrieval latency at 1M chunks
  • End-to-end query latency (retrieval + generation)
ingestion
— /s
retrieval p50
— ms
retrieval p99
— ms
memory (1M)
— MB
cost / query
— ¢
// Roadmap

Shipping in the open.

A public roadmap, with milestones and version pins. Track progress on GitHub.

V1 Shipped Q1 2026
Foundation
  • Core abstractions (zero external deps)
  • Pipeline orchestration (ingest, query, generate)
  • Ollama + SQLite for local development
  • OpenAI + Azure OpenAI providers
  • pgvector for production storage
  • PDF, DOCX, Markdown ingestion
  • Fixed-size, recursive, semantic chunking
  • Multi-tenant auth with deny-all default
  • OpenTelemetry tracing
  • ASP.NET Core integration
  • Test contract suite
V1 In progress Q2 2026
Ecosystem
  • Semantic Kernel integration
  • RAG evaluation harness
V2 Next Q3–Q4 2026
Advanced Agents
  • Multi-stage retrieval (HyDE, query expansion)
  • Cross-encoder reranking
  • Hybrid search (dense + BM25)
  • Agent orchestration + tool calling
  • Performance optimization
V2+ Planned 2027
Stability & Scale
  • API stability guarantees
  • Built-in eval framework
  • Prompt caching layer
  • Quantization + index pruning
  • Tiered storage
// Use cases

Real scenarios. Real code.

From enterprise knowledge bases to air-gapped healthcare infrastructure — one framework, any deployment model.

Enterprise knowledge base

Index 10,000 internal PDFs and let employees ask natural language questions — backed by Azure infrastructure your org already trusts.

Program.cs
services.AddNetIndex(config => config
    .UseAzureOpenAIEmbedding(opts =>
        opts.Endpoint = "https://corp.openai.azure.com/")
    .UsePgvector(connectionString)
    .UseDocumentLoader<PdfFormat>(opts =>
        opts.MaxInputSizeBytes = 100 * 1024 * 1024)
    .UseRecursiveChunking());

Air-gapped / on-premise

Query medical research papers with fully local infrastructure — Ollama models, SQLite vectors, no data leaves the network. Ideal for classified environments and HIPAA-constrained deployments.

Program.cs
services.AddNetIndex(config => config
    .UseOllamaEmbedding("http://localhost:11434",
        "nomic-embed-text")
    .UseSqliteVectorStore("Data Source=vectors.db")
    .Build());

// Custom resolver reads from JWT claims
services.AddSingleton<ITenantResolver,
    HealthcareTenantResolver>();

Customer support chat

Answer customer questions from a product documentation corpus. Streaming SSE endpoint — tokens flow to the browser as they generate.

Program.cs
app.MapGet("/chat", async (string question, HttpContext ctx) =>
{
    ctx.Response.ContentType = "text/event-stream";
    await foreach (var token in
        pipeline.GenerateAsync(question))
    {
        await ctx.Response.WriteAsync(token.Text);
        await ctx.Response.Body.FlushAsync();
    }
});

Batch document processing

Load an entire directory of DOCX files, ingest them all, and make them searchable. Recursive directory loading is built into every document loader.

BatchIngestion.cs
var loader = new WordDocumentLoader(options);

await foreach (var doc in
    loader.LoadDirectoryAsync("/data/reports",
        recursive: true))
{
    await pipeline.IngestAsync(doc);
}
// FAQ

Questions from enterprise teams.

The questions a CTO, architect, or security lead asks before approving an open-source dependency.

Yes. V1 ships SQLite (single-server) and pgvector (distributed PostgreSQL with IVFFlat/HNSW indexes), typed exception handling, build-time dimension validation, multi-tenant security, and OpenTelemetry observability. The Apache-2.0 core is fully tested with 80+ test files — contract suites, architecture tests, and property-based tests.

The core concepts — chunk → embed → store → retrieve → generate — are identical. The key difference: NetIndex is built for .NET's DI container, uses C# patterns (IAsyncEnumerable, CancellationToken, records), and integrates with Microsoft.Extensions. You don't run a Python sidecar or wrap a REST API. It's native C#.

Absolutely. The Azure OpenAI provider uses DefaultAzureCredential — works with Managed Identity, Azure CLI, Visual Studio auth, and workload identity out of the box. The pgvector backend is tested against Azure Database for PostgreSQL Flexible Server.

The ITenantResolver pattern lets you implement any authorization scheme — ASP.NET Core Identity, JWT claims, header-based resolution, API keys. The deny-all default ensures nothing leaks accidentally. Enterprise add-ons add document/chunk-level RBAC with AD/Entra ID mapping.

Yes — both QueryAsync and GenerateAsync return IAsyncEnumerable<T>. Map them directly to a streaming SSE endpoint: set ContentType = "text/event-stream" and await-foreach the pipeline. No buffering, no polling — true token-by-token streaming from LLM to browser.

The Ollama provider works with locally-hosted models — no API keys, no internet required. Combined with SQLite vector storage, you get a completely offline RAG pipeline. Perfect for classified networks, HIPAA-constrained environments, and on-premise data centers. The core contracts package has zero external dependencies.

Build() validates embedding dimension consistency at startup. If you switch from Ollama (768-dim) to OpenAI (1536-dim), you get a NetIndexConfigurationException immediately — before serving a single request. You must re-index your corpus. This prevents silent data corruption that would otherwise produce garbage retrieval results.

// Community

Built with the .NET community.

42 contributors across 14 countries. Join us — the project is what we make of it.

JM
AK
RS
MW
PN
TD
EL
+34
42 contributors have shaped NetIndex — from independent .NET devs to engineers at Microsoft, Stripe, and Shopify.
Become one →

Ship your first RAG endpoint this afternoon.

Configure, ingest, and query — all in idiomatic C#. The framework is free; the docs are extensive; the community is active.

$ dotnet add package NetIndex