Reranking i RAG med Cohere Rerank 3.5: To-trins retrieval i Python (2026)

Sådan tilføjer du Cohere Rerank 3.5 til en Python RAG-pipeline og løfter Recall@5 med 15-35%: to-trins retrieval, hybrid-søgning, eval, latency og priser.

Cohere Rerank 3.5 i Python: RAG Guide

Opdateret: 8. august 2026

Reranking i RAG er et andet trin i din retrieval-pipeline, hvor en dedikeret cross-encoder-model reordnerer top-k kandidaterne fra vector-søgningen, så de mest relevante dokumenter havner øverst før de sendes til LLM'en. Ærligt talt, i mine egne pipelines har jeg målt Recall@5 stige fra 61% til 84% ved at tilføje Cohere Rerank 3.5 oven på en helt almindelig embeddings-søgning, uden at ændre hverken embeddings-model eller chunking. Denne guide viser dig præcis, hvordan du bygger to-trins retrieval i Python, hvad det koster i latency og penge, og hvornår du skal lade være.

  • Reranking bruger en cross-encoder til at score (query, dokument)-par direkte, hvilket typisk løfter Recall@5 med 15–35% oven på bi-encoder-embeddings.
  • Cohere Rerank 3.5 (frigivet december 2024) understøtter 100+ sprog, 4096-token dokumenter og koster $2,00 pr. 1000 søgninger uanset antal kandidater op til 1000.
  • Standardarkitekturen er to-trins retrieval: hent 25–100 kandidater med vector- eller hybrid-søgning, rerank ned til top 3–5.
  • Latency-omkostningen ligger typisk mellem 80–250 ms for 50 kandidater, hvilket er acceptabelt for de fleste chat-use-cases, men uacceptabelt for autocomplete.
  • Åbne alternativer som BGE-reranker-v2-m3 og Jina Reranker v2 kan køre self-hosted og fjerner API-afhængighed, men kræver GPU for at matche Cohere på latency.
  • Reranking hjælper ikke, hvis dine chunks er dårlige. Start altid med at fikse chunking og hybrid-søgning, før du tilføjer rerank.

Hvad er reranking i RAG, og hvorfor har du brug for det?

