RAG in de praktijk: Real-World-toepassingen bouwen (Nederlands (Dutch))

RAG in de praktijk: Real-World-toepassingen bouwen

Saturday, 22 November 2025

//

21 minute read

In Deel 1 en Deel 2 Je begrijpt wat RAG is, waarom het belangrijk is, en hoe het werkt onder de kap. Nu is het tijd om die kennis in de praktijk te brengen. Dit artikel laat je zien hoe je echte RAG-systemen kunt bouwen met werkende C#-code, gemeenschappelijke uitdagingen oplost en geavanceerde technieken uit recent onderzoek gebruikt.

Inleiding

Serienavigatie: Dit is deel 3 van de RAG-reeks:

Als je de delen 1 en 2 nog niet gelezen hebt, raad ik je aan om daar te beginnen om te begrijpen:

  • Wat RAG is en waarom het belangrijk is (Deel 1)
  • De geschiedenis van trefwoord zoeken tot semantisch begrip (Deel 1)
  • Volledige RAG-pijpleiding: indexeren, ophalen, genereren (deel 2)
  • LLM internals: tokens, KV-cache, contextvensters (Deel 2)
  • RAG versus fine-tuning en andere benaderingen (deel 1)

Dit artikel gaat ervan uit dat je die basisprincipes begrijpt en focust op implementatie, optimalisatie en real-world patronen.

Real-World RAG-toepassingen op dit blog

Ik heb verschillende RAG-aangedreven functies gebouwd op deze blog. Laat me u concrete voorbeelden laten zien.

1. Aanverwante berichten aanbeveling

Elke blog post kan tonen "Verwante berichten" met behulp van semantische gelijkenis.

Hoe het werkt:

  1. Elke blog post krijgt ingebed wanneer gepubliceerd
  2. Bij het bekijken van een post, halen we de inbedding van Qdrant
  3. Zoek de 5 meest vergelijkbare post inbeddingen
  4. Weergave als "Verwante berichten"

Waarom het beter is dan tags:

  • Tags vereisen handmatige categorisatie
  • Semantische zoekopdracht vindt conceptueel gerelateerde berichten, zelfs zonder bijpassende tags
  • "Dokter Compose" en "Container orkestratie" posten zijn semantisch gerelateerd

Code knipsel:

public async Task<List<SearchResult>> GetRelatedPostsAsync(
    string currentPostSlug,
    string language,
    int limit = 5)
{
    // Get the current post's embedding
    var currentPost = await _vectorStore.GetByIdAsync(currentPostSlug);

    if (currentPost == null)
        return new List<SearchResult>();

    // Find similar posts
    var similarPosts = await _vectorStore.SearchAsync(
        currentPost.Embedding,
        limit: limit + 1,  // +1 because result includes the current post
        filter: new Filter
        {
            Must =
            {
                new Condition
                {
                    Field = "language",
                    Match = new Match { Keyword = language }
                }
            },
            MustNot =
            {
                new Condition
                {
                    Field = "slug",
                    Match = new Match { Keyword = currentPostSlug }
                }
            }
        }
    );

    return similarPosts.Take(limit).ToList();
}

2. Semantisch blog zoeken

Het zoekvak op deze blog maakt gebruik van RAG-stijl semantische zoekopdracht (hoewel zonder de generatie deel - het is gewoon ophalen).

Gebruikerservaring:

  • Zoeken naar "het opzetten van een database" → vindt berichten over PostgreSQL, Entity Framework, migraties
  • Zoeken naar "deployment" → vindt berichten over Docker, hosting, CI/CD
  • Geen exacte zoekwoord overeenkomsten nodig

Uitvoering: Ik zal dit in een volgend artikel over vector databases verwerken.

3. De "Advocaat GPT" Schrijfassistent

Ik bouw een compleet RAG-systeem om me te helpen nieuwe blogberichten te schrijven.

Use case: Wanneer ik begin te schrijven over "toevoegen van authenticatie aan ASP.NET Core," het systeem:

  1. Integreert mijn huidige concept
  2. Zoekt berichten uit het verleden voor gerelateerde inhoud over authenticatie, ASP.NET, beveiliging
  3. Stelt relevante code knipsels voor die ik eerder heb gebruikt
  4. Auto-genereert interne links naar gerelateerde posten
  5. Handhaaft consistentie met mijn schrijfstijl

