Accueil / Articles / Notes pratiques : Réclassement pour RAG : encodeurs croisés, outils de réclassement par LLM et latence

Notes pratiques : Réclassement pour RAG : encodeurs croisés, outils de réclassement par LLM et latence

Guide opérationnel des notes pratiques : Réclassement pour RAG – encodeurs croisés, outils de réclassement basés sur des LLM, et latence ; contrats, vérifications, ainsi que modules de code prêts à l’emploi pour les équipes qui implémentent ce modèle.

3096 mots

Les notes suivantes reconstituent une approche pratique concernant « Reranking pour RAG : Cross-Encoders, LLM Rerankers et compromis de latence ». L’accent est mis sur les contrats, les vérifications et les placeholders pour du code à insérer directement, plutôt que sur une présentation motivante. Lors de l’étude du aperçu général, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans avoir à lire l’ensemble du système.

Le pont entre la récupération et le classement

Le pont entre la récupération des données et le classement fonctionne au mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemple réussi, un cas d’échec ainsi que la note de réversion avant d’élargir le périmètre. Documentez en même temps le parcours optimal et celui de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Fixez un budget de tokens par tour et par session : les outils agents élargissent de manière importante le contexte ; des plafonds stricts empêchent que les démonstrations se transforment en factures inattendues.

Qu’est-ce que le reranking fait réellement

« What Reranking Actually Does » fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un exemple réussi, un cas d’échec et la note de réversion avant d’élargir le champ d’application. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit pointer vers une seule responsabilité et non vers un processus embrouillé. Fixez des limites de tokens par tour et par session. Les outils agents élargissent lourdement le contexte ; des plafonds stricts empêchent que les démonstrations ne se transforment en factures inattendues.

Pourquoi la récupération en première passe est intentionnellement bruyante

Why First-Pass Retrieval is Noisy by Design fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définites des critères de succès et refusez toute complétion partielle silencieuse. Fixez un budget de tokens par tour et par session. Les outils agents élargissent lourdement le contexte ; des plafonds stricts empêchent que les démonstrations ne se transforment en factures inattendues. Why First-Pass Retrieval is Noisy by Design fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.

Les deux principales familles de réclassement

Pour les deux familles principales de réclassement, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure. Préférez des sorties structurées avec validation de schéma plutôt que du texte libre lorsque l’étape suivante consiste en du code ou une appel à outil.

Les cross-encoders constituent le choix par défaut pratique

Puisque les encodeurs croisés constituent le choix par défaut pratique, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un pipeline embrouillé. Préférez des sorties structurées avec validation de schéma à du texte libre lorsque l’étape suivante consiste en du code ou une appel d’outil.

import cohere
import time

def rerank_cross_encoder(
    query: str,
    candidates: list[dict],
    top_n: int = 5,
    model: str = "rerank-v4.0-pro",
) -> list[dict]:
    """
    The practical default for second-stage ranking.
    Passes the query and candidate texts to a dedicated cross-encoder model.
    """
    co = cohere.ClientV2()

    # Extract just the text content for the API call
    documents = [c["content"] for c in candidates]

    resp = co.rerank(
        model=model,
        query=query,
        documents=documents,
        top_n=top_n,
    )

    # Reattach the original metadata and the new score
    reranked = []
    for r in resp.results:
        original_chunk = candidates[r.index]
        reranked.append({
            **original_chunk,
            "rerank_score": r.relevance_score
        })

    return reranked

Les outils de rérankage des LLM sont flexibles mais coûteux

Comme les outils de rérankage pour LLM sont flexibles mais coûteux, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les complétions partielles silencieuses. Préférez des sorties structurées avec validation de schéma plutôt que du texte libre lorsque l’étape suivante consiste en du code ou une appel à outil. Comme les outils de rérankage pour LLM sont flexibles mais coûteux, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système.

>
import anthropic

JUDGE_PROMPT = """\
You are a strict relevance judge. Given a user query and a candidate document chunk,
rate how well the chunk answers the query on a scale of 0 to 10.

Respond with ONLY a JSON object in this exact format:
{"score": <int>, "reason": "<one short sentence>"}

Query: {query}
Chunk: {chunk}"""

async def rerank_llm(
    query: str,
    candidates: list[dict],
    top_n: int = 5,
) -> list[dict]:
    """
    Expensive special forces. Uses an LLM to reason about nuance and completeness.
    """
    client = anthropic.AsyncAnthropic()
    scored = []

    for c in candidates:
        resp = await client.messages.create(
            model="claude-opus-4-6",
            max_tokens=128,
            messages=[{
                "role": "user",
                "content": JUDGE_PROMPT.format(query=query, chunk=c["content"]),
            }],
        )

        import json
        try:
            result = json.loads(resp.content[0].text)
            scored.append({
                **c,
                "rerank_score": result["score"],
                "reason": result.get("reason", "")
            })
        except (json.JSONDecodeError, KeyError):
            # Fallback if the model fails to follow JSON instructions
            scored.append({**c, "rerank_score": 0, "reason": "parse_error"})

    # Sort by the LLM-assigned score descending
    scored.sort(key=lambda x: x["rerank_score"], reverse=True)
    return scored[:top_n]

