Naar inhoud
lightbulb Welkom op de nieuwe kennisbank | We hebben de docs volledig vernieuwd met meer dan 160 features. Bekijk wat nieuw isarrow_forward

RAG-architectuur uitgelegd

Retrieval-Augmented Generation (RAG) combineert een vectordatabase met een taalmodel: relevante documenten worden opgezocht en als context meegegeven aan het model.

Waarom RAG?

Taalmodellen zijn getraind op een grote maar verouderde dataset. Ze weten niets over jouw interne documenten, recente gebeurtenissen of bedrijfsspecifieke informatie. Je kunt dit op drie manieren oplossen:

  1. Fine-tuning: het model trainen op jouw data. Duur, traag en slecht voor feiten die veranderen.
  2. Context stuffing: alle relevante documenten in de prompt stoppen. Werkt alleen voor kleine datasets.
  3. RAG: dynamisch de meest relevante documenten opzoeken en meegeven. Goedkoop, actueel en schaalbaar.

RAG is de standaardaanpak voor kennisbank-chatbots, interne zoekmachines en documentvragen.

info

Waar komt de naam vandaan?

RAG staat voor Retrieval-Augmented Generation. Het werd geintroduceerd door Lewis et al. (2020) bij Facebook AI Research en is sindsdien de meestgebruikte aanpak voor kennisintensieve NLP-taken.

De twee fasen van RAG

Fase 1: indexering (offline)

Documenten -> Chunking -> Embedding -> Vectordatabase
  1. Inlezen: laad documenten (PDF, Word, HTML, Markdown).
  2. Chunken: splits in kleinere stukken met overlap.
  3. Embedden: converteer elke chunk naar een vector (een array van getallen).
  4. Opslaan: bewaar de vectoren in een vectordatabase met de originele tekst als metadata.

Fase 2: retrieval en generatie (online)

Vraag -> Embedding -> Vectorzoekopdracht -> Top-k chunks -> LLM -> Antwoord
  1. Vraag embedden: converteer de gebruikersvraag naar een vector.
  2. Zoeken: vind de meest gelijkende chunks via cosine similarity.
  3. Context bouwen: combineer de top-k chunks tot een context-string.
  4. Genereren: geef vraag plus context aan het LLM en ontvang een antwoord.
lightbulb

Begin klein, meet daarna

Bouw eerst de simpelste versie (naive RAG) en meet de kwaliteit met echte vragen. Optimaliseer pas daarna gericht, bijvoorbeeld met reranking of betere chunking. Zonder meetpunt weet je niet of een toevoeging echt helpt.

Eenvoudige RAG-implementatie

In dit voorbeeld gebruiken we een vectordatabase (Chroma), een embedding-model van OpenAI en Claude voor de generatie. De modelnaam pas je aan op de versie die jij gebruikt.

import Anthropic from "@anthropic-ai/sdk";
import { ChromaClient } from "chromadb";

const anthropic = new Anthropic();
const chroma = new ChromaClient();

async function indexDocuments(documents: { id: string; content: string; source: string }[]) {
  const collection = await chroma.getOrCreateCollection({ name: "knowledge_base" });

  const batchSize = 50;
  for (let i = 0; i < documents.length; i += batchSize) {
    const batch = documents.slice(i, i + batchSize);
    const embeddings = await Promise.all(
      batch.map(doc => embedText(doc.content))
    );

    await collection.add({
      ids: batch.map(d => d.id),
      embeddings,
      documents: batch.map(d => d.content),
      metadatas: batch.map(d => ({ source: d.source })),
    });
  }
}