Volledige RAG-pijpleiding:

public async Task<WritingAssistanceResponse> GetSuggestionsAsync(
    string currentDraft,
    string topic)
{
    // 1. Embed the current draft
    var draftEmbedding = await _embeddingService.GenerateEmbeddingAsync(
        currentDraft
    );

    // 2. Retrieve related past content
    var relatedPosts = await _vectorStore.SearchAsync(
        draftEmbedding,
        limit: 5
    );

    // 3. Build context for LLM
    var prompt = BuildWritingAssistancePrompt(
        currentDraft,
        topic,
        relatedPosts
    );

    // 4. Generate suggestions using local LLM
    var suggestions = await _llmService.GenerateAsync(prompt);

    // 5. Extract and format citations
    var response = ExtractCitations(suggestions, relatedPosts);

    return response;
}

Dit is RAG in actie - retrieval (semantic search) + augmentation (add context) + generation (LLM suggestions).

Gemeenschappelijke RAG-uitdagingen en oplossingen

Bouw productie RAG systemen is niet triviaal. Hier zijn uitdagingen die ik heb ondervonden en hoe ze op te lossen.

Uitdaging 1: Chunking Strategie

Probleem: Hoe deel je documenten? Te klein = verlies van context. Te groot = irrelevante informatie.

Oplossing: Hybride chunking op basis van documentstructuur.

public class SmartChunker
{
    public List<Chunk> ChunkDocument(string markdown, string sourceId)
    {
        var chunks = new List<Chunk>();

        // Parse markdown into sections
        var document = Markdown.Parse(markdown);
        var sections = ExtractSections(document);

        foreach (var section in sections)
        {
            var wordCount = CountWords(section.Content);

            if (wordCount < MinChunkSize)
            {
                // Merge small sections
                MergeWithPrevious(chunks, section);
            }
            else if (wordCount > MaxChunkSize)
            {
                // Split large sections
                var subChunks = SplitSection(section);
                chunks.AddRange(subChunks);
            }
            else
            {
                // Just right
                chunks.Add(CreateChunk(section, sourceId));
            }
        }

        return chunks;
    }
}

Beste praktijken:

  • Documentstructuur respecteren (koppen, alinea's)
  • Overlap tussen brokken toevoegen (50-100 woorden)
  • Bewaar codeblokken intact
  • Inclusief sectiekoppen in elke brok voor context

Uitdaging 2: Inbeddingskwaliteit

Probleem: Generieke inbeddingsmodellen mogen geen domeinspecifieke semantiek vastleggen.

Oplossingen:

Optie 1: Fine-tune inbeddingen (geavanceerd)

# Using sentence-transformers in Python
from sentence_transformers import SentenceTransformer, InputExample, losses

model = SentenceTransformer('all-MiniLM-L6-v2')

# Create training examples from your domain
train_examples = [
    InputExample(texts=['Docker Compose', 'container orchestration'], label=0.9),
    InputExample(texts=['Entity Framework', 'ORM database'], label=0.9),
    InputExample(texts=['Docker', 'apple fruit'], label=0.1)
]

# Fine-tune
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)
model.fit(train_objectives=[(train_dataloader, train_loss)], epochs=1)

Optie 2: Hybride inbeddingen (meerdere modellen combineren)

public async Task<float[]> GenerateHybridEmbeddingAsync(string text)
{
    var semantic = await _semanticModel.GenerateEmbeddingAsync(text);
    var keyword = await _keywordModel.GenerateEmbeddingAsync(text);

    // Concatenate or weighted average
    return CombineEmbeddings(semantic, keyword);
}

Optie 3: Metadatafiltering toevoegen

var results = await _vectorStore.SearchAsync(
    queryEmbedding,
    limit: 10,
    filter: new Filter
    {
        Must =
        {
            new Condition { Field = "category", Match = new Match { Keyword = "ASP.NET" } },
            new Condition { Field = "date", Range = new Range { Gte = "2024-01-01" } }
        }
    }
);

Uitdaging 3: Context Vensterbeheer

Probleem: LLM's hebben tokenlimieten. Hoe past u query + context + prompt in het venster?

Oplossing: Dynamische contextselectie en -samenvatting.

