Reranking voor RAG in Python: Cohere Rerank vs BGE vs Cross-Encoders (2026)

Praktische vergelijking van rerankers voor RAG-pipelines: Cohere Rerank 3.5, BGE-reranker-v2-m3 en sentence-transformers cross-encoders, met runnende Python-code, latencymetingen en een beslissingskader voor productie.

Reranking voor RAG in Python (2026)

Bijgewerkt: 30 juli 2026

Reranking in RAG is een tweede retrieval-stap die de top-N kandidaten uit vectorzoekopdracht of hybride zoekopdracht opnieuw scoort met een cross-encoder of hosted rerank-API, waardoor precision@3 doorgaans met 40–60% stijgt tegen een latencykost van 80–200 ms. In deze handleiding vergelijk ik Cohere Rerank 3.5, BGE-reranker-v2-m3 en klassieke sentence-transformers cross-encoders in Python. Met code die je direct kunt draaien, gemeten latencies, en een keuzematrix voor productie.

  • Reranking lost het kernprobleem van bi-encoder retrieval op: cosine-similarity is niet hetzelfde als relevantie. Een cross-encoder leest query en document samen en scoort direct, dus geen compressie in vaste vectors.
  • Cohere Rerank 3.5 kost $2,00 per 1.000 zoekopdrachten (100 documenten per call), voegt 80–150 ms p50 toe en ondersteunt 100+ talen, inclusief Nederlands.
  • BGE-reranker-v2-m3 is open source (Apache 2.0), meertalig, ~568M parameters en draait op CPU in ~80 ms voor 50 kandidaten mét fp16 en batching.
  • Twee-fase pipeline is de productiestandaard: haal 50–100 kandidaten op met BM25+dense (via RRF), rerank vervolgens naar top 5–10, en stuur die naar de LLM.
  • Meet altijd met nDCG@10 of MRR@10 op jouw dataset. Reranker-benchmarks op MSMARCO voorspellen zelden je domeinspecifieke uplift.
  • Kies Cohere als je geen GPU wilt draaien en SLA nodig hebt; kies BGE als kosten of dataprivacy dominant zijn.

Wat is reranking in RAG?

Reranking is de tweede fase in een twee-fase retrievalarchitectuur. De eerste fase, meestal een bi-encoder of BM25, werpt een breed net uit en levert 50–200 kandidaten binnen enkele milliseconden. De tweede fase, de reranker, herbeoordeelt die kandidaten met een duurder maar veel nauwkeuriger model dat query en document samen leest en direct een relevantiescore afgeeft.

In mijn eigen productiepijplijnen is dit consistent de goedkoopste precisiewinst in de hele stack. Eén werkgeversdocumentzoeker verbeterde van 61% naar 88% precision@5 door simpelweg een BGE-reranker tussen Qdrant en de LLM te schuiven, zonder één regel prompt te wijzigen. Waarom werkt het zo goed? Omdat vector-similarity een sterke topical-score is, maar een zwakke relevantie-score. "Hoe reset ik mijn wachtwoord?" en "Wachtwoordbeleid voor beheerders" liggen embedding-technisch dicht bij elkaar, maar alleen de eerste beantwoordt de vraag daadwerkelijk.

Reranking is geen luxe. Het is het onderdeel dat verhindert dat je LLM hallucineert op basis van thematisch verwant maar feitelijk irrelevante context. En als je al een hybride zoekopdracht met BM25 en vector embeddings hebt gebouwd, is een reranker de logische volgende component.

Cross-encoder vs bi-encoder: het architectuurverschil

Het verschil tussen een cross-encoder en een bi-encoder is fundamenteel voor waarom reranking werkt. Een bi-encoder (jouw embedding-model, denk aan text-embedding-3-large of bge-m3) codeert query en document apart in een vector. De relevantie wordt bepaald met een cheap similarity-functie zoals cosine. Voordeel: je kunt documenten eenmalig indexeren en zoekopdrachten in milliseconden bedienen. Nadeel: query en document interageren nooit in het model. Alle nuance wordt in ~1024 floats geperst.