async function embedText(text: string): Promise<number[]> {
  const response = await fetch("https://api.openai.com/v1/embeddings", {
    method: "POST",
    headers: { "Authorization": `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ input: text, model: "text-embedding-3-small" }),
  });
  const data = await response.json();
  return data.data[0].embedding;
}

async function ragQuery(question: string): Promise<string> {
  const collection = await chroma.getCollection({ name: "knowledge_base" });

  const questionEmbedding = await embedText(question);
  const results = await collection.query({
    queryEmbeddings: [questionEmbedding],
    nResults: 5,
  });

  const context = results.documents[0]
    ?.map((doc, i) => `[Bron ${i + 1}: ${results.metadatas[0]?.[i]?.source}]
${doc}`)
    .join("

") ?? "";

  const response = await anthropic.messages.create({
    model: "claude-opus-4-8",
    max_tokens: 1024,
    system: `Je bent een behulpzame assistent. Beantwoord vragen alleen op basis van de verstrekte context.
Als de context de informatie niet bevat, zeg dat dan expliciet.`,
    messages: [{
      role: "user",
      content: `Context:
${context}

Vraag: ${question}`,
    }],
  });

  return response.content[0].type === "text" ? response.content[0].text : "";
}
warning

Voorkom hallucinaties

De systeeminstructie dwingt het model om alleen op de meegegeven context te antwoorden en eerlijk te zijn als het antwoord ontbreekt. Laat dit nooit weg: zonder die instructie vult het model gaten op met verzonnen feiten die geloofwaardig klinken.

RAG-varianten

Er zijn grofweg drie volwassenheidsniveaus, oplopend in kwaliteit en complexiteit:

  • Naive RAG: een eenvoudige opzoek-en-genereer pipeline. Goed voor een eerste versie. De kwaliteit hangt sterk af van chunking en embedding-kwaliteit.
  • Advanced RAG: voegt pre-retrieval (query rewriting, HyDE) en post-retrieval (reranking, filtering) toe. Hogere kwaliteit, meer configuratie.
  • Modular RAG: componenten zijn uitwisselbaar, met een andere retriever, reranker of generator per use case. Maximale flexibiliteit voor productiesystemen.

Kwaliteitsindicatoren

Meet je RAG-systeem op zowel de retrieval-stap als het uiteindelijke antwoord:

Metric Wat je meet Streefwaarde
Context precision Aandeel opgehaalde chunks dat relevant is groter dan 80%
Context recall Aandeel relevante chunks dat is opgehaald groter dan 70%
Answer faithfulness Antwoord blijft consistent met de context groter dan 90%
Answer relevance Antwoord beantwoordt daadwerkelijk de vraag groter dan 85%

RAG versus fine-tuning

Aspect RAG Fine-tuning
Actualiseren Direct (opnieuw indexeren) Nieuwe trainingsrun nodig
Kosten Laag Hoog
Transparantie Bronnen zichtbaar Zwarte doos
Feitelijke nauwkeurigheid Hoog (brongebonden) Kan hallucineren
Stijl en gedrag aanpassen Nee Ja

In de praktijk combineer je beide vaak: fine-tuning voor toon en gedrag, RAG voor de actuele feiten.

Hoeveel documenten kan een RAG-systeem aan?

Miljoenen, afhankelijk van de vectordatabase. Chroma schaalt tot tientallen miljoenen vectoren op een enkele server. Pinecone en Weaviate schalen horizontaal naar miljarden.

Welk embedding-model moet ik kiezen?

Voor Nederlands gebruik je bij voorkeur een meertalig model. Goede keuzes in 2026 zijn text-embedding-3-large van OpenAI, of open-weight modellen zoals bge-m3 en multilingual-e5-large die je zelf kunt hosten. Open modellen scoren tegenwoordig vergelijkbaar met de gesloten API's op meertalige benchmarks. Zie het artikel over embedding-modellen kiezen voor een volledige vergelijking.

Hoe update ik de index als documenten wijzigen?

Implementeer een incremental update: verwijder de oude chunks op document-ID, chunk het bijgewerkte document opnieuw en voeg de nieuwe chunks toe. Zo blijft de index actueel zonder volledige herbouw.

Kan RAG ook met lokale modellen?

Ja. Gebruik Ollama voor het LLM en een lokaal embedding-model, bijvoorbeeld nomic-embed-text via Ollama. Geen data verlaat dan je eigen server, wat handig is voor gevoelige of vertrouwelijke documenten.

Wat is het verschil tussen RAG en context stuffing?

Bij context stuffing plak je alle documenten in de prompt. Dat werkt alleen bij weinig data en wordt snel duur en traag. RAG zoekt per vraag alleen de relevante stukken op, waardoor het schaalt naar grote kennisbanken.

Welk Claude-model gebruik ik in het voorbeeld?

Vul de API-ID in van het model dat jij gebruikt. In dit voorbeeld staat claude-opus-4-8, het krachtigste Claude-model van medio 2026. Voor snellere of goedkopere antwoorden kun je een Sonnet- of Haiku-model kiezen. Controleer de officiele modeloverzicht-pagina voor de actuele model-ID's.