public string BuildContextAwarePrompt(
    string query,
    List<SearchResult> retrievedDocs,
    int maxTokens = 4096)
{
    var promptTemplate = GetPromptTemplate();
    var queryTokens = CountTokens(query);
    var templateTokens = CountTokens(promptTemplate);

    // Reserve tokens for: prompt + query + response
    var availableForContext = maxTokens - queryTokens - templateTokens - 500; // 500 for response

    // Add context until we hit limit
    var selectedContext = new List<SearchResult>();
    var currentTokens = 0;

    foreach (var doc in retrievedDocs.OrderByDescending(d => d.Score))
    {
        var docTokens = CountTokens(doc.Text);

        if (currentTokens + docTokens <= availableForContext)
        {
            selectedContext.Add(doc);
            currentTokens += docTokens;
        }
        else
        {
            // Try summarizing the doc if it's important
            if (doc.Score > 0.85)
            {
                var summary = await SummarizeAsync(doc.Text, maxTokens: 200);
                var summaryTokens = CountTokens(summary);

                if (currentTokens + summaryTokens <= availableForContext)
                {
                    selectedContext.Add(new SearchResult
                    {
                        Text = summary,
                        Title = doc.Title,
                        Score = doc.Score
                    });
                    currentTokens += summaryTokens;
                }
            }
        }
    }

    return FormatPrompt(query, selectedContext);
}

Uitdaging 4: Hallucinatie Ondanks RAG

Probleem: Zelfs met context negeren LLM's het soms en hallucineren.

Oplossingen:

1. Prompt engineering:

var systemPrompt = @"
You are a technical assistant.

CRITICAL RULES:
1. ONLY use information from the provided CONTEXT sections
2. If the context doesn't contain the answer, say 'I don't have enough information in the provided context to answer that'
3. DO NOT use your training data to supplement answers
4. Always cite the source using [1], [2] notation
5. If you're unsure, say so

CONTEXT:
{context}

QUESTION: {query}

ANSWER (following all rules above):
";

2. Validatie na de generatie:

public async Task<bool> ValidateResponseAgainstContext(
    string response,
    List<SearchResult> context)
{
    // Check if response contains claims not in context
    var responseSentences = SplitIntoSentences(response);

    foreach (var sentence in responseSentences)
    {
        var isSupported = await IsClaimSupportedByContext(sentence, context);

        if (!isSupported)
        {
            _logger.LogWarning("Hallucination detected: {Sentence}", sentence);
            return false;
        }
    }

    return true;
}

3. Iteratieve verfijning:

public async Task<string> GenerateWithValidationAsync(
    string query,
    List<SearchResult> context,
    int maxAttempts = 3)
{
    for (int attempt = 0; attempt < maxAttempts; attempt++)
    {
        var response = await _llm.GenerateAsync(
            BuildPrompt(query, context)
        );

        var isValid = await ValidateResponseAgainstContext(response, context);

        if (isValid)
            return response;

        // Refine prompt for next attempt
        query = $"{query}\n\nPrevious attempt hallucinated. Stick strictly to the context.";
    }

    return "I couldn't generate a reliable answer. Please rephrase your question.";
}

Uitdaging 5: De index up-to-date houden

Probleem: Als je nieuwe documenten toevoegt, moet de vectordatabase actueel blijven.

Oplossing: Automatische indexeringspijplijn.

public class BlogIndexingBackgroundService : BackgroundService
{
    private readonly IVectorStoreService _vectorStore;
    private readonly IMarkdownService _markdownService;
    private readonly ILogger<BlogIndexingBackgroundService> _logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await IndexNewPostsAsync(stoppingToken);

                // Check for updates every hour
                await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in indexing service");
            }
        }
    }

    private async Task IndexNewPostsAsync(CancellationToken ct)
    {
        var allPosts = await _markdownService.GetAllPostsAsync();

        foreach (var post in allPosts)
        {
            var existingDoc = await _vectorStore.GetByIdAsync(post.Slug);

            // Check if content changed
            var currentHash = ComputeHash(post.Content);

            if (existingDoc == null || existingDoc.ContentHash != currentHash)
            {
                _logger.LogInformation("Indexing updated post: {Title}", post.Title);

                var chunks = _chunker.ChunkDocument(post.Content, post.Slug);

                foreach (var chunk in chunks)
                {
                    var embedding = await _embeddingService.GenerateEmbeddingAsync(chunk.Text);

                    await _vectorStore.UpsertAsync(
                        id: $"{post.Slug}_{chunk.Index}",
                        embedding: embedding,
                        metadata: new Dictionary<string, object>
                        {
                            ["slug"] = post.Slug,
                            ["title"] = post.Title,
                            ["chunk_index"] = chunk.Index,
                            ["content_hash"] = currentHash
                        },
                        ct: ct
                    );
                }
            }
        }
    }
}

