A C# / ASP.NET Core Retrieval-Augmented Generation (RAG) API that allows users to upload documents and ask questions based on their content.
The system uses vector search with Qdrant and local LLM inference with Ollama to provide answers based on uploaded documents.
- ASP.NET Core Web API
- C#
- Qdrant (Vector Database)
- Ollama (Local LLM)
- nomic-embed-text (Embedding Model)
- FluentValidation
- Docker / Docker Compose
- Document upload and processing
- Text extraction from documents
- Document chunking
- Vector embeddings generation
- Semantic similarity search
- RAG-based question answering
- Source tracking for generated answers
- Request validation using FluentValidation
- Logging and error handling
- Retry policies for external services
All endpoints below require a JWT bearer token — see Authentication.
POST /api/documents
Uploads a document, processes its content, generates embeddings, and stores the vectors in Qdrant.
POST /api/chat
Searches for relevant document chunks using vector similarity search and generates an answer using the LLM.
UserAssistant is a production-oriented Retrieval-Augmented Generation (RAG) system that combines document ingestion, vector search, LLM orchestration, and user memory.
The system is divided into two main flows:
- Document Ingestion Pipeline - processes uploaded documents and stores knowledge in the vector database.
- Chat Pipeline - retrieves relevant information, combines it with user memory, and generates an LLM response.
UserAssistant
|
+-------------------+-------------------+
| |
v v
Document Ingestion Chat Request
Pipeline Pipeline
Upload Document User Question
| |
v v
Document Processing Query Processing
| |
v |
Extract Text |
| |
v |
Split Into Chunks |
| |
v v
Generate Embeddings Generate Query Embedding
| |
+-------------------+-------------------+
|
v
Qdrant Vector Database
======================
Stores:
- Embeddings
- Document chunks
- Metadata
|
v
Hybrid Search
|
+-------------------+-------------------+
| |
v v
Vector Similarity Keyword Matching
| |
+-------------------+-------------------+
|
v
ReRanking Service
|
v
Retrieve Relevant Context
|
+-------------------+-------------------+
| |
v v
User Memory DB Retrieved Documents
| |
+-------------------+-------------------+
|
v
Prompt Builder
|
v
LLM
|
+-------------------+-------------------+
| |
v v
Generate Answer Extract User Memory
| |
v v
Return Response Save Important Memories
|
v
User Memory Database
- User uploads a document.
- The document is processed and split into smaller chunks.
- Each chunk is converted into an embedding vector.
- Vectors and metadata are stored in Qdrant.
Stored information includes:
- Document text
- Source file
- Chunk identifier
- Vector representation
When a user sends a question:
- The query is processed and optimized.
- The question is converted into an embedding vector.
- Hybrid search retrieves relevant documents from Qdrant:
- Semantic vector similarity
- Keyword matching
- Results are re-ranked to improve relevance.
- User memories are loaded from the memory database.
- A prompt is created containing:
- User information
- Retrieved context
- User question
- The LLM generates:
- Final answer
- New user memories to store.
- The response is returned to the user.
- RAG (Retrieval-Augmented Generation) - combines information retrieval with LLM generation.
- Embeddings - represent text as vectors for semantic search.
- Vector Database - stores and retrieves relevant knowledge using similarity search.
- Hybrid Search - combines semantic search with keyword-based matching.
- LLM Orchestration - builds context-aware prompts and manages AI responses.
- Memory System - stores long-term user information to improve future conversations.
- .NET 10 SDK
- Docker (for Qdrant and Ollama)
dotnet-eftool:dotnet tool install --global dotnet-ef
Start Qdrant and Ollama using Docker Compose:
docker compose up -dOpen:
http://localhost:6333/dashboard
Run:
curl http://localhost:11434/api/tags
docker exec -it ollama ollama pull qwen2.5:1.5b
docker exec -it ollama ollama pull nomic-embed-text
The JWT signing key must not be committed to appsettings.json. Set it locally via user secrets:
dotnet user-secrets init --project UserAssistant.Api
dotnet user-secrets set "Jwt:Key" "$(openssl rand -base64 32)" --project UserAssistant.ApiMigrations are applied automatically on startup, so you just need to run the API:
dotnet run --project UserAssistant.ApiThis creates rag.db (SQLite) in the build output folder and applies all pending migrations, then starts
the API.
In Development, API docs are available at /scalar/v1.
Most endpoints require a JWT bearer token. Register a user, log in, and pass the returned token as
Authorization: Bearer <token> on subsequent requests.
POST /api/auth/register
{
"email": "user@example.com",
"password": "P@ssw0rd123"
}POST /api/auth/login
{
"email": "user@example.com",
"password": "P@ssw0rd123"
}Both endpoints return a JWT token valid for Jwt:ExpirationDays (default 7 days).
The API switches providers based on environment:
- Development — uses Ollama (local, no API key required) for both chat and embeddings.
- Non-Development (e.g.
Production) — uses OpenAI for both chat and embeddings. RequiresOpenApiOptions:ApiKeyandOpenAiEmbeddingOptions:ApiKeyto be set via user secrets or environment variables — never commit a real API key toappsettings.json.
- A user uploads a document.
- The document content is extracted and split into chunks.
- Each chunk is converted into a vector embedding.
- The vectors and metadata are stored in Qdrant.
- A user sends a question.
- The question is converted into an embedding vector.
- Qdrant searches for the most similar document chunks.
- The retrieved chunks are added as context to the prompt.
- The LLM generates an answer based on the retrieved information.
This project is under active development. Known gaps, in priority order:
- Automated test suite — unit tests for validators,
AuthService,ChatService, and the retrieval pipeline are scoped and in progress, not yet merged. - CI pipeline — GitHub Actions workflow to run
dotnet build+dotnet teston every push. - API containerization — Qdrant and Ollama run via Docker Compose; the API itself currently
runs via
dotnet run, not containerized.