Shared Configuration and Contracts
How shared options and interfaces keep model providers and storage details out of workflow code.
The contracts keep model vendors out of workflow code
public interface IEmbeddingProvider
{
Task<float[]> GenerateEmbeddingAsync(string input, CancellationToken cancellationToken);
}
public interface IChatCompletionProvider
{
Task<string> GenerateAnswerAsync(string question, IReadOnlyList<RetrievedChunk> chunks, CancellationToken cancellationToken);
}RAG.Core/Configuration/RagOptions.cs defines strongly typed settings:
StorageOptionsAiOptionsQdrantOptionsIngestionOptionsRequestOptions
This keeps configuration access consistent. Instead of reading raw strings throughout the app, services receive IOptions<RagOptions>.
The key interfaces live in RAG.Core/Services/Contracts.cs.
Important contracts:
IObjectStorage: upload and read original files.ITextExtractor: extract text from PDFs and TXT files.ITextChunker: split extracted text into chunks.ITokenEstimator: estimate tokens for chunking while making the approximation explicit.IEmbeddingProvider: turn text into vectors.IChatCompletionProvider: produce final answers from evidence.ILiteraryAnalysisProvider: generate book-club profiles.IVectorStore: upsert, search, and retrieve Qdrant chunks.IRetrievalReranker: turn vector candidates plus question context into ranked chunks with reasons.IDocumentIngestionService: process pending documents.IIngestionWorkSource: decide which document IDs should be ingested next.IDocumentManagementService: delete documents and queue reindexing.IChatAnswerService: answer user questions.
These interfaces are the main teaching point of the project. The application workflow depends on stable capabilities, not on a specific vendor SDK.
Request Guardrails
RequestOptions adds limits around the ask path: maximum question characters, maximum selected documents, maximum generated retrieval queries, and provider timeout seconds. Those limits are deliberately configuration-backed because RAG cost and latency are operational concerns, not only code concerns.
public sealed class RequestOptions
{
public int MaxQuestionCharacters { get; set; } = 2000;
public int MaxSelectedDocuments { get; set; } = 20;
public int MaxRetrievalQueries { get; set; } = 12;
public int ProviderTimeoutSeconds { get; set; } = 90;
}This is a small but important shift: the sample no longer treats user questions as harmless strings. The application validates request shape before it starts embedding queries, searching vectors, or calling a paid/remote model.