RAG Die Niet Verouderd Is: Verse Webgegevens en Vernieuwingslussen

Een RAG-systeem is slechts zo vers als zijn laatste crawl. De wiskunde voor het ophalen is het makkelijke deel — het moeilijke deel is een vernieuwingslus die elke bron opnieuw crawlt, opnieuw opdeelt en opnieuw embed voordat de antwoorden verouderen.

Retrieval-augmented generation is nu in ongeveer de helft van de enterprise AI-systemen — adoptie is gestegen naar 51% in de laatste enquêtes, van 31% een jaar eerder. Toch delen de meeste RAG-projecten dezelfde faalmodus: ze werden eenmalig gevuld, en daarna ging de wereld verder. De wiskunde voor retrieval (embed de query, zoek de vectors, stop de topchunks in de prompt) is het makkelijke, goed gedocumenteerde deel. Het moeilijke deel, het deel dat bepaalt of je assistent betrouwbaar is of zelfverzekerd fout, is versheid — een pijplijn die elke bron opnieuw crawlt, opnieuw opdeelt en opnieuw embed voordat de antwoorden verouderen. Deze gids gaat over het bouwen van die vernieuwingslus, met markdown-first opname en per-bron TTLs als kern.

Verouderde retrieval is erger dan geen retrieval

De hele belofte van RAG is dat het model antwoorden geeft op basis van opgehaalde bewijzen in plaats van bevroren trainingsgegevens. Breek de versheid en je breekt de belofte: een supportbot citeert het retourbeleid van het vorige kwartaal, een prijsassistent vermeldt een beëindigde prijs, een onderzoeksco-piloot mist de update die het antwoord veranderde. Erger nog, het model stelt het met volle overtuiging omdat het opgehaalde chunk gezaghebbend lijkt. Een verouderde index faalt niet luidruchtig — het faalt stil en aannemelijk, wat het gevaarlijkst is. Het actueel houden van het corpus is geen luxe; het is het verschil tussen onderbouwing en hallucinatie met extra stappen.

Neem markdown op, geen ruwe HTML

De kwaliteit van alles stroomafwaarts wordt bepaald bij opname. Ruwe HTML is een ramp voor retrieval — navigatie, cookiebanners, voetteksten en scripttags worden ruischunks die de gelijkeniszoektocht vervuilen en contexttokens verspillen. De schone aanpak is markdown-first: crawl elke pagina en extraheer alleen de primaire inhoud als gestructureerde markdown, waarbij boilerplate wordt weggegooid voordat iets wordt opgedeeld. Onze Scraper API retourneert direct markdown, rendert JavaScript op aanvraag en roteert IP's zodat geblokkeerde pagina's geen gaten in je corpus laten — het vervangt de fragiele Puppeteer-en-BeautifulSoup opname-laag waar de meeste RAG-stacks op hinken. Het web-data-for-LLMs eindpunt bundelt crawl, schoonmaken en structureren in één oproep die hiervoor is gebouwd.

import requests

# markdown-first ingestion: clean text, JS rendered, IPs rotated
def fetch_markdown(url):
    r = requests.post(
        "https://api.quantumproxies.io/scrape",
        headers={"Authorization": f"Bearer {QP_KEY}"},
        json={"url": url, "format": "markdown", "render": True},
        timeout=60,
    )
    doc = r.json()
    return {
        "url": url,
        "markdown": doc["markdown"],       # no nav, no boilerplate
        "fetched_at": doc["fetchedAt"],     # timestamp for TTL logic
        "content_hash": doc["contentHash"], # skip re-embed if unchanged
    }

Voed je RAG-stack met schone webgegevens

Chunk klein, draag metadata

Zodra je schone markdown hebt, splits het in chunks van ongeveer 300 tot 500 tokens — klein genoeg zodat een opgehaald chunk strak on-topic is, groot genoeg om een samenhangende gedachte te behouden. Gebruik een recursieve splitter (LangChain of LlamaIndex leveren er beide een) die zins- en kopgrenzen respecteert in plaats van midden in een woord te knippen. De kritieke, vaak overgeslagen stap is metadata: elke chunk moet zijn bron-URL, een tijdstempel en een inhoudshash dragen. Die metadata maakt versheid mogelijk — retrieval kan filteren op recentheid, en je vernieuwingslus kan zien welke chunks bij een bron horen die net is veranderd.

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1800, chunk_overlap=200,  # ~400 tokens per chunk
)

def to_chunks(doc):
    parts = splitter.split_text(doc["markdown"])
    return [{
        "text": p,
        "source": doc["url"],
        "fetched_at": doc["fetched_at"],   # drives recency filtering
        "ttl": ttl_for(doc["url"]),          # per-source refresh clock
    } for p in parts]

Embed die chunks met een actueel model — text-embedding-3-large, een Cohere-model, of een open Sentence-BERT-variant — en sla de vectors samen met de metadata op in een vectordatabase zoals Pinecone, Weaviate, Milvus of FAISS. Stem gelijkenisdrempels af en, belangrijk, schakel metadatafiltering in zodat een query bijvoorbeeld alleen chunks kan eisen die in de laatste 30 dagen zijn opgehaald voor een prijsvraag.

Diagram van een RAG versheidsvernieuwingslus van markdown crawl via chunk en embed naar indexering en TTL-gedreven her-crawl
De lus die RAG eerlijk houdt: markdown-first crawl, kleine metadatarijke chunks, en een TTL die opnieuw embedden triggert voordat er drift optreedt.