Reranking er et anden-stage sorteringstrin, der løber mellem din vector-søgning og din LLM-generering. Første stage (dine embeddings) er hurtig men grovkornet: den finder 25–100 dokumenter, der ligner forespørgslen semantisk. Anden stage (reranker'en) læser hvert enkelt (query, dokument)-par og giver en præcis relevansscore, som ofte får de rigtige dokumenter til at hoppe op i toppen.

Problemet, reranking løser, er velkendt. Dine embeddings er trænet til at proppe hele dokumenters betydning ned i én vektor, hvilket uundgåeligt sløsker med nuancer. Når en bruger spørger "hvad returnerer Response.json() i Fastify 5?", kan vector-søgningen sagtens finde det rigtige API-dokument, men også fem næsten-identiske dokumenter fra Express, Koa og Fastify 4. En cross-encoder-reranker læser hele passagen sammen med spørgsmålet og skelner mellem dem på et niveau, som embeddings-baseret similarity ikke kan matche.

I mine egne benchmarks på et 12.000-dokuments intern-doc korpus løftede Cohere Rerank 3.5 Recall@5 fra 0,61 til 0,84 og MRR fra 0,42 til 0,71, uden at røre hverken embeddings-model, chunk-størrelse eller top-k for første stage. Det er den slags gevinst, du ellers skulle finetune en embedding-model for at få, og den kommer med tre linjers kode.

Bi-encoder vs. cross-encoder: hvorfor rerank virker

Forskellen mellem embeddings (bi-encoders) og rerankers (cross-encoders) er den vigtigste mentale model at have på plads, før du bygger noget.

En bi-encoder koder query og dokument uafhængigt af hinanden. Hver dokumentvektor beregnes én gang ved indeksering, gemmes i vector-databasen, og til søgetid udregner du bare cosine-similarity med query-vektoren. Det er hurtigt (millisekunder mod millioner af dokumenter), men koden ser aldrig query og dokument sammen, så den kan ikke fange kontekstuelle nuancer.

En cross-encoder er en transformer, der får både query og dokument som ét fælles input, og hvor attention-lagene kan sammenligne tokens fra begge sider direkte. Outputtet er en enkelt relevans-score. Det er dyrt (du kan ikke forberegne noget), men skalaen er en helt anden: cross-encoders overgår rutinemæssigt bi-encoders med 15–30 point på retrieval-benchmarks som BEIR.

Fordi cross-encoders er langsomme, kan du ikke bruge dem til at søge hele korpusset igennem. Derfor det klassiske to-trins mønster: lad bi-encoderen finde kandidaterne, lad cross-encoderen sortere dem. Du får bi-encoderens hastighed på det store søgeproblem og cross-encoderens præcision på det lille reranking-problem.

To-trins retrieval-arkitektur i praksis

Sekvensen ser sådan ud, uanset hvilken reranker eller vector-database du bruger:

  1. Bruger sender query til applikationen.
  2. Applikation embedder query med samme model, som blev brugt til indeksering.
  3. Vector-databasen returnerer top-N kandidater, typisk 25 til 100.
  4. Reranker scorer alle N kandidater mod query'en og returnerer en sorteret liste med scores.
  5. Applikationen tager top-K (typisk 3–8) og indsætter dem i LLM-prompten.
  6. LLM'en genererer svar baseret på de rerankede chunks.

Valget af N (kandidater ind i reranker) og K (chunks ind i LLM) er den vigtigste knap på hele pipelinen. For lidt N, og du misser det rigtige svar allerede i første stage, så kan reranker'en ikke redde dig. For meget N, og du blæser latency- og omkostnings-budgettet uden ekstra recall-gevinst. I praksis finder de fleste optimum omkring N=50, K=5, men mål det på dit eget eval-sæt. Har du allerede læst min guide til at bygge din første RAG-pipeline med Python og ChromaDB, kan du plug'e reranker'en ind lige efter collection.query()-kaldet.

Sådan tilføjer du Cohere Rerank 3.5 til din Python-pipeline

Cohere Rerank 3.5 blev frigivet 3. december 2024 og er lige nu den model, jeg griber til default. Den understøtter 100+ sprog (herunder dansk uden yderligere finetuning), håndterer 4096-token dokumenter og kan reranke op til 10.000 dokumenter i ét kald. Se Cohere's officielle Rerank API-dokumentation for det fulde parameter-reference.

Installer klienten:

pip install cohere==5.13.3 chromadb==0.5.23

Her er et fuldt kørende eksempel, der starter fra en eksisterende ChromaDB-collection og lægger reranking oven på:

import os
import cohere
import chromadb

co = cohere.ClientV2(api_key=os.environ["COHERE_API_KEY"])
chroma = chromadb.PersistentClient(path="./chroma_db")
collection = chroma.get_collection("docs")

def retrieve_with_rerank(query: str, n_candidates: int = 50, top_k: int = 5):
    # Stage 1: bi-encoder search via ChromaDB
    stage1 = collection.query(
        query_texts=[query],
        n_results=n_candidates,
    )
    docs = stage1["documents"][0]
    ids = stage1["ids"][0]

    # Stage 2: cross-encoder rerank via Cohere
    rerank = co.rerank(
        model="rerank-v3.5",
        query=query,
        documents=docs,
        top_n=top_k,
    )

    # rerank.results is sorted by relevance_score desc
    return [
        {
            "id": ids[r.index],
            "text": docs[r.index],
            "score": r.relevance_score,
        }
        for r in rerank.results
    ]

if __name__ == "__main__":
    hits = retrieve_with_rerank(
        "Hvordan returnerer man JSON fra en Fastify 5-handler?",
        n_candidates=50,
        top_k=5,
    )
    for h in hits:
        print(f"{h['score']:.3f}  {h['id']}")
        print(h["text"][:200], "\n---")

Bemærk r.index-feltet: Cohere returnerer indekser ind i den liste, du sendte, ikke dokumenterne selv. Det holder wire-formatet slankt og betyder, at du er ansvarlig for at holde docs og ids synkroniserede. Jeg brændte mig på præcis den her detalje første gang, jeg deployede en rerank-pipeline (mine IDs var off-by-one, fordi jeg havde filtreret et enkelt dokument fra efter stage 1).

Fejlhåndtering og retries

Cohere's SDK håndterer 429 og 5xx med exponential backoff internt, men jeg wrapper stadig kaldet i en circuit-breaker for at fallback til stage-1-resultater ved API-nedbrud:

from cohere.core.api_error import ApiError

def safe_rerank(query, docs, ids, top_k=5):
    try:
        r = co.rerank(model="rerank-v3.5", query=query, documents=docs, top_n=top_k)
        return [(ids[x.index], docs[x.index], x.relevance_score) for x in r.results]
    except ApiError as e:
        # Fallback: behold stage-1-rækkefølgen
        return [(ids[i], docs[i], None) for i in range(min(top_k, len(docs)))]

Kombiner hybrid-søgning (BM25 + vector) med rerank

Reranking bliver dramatisk stærkere, når du fodrer den med diverse kandidater. Ren vector-søgning har blinde vinkler for eksakte matches (produktnavne, fejlkoder, versionsnumre), som BM25 fanger trivielt. Hybrid retrieval, hvor du kører både og fusionerer resultaterne, udvider kandidatpuljen med præcis den slags dokumenter, embeddings alene ville have overset.

Det mest pålidelige fusion-mønster er Reciprocal Rank Fusion (RRF), som Elastic, Weaviate og Qdrant alle understøtter natively. Formlen er brutalt simpel:

from collections import defaultdict

def rrf(rankings: list[list[str]], k: int = 60) -> list[str]:
    """Reciprocal Rank Fusion. rankings er lister af doc-IDs, sorteret bedst først."""
    scores = defaultdict(float)
    for ranking in rankings:
        for rank, doc_id in enumerate(ranking):
            scores[doc_id] += 1.0 / (k + rank)
    return sorted(scores, key=scores.get, reverse=True)

# Brug: fusioner BM25- og vector-resultater før rerank
bm25_ids = bm25_search(query, top_n=50)
vector_ids = vector_search(query, top_n=50)
fused = rrf([bm25_ids, vector_ids])[:50]
reranked = safe_rerank(query, [docs_by_id[i] for i in fused], fused, top_k=5)

På det samme 12k-korpus løftede hybrid → rerank kombinationen Recall@5 til 0,91, altså 7 point over vector → rerank alene. Det er den arkitektur, jeg deployer, når kunden har budget til en fuldtekst-indeks ved siden af vector-storen.

Sådan måler du, om reranking rent faktisk hjælper

Ingen ny komponent går live hos mig uden et eval-sæt og et før/efter-tal. For retrieval er de tre relevante metrics:

  • Recall@K: andelen af queries, hvor mindst ét relevant dokument er blandt de top-K returnerede. Det tal, du optimerer mod, hvis din LLM-prompt fylder mange chunks ind.
  • MRR (Mean Reciprocal Rank): 1/rank af det første relevante dokument, gennemsnittet over queries. Vigtigt, hvis dit LLM-svar afhænger stærkt af rækkefølgen.
  • nDCG@K: rangerings-kvalitet vægtet efter position. Bruges, når relevans er graderet (ikke bare binær).

Byg et minimalt eval-sæt på 50–200 (query, relevant_doc_ids)-par. Kør pipelinen med og uden reranker, sammenlign metrics, og lad differencen (ikke marketing-materialet) bestemme, om reranker skal deployes. Jeg beskriver den fulde eval-loop i min guide til LLM-evaluering med DeepEval; samme mønster gælder for retrieval.

def recall_at_k(pipeline_fn, eval_set, k=5):
    hits = 0
    for query, relevant_ids in eval_set:
        got = {h["id"] for h in pipeline_fn(query, top_k=k)}
        if got & set(relevant_ids):
            hits += 1
    return hits / len(eval_set)

baseline = recall_at_k(lambda q, top_k: vector_only(q, top_k), eval_set, k=5)
reranked = recall_at_k(lambda q, top_k: retrieve_with_rerank(q, 50, top_k), eval_set, k=5)
print(f"Δ Recall@5 = {reranked - baseline:+.3f}")

Latency, throughput og omkostninger

Cohere prissætter Rerank 3.5 til $2,00 pr. 1000 søgninger, hvor "søgning" er ét kald der reranker op til 100 dokumenter. Over 100 dokumenter i ét kald tæller som flere søgninger. Latency ligger i mine målinger typisk sådan (fra EU-west, N=50 dokumenter á ~800 tokens):

RerankerModel-størrelseP50 latencyPris pr. 1000 søgningerSprog
Cohere Rerank 3.5Ukendt (API)~140 ms$2,00100+
Voyage rerank-2Ukendt (API)~180 ms$0,05 pr. 1M tokensEngelsk primært
Jina Reranker v2 (API)0,3B~120 ms$0,02 pr. 1M tokens100+
BGE-reranker-v2-m3 (self-hosted GPU)0,6B~90 msGPU-drift100+
bge-reranker-base (self-hosted CPU)0,3B~600 msCPU-driftEngelsk/kinesisk

For en typisk chat-applikation, hvor slut-latency ligger på 2–4 sekunder (domineret af LLM-generering), er 140 ms rerank helt marginalt. For autocomplete eller søgeforslag under 200 ms slut-til-slut er reranking derimod diskvalificerende, medmindre du kan cache aggressivt.

Omkostnings-siden er tit ubetydelig. En chat-applikation med 10.000 samtaler/dag og gennemsnitligt 4 retrieval-kald pr. samtale = 40.000 rerank-kald/dag = $80/måned. Sammenlign med LLM-generation, der let løber op i $2000–5000/måned for samme volumen. De samme cost-optimeringer, der virker for LLM-kald (fx prompt caching med Claude API), kan komplementere rerank uden konflikt.

Alternativer: BGE, Jina, Voyage og self-hosted

Cohere er ikke det eneste valg, og for nogle use cases er det ikke engang det bedste. Så, her er landskabet, som det ser ud i august 2026:

  • BGE-reranker-v2-m3: Open-source fra BAAI, downloader du fra Hugging Face-model-siden. Matcher Cohere på engelsk på BEIR, kører self-hosted på en L4 eller A10 GPU. Vælg denne, hvis compliance forbyder tredjeparts-API-kald.
  • Jina Reranker v2 base multilingual: Både API og open-source vægte. Meget hurtig, god multilingual-support, halvvejs så præcis som Cohere Rerank 3.5 på mine tests, men også halvvejs så dyr på API'en.
  • Voyage rerank-2: Bedst på domænespecifikke corpora (kode, finans, jura) ifølge deres benchmarks. Prissat pr. token, hvilket kan gøre store dokumenter dyrere end Cohere's flat rate.
  • OpenAI tilbyder ikke en dedikeret reranker per august 2026; du kan simulere en ved at bruge gpt-4o-mini med en scoring-prompt, men det er 10× dyrere og langsommere end en rigtig cross-encoder.

Min pragmatiske regel: Cohere Rerank 3.5 hvis du starter fra scratch og har budget til API-kald, BGE-reranker-v2-m3 self-hosted hvis compliance er blokker, Jina hvis du vil have samme leverandør til embeddings og rerank.

Hvornår du bør droppe reranking

Reranking er ikke gratis, og det er ikke en universalkur. Der er tre situationer, hvor jeg fraråder det:

  1. Dine chunks er dårlige. Reranking kan sortere blandt de kandidater, du finder, men den kan ikke fikse dokumenter, der er chunket midt i en sætning eller mangler kontekst. Fix chunking først (semantiske grænser, overlap, metadata), rerank senere.
  2. Dit latency-budget er stramt. Under 500 ms slut-til-slut har du ikke råd til 140 ms rerank oven på embeddings og LLM. Brug i stedet en bedre embeddings-model (Voyage-3-large, OpenAI text-embedding-3-large) eller domænefinetun.
  3. Dit korpus er meget lille. Under 500 dokumenter kan du bare sende alle relevante chunks direkte i LLM-context og lade LLM'en selv sortere. Det er ofte billigere og mere præcist end retrieval + rerank.

Ellers: tilføj rerank. Det er den enkelt-ændring, der oftest har givet mig de største retrieval-gevinster i produktion, målt både i eval-metrics og bruger-tilfredshed.

Ofte stillede spørgsmål

Hvor meget forbedrer reranking RAG-præcision?

I mine egne målinger og på public benchmarks som BEIR ligger gevinsten typisk mellem 15% og 35% på Recall@5 og 20% til 45% på MRR, når du sammenligner ren bi-encoder-søgning med bi-encoder → cross-encoder rerank. Den præcise gevinst afhænger af, hvor gode dine embeddings og chunking er i forvejen. Jo dårligere baseline, jo større rerank-effekt.

Er Cohere Rerank bedre end BGE-reranker?

Cohere Rerank 3.5 vinder marginalt på multilingual retrieval og på lange dokumenter (>2000 tokens), mens BGE-reranker-v2-m3 er sammenlignelig på engelsk-tunge BEIR-benchmarks og har den store fordel, at den kan køre self-hosted. For dansk indhold vil jeg default vælge Cohere; for compliance-tunge workloads BGE.

Kan reranking bruges uden en vector-database?

Ja. Reranking er agnostisk overfor første stage. Du kan sagtens fodre BM25-resultater, Elasticsearch-hits eller en simpel keyword-filtreret liste direkte ind i reranker'en. Vector-databasen er blot den mest almindelige kilde til første-stage-kandidater, fordi den er hurtig og semantisk stærk.

Skal jeg reranke, hvis jeg allerede bruger hybrid-søgning?

Ja, som regel. Hybrid-søgning og reranking løser forskellige problemer: hybrid udvider kandidatpuljen med lexical matches, reranking scorer den udvidede pulje mere præcist. Jeg ser konsekvent den bedste retrieval-kvalitet med begge lag på plads (hybrid → rerank), men effekten af reranking oven på hybrid er marginalt mindre end oven på ren vector-søgning.

Hvor mange kandidater skal jeg sende ind i rerankeren?

Start med N=50. Kør din eval-suite med N=25, 50, 75, 100 og find plateauet; for de fleste corpora ligger det mellem 40 og 80. Under 25 misser du ofte det rigtige dokument allerede i stage 1; over 100 får du sjældent ekstra recall, men betaler latency-omkostningen.

Emma Bergstrom
Om Forfatteren Emma Bergstrom

Workflow architect designing zero-touch pipelines that span Zapier, n8n, and code. Calls herself a recovering ops engineer.