Reordenación de fragmentos recuperados
Reordenación mediante un cross-encoder.
Reordenación de fragmentos recuperados es una lección gratuita de AI Prompt Engineering en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Prompt Engineering, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Prompt Engineering incluye 4 lecciones en total.
Por qué hacer re-ranking
La recuperación de primera etapa (densa o dispersa) optimiza el recall a escala: encontrar el chunk de referencia en algún lugar de los 50 primeros. Es rápida, pero aproximada. Después, un re-ranker de segunda etapa reordena esa lista reducida para mejorar la precisión y colocar arriba los chunks realmente relevantes.
Este patrón de recuperar ampliamente y después hacer re-ranking con precisión es la base del RAG avanzado.
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 frente a cross-encoder
Un bi-encoder codifica la consulta y el documento por separado en vectores y los compara mediante el coseno; es rápido y se puede indexar, pero pierde la interacción entre consulta y documento. Un cross-encoder introduce juntos la consulta y un candidato en el modelo y genera una puntuación de relevancia, capturando una interacción detallada.
Los cross-encoders son mucho más precisos, pero no se pueden precalcular, por lo que solo se ejecutan sobre la lista reducida.
# 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 attentionUna pasada de re-ranking con cross-encoder
El re-ranker puntúa cada candidato frente a la consulta y después los ordena de mayor a menor. Como el cross-encoder atiende conjuntamente a la consulta y al documento, resuelve matices de relevancia que el bi-encoder no detecta: negación, necesidades de coincidencia exacta y distinciones entre respuesta y tema.
Esta etapa suele mejorar la precisión de las respuestas más que cualquier otra mejora individual de 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 como re-ranker
Cuando ningún cross-encoder entrenado se adapta a su dominio, un LLM puede hacer el re-ranking. El prompting listwise pide al modelo que ordene una lista de pasajes por relevancia en una sola llamada; el enfoque pointwise puntúa cada pasaje de forma independiente.
Listwise captura comparaciones relativas y utiliza los tokens de forma eficiente, pero debe vigilar el sesgo de posición y garantizar que el análisis de la salida tolere que el modelo omita o duplique IDs.
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]Latencia y tamaño de la lista reducida
El coste del re-ranking aumenta con el tamaño de la lista reducida. Ejecutar un cross-encoder sobre 50 candidatos es mucho más barato que hacerlo sobre 500. Elija un k de primera etapa lo bastante grande para incluir el chunk de referencia (valide el recall@k), pero lo bastante pequeño para poder hacer el re-ranking dentro de su presupuesto de latencia.
Procese las puntuaciones de los pares por lotes y ejecútelas en hardware acelerado; los cross-encoders se paralelizan bien entre pares.
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}Primera etapa híbrida más reordenamiento
La mayor capacidad de recuperación se obtiene con una primera etapa híbrida (dense + BM25 fusionados), que proporciona una única lista corta sin duplicados al re-ranker. La búsqueda densa recupera paráfrasis; la dispersa recupera identificadores exactos; después, el cross-encoder ordena la unión según la relevancia real.
Esta combinación es robusta para distintos tipos de consultas, desde preguntas en lenguaje natural hasta búsquedas literales.
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]Umbrales y límites de puntuación
Las puntuaciones del re-ranker se pueden calibrar. En lugar de tomar siempre los primeros n, aplique un umbral de relevancia: conserve los fragmentos que superen cierta puntuación y, si ninguno cumple el criterio, devuelva honestamente que no hay respuesta. Esto evita llenar el prompt con contenido débilmente relevante.
Ajuste el umbral con un conjunto de validación para equilibrar la cobertura de preguntas respondibles y la inclusión de elementos distractores.
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 keptDiversidad después del reordenamiento
Un ordenamiento basado únicamente en la relevancia puede devolver varios fragmentos casi duplicados del mismo documento y desperdiciar el presupuesto de contexto. Aplique MMR o límites por documento después del reordenamiento para garantizar que el conjunto final cubra aspectos y fuentes distintos.
Esto es importante para las preguntas de varios saltos, cuya respuesta abarca varios documentos.
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 outOrdenamiento para el generador
Después de seleccionar los fragmentos principales, colóquelos para aprovechar la atención. Dado el fenómeno de pérdida en el centro, coloque el fragmento con mayor puntuación al principio o al final del contexto, no enterrado entre los demás.
Algunos pipelines ordenan los fragmentos por relevancia ascendente para que el mejor quede más cerca de la pregunta, siguiendo una estrategia de recencia similar a la de los ejemplos 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 questionEvaluación del re-ranker
Mida el re-ranker con métricas de ordenamiento, principalmente NDCG y MRR, usando la relevancia etiquetada entre consultas y fragmentos, y mida después la precisión de las respuestas. Un re-ranker que mejora NDCG pero no las respuestas podría estar reordenando fragmentos que el generador ya gestionaba correctamente.
Cierre siempre el ciclo evaluando la calidad de la tarea final, no solo las métricas de ordenamiento.
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.0Un pipeline de reordenamiento para producción
De principio a fin: recupere mediante un sistema híbrido una lista corta de 50 candidatos, elimine duplicados, aplique el reordenamiento con un cross-encoder, use un umbral de puntuación, diversifique por documento, ordene los fragmentos para aprovechar la atención y genere la respuesta con citas. Use el umbral como condición para devolver honestamente que no hay respuesta.
Almacene en caché los embeddings y las puntuaciones del re-ranker por cada par (consulta, fragmento) cuando el tráfico se repita, para reducir los costes.
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)Comprobación rápida
Elija la arquitectura de reordenamiento adecuada.
Resumen
Conclusiones principales:
- Recupere ampliamente para maximizar la recuperación y después reordene la lista corta para maximizar la precisión.
- Los cross-encoders puntúan conjuntamente los pares consulta-documento (son precisos, pero no permiten indexación); los bi-encoders son rápidos, pero menos precisos.
- El reordenamiento listwise/pointwise con un LLM es una alternativa de respaldo; vigile el sesgo por posición y el análisis de los resultados.
- Ajuste el tamaño de la lista corta para saturar la recuperación dentro del presupuesto de latencia y combínelo con la recuperación híbrida de la primera etapa.
- Aplique umbrales de puntuación, diversifique por documento, ordene los fragmentos para aprovechar la atención y evalúe con NDCG y la precisión de las respuestas posteriores.
Preguntas frecuentes
¿La lección «Reordenación de fragmentos recuperados» es gratis?
Sí — el texto completo de «Reordenación de fragmentos recuperados» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Prompt Engineering, actualiza a CoddyKit PRO. El curso de AI Prompt Engineering incluye 4 lecciones en total.
¿Qué aprenderé en «Reordenación de fragmentos recuperados»?
Reordenación mediante un cross-encoder. Practicas AI Prompt Engineering con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar AI Prompt Engineering?
No se requiere experiencia previa. AI Prompt Engineering en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Reordenación de fragmentos recuperados»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de AI Prompt Engineering?
Sí. Cada lección de AI Prompt Engineering incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Más allá del RAG ingenuo
- Reordenación de fragmentos recuperados
- Compresión del contexto
- Reescritura de consultas y HyDE