L’équilibre entre latence et performances

Lorsque vous travaillez sur l’équilibre entre latence et performances, notez d’abord les exigences : entrées requises, signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez à la fois le parcours normal et les scénarios de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Mémorisez les instructions système stables et les schémas des outils. Envoyer à nouveau un préambule identique est une cause fréquente de gaspillage de ressources.

def rerank_with_timing(
    rerank_fn: callable,
    query: str,
    candidates: list[dict],
    top_n: int = 5,
) -> tuple[list[dict], float]:
    """
    Measure the exact cost of the reranking stage.
    """
    t0 = time.perf_counter()

    results = rerank_fn(query, candidates, top_n)

    latency_ms = (time.perf_counter() - t0) * 1000
    return results, latency_ms

Lorsque le réclassement en vaut la peine

Lorsque vous travaillez sur le sujet de savoir quand il est utile de réclasser, notez d’abord les éléments essentiels : les données requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Mémorisez les instructions du système stables ainsi que les schémas des outils. Envoyer à nouveau un préambule identique est une cause fréquente de surconsommation.

Lorsque la réclassification est excessive

Lorsque vous travaillez sur le sujet du « Quand le reranking est excessif », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses. Cachez les instructions du système stable ainsi que les schémas des outils. L’envoi répété d’un préambule identique est une cause fréquente de surconsommation. Lorsque vous travaillez sur le sujet du « Quand le reranking est excessif », notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de garantir l’honnêteté des modifications ultérieures du code. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système.

Modèles d’échec du reranking

La méthode des modes de défaillance de réclassement fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript exemplaire, un cas de défaillance et la note de réversion avant d’élargir le périmètre. Documentez en même temps le parcours optimal et le parcours de récupération. Les tentatives répétées, les contrôles humains et le traitement des messages non livrés font partie intégrante du produit, et non d’améliorations apportées ultérieurement. Fixez des limites budgétaires par tour et par session : les outils agents élargissent de manière importante le contexte ; des plafonds stricts empêchent que les démonstrations se transforment en factures inattendues.

def dedupe_candidates(
    candidates: list[dict],
    similarity_threshold: float = 0.85,
) -> list[dict]:

    seen_tokens: list[set[str]] = []
    deduped = []

    for c in candidates:
        tokens = set(c["content"].lower().split())
        is_dup = False

        for s in seen_tokens:
            # Calculate simple Jaccard similarity
            overlap = len(tokens & s) / max(len(tokens | s), 1)
            if overlap >= similarity_threshold:
                is_dup = True
                break

        if not is_dup:
            deduped.append(c)
            seen_tokens.append(tokens)

    return deduped
from datetime import datetime, timezone

def apply_metadata_boost(
    candidates: list[dict],
    freshness_halflife_days: int = 90,
) -> list[dict]:

    now = datetime.now(timezone.utc)
    boosted = []

    for c in candidates:
        score = c.get("rerank_score", 0.0)

        # Hard penalty for superseded documentation
        if c.get("status") == "superseded":
            score *= 0.4

        # Gradual decay for older documents
        updated = c.get("updated_at")
        if updated:
            age_days = (now - updated).days
            decay_factor = max(0.5, 1 - age_days / (freshness_halflife_days * 2))
            score *= decay_factor

        boosted.append({**c, "rerank_score": score})

    # Sort again based on the adjusted scores
    boosted.sort(key=lambda x: x["rerank_score"], reverse=True)
    return boosted

Comment évaluer correctement le réclassement

« Comment évaluer correctement le reranking » fonctionne le mieux lorsqu’il est considéré comme une surface mesurable. Recueillez un transcript parfait, un cas d’échec et la note de réversion avant d’élargir le périmètre. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’erreur doit pointer vers une seule responsabilité plutôt qu’un processus embrouillé. Fixez un budget de tokens par tour et par session. Les outils agents élargissent lourdement le contexte ; des plafonds stricts empêchent que les démonstrations ne se transforment en factures inattendues.

from dataclasses import dataclass

@dataclass
class RerankEvalCase:
    query: str
    expected_substring: str
    category: str  # e.g., "identifier", "procedure", "troubleshooting"

def eval_reranking(
    cases: list[RerankEvalCase],
    retrieve_fn: callable,
    rerank_fns: dict[str, callable | None],
    top_n: int = 3,
) -> dict:
    """
    Compare multiple reranking strategies against a baseline.
    Measures hit rate at top-N and tracks latency overhead.
    """
    results = {}

    for name, rerank_fn in rerank_fns.items():
        hits = 0
        total_latency = 0.0
        by_type: dict[str, dict] = {}

        for case in cases:
            # Get the exact same starting candidates for every strategy
            candidates = retrieve_fn(case.query)

            if rerank_fn is not None:
                reranked, lat = rerank_with_timing(
                    rerank_fn, case.query, candidates, top_n
                )
                total_latency += lat
            else:
                # Baseline: just take the top-N from first-pass retrieval
                reranked = candidates[:top_n]

            # Check if the expected evidence made it into the final prompt window
            top_contents = [r["content"] for r in reranked]
            found = any(case.expected_substring in c for c in top_contents)
            hits += int(found)

            # Track metrics by query category
            by_type.setdefault(case.category, {"hit": 0, "total": 0})
            by_type[case.category]["total"] += 1
            by_type[case.category]["hit"] += int(found)

        total = len(cases)
        results[name] = {
            "hit_rate": hits / total if total else 0,
            "avg_latency_ms": total_latency / total if total else 0,
            "by_type": {
                t: {**v, "rate": v["hit"] / v["total"]}
                for t, v in by_type.items()
            },
        }

    return results

