0Pricing
AI Prompt Engineering · Lezione

Riordinamento dei chunk recuperati

Riordinamento con cross-encoder.

Riordinamento dei chunk recuperati è una lezione AI Prompt Engineering gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AI Prompt Engineering, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AI Prompt Engineering include 4 lezioni in totale.

Perché fare il re-ranking

Il retrieval di prima fase (denso o sparso) ottimizza il recall su larga scala: trovare il chunk di riferimento da qualche parte nei primi 50. È veloce, ma grossolano. Un re-ranker di seconda fase riordina quindi quella rosa di candidati per la precisione, portando in cima i chunk realmente rilevanti.

Questo pattern, che consiste nel recuperare in modo ampio e poi eseguire un re-ranking preciso, è la struttura portante del RAG avanzato.

def two_stage(query, k_retrieve=50, k_final=5):
    candidates = first_stage_retrieve(query, k_retrieve)  # high recall
    reranked = rerank(query, candidates)                  # high precision
    return reranked[:k_final]

Bi-encoder e cross-encoder a confronto

Un bi-encoder codifica query e documento separatamente in vettori e li confronta tramite il coseno: è veloce e indicizzabile, ma perde l'interazione tra query e documento. Un cross-encoder passa invece query e candidato insieme attraverso il modello e restituisce un punteggio di rilevanza, cogliendo le interazioni a grana fine.

I cross-encoder sono molto più accurati, ma non possono essere precalcolati, quindi vengono eseguiti solo sulla rosa di candidati.

# Bi-encoder: score = cos(enc(q), enc(d))     -> precomputable
# Cross-encoder: score = model(q, d) -> 0..1   -> per-pair, no index
def cross_encode(query, doc):
    return cross_encoder.predict([(query, doc)])[0]  # joint attention

Un passaggio di re-ranking con cross-encoder

Il re-ranker assegna un punteggio a ogni candidato rispetto alla query, quindi li ordina in senso decrescente. Poiché il cross-encoder presta attenzione congiuntamente a query e documento, risolve aspetti sottili della rilevanza che il bi-encoder non coglie: negazioni, esigenze di corrispondenza esatta e distinzione tra risposta e argomento.

Questa fase aumenta in genere l'accuratezza delle risposte più di qualsiasi altro singolo miglioramento del RAG.

def rerank(query, candidates):
    pairs = [(query, c.text) for c in candidates]
    scores = cross_encoder.predict(pairs)        # batched
    for c, s in zip(candidates, scores):
        c.rerank_score = s
    return sorted(candidates, key=lambda c: c.rerank_score, reverse=True)

LLM come re-ranker

Quando nessun cross-encoder addestrato è adatto al proprio dominio, è possibile usare un LLM per il re-ranking. Il prompting listwise chiede al modello di ordinare un elenco di passaggi in base alla rilevanza con un'unica chiamata; quello pointwise assegna un punteggio a ogni passaggio in modo indipendente.

Il listwise coglie i confronti relativi ed è efficiente in termini di token, ma presti attenzione al bias di posizione e si assicuri che l'analisi dell'output sia robusta rispetto all'omissione o alla duplicazione degli ID da parte del modello.

def llm_listwise(query, candidates):
    passages = '\n'.join(
        '[' + str(i) + '] ' + c.text for i, c in enumerate(candidates)
    )
    prompt = (
        'Rank the passages by relevance to the query. '
        'Return only IDs, most relevant first.\nQuery: ' + query +
        '\n' + passages
    )
    order = parse_ids(llm(prompt, temperature=0))
    return [candidates[i] for i in order]

Latenza e dimensione della rosa di candidati

Il costo del re-ranking cresce con la dimensione della rosa di candidati. Applicare un cross-encoder a 50 candidati è molto meno costoso che applicarlo a 500. Scelga un k della prima fase abbastanza grande da includere il chunk di riferimento (convalidi il recall@k), ma abbastanza piccolo da consentire il re-ranking entro il budget di latenza.

Raggruppi i punteggi delle coppie in batch ed esegua l'operazione su hardware accelerato; i cross-encoder si parallelizzano bene tra le coppie.

def tune_shortlist(eval_set, ks=(20, 50, 100, 200)):
    # find smallest k where recall@k saturates -> rerank fewer pairs
    return {k: (recall_at_k(eval_set, k), rerank_latency(k)) for k in ks}

Prima fase ibrida e reranking

Il recall più elevato si ottiene con una prima fase ibrida (recupero denso e BM25 fusi), che alimenta una singola lista ristretta deduplicata per il reranker. Il recupero denso individua le parafrasi; quello sparso recupera gli identificatori esatti; il cross-encoder ordina quindi l'unione in base alla rilevanza effettiva.

Questa combinazione è robusta con diversi tipi di query, dalle domande in linguaggio naturale alle ricerche di corrispondenze letterali.

def hybrid_then_rerank(query, k_final=6):
    dense = dense_retrieve(query, 50)
    sparse = bm25_retrieve(query, 50)
    fused = dedup(rrf(dense, sparse))     # reciprocal rank fusion
    return rerank(query, fused)[:k_final]

Soglie e limiti di punteggio