Geavanceerde RAG-technieken

Laten we de geavanceerde RAG-technieken uit recent onderzoek onderzoeken.

1. Hypothetisch document inbeddingen (HyDE)

Probleem: Gebruiker queries zijn vaak kort en slecht gevormd. Document brokken zijn gedetailleerd en goed geschreven. Deze mismatch doet het ophalen pijn.

Oplossing: Genereer een hypothetisch ideaal document dat de query zou beantwoorden, insluiten dat, dan zoeken.

public async Task<List<SearchResult>> HyDESearchAsync(string query)
{
    // Generate hypothetical answer (even if hallucinated)
    var hypotheticalAnswer = await _llm.GenerateAsync($@"
        Write a detailed, technical paragraph that would perfectly answer this question:

        Question: {query}

        Paragraph:"
    );

    // Embed the hypothetical answer
    var embedding = await _embeddingService.GenerateEmbeddingAsync(
        hypotheticalAnswer
    );

    // Search using this embedding
    return await _vectorStore.SearchAsync(embedding);
}

Waarom het werkt: Het hypothetische antwoord maakt gebruik van dezelfde taal en structuur als de feitelijke documenten, waardoor het opvragen wordt verbeterd.

2. Zelf-Querying

Probleem: Gebruikersqueries mengen vaak semantisch zoeken met metadatafilters.

Voorbeeld: "Recente berichten over Docker" = semantisch("Docker") + filter(datum > 2024-01-01)

Oplossing: Gebruik LLM om de query te verwerken in semantische + metadata filters.

public async Task<SearchQuery> ParseSelfQueryAsync(string naturalLanguageQuery)
{
    var parsingPrompt = $@"
        Parse this search query into:
        1. Semantic search query (what the user is looking for)
        2. Metadata filters (category, date range, etc.)

        User Query: {naturalLanguageQuery}

        Output JSON:
        {{
            ""semantic_query"": ""the core concept"",
            ""filters"": {{
                ""category"": ""...",
                ""date_after"": ""..."",
                ""date_before"": ""...""
            }}
        }}
    ";

    var jsonResponse = await _llm.GenerateAsync(parsingPrompt);
    var parsed = JsonSerializer.Deserialize<SearchQuery>(jsonResponse);

    return parsed;
}

// Use parsed query
var parsedQuery = await ParseSelfQueryAsync("Recent ASP.NET posts about authentication");
// semantic_query: "authentication"
// filters: { category: "ASP.NET", date_after: "2024-01-01" }

var results = await _vectorStore.SearchAsync(
    embedding: await _embeddingService.GenerateEmbeddingAsync(parsedQuery.SemanticQuery),
    filter: BuildFilter(parsedQuery.Filters)
);

3. Multi-Query RAG

Probleem: Een enkele query kan relevante documenten missen als gevolg van frasering.

Oplossing: Genereer meerdere variaties van de query, zoek met alles, combineer resultaten.