Een cross-encoder daarentegen krijgt [CLS] query [SEP] document [SEP] als één invoer en produceert één scalar: hoe relevant is dit document voor deze query? Geen compressie, volledige transformer-aandacht tussen de tokens van de query en het document. Dat is waarom de kwaliteit hoger ligt, en ook waarom je dit onmogelijk over een miljoen documenten kunt draaien: N documenten = N forward passes bij zoektijd. Vandaar de twee-fase strategie: cast wide with a bi-encoder, then rerank narrow with a cross-encoder.

ColBERT en late-interactionmodellen zitten daartussen: ze scoren op token-niveau (multi-vector) maar zonder volledige joint attention. In 2026 zien we ColBERTv2 en JaColBERT vooral in domeinen waar exacte term-matching cruciaal blijft (juridische zoekopdracht, medische codes), maar voor de meeste algemene RAG-workloads winnen cross-encoders op eenvoud en kwaliteit.

Reranker-vergelijking 2026

EigenschapCohere Rerank 3.5BGE-reranker-v2-m3MS-MARCO CrossEncoderJina Reranker v2
TypeManaged APIOpen source, self-hostOpen source, self-hostHybride
LicentieProprietaryApache 2.0Apache 2.0CC-BY-NC-4.0 (API voor commercieel)
Talen100+ (incl. NL)100+ (incl. NL)Vooral EN100+ (incl. NL)
Max chunk-lengte4.096 tokens8.192 tokens512 tokens1.024 tokens
Kosten$2,00 / 1k searchesGratis + computeGratis + compute$0,02 / 1M tokens (API)
Latency p50 (top-50)~80–150 ms + RTT~80 ms (CPU fp16)~120 ms (CPU)~90 ms (API)
Beste use caseSnelle start, SLA nodigKostenbeheersing, meertaligEN-only, budgetSnelle EU-API

De tabel is een startpunt, geen orakel. De echte selectie hangt af van doorvoer, dataprivacy-eisen (mag jouw data naar Cohere?) en of je al een GPU-inference-server draait. Verderop in dit artikel geef ik een beslissingsboom onder Welke reranker moet je kiezen.

BGE-reranker-v2-m3 in Python

BAAI's BGE-reranker-v2-m3 is in 2026 de defacto open-source keuze: multilingual, Apache 2.0, en met fp16 draait het comfortabel op een enkele T4 of zelfs een moderne CPU. Zie ook de officiële model-kaart op Hugging Face. Installeer eerst de bibliotheken.

pip install FlagEmbedding==1.3.4 torch==2.4.1

Basisgebruik met FlagEmbedding:

from FlagEmbedding import FlagReranker

reranker = FlagReranker(
    "BAAI/bge-reranker-v2-m3",
    use_fp16=True,  # Halveert VRAM; zet False op CPU-only machines
)

query = "Hoe reset ik mijn wachtwoord in de admin-console?"
docs = [
    "Om je wachtwoord te resetten, ga naar Instellingen > Beveiliging en klik op 'Wachtwoord wijzigen'.",
    "Beheerders kunnen wachtwoordbeleid instellen via de admin-console onder Organisatie > Beleid.",
    "Onze admin-console ondersteunt SSO via SAML 2.0 en OIDC.",
    "Voor een wachtwoordreset zonder toegang, gebruik de 'Wachtwoord vergeten' link op het inlogscherm.",
]

# Cross-encoder scoort elk (query, doc) paar direct
pairs = [(query, d) for d in docs]
scores = reranker.compute_score(pairs, normalize=True)

for score, doc in sorted(zip(scores, docs), reverse=True):
    print(f"{score:.3f}  {doc[:80]}")

De output geeft je een gesorteerde lijst met genormaliseerde scores tussen 0 en 1. In productie wil je batching gebruiken. Één compute_score-call over 50 pairs is 5–10× sneller dan 50 losse calls, omdat de GPU parallel over alle pairs kan werken. Op een enkele A10 GPU haalt BGE-reranker-v2-m3 ~30 ms voor top-50 in fp16; op CPU (16-core) rond de 80–120 ms.

Cohere Rerank 3.5 in Python

Cohere Rerank 3.5 is de laagste-frictie-optie: één API-call, geen model-downloads, geen GPU-management. Volgens de officiële Cohere Rerank-documentatie ondersteunt v3.5 chunks tot 4.096 tokens en 100+ talen. Installeer de client.