I punteggi del reranker possono essere calibrati. Invece di selezionare sempre i primi n risultati, applicare una soglia di rilevanza: mantenere i chunk che superano un determinato punteggio e, se nessuno la supera, restituire esplicitamente l'assenza di una risposta. In questo modo si evita di riempire il prompt con contenuti debolmente pertinenti.

Regolare la soglia su un set di validazione per bilanciare la copertura delle domande a cui è possibile rispondere e l'inclusione di elementi fuorvianti.

def threshold_select(reranked, tau=0.3, max_n=8):
    kept = [c for c in reranked if c.rerank_score >= tau][:max_n]
    if not kept:
        return None        # signal: no sufficiently relevant context
    return kept

Diversità dopo il reranking

Un ordinamento basato esclusivamente sulla rilevanza può restituire diversi chunk quasi duplicati dello stesso documento, sprecando il budget di contesto. Applicare MMR o limiti per documento dopo il reranking, così da garantire che il set finale copra aspetti e fonti distinti.

Questo è importante per le domande multi-hop, la cui risposta si estende su diversi documenti.

def diversify(reranked, max_per_doc=2, k=6):
    out, per_doc = [], {}
    for c in reranked:
        d = c.meta['doc_id']
        if per_doc.get(d, 0) < max_per_doc:
            out.append(c)
            per_doc[d] = per_doc.get(d, 0) + 1
        if len(out) == k:
            break
    return out

Ordinamento per il generatore

Dopo aver selezionato i chunk migliori, posizionarli in modo da sfruttare l'attenzione. Considerato l'effetto della perdita nel mezzo, collocare il singolo chunk con il punteggio più alto all'inizio o alla fine del contesto, senza seppellirlo tra gli altri.

Alcune pipeline ordinano i chunk in base alla rilevanza crescente, così che il migliore si trovi più vicino alla domanda, rispecchiando la strategia di recency dei prompt few-shot.

def order_for_llm(chunks):
    chunks = sorted(chunks, key=lambda c: c.rerank_score)  # ascending
    return chunks                 # most relevant chunk ends up last,
                                  # nearest the trailing question

Valutazione del reranker

Misurare il reranker con metriche di ranking, principalmente NDCG e MRR, sulla rilevanza etichettata tra query e chunk, e successivamente con l'accuratezza della risposta. Un reranker che migliora l'NDCG ma non le risposte potrebbe limitarsi a riordinare chunk che il generatore gestiva già correttamente.

Chiudere sempre il ciclo valutando la qualità del compito finale, non soltanto le metriche di ranking.

import math

def ndcg_at_k(relevances, k):
    dcg = sum(r / math.log2(i + 2) for i, r in enumerate(relevances[:k]))
    ideal = sorted(relevances, reverse=True)
    idcg = sum(r / math.log2(i + 2) for i, r in enumerate(ideal[:k]))
    return dcg / idcg if idcg else 0.0

Una pipeline di reranking per la produzione

Dall'inizio alla fine: recuperare 50 candidati con un approccio ibrido, deduplicare, applicare il reranking con un cross-encoder, applicare una soglia di punteggio, diversificare per documento, ordinare i chunk in base all'attenzione e generare la risposta con le citazioni. Utilizzare la soglia come condizione per restituire esplicitamente l'assenza di una risposta.

Memorizzare nella cache gli embedding e i punteggi del reranker per ogni coppia (query, chunk) quando il traffico ripete le stesse richieste, così da ridurre i costi.

def pipeline(query):
    shortlist = hybrid_then_rerank(query, k_final=20)
    kept = threshold_select(shortlist, tau=0.3, max_n=8)
    if kept is None:
        return 'No relevant information found.'
    ctx = order_for_llm(diversify(kept))
    return generate_with_citations(query, ctx)

Verifica rapida

Scegliere l'architettura di reranking corretta.

Riepilogo

Punti chiave:

  • Recuperare ampiamente per ottenere recall, quindi applicare il reranking alla lista ristretta per ottenere precisione.
  • I cross-encoder valutano congiuntamente le coppie query-documento (accurati, ma non indicizzabili); i bi-encoder sono veloci, ma approssimativi.
  • Il reranking listwise/pointwise con LLM è un'opzione di ripiego; prestare attenzione al bias di posizione e al parsing.
  • Regolare la dimensione della lista ristretta fino a saturare il recall entro il budget di latenza; combinarla con il recupero ibrido nella prima fase.
  • Applicare soglie di punteggio, diversificare per documento, ordinare i chunk in base all'attenzione e valutare con NDCG e accuratezza della risposta a valle.

Domande Frequenti

La lezione «Riordinamento dei chunk recuperati» è gratuita?

Sì — il testo completo di «Riordinamento dei chunk recuperati» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AI Prompt Engineering, passa a CoddyKit PRO. Il corso AI Prompt Engineering include 4 lezioni in totale.

Cosa imparerò in «Riordinamento dei chunk recuperati»?

Riordinamento con cross-encoder. Eserciti AI Prompt Engineering con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AI Prompt Engineering?

Non è richiesta alcuna esperienza precedente. AI Prompt Engineering su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Riordinamento dei chunk recuperati»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AI Prompt Engineering?

Sì. Ogni lezione AI Prompt Engineering include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Oltre il RAG ingenuo
  2. Riordinamento dei chunk recuperati
  3. Compressione del contesto
  4. Riscrittura delle query e HyDE
← Torna a AI Prompt Engineering