Ophaalpatronen implementeren voor RAG-pipelines
Rag (Retrieval-augmented generation) verbetert de antwoorden van taalmodellen door relevante context van uw gegevens te bieden. Het retriever-onderdeel doorzoekt uw knowledge base en retourneert documenten die door de LLM worden gebruikt om nauwkeurige, geaarde antwoorden te genereren. Azure Database for PostgreSQL met pgvector fungeert als een effectief hulpmiddel voor ophalen, waarbij uw document embeddings worden opgeslagen en gelijksoortigheidsquery's worden uitgevoerd die gebruikt worden in de generatiestap.
In deze les wordt beschreven hoe u schema's voor RAG-workloads ontwerpt, segmentquery's implementeert, pijplijnen voor documentopname bouwt, metagegevens van bronvermeldingen retourneert en de kwaliteit van het ophalen evalueert.
Inzicht in de RAG-architectuur en de rol van de retriever
RAG-systemen volgen een patroon in drie stappen:
- Query's insluiten: De vraag van de gebruiker converteren naar een vector met behulp van een insluitmodel
- Ophalen: Zoek in de vectorstore naar documenten die vergelijkbaar zijn met de query-embedding.
- Generatie: Geef de opgehaalde documenten door als context aan een LLM, waarmee een antwoord wordt gegenereerd
PostgreSQL fungeert als de retriever in deze architectuur. De kwaliteit van het ophalen heeft rechtstreeks invloed op de generatiekwaliteit. Als de retriever irrelevante documenten retourneert, ontbreekt de LLM aan de context die nodig is om correct te beantwoorden en kan er onjuiste informatie worden gegenereerd. Als de retriever relevante documenten mist, is het antwoord mogelijk onvolledig.
Voor een juridische onderzoeksassistent betekent effectief ophalen het vinden van de precedenten, statuten en contractclausules van de zaak die betrekking hebben op de vraag van de advocaat. De LLM synthetiseert deze bronnen vervolgens in een coherent antwoord met bronvermeldingen.
Schema's ontwerpen voor RAG-workloads
RAG-toepassingen werken doorgaans met documentsegmenten in plaats van hele documenten. Lange documenten overschrijden de LLM-contextlimieten en kunnen secties bevatten die niet relevant zijn voor een specifieke query. Chunking verdeelt documenten in kleinere, gerichte segmenten die onafhankelijk kunnen worden opgehaald.
Documenten en stukken scheiden
Gebruik twee tabellen: één voor brondocumenten en één voor segmenten met insluitingen:
CREATE TABLE source_documents (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
source_url TEXT,
document_type TEXT,
ingested_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE document_chunks (
id SERIAL PRIMARY KEY,
document_id INTEGER REFERENCES source_documents(id) ON DELETE CASCADE,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
embedding vector(1536),
token_count INTEGER,
start_char INTEGER,
end_char INTEGER,
UNIQUE (document_id, chunk_index)
);
Dit ontwerp biedt verschillende voordelen:
- Flexibel ophalen: U kunt afzonderlijke segmenten ophalen of documentsecties reconstrueren
- Brontracering: Elk segment wordt teruggezet naar de bron voor bronvermeldingen
- Efficiënte opslag: Documentmetagegevens worden eenmaal opgeslagen, niet gedupliceerd per segment
- Eenvoudige updates: Het vervangen van een document betekent het verwijderen van de segmenten en het opnieuw opnemen van het document
Metagegevens toevoegen voor filteren en context
Voeg metagegevens toe die u helpen bij het filteren en reconstrueren van context:
CREATE TABLE document_chunks (
id SERIAL PRIMARY KEY,
document_id INTEGER REFERENCES source_documents(id) ON DELETE CASCADE,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
embedding vector(1536),
token_count INTEGER,
section_title TEXT,
page_number INTEGER,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Sectietitels en paginanummers helpen gebruikers om opgehaalde informatie te verifiëren en de LLM te helpen de context van elk segment te begrijpen.
Indexen maken voor RAG-querypatronen
Vector-overeenkomstenindexen (HNSW of IVFFlat) maken snelle insluitingszoekacties mogelijk, zoals eerder in deze module is besproken. RAG-workloads profiteren ook van B-tree-indexen die de joins en filters versnellen die uniek zijn voor het ophalen van segmenten. Wanneer u segmenten ophaalt, voegt u regelmatig opnieuw toe aan brondocumenten voor citiatiemetadata en filtert of sorteert u op basis van segmentpositie in een document. Zonder deze indexen scant PostgreSQL de hele segmententabel voor elke ophaalquery.
-- B-tree index for document lookups (speeds up JOINs to source_documents)
CREATE INDEX document_chunks_document_id_idx
ON document_chunks (document_id);
-- Composite index for chunk ordering (supports context window queries)
CREATE INDEX document_chunks_doc_chunk_idx
ON document_chunks (document_id, chunk_index);
De samengestelde index op (document_id, chunk_index) is met name belangrijk voor contextvenster-ophaling, waarbij aangrenzende stukjes worden opgehaald om de omringende context te bieden. Zonder dit moet PostgreSQL alle brokken scannen om in een document buren te vinden.
Chunk-ophaling implementeren om context op te bouwen
RAG-query's halen segmenten op die de context van de LLM worden. Het ophaalpatroon dat u kiest, is van invloed op de efficiëntie van zowel de antwoordkwaliteit als het token. U moet drie concurrerende problemen verdelen: voldoende relevante inhoud ophalen, binnen tokenlimieten blijven en voldoende context bieden voor de LLM om elk segment te begrijpen.
De eenvoudigste methode haalt de top-k meest vergelijkbare segmenten rechtstreeks op. Dit werkt goed wanneer uw segmenten op zichzelf staan en uw vragen op een schone manier overeenkomen met afzonderlijke segmenten. Juridische documenten, technische specificaties en verhaalinhoud verspreiden echter vaak concepten over meerdere alinea's. Een segment kan verwijzen naar 'de voorgaande voorwaarden' of 'zoals hierboven beschreven' zonder de tekst waarnaar wordt verwezen.
Voor deze gevallen haalt het ophalen van contextvensters aangrenzende segmenten samen met elke overeenkomst op. Als segment 47 het meest lijkt op de query, haalt u ook segmenten 46 en 48 op. Dit verhoogt het tokengebruik, maar vermindert het risico op onvolledige antwoorden. De afweging is afhankelijk van uw inhoudsstructuur: zeer gestructureerde documenten met duidelijke sectiegrenzen hebben minder omringende context nodig dan het stromen van verhaaltekst.
Tokenlimieten voegen nog een beperking toe. LLM's hebben vaste contextvensters en u hebt ruimte nodig voor de systeemprompt, gebruikersvraag en gegenereerde reactie. Als u 10 segmenten met een gemiddelde van 500 tokens ophaalt, hebt u 5000 tokens verbruikt voordat de LLM één woord schrijft. Houd de cumulatieve tokenaantallen bij tijdens het opvragen om binnen het budget te blijven.
In de volgende query worden deze patronen gecombineerd. Hiermee worden de meest vergelijkbare blokken, samen met de aangrenzende gedeelten, opgehaald, worden cumulatieve tekens bijgehouden, en worden metagegevens over bronvermeldingen opgenomen.
WITH matched_chunks AS (
-- Find the most similar chunks
SELECT id, document_id, chunk_index, embedding <=> $1 AS distance
FROM document_chunks
ORDER BY embedding <=> $1
LIMIT 3
),
context_window AS (
-- Expand to include adjacent chunks for context
SELECT DISTINCT dc.id, dc.document_id, dc.chunk_index, dc.content,
dc.token_count, mc.distance
FROM matched_chunks mc
JOIN document_chunks dc ON dc.document_id = mc.document_id
AND dc.chunk_index BETWEEN mc.chunk_index - 1 AND mc.chunk_index + 1
),
token_limited AS (
-- Track cumulative tokens to respect LLM context limits
SELECT cw.*, sd.title AS source_title, sd.source_url,
SUM(cw.token_count) OVER (ORDER BY cw.distance, cw.chunk_index) AS cumulative_tokens
FROM context_window cw
JOIN source_documents sd ON cw.document_id = sd.id
)
SELECT id, content, source_title, source_url, distance
FROM token_limited
WHERE cumulative_tokens <= 3000
ORDER BY distance, chunk_index;
Pas de parameters aan op basis van uw use-case. Voor een juridische onderzoeksassistent die specifieke vragen over clausules beantwoordt, kan men vijf overeenkomende resultaten zonder contextvenster ophalen. Voor een klantondersteuningsbot die open vragen over productdocumentatie beantwoordt, kunt u drie overeenkomsten met elk twee segmenten omringende context ophalen.
Pijplijnen voor documentopname verwerken
Nieuwe documenten moeten worden verwerkt voordat ze doorzoekbaar zijn. De opnamepijplijn splitst documenten in segmenten, genereert insluitingen en slaat alles op in PostgreSQL.
Hoe u documenten rechtstreeks splitst, is van invloed op de kwaliteit van het ophalen. De juiste segmenteringsstrategie is afhankelijk van uw inhoudsstructuur en querypatronen:
Segmenten met vaste grootte: Splits elke N tekens of tokens. Deze benadering is eenvoudig te implementeren en produceert voorspelbare tokenaantallen, maar kan zinnen of alinea's midden in een gedachte afbreken. Gebruik segmentering met vaste grootte wanneer uw inhoud geen duidelijke structurele grenzen heeft of wanneer u consistente segmentgrootten nodig hebt voor het plannen van tokenbudgetten.
Semantische segmenten: Opsplitsen op natuurlijke grenzen, zoals alinea's, secties of zinnen. Dit behoudt betekenis binnen elk segment, maar produceert variabele grootten. Gebruik semantische segmentering voor gestructureerde documenten waarbij secties volledige gedachten vertegenwoordigen, zoals juridische componenten, API-documentatie of veelgestelde vragen.
Overlappende segmenten: Voeg tekst uit aangrenzende segmenten toe om context bij grenzen te behouden. Zo zorgt een overlapping van 200 tekens tussen segmenten van 1.000 tekens ervoor dat concepten die deelgrenzen overspannen, in ten minste één volledig segment worden weergegeven. Gebruik overlappende gegevens wanneer uw query's mogelijk overeenkomen met inhoud in de buurt van segmentgrenzen.
Voor het scenario van de juridische onderzoeksassistent werkt semantische segmentering op alinea- of sectiegrenzen goed omdat juridische tekst is ingedeeld in discrete componenten en argumenten. Elk segment vertegenwoordigt een volledig juridisch concept dat zelfstandig kan zijn in de context van de LLM.
Nadat u uw documenten hebt gesegmenteerd en insluitingen hebt gegenereerd, voegt u deze efficiënt in met behulp van batchbewerkingen. Voor het ontwerp met twee tabellen moet eerst het brondocument worden ingevoegd om de ID op te halen, en vervolgens moeten alle delen in een enkele multi-rij INSERT worden ingevoegd. Met deze benadering worden rondreizen naar de database geminimaliseerd en blijft de relatie tussen documenten en chunks intact. De meeste insluitings-API's accepteren ook meerdere teksten per aanvraag, zodat u insluitingen kunt genereren voor de segmenten van een heel document in één API-aanroep voordat u het invoegt.
Voor documentupdates is een beslissing vereist: worden documenten geversied of vervangen? Voor de meeste RAG-toepassingen is vervanging eenvoudiger. Verwijder de bestaande segmenten (de ON DELETE CASCADE beperking verwerkt dit automatisch wanneer u het brondocument verwijdert) en neem vervolgens de bijgewerkte inhoud opnieuw op. Deze methode zorgt ervoor dat de resultaten van het ophalen altijd overeenkomen met de huidige documentstatus. Als u versiegeschiedenis nodig hebt, voegt u een version kolom toe aan source_documents en houdt u oude segmenten naast nieuwe segmenten, door te filteren op versie tijdens de query.
HNSW-indexen verwerken verwijderingen zonder opnieuw te bouwen. IVFFlat-indexen kunnen fragmentatie verzamelen na belangrijke wijzigingen, waardoor periodiek opnieuw moet worden opgebouwd om de queryprestaties te behouden.
Ophalen met bronvermeldingen implementeren
Bronvermeldingen transformeren RAG van een zwarte doos naar een transparant onderzoekshulpmiddel. Wanneer de juridische onderzoeksassistent een contractclausule citeert, moet de advocaat dat citaat controleren tegen het oorspronkelijke document. Zonder bronvermeldingsgegevens moeten gebruikers de uitvoer van de LLM blind vertrouwen: een aanzienlijke aansprakelijkheid in juridische, medische of financiële contexten waar nauwkeurigheid essentieel is.
Effectieve bronvermeldingen vereisen meer dan alleen de segmentinhoud. De query's voor het ophalen van het brondocument moeten de titel, URL of document-id, sectiekop en paginanummer retourneren, indien beschikbaar. Met deze metagegevens kunnen bronvermeldingen in de toepassing worden opgemaakt die gebruikers daadwerkelijk kunnen volgen naar de bron. De afstandsscore helpt ook: u kunt bronvermeldingen met een hoge betrouwbaarheid prominent weergeven terwijl u overeenkomsten met een lagere betrouwbaarheid markeert voor gebruikersverificatie.
Er ontstaat een veelvoorkomende uitdaging wanneer meerdere segmenten uit hetzelfde document overeenkomen met een query. Als chunks 12, 15 en 18 van "Arbeidsovereenkomstsjabloon" allemaal verschijnen in de bovenste resultaten, zal het vermelden als drie afzonderlijke bronvermeldingen de respons rommelig maken. In plaats daarvan groepeert u segmenten op brondocument en presenteert u deze als één bronvermelding met meerdere relevante fragmenten. Deze benadering produceert schonere uitvoer en helpt gebruikers de volledige context van elke bron te zien.
De volgende query demonstreert het document-gegroepeerd ophalen. Het rangschikt stukken binnen elk document, beperkt tot drie stukken per document om te voorkomen dat de context overweldigd raakt, en voegt de inhoud samen voor een schonere opmaak van bronvermeldingen.
WITH ranked_chunks AS (
SELECT
dc.*,
sd.title AS document_title,
sd.source_url,
dc.embedding <=> $1 AS distance,
ROW_NUMBER() OVER (PARTITION BY dc.document_id ORDER BY dc.embedding <=> $1) AS rank_in_doc
FROM document_chunks dc
JOIN source_documents sd ON dc.document_id = sd.id
WHERE dc.embedding <=> $1 < 0.5
)
SELECT
document_id,
document_title,
source_url,
array_agg(content ORDER BY chunk_index) AS chunks,
MIN(distance) AS best_distance
FROM ranked_chunks
WHERE rank_in_doc <= 3
GROUP BY document_id, document_title, source_url
ORDER BY best_distance
LIMIT 5;
Uw toepassing kan dit vervolgens opmaken in gebruiksvriendelijke bronvermeldingen:
"De werkgever kan deze overeenkomst met 30 dagen opzeggen." — Arbeidsovereenkomstsjabloon, sectie 4.2, pagina 3
Kwaliteit van ophalen evalueren en verbeteren
De kwaliteit van het ophalen bepaalt de RAG-effectiviteit. Slecht ophalen van informatie leidt tot slechte resultaten, ongeacht hoe capabel uw LLM is. Als de retriever irrelevante segmenten retourneert, verspilt de LLM contextvenstercapaciteit voor nutteloze tekst. Als de retriever relevante segmenten mist, ontbreekt de LLM aan de informatie die nodig is om correct te antwoorden. Door de kwaliteit van het ophalen afzonderlijk van de generatiekwaliteit te meten, kunt u vaststellen waar de RAG-pijplijn moet worden verbeterd.
Met drie metrische gegevens worden verschillende aspecten van het ophalen van prestaties vastgelegd:
Precisie: De fractie van opgehaalde segmenten die daadwerkelijk relevant zijn. Lage precisie betekent dat de LLM irrelevante context ontvangt die deze kan verwarren of ertoe kan leiden dat er onjuiste informatie wordt gegenereerd. Verbeter de precisie door de drempelwaarden voor afstanden aan te scherpen of het aantal opgehaalde segmenten te verminderen.
Recall: De fractie van relevante brokstukken die worden opgehaald. Lage recall betekent dat de LLM belangrijke informatie mist, wat leidt tot onvolledige of onjuiste antwoorden. Verbeter het ophalen door afstandsdrempels te versoepelen, het aantal opgehaalde segmenten te verhogen of door indexparameters zoals
ef_searchaan te passen.Mean Reciprocal Rank (MRR): Hoe hoog het eerste relevante resultaat wordt weergegeven in de gerangschikte lijst. MRR is belangrijk omdat LLM's een eerdere context zwaarder wegen en gebruikers die bronvermeldingen scannen, zien eerst de belangrijkste resultaten. Verbeter MRR door het insluitmodel of het vooraf verwerken van query's te verfijnen.
Als u deze metrische gegevens wilt meten, hebt u een evaluatiegegevensset nodig: een set representatieve query's die zijn gekoppeld aan menselijke beoordelingen over welke segmenten relevant zijn. Voor het bouwen van deze gegevensset is vooraf inspanning vereist: iemand moet voorbeeldquery's uitvoeren en de resultaten labelen, maar het betaalt af door u gegevensgestuurde verbeteringen te laten aanbrengen in plaats van te raden. Begin met 20-50 query's die de typen vragen vertegenwoordigen die uw gebruikers daadwerkelijk stellen. Haal voor elke query de bovenste 10-20 segmenten op en laat een domeinexpert hun relevantie beoordelen op een eenvoudige schaal (irrelevant, enigszins relevant, zeer relevant).
Als deze evaluatieset is ingesteld, kunt u precisie en terugroepwaarde meten bij verschillende cutoffs (precision@5, recall@10) en bijhouden hoe wijzigingen in uw pijplijn van invloed zijn op deze metriek. Voer uw ophaalquery's uit op de evaluatieset, vergelijk de resultaten met de menselijke beoordelingen en bereken de metrische gegevens. De meeste teams automatiseren dit in een scorescript dat wordt uitgevoerd wanneer ze segmenteringsstrategieën wijzigen, modellen insluiten of indexparameters.
Wanneer metrieken onder de streefwaarde vallen, experimenteert u systematisch met parameters die van invloed zijn op de precisie-recall-afweging.
Segmentgrootte: Kleinere segmenten verbeteren de precisie door meer gerichte inhoud te retourneren, maar kunnen de terugroep betasten doordat relevante informatie over meerdere segmenten wordt gefragmenteerd. Grotere segmenten verbeteren het herinneringsvermogen maar verminderen de relevantie.
Segmentoverlapping: Meer overlapping behoudt context over grenzen en helpt bij query's die overeenkomen met inhoud nabij de segmentranden. Overlapping verhoogt echter de opslagvereisten en kan redundante inhoud retourneren.
Model voor insluiten: Verschillende modellen leggen verschillende semantische relaties vast. Een model dat is getraind op juridische tekst, presteert mogelijk beter dan een model voor algemeen gebruik voor juridisch onderzoek. Overweeg domeinspecifieke of verfijnde modellen als algemene modellen minder goed presteren.
Indexparameters: Hogere
ef_search(HNSW) ofprobes(IVFFlat) verbeteren de recall door meer kandidaten te onderzoeken, ten koste van de vertraging van het opvragen. Start met standaardparameters en verhoog deze alleen als het herkenningspercentage onvoldoende is.Drempelwaarden voor afstanden: Strakkere drempelwaarden verbeteren de precisie door marginale overeenkomsten uit te sluiten. Lossere drempelwaarden verbeteren de herinnering door meer kandidaten mee te nemen. Gebruik uw evaluatiegegevensset om de drempelwaarde te vinden die beide metrische gegevens voor uw use-case in balans brengt.