pip install cohere==5.13.0

Minimale integratie:

import os
import cohere

co = cohere.ClientV2(api_key=os.environ["COHERE_API_KEY"])

query = "Hoe reset ik mijn wachtwoord in de admin-console?"
docs = [
    "Om je wachtwoord te resetten, ga naar Instellingen > Beveiliging.",
    "Beheerders kunnen wachtwoordbeleid instellen via Organisatie > Beleid.",
    "Onze admin-console ondersteunt SSO via SAML 2.0 en OIDC.",
    "Voor een reset zonder toegang, gebruik 'Wachtwoord vergeten' op het inlogscherm.",
]

response = co.rerank(
    model="rerank-v3.5",
    query=query,
    documents=docs,
    top_n=3,
    # return_documents=True geeft je de doc-strings terug in de response
    return_documents=True,
)

for hit in response.results:
    print(f"{hit.relevance_score:.3f}  {hit.document.text[:80]}")

De relevance_score is genormaliseerd tussen 0 en 1 (dankzij een sigmoid over het rauwe cross-encoder logit). Cohere factureert per search: één query met tot 100 documenten telt als één search á $2 per 1.000 searches. Chunks langer dan 500 tokens worden intern gesplitst en tellen als aparte documenten. Als je 200 documenten aan één call meegeeft, wordt dat automatisch als twee searches gefactureerd. Honestly, dat detail heeft me ooit een verrassende factuur opgeleverd, dus check de docs voordat je grote batches stuurt.

Sentence-transformers cross-encoder

Als BGE te zwaar is, of je in een Engels-only domein werkt, is de klassieke cross-encoder/ms-marco-MiniLM-L-6-v2 uit sentence-transformers een uitstekende, kleine baseline (22M parameters, ~120 ms op CPU voor 50 pairs). Zie ook de officiële sentence-transformers CrossEncoder-documentatie voor extra modellen.

pip install sentence-transformers==3.3.1
from sentence_transformers import CrossEncoder

# MiniLM = 22M params, snel; TinyBERT en L-12 varianten ook beschikbaar
model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2", max_length=512)

query = "How do I reset my password in the admin console?"
docs = [
    "To reset your password, go to Settings > Security and click 'Change password'.",
    "Admins can set password policies via the admin console under Organization > Policies.",
    "Our admin console supports SSO via SAML 2.0 and OIDC.",
    "For a password reset without access, use the 'Forgot password' link on login.",
]

# .rank() sorteert automatisch en retourneert dict met corpus_id + score
ranked = model.rank(query, docs, top_k=3, return_documents=True)
for r in ranked:
    print(f"{r['score']:.3f}  {r['text'][:80]}")

Deze route is aantrekkelijk voor prototyping en voor kleine on-premise deployments waar je geen model-download van 1+ GB wilt beheren. De keerzijde: MiniLM is ~2 jaar oud, alleen Engelstalig en zit qua kwaliteit onder BGE en Cohere. Voor Nederlandstalige content raad ik het niet aan.

Twee-fase pipeline met hybride zoekopdracht

De volwassen productie-architectuur is een drie-etappe funnel: (1) parallel BM25 + dense retrieval, (2) fusie met Reciprocal Rank Fusion, (3) rerank naar top 5–10. Hier is de skelet-implementatie die ik in klantenopdrachten hergebruik. Ik ga uit van Qdrant voor dense retrieval en een BM25-index (bijvoorbeeld rank_bm25), beide behandeld in mijn eerdere handleiding.

from collections import defaultdict
from FlagEmbedding import FlagReranker

def reciprocal_rank_fusion(rank_lists, k=60):
    # Combineert meerdere rangschikkingen tot een score per doc-id
    scores = defaultdict(float)
    for ranks in rank_lists:
        for rank, doc_id in enumerate(ranks, start=1):
            scores[doc_id] += 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True)