public async Task<List<SearchResult>> MultiQuerySearchAsync(string query)
{
    // Generate query variations
    var variations = await _llm.GenerateAsync($@"
        Generate 3 different ways to phrase this search query:

        Original: {query}

        Variations (one per line):
    ");

    var queries = variations.Split('\n', StringSplitOptions.RemoveEmptyEntries)
        .Prepend(query) // Include original
        .ToList();

    // Search with all variations
    var allResults = new List<SearchResult>();

    foreach (var q in queries)
    {
        var embedding = await _embeddingService.GenerateEmbeddingAsync(q);
        var results = await _vectorStore.SearchAsync(embedding, limit: 10);
        allResults.AddRange(results);
    }

    // Deduplicate and merge scores
    var merged = allResults
        .GroupBy(r => r.Id)
        .Select(g => new SearchResult
        {
            Id = g.Key,
            Text = g.First().Text,
            Title = g.First().Title,
            Score = g.Max(r => r.Score) // Take best score
        })
        .OrderByDescending(r => r.Score)
        .ToList();

    return merged;
}

4. Contextuele compressie

Probleem: De opgehaalde stukken bevatten irrelevante informatie.

Oplossing: Gebruik een kleinere LLM om opgehaalde context te comprimeren naar alleen relevante delen.

public async Task<string> CompressContextAsync(
    string query,
    List<SearchResult> retrievedDocs)
{
    var compressed = new List<string>();

    foreach (var doc in retrievedDocs)
    {
        var compressionPrompt = $@"
            Extract only the sentences from this document that are relevant to answering the question.

            Question: {query}

            Document:
            {doc.Text}

            Relevant excerpts (maintain original wording):
        ";

        var relevantExcerpt = await _smallLLM.GenerateAsync(compressionPrompt);

        if (!string.IsNullOrWhiteSpace(relevantExcerpt))
        {
            compressed.Add($"From '{doc.Title}':\n{relevantExcerpt}");
        }
    }

    return string.Join("\n\n", compressed);
}

5. Iterative Retrieval (Multi-Hop RAG)

Probleem: Complexe vragen vereisen informatie uit meerdere bronnen die moeten worden aangesloten.

Voorbeeld: "Welke database gebruikt de blog en hoe wordt semantisch zoeken geïmplementeerd?"

  • Hop 1: Zoek uit welke database wordt gebruikt (PostgreSQL)
  • Hop 2: Zoek hoe semantisch zoeken werkt met die database

Oplossing: Iteratieve ophaling en synthese.

public async Task<string> MultiHopRAGAsync(string complexQuery, int maxHops = 3)
{
    var currentQuery = complexQuery;
    var allContext = new List<SearchResult>();

    for (int hop = 0; hop < maxHops; hop++)
    {
        // Retrieve for current query
        var results = await SearchAsync(currentQuery, limit: 5);
        allContext.AddRange(results);

        // Check if we have enough information
        var synthesisPrompt = $@"
            Original question: {complexQuery}

            Context so far:
            {FormatContext(allContext)}

            Can you answer the original question with this context?
            If yes, provide the answer.
            If no, what additional information do you need? (be specific)
        ";

        var synthesis = await _llm.GenerateAsync(synthesisPrompt);

        if (synthesis.Contains("yes", StringComparison.OrdinalIgnoreCase))
        {
            // We have enough information
            return ExtractAnswer(synthesis);
        }

        // Extract what we need for next hop
        currentQuery = ExtractNextQuery(synthesis);
    }

    // Final synthesis with all gathered context
    return await GenerateFinalAnswerAsync(complexQuery, allContext);
}

6. Contextgeheugen op lange termijn met RAG + samenvatting

Probleem: Hoe bouw je AI-systemen die gesprekken van maanden of jaren geleden herinneren? Traditionele chatbots verliezen de context na elke sessie.

Oplossing: Combineer RAG met progressieve opsomming om persistent, doorzoekbaar geheugen te creëren.

Dit is de benadering die wordt gebruikt in DiSE (Directed Synthetic Evolution) - een geavanceerd systeem dat gebruik maakt van RAG-gebaseerde contextgeheugen om de gedeelde conversatiegeschiedenis voor onbepaalde tijd te behouden.

Voorbeeldscenario:

User (Today): "Remember George's specs?"
AI: "Yes, you discussed George's prescription requirements in our conversation
     from 5 years ago (2019-03-15). He needed progressive lenses with..."

Hoe het werkt:

flowchart TB
    A[User Message] --> B[Store in RAG Memory]
    B --> C[Extract Key Entities & Topics]
    C --> D[Link to Past Conversations]

    E[Periodic Summarization] --> F[Summarize Old Conversations]
    F --> G[Store Summary with High-Level Tags]
    G --> H[Keep Original for Retrieval]

    I[Future Query: 'George's specs'] --> J[Semantic Search in RAG]
    J --> K[Find: 2019 conversation]
    K --> L[Retrieve Original Context]
    L --> M[LLM generates response with 5-year-old context!]

    style B stroke:#f9f,stroke-width:3px
    style J stroke:#bbf,stroke-width:3px

Uitvoering:

public class LongTermConversationalMemory
{
    private readonly IVectorStoreService _vectorStore;
    private readonly IEmbeddingService _embeddingService;

    public async Task StoreConversationAsync(
        string conversationId,
        string userId,
        List<ConversationTurn> turns,
        DateTime timestamp)
    {
        // Extract key entities and topics
        var entities = await ExtractEntitiesAsync(turns);
        var topics = await ExtractTopicsAsync(turns);

        // Create searchable representation
        var conversationText = string.Join("\n", turns.Select(t =>
            $"{t.Speaker}: {t.Message}"));

        // Generate embedding
        var embedding = await _embeddingService.GenerateEmbeddingAsync(
            conversationText);

        // Store in RAG with rich metadata
        await _vectorStore.IndexDocumentAsync(
            id: $"conv_{conversationId}",
            embedding: embedding,
            metadata: new Dictionary<string, object>
            {
                ["user_id"] = userId,
                ["timestamp"] = timestamp.ToString("O"),
                ["entities"] = entities,  // ["George", "specs", "prescription"]
                ["topics"] = topics,      // ["healthcare", "eyewear"]
                ["full_text"] = conversationText,
                ["turn_count"] = turns.Count
            }
        );
    }

    public async Task<List<PastContext>> RetrieveRelevantPastAsync(
        string currentQuery,
        string userId,
        int limit = 5)
    {
        // Embed the current query
        var queryEmbedding = await _embeddingService.GenerateEmbeddingAsync(
            currentQuery);

        // Search past conversations
        var results = await _vectorStore.SearchAsync(
            queryEmbedding,
            limit: limit,
            filter: new Filter
            {
                Must =
                {
                    new Condition { Field = "user_id", Match = new Match { Keyword = userId } }
                }
            }
        );

        return results.Select(r => new PastContext
        {
            ConversationId = r.Id,
            Timestamp = DateTime.Parse(r.Metadata["timestamp"].ToString()),
            Entities = (List<string>)r.Metadata["entities"],
            FullText = r.Metadata["full_text"].ToString(),
            Relevance = r.Score
        }).ToList();
    }

    // Periodic summarization to keep memory manageable
    public async Task SummarizeOldConversationsAsync(DateTime olderThan)
    {
        var oldConversations = await _vectorStore.FindByDateRangeAsync(
            endDate: olderThan);

        foreach (var conv in oldConversations)
        {
            // Generate summary using LLM
            var summary = await _llm.GenerateAsync($@"
                Summarize this conversation, preserving key facts and entities:

                {conv.FullText}

                Summary:");

            // Update document with summary while keeping original
            await _vectorStore.UpdateAsync(
                id: conv.Id,
                additionalMetadata: new Dictionary<string, object>
                {
                    ["summary"] = summary,
                    ["summarized_at"] = DateTime.UtcNow.ToString("O")
                }
            );
        }
    }
}

Waarom dit krachtig is:

  1. Oneindig geheugenbereik - Gesprekken van jaren geleden zijn net zo toegankelijk als gisteren
  2. Semantisch zoeken - "George's specs" vindt het gesprek zelfs als opgeslagen als "George's recept brillen eisen"
  3. Verbinding tussen entiteit en entiteit - Alle gesprekken met "George" zijn verbonden
  4. Privacybehoud - Kan per-gebruiker geheugen isolatie implementeren
  5. Kosteneffectief - Samengevatheid voorkomt opslagexplosie met behoud van doorzoekbaarheid

Real-world voorbeeld van DiSE:

DiSE gebruikt deze benadering om te onthouden:

  • Aanroepingen voor gereedschap van maanden geleden (wat werkte, wat mislukte)
  • Codepatronen die succesvol waren in eerdere projecten
  • Gebruikersvoorkeuren gevestigd in eerdere gesprekken
  • Domeinkennis na verloop van tijd opgebouwd

Dit creëert een AI-systeem dat echt "leren" van elke interactie en opbouwt institutionele geheugen, in plaats van het starten van verse elke sessie.

Uitdagingen om na te denken:

  1. Privacy: Oude gesprekken moeten goed worden geïsoleerd per gebruiker
  2. Relevantiebederf: Niet alle oude informatie blijft relevant
  3. Opslagkosten: Structureringsstrategie voor schaalvergroting nodig
  4. Samenhang: Samenvattingen op één lijn houden met originelen
  5. Retrieval-precisie: Evenwicht tussen recente en historisch belangrijke context

Deze techniek transformeert RAG van "zoek mijn documenten" in "herinner alles wat we ooit hebben besproken" - een game-changer voor langdurige AI assistenten.

Wanneer NIET RAG gebruiken

RAG is niet altijd het antwoord.

1. Algemene kennisvragen

  • "Wat is de hoofdstad van Frankrijk?" - LLM weet dit al
  • RAG voegt latency en kosten voor geen voordeel

2. Creatief schrijven

  • Poëzie, fictie, brainstormen
  • RAG beperkt creativiteit door gronding in bestaande tekst

3. Real-time gegevensbehoeften

  • Voorraadprijzen, live sportscores
  • Beter bediend door API-integraties, niet door RAG

4. Wiskundige redenering

  • "Bereken 15% tip op $83,47"
  • LLM's kunnen rekenen; RAG helpt niet

5. Zeer kleine kennisbases

  • Als uw kennisbestand past in het contextvenster, gewoon alles opnemen
  • RAG overhead is het niet waard voor 5 documenten

6. Wanneer u de training van de LLM controleert

  • Als je een model vanaf nul traint op je data, kan het beter zijn om het af te stemmen.
  • RAG is het beste bij het gebruik van voorgetrainde modellen die je niet onder controle hebt

Aan de slag met RAG: een praktische handleiding

Wilt u uw eigen RAG-systeem bouwen? Hier is een stap-voor-stap benadering.

Week 1: Eenvoudig starten

Doelstelling: Krijg basis retrieval werken met geen LLM.

// 1. Choose an embedding service (start with API for simplicity)
var openAI = new OpenAIClient(apiKey);

// 2. Embed a few test documents
var docs = new[]
{
    "Docker is a containerization platform",
    "Kubernetes orchestrates containers",
    "Entity Framework is an ORM for .NET"
};

var embeddings = new List<float[]>();
foreach (var doc in docs)
{
    var response = await openAI.GetEmbeddingsAsync(
        new EmbeddingsOptions("text-embedding-3-small", new[] { doc })
    );
    embeddings.Add(response.Value.Data[0].Embedding.ToArray());
}

// 3. Implement basic search (in-memory for now)
var query = "container orchestration";
var queryEmbedding = await GetEmbeddingAsync(query);

var results = embeddings
    .Select((emb, idx) => new
    {
        Text = docs[idx],
        Score = CosineSimilarity(queryEmbedding, emb)
    })
    .OrderByDescending(r => r.Score)
    .ToList();

// 4. Verify search works
foreach (var result in results)
{
    Console.WriteLine($"{result.Score:F3}: {result.Text}");
}
// Expected: Kubernetes scores highest

Week 2: Vectordatabase toevoegen

Doelstelling: Schaal naar echte documentverzamelingen.

Volgende te implementeren stappen:

  1. Qdrant uitvoeren in Docker
  2. Indexeer uw documenten
  3. Zoekeindpunt implementeren

Ik zal dit in detail behandelen in een volgend artikel over vector databases.

Week 3: LLM-generatie toevoegen

Doelstelling: Voltooi de RAG-pijpleiding.

// 1. Retrieve context
var context = await SearchAsync(query, limit: 3);

// 2. Build prompt
var prompt = $@"
    Answer the question using this context:

    {FormatContext(context)}

    Question: {query}

    Answer:";

// 3. Generate (start with API)
var response = await openAI.GetChatCompletionsAsync(new ChatCompletionsOptions
{
    Messages =
    {
        new ChatMessage(ChatRole.System, "You are a helpful assistant."),
        new ChatMessage(ChatRole.User, prompt)
    },
    Temperature = 0.7f,
    MaxTokens = 500
});

return response.Value.Choices[0].Message.Content;

Week 4: Optimaliseren en Pools

  • Caching toevoegen (besparen op API-kosten)
  • Verbeteren van de chunking-strategie
  • Metadatafilter toevoegen
  • Hybride zoekopdracht uitvoeren
  • Citatie-extractie toevoegen

Going Local (facultatief)

Zodra de basis werkt, migreren naar lokale conclusie (ik zal dit behandelen in de komende artikelen):

  • Lokale LLM-invloed met GPU-versnelling
  • CPU-vriendelijke inbeddingen met ONNX Runtime

Conclusie

RAG (Retrieval-Augmented Generation) is een krachtige techniek om LLM's nauwkeuriger, up-to-date en betrouwbaarder te maken door hun antwoorden aan de hand van feitelijke documenten. In plaats van te vertrouwen op de trainingsgegevens van het model alleen, RAG-systemen:

  1. Ophalen relevante informatie uit een kennisbasis met behulp van semantische zoekopdrachten
  2. Augment de prompt van de LLM met die opgehaalde context
  3. Genereren reacties die in reële bronnen zijn gebaseerd met aanhalingstekens

Belangrijkste voordelen van RAG:

  • Altijd up-to-date (werkt gewoon uw kennisbestand bij)
  • Geaard in bronnen (vermindert hallucinatie)
  • Verklaarbaar (kan bronnen aanhalen)
  • Kosteneffectief (geen dure omscholing)
  • Privacybehoud (kan volledig lokaal worden uitgevoerd)
  • Domeinspecifiek (gebruik uw eigen documenten)

Wanneer wordt RAG gebruikt:

  • Kennisintensieve taken
  • Vaak bijgewerkte informatie
  • Particuliere/private gegevens
  • Noodzaak van aanhaling en controle
  • Kosten en eenvoud

Wanneer RAG te vermijden:

  • Gge-bases

Het veld evolueert snel met geavanceerde technieken zoals HyDE, multiquery retrieval, en contextuele compressie, maar het kernconcept blijft eenvoudig: geef LLM's toegang tot de juiste informatie op het juiste moment.

Start eenvoudig, meet resultaten en itereer. RAG is een van de meest praktische manieren om betrouwbare AI-systemen te bouwen vandaag.

Conclusie: Van theorie tot productie

Je hebt nu de drie-delige RAG serie voltooid:

Deel 1: Oorsprongen en Fundamentelen

  • Wat RAG is en waarom het belangrijk is
  • Geschiedenis van zoekwoord zoeken naar semantisch begrip
  • RAG versus fine-tuning, lange context en andere benaderingen

Deel 2: Architectuur en interne aangelegenheden

  • Complete RAG-pijpleiding: indexeren, ophalen, genereren
  • LLM internals: tokens, KV cache, contextvensters
  • Technische implementatiedetails met C#-code

Deel 3: RAG in de praktijk (dit artikel)

  • Real-world implementaties (Related Posts, Semantic Search, Writing Assistant)
  • Gemeenschappelijke uitdagingen en oplossingen (chunken, inbedding kwaliteit, hallucinatie)
  • Geavanceerde technieken (HyDE, Zelf-Query, Multi-Query, Contextuele Compressie, Lange termijn Geheugen)
  • Wanneer mag u RAG NIET gebruiken
  • Aan de slag guide

Je hebt nu het volledige plaatje: Van het begrijpen van de oorsprong van RAG tot het bouwen van productiesystemen met geavanceerde optimalisaties.

Wat is het volgende?

Nu je RAG van theorie tot praktijk begrijpt, zullen de komende artikelen je laten zien hoe je complete RAG-systemen kunt bouwen in C#:

Binnenkort:

  • CPU-Vriendelijk Semantisch zoeken - Semantische zoekopdracht bouwen met ONNX inbeddingen die draaien op elke VPS zonder GPU eisen
  • Self-Hosted Vector Databases - Complete setup gids voor Qdrant met Docker, inclusief indexering, zoeken en optimalisatie strategieën
  • Bouwen van een RAG schrijfassistent - Een volledige serie over het creëren van een AI-aangedreven schrijfassistent die uw bestaande content gebruikt als kennisbasis

Deze artikelen brengen u van theorie naar praktijk, met volledige werkcode, implementatiestrategieën en real-world optimalisaties gebaseerd op het uitvoeren van deze systemen in productie op deze blog.

Blijf kijken voor hands-on implementatiehandleidingen die deze RAG kennis omzetten in werkende systemen!

Middelen

Fundamentele papers:

Instrumenten en kaders:

Meer lezen:

Deze RAG-serie:

Gelukkig gebouw!

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.