Une recommandation par défaut pratique

Une Recommandation Par défaut Pratique fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez toute mise en œuvre partielle silencieuse. Fixez un budget de tokens par tour et par session. Les outils agents élargissent lourdement le contexte ; des plafonds stricts empêchent que les démonstrations ne se transforment en factures inattendues. Une Recommandation Par défaut Pratique fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les flags fonctionnels doivent se trouver en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.

def two_stage_retrieve(
    query: str,
    retrieve_fn: callable,
    top_k: int = 20,
    top_n: int = 5,
) -> tuple[list[dict], dict]:
    """
    The complete production pipeline for second-stage ranking.
    """
    t0 = time.perf_counter()

    # Stage 1: Fast hybrid retrieval
    candidates = retrieve_fn(query)[:top_k]

    # Clean up the candidate pool
    candidates = dedupe_candidates(candidates)

    # Stage 2: Cross-encoder rerank
    # We score slightly more than top_n to allow metadata boosts to reorder the edges
    score_limit = min(top_n * 2, len(candidates))
    reranked = rerank_cross_encoder(query, candidates, top_n=score_limit)

    # Apply business logic for freshness and status
    final = apply_metadata_boost(reranked)[:top_n]

    latency = (time.perf_counter() - t0) * 1000

    trace = {
        "query": query,
        "first_pass_count": len(candidates),
        "post_rerank_count": len(reranked),
        "final_count": len(final),
        "latency_ms": latency,
    }

    return final, trace

Que faire ensuite

Pour ce qui vient ensuite, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours idéal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non livrés font partie du produit, et non d’une mise en forme ultérieure. Préférez des sorties structurées avec validation de schéma plutôt que du texte libre lorsque l’étape suivante consiste en du code ou une appel à outil.

Continuer la lecture

Pour « Continuer la lecture », définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé. Préférez des sorties structurées avec validation de schéma à du texte libre lorsque l’étape suivante consiste en du code ou une appel d’outil.

Liste de contrôle opérationnelle

Pour la « Liste de contrôle opérationnelle », définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché.

Enregistrez les temps d’exécution ainsi que le coût des jetons ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce du coût permet d’éviter des factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés.

Préférez des sorties structurées avec validation de schéma plutôt que du texte libre lorsque l’étape suivante consiste à générer du code ou à appeler une outil.

Évaluez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Changer fréquemment les prompts ne résout que rarement un système de récupération inefficace.

Fixez les versions des dépendances et enregistrez le résumé de l’image utilisée pour la démonstration. La reproductibilité vaut mieux que les connaissances propres à un groupe.

Préférez de petites unités testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’erreur doit indiquer une seule responsabilité et non un processus embrouillé.

Au préalable de promouvoir la pile logicielle, figez les versions, conservez une transcription parfaite pour le chemin critique et confirmez les étapes de rollback. Les environnements partagés nécessitent des limites de débit, des vérifications d’attribution et un responsable clair pour la rotation des secrets. Préférez une fiabilité banale à des démonstrations ingénieuses ponctuelles.

Note de lot pour cdeb69942ea2 : gardez les clés du fournisseur hors du répertoire, fixez un plafond pour les tokens par session et stockez les transcriptions à côté des fichiers de configuration d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.

Lorsque vous travaillez sur la note de renforcement n°0, écrivez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables à des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé.

Détail de renforcement 0/916 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.

La note de renforcement 1 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Recueillir un enregistrement idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Enregistrer les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés.

Détail de renforcement 1/916 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.

Pour la note de renforcement 2, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie intégrante du produit, et non d’améliorations ultérieures.

Détail de renforcement 2/916 : mesurez le temps d’exécution, la classe d’erreur et l’utilisation des tokens pour cette note, puis décidez s’il convient de conserver la modification en vous basant sur un ensemble de questions prédéfini plutôt que sur des observations subjectives.

Lorsque vous travaillez sur la note de renforcement 3, écrivez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle garantit l’honnêteté des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez les contrôles de succès et refusez toute mise en œuvre partielle silencieuse.

Détail de renforcement 3/916 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.

La note de renforcement 4 fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Recueillir un enregistrement idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre. Conserver la configuration en dehors du code de l’application. Les fichiers d’environnement, les stocks de secrets et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système.

Détail de renforcement 4/916 : mesurer le temps d’exécution, la classe d’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des anecdotes.