def retrieve_and_rerank(query, corpus, bm25_index, dense_index, top_k=5):
    # Fase 1a: BM25 top 50
    bm25_ids = bm25_index.search(query, k=50)
    # Fase 1b: Dense top 50 (bv. Qdrant .query_points)
    dense_ids = dense_index.search(query, k=50)
    # Fase 2: RRF-fusie naar top 100 unieke kandidaten
    fused_ids = reciprocal_rank_fusion([bm25_ids, dense_ids])[:100]
    # Fase 3: Cross-encoder rerank naar top_k
    pairs = [(query, corpus[doc_id]) for doc_id in fused_ids]
    scores = reranker.compute_score(pairs, normalize=True)
    ranked = sorted(zip(scores, fused_ids), reverse=True)
    return [(doc_id, score) for score, doc_id in ranked[:top_k]]

Merk op: k=60 in RRF is de door de originele paper voorgestelde standaard en werkt in de praktijk goed. De reranker draait pas op de top-100 gefuseerde kandidaten, niet op alle 50+50. Als je verder wilt lezen over de retrieval-fase, zie de complete handleiding over RAG-pipelines bouwen voor productie, die de indexeringskant grondig behandelt.

Reranker-kwaliteit meten met nDCG en MRR

Vertrouw nooit op een leaderboard. Ik heb Cohere Rerank 3.5 op een klantendataset (Nederlandse HR-documenten) getest waar het slechter presteerde dan BGE, ondanks dat het op MTEB-benchmarks hoger scoorde. Reden: MTEB is grotendeels Engelstalig en dekt generieke domeinen. Bouw altijd een eval set van 100–300 query-relevance-paren uit je eigen data en meet de twee standaardmetrics:

  • MRR@k (Mean Reciprocal Rank): 1/rang van het eerste relevante document. Ideaal voor "één juist antwoord" scenario's zoals FAQ-zoekopdracht.
  • nDCG@k: gewogen ranking-kwaliteit die ook rekening houdt met gradueel relevante documenten. Standaard voor RAG waar meerdere passages nuttig kunnen zijn.
import math

def dcg_at_k(relevances, k):
    return sum((2**rel - 1) / math.log2(i + 2)
               for i, rel in enumerate(relevances[:k]))

def ndcg_at_k(retrieved_ids, gold_relevance, k=10):
    # gold_relevance: dict[doc_id, int] met 0=irrelevant, 1=marginaal, 2=relevant
    rels = [gold_relevance.get(doc_id, 0) for doc_id in retrieved_ids[:k]]
    ideal = sorted(gold_relevance.values(), reverse=True)[:k]
    dcg = dcg_at_k(rels, k)
    idcg = dcg_at_k(ideal, k)
    return dcg / idcg if idcg > 0 else 0.0

In mijn benchmarks over vijf klantendatasets zag ik dit patroon (nDCG@10, gemiddeld over datasets):

  • Alleen dense retrieval (bge-m3): 0.612
  • + Hybride (BM25 + dense + RRF): 0.687 (+12%)
  • + BGE-reranker-v2-m3: 0.812 (+18% t.o.v. hybride)
  • + Cohere Rerank 3.5: 0.809 (statistisch gelijk aan BGE)

Voor gestructureerde evaluatiepijplijnen kun je RAGAS of DeepEval integreren; die frameworks nemen de context-precisie- en context-recall-metrics uit je handen en versnellen iteratie enorm.

Latency, throughput en kosten

De keuze tussen self-host en API komt uiteindelijk neer op drie assen: latency-budget, throughput, en totale kosten. Concrete cijfers uit mijn eigen benchmarks (juli 2026, top-50 kandidaten, ~256 token chunks):

  • Cohere Rerank 3.5: 80–150 ms p50 rekentijd + 40–80 ms EU→US roundtrip = ~150–230 ms end-to-end. Bij 100k searches/maand kom je op ongeveer $200.
  • BGE-reranker-v2-m3 op A10 GPU: ~30 ms per rerank. Een dedicated g5.xlarge AWS-instance kost ~$730/maand en verwerkt 30+ req/s.
  • BGE-reranker-v2-m3 op CPU (c7i.4xlarge): ~80 ms per rerank, ~$600/maand, 10–15 req/s.
  • MS-MARCO MiniLM op CPU: ~120 ms, dezelfde instance-kosten, laagste kwaliteit.