Per-bron TTLs: het hart van versheid

De fout die de meeste teams maken is alles op één schema vernieuwen — een nachtelijke volledige her-crawl die tegelijkertijd te traag is voor prijzen en verspild voor referentiedocumenten. Verschillende bronnen verouderen in verschillende snelheden, dus geef elke een time-to-live die overeenkomt met hoe snel de inhoud daadwerkelijk beweegt. Live prijzen en voorraad hebben misschien een TTL van minuten nodig; nieuws, lijsten en forumthreads een uur; ranglijsten en catalogi een dag; documentatie, beleid en referentiemateriaal een week of langer. Label elke chunk met de TTL van zijn bron, en een planner her-crawlt alleen de bronnen waarvan de klok is verlopen. De inhoudshash is je efficiëntieklep: als de her-crawlde pagina byte-identiek is, sla de embedstap volledig over en reset alleen de tijdstempel.

import time

TTL = {"prices": 300, "news": 3600, "catalog": 86400, "docs": 604800}

def needs_refresh(chunk, now=None):
    now = now or time.time()
    return (now - chunk["fetched_at"]) > chunk["ttl"]

# refresh only what has expired; re-embed only what actually changed
for src in due_sources(TTL):
    doc = fetch_markdown(src.url)
    if doc["content_hash"] != src.last_hash:
        reindex(to_chunks(doc))     # re-chunk + re-embed
    src.touch(doc["content_hash"])  # reset the clock either way

Dit is de lus die een demo scheidt van een productiesysteem. Het houdt retrieval voorspelbaar, besteedt rekenkracht alleen waar de wereld daadwerkelijk is veranderd, en laat je een echte versheidsgarantie per bron maken. Voor het bredere beeld van het onderbouwen van LLMs met live gegevens, zie onze gids voor het voeden van LLMs met verse webgegevens, en voor het omzetten van een enkele site in een citeerbare kennisbank, het bouwen van een support-bot kennisbank.

Gelaagde TTL-banden die minuten voor prijzen en voorraad, per uur voor nieuws, dagelijks voor catalogi en wekelijks voor referentiedocumenten tonen
Ververs elke bron op de klok waarop de inhoud daadwerkelijk beweegt — minuten voor prijzen, een week voor referentiedocumenten.

Haal op voor dekking, niet alleen voor gelijkenis

Een laatste upgrade op querytijd. Pure vectorgelijkenis mist exacte termen — productcodes, foutstrings, eigennamen — die een zoekwoordzoekopdracht perfect vangt. Hybride retrieval mengt de twee, en past dan je recentheidsfilter toe zodat een tijdgevoelige query de voorkeur geeft aan verse chunks. Log de opgehaalde chunks bij elk antwoord zodat je kunt controleren welk bewijs het model daadwerkelijk heeft gebruikt; die traceerbaarheid is wat RAG verklaarbaar maakt, en het is hoe je een verouderd chunk vangt voordat een gebruiker dat doet. Technieken van LLM-gestuurde extractie passen hier goed wanneer je gestructureerde velden uit de opgehaalde pagina's nodig hebt in plaats van proza.

Veelgestelde vragen

Hoe houd ik de gegevens van een RAG-pijplijn vers?

Ken elke bron een time-to-live toe die overeenkomt met hoe snel de inhoud verandert, label elke chunk met een bron-URL en tijdstempel, en voer een planner uit die alleen verlopen bronnen her-crawlt. Gebruik een inhoudshash om het opnieuw embedden van pagina's die niet daadwerkelijk zijn veranderd over te slaan, en schakel recentheidsfiltering in bij retrieval zodat tijdgevoelige queries de voorkeur geven aan verse chunks.

Waarom markdown opnemen in plaats van HTML voor RAG?

Ruwe HTML bevat navigatie, banners, voetteksten en scripts die ruischunks worden, waardoor de gelijkeniszoektocht vervuild raakt en contexttokens verspild worden. Markdown-first opname extraheert alleen de primaire inhoud, zodat chunks schoon en on-topic zijn. Een scraping API die gerenderde markdown retourneert, verwijdert de fragiele aangepaste parsing waar de meeste RAG-stacks op vertrouwen.

Welke chunkgrootte moet ik gebruiken voor RAG?

Ongeveer 300–500 tokens is de gebruikelijke sweet spot: klein genoeg zodat een opgehaald chunk strak relevant blijft, groot genoeg om een volledige gedachte te behouden. Gebruik een recursieve splitter die zins- en kopgrenzen respecteert, voeg een kleine overlap toe zodat context niet verloren gaat bij de naden, en voeg bronmetadata toe aan elke chunk.

Heb ik proxies nodig om een RAG-gegevenspijplijn te bouwen?

Als je het open web op enige schaal crawlt, ja — sites beperken en blokkeren herhaalde verzoeken van één IP, waardoor er gaten in je corpus ontstaan. Een scraping API of roterende proxies houden de crawl betrouwbaar zodat bronnen volledig en op schema vernieuwen, wat precies is waar per-bron TTLs van afhankelijk zijn.

RAG leeft of sterft op versheid. Neem schone markdown op, chunk klein met metadata, en stuur opnieuw embedden aan vanuit per-bron TTLs zodat elke bron op zijn eigen klok ververst. Bouw die lus eenmalig en je assistent antwoordt van het web van vandaag, niet van de momentopname van vorige maand.

Bouw een verse RAG-pijplijn op de Scraper API