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.
// 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);
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.
dotnet new install NetIndex.Templates dotnet new netindex --name MyRagApp Creates Program.cs, appsettings.json, and a sample ingest/query/generate API — pre-wired for local Ollama + SQLite.
dotnet run Full RAG pipeline live at localhost:5000. POST a document, GET a query, stream a generated answer.
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.
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 | ✓ | ✓ | ✓ |
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.
IDocumentReranker. Tenant-authorized.IChatClient.Walk the pipeline, line by line.
Each stage is a typed contract. Click through to see the actual code you'd write.
// 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);
Same code. Any model, any store.
The contract is the surface. Swap implementations via the fluent builder — your pipeline code never changes.
// OpenAI — needs an API key from environment services.AddNetIndex(builder => builder .UseOpenAIEmbedding("sk-...", "text-embedding-3-small") .UseSqliteVectorStore("Data Source=vectors.db") .Build());
// InMemory — perfect for tests & quickstarts services.AddNetIndex(builder => builder .UseOllamaEmbedding("http://localhost:11434", "nomic-embed-text") .UseInMemoryVectorStore() .Build());
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);
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.
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
Pluggable everything, out of the box.
Every stage of the RAG pipeline is an interface. Swap implementations without touching your application code.
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.
ITenantResolver
Every pipeline call runs AuthorizeAsync() first. Return a tenant ID to proceed; return null or throw to deny with a typed NetIndexAuthorizationException.
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.
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.
public sealed class DenyAllTenantResolver : ITenantResolver { public Task<string?> ResolveTenantIdAsync(CancellationToken ct) => Task.FromResult<string?>(null); // Always denies }
// 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>();
ITenantResolver.ResolveTenantIdAsync() null → NetIndexAuthorizationException 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.
netindex.generatebuilder.Services.AddOpenTelemetry() .WithTracing(tracing => tracing .AddSource("NetIndex") .AddConsoleExporter() .AddAspNetCoreInstrumentation());
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.
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
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
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.
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)
Shipping in the open.
A public roadmap, with milestones and version pins. Track progress on GitHub.
- 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
- Semantic Kernel integration
- RAG evaluation harness
- Multi-stage retrieval (HyDE, query expansion)
- Cross-encoder reranking
- Hybrid search (dense + BM25)
- Agent orchestration + tool calling
- Performance optimization
- API stability guarantees
- Built-in eval framework
- Prompt caching layer
- Quantization + index pruning
- Tiered storage
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.
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.
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.
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.
var loader = new WordDocumentLoader(options); await foreach (var doc in loader.LoadDirectoryAsync("/data/reports", recursive: true)) { await pipeline.IngestAsync(doc); }
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.
Built with the .NET community.
42 contributors across 14 countries. Join us — the project is what we make of it.
GitHub
Source, issues, PRs, releases. Star us — it genuinely helps.
github.com/M-Sahin/NetIndex →Discord
Real-time help, design discussions, demo days, weekly office hours.
discord.gg/netindex →Discussions
Long-form Q&A, RFCs, and design proposals on GitHub Discussions.
Browse threads →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.