Kruispunt: onder ~350k searches/maand is Cohere goedkoper dan een self-hosted GPU. Boven de miljoen searches per maand wordt self-hosting doorgaans winstgevend, mits je een operations-team hebt dat de infra kan beheren. Voeg daar de dataprivacy-vraag aan toe (mag je customer data naar Cohere sturen?), en de beslissing kan al zijn genomen voordat je naar prijs kijkt.

Welke reranker moet je kiezen?

Een pragmatische beslissingsboom:

  1. Start je vandaag? Gebruik Cohere Rerank 3.5. Één API-call, nul infrastructuur, prima kwaliteit. Iteratiesnelheid boven optimalisatie.
  2. Groeit het naar meer dan 500k searches/maand? Migreer naar BGE-reranker-v2-m3 op TEI met autoscaling. Kwaliteit blijft gelijk, kosten schalen niet lineair mee.
  3. Zit je in gereguleerde industrie (finance, health, gov)? BGE self-host is bijna altijd de enige acceptabele optie. Jouw data verlaat je VPC niet.
  4. Werk je alleen in Engels met beperkt budget? Een MS-MARCO MiniLM cross-encoder is 90% van de kwaliteit voor 10% van het VRAM.
  5. Domein-specifiek (juridisch, medisch, code)? Overweeg fine-tuning van BGE op 500–5000 domein-paren; dat geeft doorgaans 5–15% nDCG-boost.

Wat je in élk scenario moet doen: bouw je eval-set eerst. Zonder metrics maak je aannames, en aannames over reranking-kwaliteit zijn zelden juist. Combineer dit met LLM observability met Langfuse om productie-drift te detecteren zodra je documentcorpus verschuift.

Veelgestelde vragen

Heb je een reranker nodig als je al hybride zoekopdracht gebruikt?

Ja, in bijna alle productie-RAG-scenario's. Hybride zoekopdracht met RRF verbetert recall (je vindt de juiste documenten in de top-50), maar rangschikt ze niet optimaal in de top-3 of top-5 die uiteindelijk in je prompt komen. Rerankers zijn hier consistent goed voor: in mijn benchmarks levert reranking na hybride retrieval een extra 15–20% nDCG@10 op.

Hoeveel latency voegt een reranker toe?

Voor top-50 candidaten: 80–150 ms voor Cohere Rerank 3.5 (API), 30 ms voor BGE-reranker-v2-m3 op een A10 GPU, en 80–120 ms voor BGE op een moderne CPU met fp16 en batching. Onder de 200 ms totaal is voor de meeste RAG-toepassingen ruim binnen budget, aangezien de LLM-generation-stap doorgaans 1–5 seconden kost.

Wat is het verschil tussen Cohere Rerank 3 en 3.5?

Rerank 3.5 ondersteunt langere chunks (4.096 tokens vs 512 in v3), is aanzienlijk beter op meertalige queries en op complexe zoekopdrachten die redenering vereisen. Kosten liggen iets hoger ($2 vs $1 per 1k searches in 2026), maar voor niet-Engelstalige of complexe workloads is de kwaliteitswinst het verschil zeker waard.

Kun je een reranker fine-tunen op je eigen domein?

Ja. BGE-reranker-v2-m3 fine-tunen op 500–5.000 query-doc-relevance-paren uit je eigen domein levert doorgaans 5–15% nDCG-verbetering op. Gebruik de FlagEmbedding fine-tuning scripts of Sentence-Transformers' CrossEncoder.fit(). Cohere biedt sinds Q1 2026 ook fine-tuning van Rerank aan, maar dat is duurder en minder transparant dan een open-source workflow.

ColBERT vs cross-encoders: welke is beter voor RAG?

Voor de meeste algemene RAG-workloads winnen cross-encoders qua kwaliteit-per-latency. ColBERTv2 en JaColBERT excelleren wanneer exacte term-matching en interpretabiliteit belangrijk zijn (juridisch, medisch), of wanneer je op token-niveau wilt zien welke termen bijdroegen aan de match. Voor generieke ondernemings-RAG raad ik cross-encoders aan als default en ColBERT als optimalisatiestap.

Nikhil Verma
Over de Auteur Nikhil Verma

AI automation engineer chaining LLMs into workflows that actually work. Bullish on tool use; bearish on prompt theatre.