Phase 11 - Lesson 07
RAG Avanzado (Chunking, Reranking, Búsqueda Híbrida)
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
El RAG básico recupera los top-k chunks más similares. Esto funciona para preguntas simples. Se desmorona para el razonamiento multi-hop (de varios pasos), consultas ambiguas y grandes corpora. El RAG avanzado es la diferencia entre una demostración que funciona con 10 documentos y un sistema que funciona con 10 millones.
Tipo: Build Lenguajes: Python Prerrequisitos: Fase 11, Lección 06 (RAG) Tiempo: ~90 minutos Relacionado: La Fase 5 · 23 (Estrategias de Chunking para RAG) cubre los seis algoritmos de chunking: recursivo, semántico, sentencia, documento padre, late chunking, recuperación contextual, con benchmarks de Vectara/Anthropic. Esta lección se basa en eso: búsqueda híbrida, reranking, transformación de consultas.
Objetivos de Aprendizaje
- Implementar estrategias avanzadas de chunking (semántico, recursivo, padre-hijo) que preserven la estructura y el contexto del documento
- Construir un pipeline de búsqueda híbrida combinando la coincidencia de palabras clave de BM25 con búsqueda vectorial semántica y un reranker cross-encoder
- Aplicar técnicas de transformación de consultas (HyDE, multi-query, step-back) para mejorar la recuperación en preguntas ambiguas o complejas
- Diagnosticar y corregir fallas comunes de RAG: chunk incorrecto recuperado, respuesta ausente en el contexto, ruptura en el razonamiento multi-hop
El Problema
Construyó un pipeline RAG básico en la Lección 06. Funciona para preguntas directas en un corpus pequeño. Ahora intente estos casos:
Consulta ambigüa: "What was revenue last quarter?" La búsqueda semántica devuelve chunks sobre estrategia de ingresos, proyecciones de ingresos y las reflexiones del CFO sobre el crecimiento de los ingresos. Todos semánticamente similares a la palabra "revenue". Ninguno contiene la cifra real. El chunk correcto dice "$47.2M en Q3 2025" pero usa la palabra "earnings" en lugar de "revenue". El modelo de embedding piensa que "revenue strategy" está más cerca de la consulta que "Q3 earnings were $47.2M".
Pregunta multi-hop: "Which team had the highest customer satisfaction score improvement?" Esto requiere encontrar las puntuaciones de satisfacción de cada equipo, compararlas e identificar la máxima. Ningún chunk individual contiene la respuesta. La información está dispersa en los informes de los equipos.
Problema de corpus grande: Tiene 2 millones de chunks. La respuesta correcta está en el chunk #1,847,293. Su recuperación top-5 extrae los chunks #14, #89,201, #1,200,000, #44 y #901,333. Cercanos en el espacio de embedding, pero ninguno contiene la respuesta. A esta escala, la búsqueda aproximada de vecinos más cercanos (ANN) introduce suficiente error como para que los resultados relevantes queden fuera del top-k.
El RAG básico falla porque la similitud vectorial no es lo mismo que la relevancia. Un chunk puede ser semánticamente similar a una consulta sin ser útil para responderla. El RAG avanzado aborda esto con cuatro técnicas: búsqueda híbrida (agregar coincidencia de palabras clave), reranking (puntuar candidatos de manera más cuidadosa), transformación de consultas (corregir la consulta antes de buscar) y mejor chunking (recuperar con la longitud de fragmento adecuada).
El Concepto
Búsqueda Híbrida: Semántica + Palabras Clave
La búsqueda semántica (similitud vectorial) es buena para comprender el significado. "How do I cancel my subscription?" coincide con "Steps to terminate your plan" aunque no compartan palabras. Pero no detecta coincidencias exactas. El "Error code E-4021" podría no coincidir con un chunk que contenga "E-4021" si el modelo de embedding lo trata como ruido.
La búsqueda por palabras clave (BM25) es lo opuesto. Destaca en coincidencias exactas. "E-4021" coincide perfectamente. Pero "cancel my subscription" devuelve cero resultados si el documento dice "terminate your plan".
La búsqueda híbrida ejecuta ambas y luego fusiona los resultados.
El BM25 (Best Matching 25) es el algoritmo estándar de búsqueda por palabras clave. Ha sido la columna vertebral de los motores de búsqueda desde la década de 1990. La fórmula:
BM25(q, d) = sum over terms t in q:
IDF(t) * (tf(t,d) * (k1 + 1)) / (tf(t,d) + k1 * (1 - b + b * |d| / avgdl))
Donde tf(t,d) es la frecuencia del término t en el documento d, IDF(t) es la frecuencia inversa de documento, |d| es la longitud del documento, avgdl es la longitud promedio del documento, k1 controla la saturación de la frecuencia del término (por defecto 1.2) y b controla la normalización de la longitud (por defecto 0.75).
En términos sencillos: BM25 puntúa los documentos más alto cuando contienen términos de la consulta (especialmente los poco comunes), pero con rendimientos decrecientes para términos repetidos. Un documento con la palabra "revenue" 50 veces no es 50 veces más relevante que uno que la contiene una sola vez.
Fusión de Rango Recíproco (RRF)
Tiene dos listas ordenadas por rango: una de la búsqueda vectorial y otra de BM25. ¿Cómo las combina? La fusión de rango recíproco (Reciprocal Rank Fusion) es el enfoque estándar.
RRF_score(d) = sum over rankings R:
1 / (k + rank_R(d))
Donde k es una constante (típicamente 60) que evita que el resultado mejor clasificado domine.
Un documento clasificado #1 en búsqueda vectorial y #5 en BM25 obtiene: 1/(60+1) + 1/(60+5) = 0.0164 + 0.0154 = 0.0318
Un documento clasificado #3 en búsqueda vectorial y #2 en BM25 obtiene: 1/(60+3) + 1/(60+2) = 0.0159 + 0.0161 = 0.0320
RRF equilibra naturalmente las dos señales. Un documento que se clasifica alto en ambas listas obtiene la mejor puntuación. Un documento que se clasifica #1 en una lista pero está ausente en la otra obtiene una puntuación moderada. Esto es robusto porque utiliza rangos, no puntuaciones brutas, por lo que las diferencias en las distribuciones de puntuación entre los dos sistemas no importan.
Reranking
La recuperación (ya sea vectorial, de palabras clave o híbrida) es rápida pero imprecisa. Utiliza bi-encoders: la consulta y cada documento se incrustan de forma independiente y luego se comparan. Los embeddings se calculan una vez y se guardan en caché. Esto se escala a millones de documentos.
El reranking utiliza cross-encoders: la consulta y un documento candidato se alimentan juntos a un modelo que genera una puntuación de relevancia. El modelo ve ambos textos simultáneamente y puede capturar interacciones detalladas entre ellos. Un cross-encoder puede entender que "¿Cuáles fueron las ganancias del Q3?" es muy relevante para un chunk que contiene "$47.2M en Q3" incluso si un bi-encoder no detectó la conexión.
La desventaja: los cross-encoders son de 100 a 1000 veces más lentos que los bi-encoders porque procesan el par consulta-documento de forma conjunta. No se pueden precalcular las puntuaciones del cross-encoder para un millón de documentos. La solución: recuperar un conjunto de candidatos más grande (los top-50 de la búsqueda híbrida) y luego reranquear con un cross-encoder para obtener el top-5 final.
graph LR
Q["Consulta"] --> H["Búsqueda Híbrida"]
H --> C50["Top 50 candidatos"]
C50 --> RR["Reranker Cross-Encoder"]
RR --> C5["Top 5 resultados finales"]
C5 --> P["Construir prompt"]
P --> LLM["Generar respuesta"]
Modelos de reranking comunes (catálogo de 2026):
- Cohere Rerank 3.5: API gestionada, multilingüe, mejor ganancia de recall en corpora mixtos
- Voyage rerank-2.5: API gestionada, menor latencia de las opciones alojadas
- Jina-Reranker-v2 Multilingual: pesos abiertos, más de 100 idiomas
- bge-reranker-v2-m3: pesos abiertos, línea base sólida
- cross-encoder/ms-marco-MiniLM-L-6-v2: pesos abiertos, se ejecuta en CPU para prototipos
- ColBERTv2 / Jina-ColBERT-v2: rerankers multivectoriales de interacción tardía (late-interaction) — O(tokens) en lugar de O(docs) al momento de la puntuación
Transformación de Consultas
A veces el problema no es la recuperación sino la consulta misma. "What was that thing about the new policy change?" es una pésima consulta de búsqueda. No contiene términos específicos. El embedding es vago. Ningún sistema de recuperación puede encontrar los documentos adecuados con esto.
Reescritura de consultas: reformular la consulta del usuario en una mejor consulta de búsqueda. Un LLM puede hacer esto:
User: "What was that thing about the new policy change?"
Rewritten: "Recent policy changes and updates"
HyDE (Hypothetical Document Embeddings): en lugar de buscar con la consulta, se genera una respuesta hipotética, se genera su embedding y se buscan documentos reales similares.
Query: "What is the refund policy for enterprise?"
Hypothetical answer: "Enterprise customers are eligible for a full refund
within 60 days of purchase. Refunds are pro-rated based on the remaining
subscription period and processed within 5-7 business days."
Incruste la respuesta hipotética y busque documentos reales similares a ella. La intuición: la respuesta hipotética se encuentra más cerca en el espacio de embedding de la respuesta real que la pregunta original. Las preguntas y las respuestas tienen estructuras lingüísticas diferentes. Al generar una respuesta hipotética, se cierra la brecha entre el "espacio de la pregunta" y el "espacio de la respuesta" en el embedding.
HyDE añade una llamada de LLM antes de la recuperación. Esto aumenta la latencia en 500-2000ms. Vale la pena cuando la calidad de la recuperación es baja en consultas brutas.
Chunking Padre-Hijo (Parent-Child Chunking)
El chunking estándar obliga a un compromiso: chunks pequeños para una recuperación precisa, o chunks grandes para un contexto suficiente. El chunking padre-hijo elimina este compromiso.
Indexar chunks pequeños (128 tokens) para la recuperación. Cuando se recupere un chunk pequeño, devuelva su chunk padre (512 tokens) para el prompt. El chunk pequeño coincide con la consulta con precisión. El chunk padre proporciona suficiente contexto para que el LLM genere una buena respuesta.
graph TD
P["Chunk padre (512 tokens)<br/>Sección completa sobre la política de reembolsos"]
C1["Chunk hijo (128 tokens)<br/>Plan estándar: reembolso de 30 dias"]
C2["Chunk hijo (128 tokens)<br/>Enterprise: 60 días prorrateado"]
C3["Chunk hijo (128 tokens)<br/>Tiempo de procesamiento: 5-7 dias"]
C4["Chunk hijo (128 tokens)<br/>Cómo enviar una solicitud"]
P --> C1
P --> C2
P --> C3
P --> C4
Q["Consulta: ¿reembolso enterprise?"] -.->|"coincide con hijo"| C2
C2 -.->|"devolver padre"| P
La consulta "¿reembolso enterprise?" coincide con el chunk hijo C2 con precisión. Pero el prompt recibe el chunk padre P completo, que incluye el contexto circundante sobre el tiempo de procesamiento y el proceso de envío.
Filtrado de Metadatos
Antes de ejecutar la búsqueda vectorial, filtre el corpus por metadados: fecha, origen, categoría, autor, idioma. Esto reduce el espacio de búsqueda y evita resultados irrelevantes.
"¿Qué cambió en la política de seguridad el mes pasado?" solo debería buscar en documentos de los últimos 30 días en la categoría de seguridad. Sin filtrado de metadados, se busca en todo el corpus y se podría recuperar un documento de seguridad de hace 2 años que resulta ser semánticamente similar.
Los sistemas RAG de producción almacenan metadados junto a cada chunk: documento de origen, fecha de creación, categoría, autor, versión. Las bases de datos vectoriales admiten el filtrado previo por metadados antes de la búsqueda de similitud, lo cual es fundamental para el rendimiento a escala.
Evaluación
Ha construido un sistema RAG. ¿Cómo sabe si funciona? Tres métricas:
Relevancia de la recuperación (Recall@k): para un conjunto de preguntas de prueba con documentos relevantes conocidos, ¿qué porcentaje de documentos relevantes aparece en los top-k resultados? Si la respuesta a una pregunta está en el chunk #47, ¿aparece el chunk #47 en el top-5?
Fidelidad (Faithfulness): ¿la respuesta generada se basa en los documentos recuperados? Si los chunks recuperados dicen "plazo de reembolso de 60 días" y el modelo dice "plazo de reembolso de 90 días", esa es una falla de fidelidad. El modelo alucinó a pesar de tener el contexto correcto.
Precisión de la respuesta (Answer correctness): ¿la respuesta generada coincide con la respuesta esperada? Esta es la métrica de extremo a extremo. Combina la calidad de la recuperación y la calidad de la generación.
Una verificación de fidelidad simple: tome cada afirmación en la respuesta generada y verifique que aparezca (en esencia) en los chunks recuperados. Si la respuesta contiene un hecho que no está en ningún chunk recuperado, es probable que sea una alucinación.
graph TD
subgraph "Marco de Evaluación"
Q["Preguntas de prueba<br/>+ respuestas esperadas<br/>+ IDs de docs relevantes"]
Q --> Ret["Evaluación de recuperación<br/>Recall@k: ¿se recuperaron<br/>los docs correctos?"]
Q --> Faith["Evaluación de fidelidad<br/>¿Está la respuesta basada<br/>en los docs recuperados?"]
Q --> Correct["Evaluación de precisión<br/>¿Coincide la respuesta<br/>con la esperada?"]
end
Implementación
Paso 1: Implementación de BM25
import math
from collections import Counter
class BM25:
def __init__(self, k1=1.2, b=0.75):
self.k1 = k1
self.b = b
self.docs = []
self.doc_lengths = []
self.avg_dl = 0
self.doc_freqs = {}
self.n_docs = 0
def index(self, documents):
self.docs = documents
self.n_docs = len(documents)
self.doc_lengths = []
self.doc_freqs = {}
for doc in documents:
words = doc.lower().split()
self.doc_lengths.append(len(words))
unique_words = set(words)
for word in unique_words:
self.doc_freqs[word] = self.doc_freqs.get(word, 0) + 1
self.avg_dl = sum(self.doc_lengths) / self.n_docs if self.n_docs else 1
def score(self, query, doc_idx):
query_words = query.lower().split()
doc_words = self.docs[doc_idx].lower().split()
doc_len = self.doc_lengths[doc_idx]
word_counts = Counter(doc_words)
score = 0.0
for term in query_words:
if term not in word_counts:
continue
tf = word_counts[term]
df = self.doc_freqs.get(term, 0)
idf = math.log((self.n_docs - df + 0.5) / (df + 0.5) + 1)
numerator = tf * (self.k1 + 1)
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avg_dl)
score += idf * numerator / denominator
return score
def search(self, query, top_k=10):
scores = [(i, self.score(query, i)) for i in range(self.n_docs)]
scores.sort(key=lambda x: x[1], reverse=True)
return scores[:top_k]
Paso 2: Fusión de Rango Recíproco
def reciprocal_rank_fusion(ranked_lists, k=60):
scores = {}
for ranked_list in ranked_lists:
for rank, (doc_id, _) in enumerate(ranked_list):
if doc_id not in scores:
scores[doc_id] = 0.0
scores[doc_id] += 1.0 / (k + rank + 1)
fused = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return fused
Paso 3: Pipeline de Búsqueda Híbrida
def hybrid_search(query, chunks, vector_embeddings, vocab, idf, bm25_index, top_k=5, fusion_k=60):
query_emb = tfidf_embed(query, vocab, idf)
vector_results = search(query_emb, vector_embeddings, top_k=top_k * 3)
bm25_results = bm25_index.search(query, top_k=top_k * 3)
fused = reciprocal_rank_fusion([vector_results, bm25_results], k=fusion_k)
return fused[:top_k]
Paso 4: Reranker Simple
En producción, se usaría un modelo cross-encoder. Aquí construimos un reranker que califica la relevancia entre la consulta y el documento utilizando la superposición de palabras, la importancia del término y la coincidencia de frases.
def rerank(query, candidates, chunks):
query_words = set(query.lower().split())
stop_words = {"the", "a", "an", "is", "are", "was", "were", "what", "how",
"why", "when", "where", "do", "does", "for", "of", "in", "to",
"and", "or", "on", "at", "by", "it", "its", "this", "that",
"with", "from", "be", "has", "have", "had", "not", "but"}
query_terms = query_words - stop_words
scored = []
for doc_id, initial_score in candidates:
chunk = chunks[doc_id].lower()
chunk_words = set(chunk.split())
term_overlap = len(query_terms & chunk_words)
query_bigrams = set()
q_list = [w for w in query.lower().split() if w not in stop_words]
for i in range(len(q_list) - 1):
query_bigrams.add(q_list[i] + " " + q_list[i + 1])
bigram_matches = sum(1 for bg in query_bigrams if bg in chunk)
position_boost = 0
for term in query_terms:
pos = chunk.find(term)
if pos != -1 and pos < len(chunk) // 3:
position_boost += 0.5
rerank_score = (
term_overlap * 1.0
+ bigram_matches * 2.0
+ position_boost
+ initial_score * 5.0
)
scored.append((doc_id, rerank_score))
scored.sort(key=lambda x: x[1], reverse=True)
return scored
Paso 5: HyDE (Hypothetical Document Embeddings)
def hyde_generate_hypothesis(query):
templates = {
"what": "The answer to '{query}' is as follows: Based on our documentation, {topic} involves specific policies and procedures that define how the process works.",
"how": "To address '{query}': The process involves several steps. First, you need to initiate the request. Then, the system processes it according to the defined rules.",
"default": "Regarding '{query}': Our records indicate specific details and policies related to this topic that provide a comprehensive answer."
}
query_lower = query.lower()
if query_lower.startswith("what"):
template = templates["what"]
elif query_lower.startswith("how"):
template = templates["how"]
else:
template = templates["default"]
topic_words = [w for w in query.lower().split()
if w not in {"what", "is", "the", "how", "do", "does", "a", "an",
"for", "of", "to", "in", "on", "at", "by", "and", "or"}]
topic = " ".join(topic_words) if topic_words else "this topic"
return template.format(query=query, topic=topic)
def hyde_search(query, chunks, vector_embeddings, vocab, idf, top_k=5):
hypothesis = hyde_generate_hypothesis(query)
hypothesis_emb = tfidf_embed(hypothesis, vocab, idf)
results = search(hypothesis_emb, vector_embeddings, top_k)
return results, hypothesis
Paso 6: Chunking Padre-Hijo
def create_parent_child_chunks(text, parent_size=200, child_size=50):
words = text.split()
parents = []
children = []
child_to_parent = {}
parent_idx = 0
start = 0
while start < len(words):
parent_end = min(start + parent_size, len(words))
parent_text = " ".join(words[start:parent_end])
parents.append(parent_text)
child_start = start
while child_start < parent_end:
child_end = min(child_start + child_size, parent_end)
child_text = " ".join(words[child_start:child_end])
child_idx = len(children)
children.append(child_text)
child_to_parent[child_idx] = parent_idx
child_start += child_size
parent_idx += 1
start += parent_size
return parents, children, child_to_parent
Paso 7: Evaluación de Fidelidad
def evaluate_faithfulness(answer, retrieved_chunks):
answer_sentences = [s.strip() for s in answer.split(".") if len(s.strip()) > 10]
if not answer_sentences:
return 1.0, []
grounded = 0
ungrounded = []
context = " ".join(retrieved_chunks).lower()
for sentence in answer_sentences:
words = set(sentence.lower().split())
stop_words = {"the", "a", "an", "is", "are", "was", "were", "and", "or",
"to", "of", "in", "for", "on", "at", "by", "it", "this", "that"}
content_words = words - stop_words
if not content_words:
grounded += 1
continue
matched = sum(1 for w in content_words if w in context)
ratio = matched / len(content_words) if content_words else 0
if ratio >= 0.5:
grounded += 1
else:
ungrounded.append(sentence)
score = grounded / len(answer_sentences) if answer_sentences else 1.0
return score, ungrounded
def evaluate_retrieval_recall(queries_with_relevant, retrieval_fn, k=5):
total_recall = 0.0
results = []
for query, relevant_indices in queries_with_relevant:
retrieved = retrieval_fn(query, k)
retrieved_indices = set(idx for idx, _ in retrieved)
relevant_set = set(relevant_indices)
hits = len(retrieved_indices & relevant_set)
recall = hits / len(relevant_set) if relevant_set else 1.0
total_recall += recall
results.append({
"query": query,
"recall": recall,
"hits": hits,
"total_relevant": len(relevant_set)
})
avg_recall = total_recall / len(queries_with_relevant) if queries_with_relevant else 0
return avg_recall, results
Cómo usarlo
Con un cross-encoder real para el reranking:
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def rerank_with_cross_encoder(query, candidates, chunks, top_k=5):
pairs = [(query, chunks[doc_id]) for doc_id, _ in candidates]
scores = reranker.predict(pairs)
scored = list(zip([doc_id for doc_id, _ in candidates], scores))
scored.sort(key=lambda x: x[1], reverse=True)
return scored[:top_k]
Con el reranker gestionado de Cohere:
import cohere
co = cohere.Client()
def rerank_with_cohere(query, candidates, chunks, top_k=5):
docs = [chunks[doc_id] for doc_id, _ in candidates]
response = co.rerank(
model="rerank-english-v3.0",
query=query,
documents=docs,
top_n=top_k
)
return [(candidates[r.index][0], r.relevance_score) for r in response.results]
Para HyDE con un LLM real:
import anthropic
client = anthropic.Anthropic()
def hyde_with_llm(query):
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=256,
messages=[{
"role": "user",
"content": f"Write a short paragraph that would be a good answer to this question. Do not say you don't know. Just write what the answer would look like.\n\nQuestion: {query}"
}]
)
return response.content[0].text
Para búsqueda híbrida en producción con Weaviate:
import weaviate
client = weaviate.connect_to_local()
collection = client.collections.get("Documents")
response = collection.query.hybrid(
query="enterprise refund policy",
alpha=0.5,
limit=10
)
El parámetro alpha controla el equilibrio: 0.0 = palabra clave pura (BM25), 1.0 = vector puro, 0.5 = igual peso. La mayoría de los sistemas de producción utilizan un alpha de entre 0.3 y 0.7.
Entregables
Esta lección produce:
outputs/prompt-advanced-rag-debugger.md-- un prompt para diagnosticar y corrigir problemas de calidad de RAGoutputs/skill-advanced-rag.md-- una skill para construir RAG de nivel de producción con búsqueda híbrida y reranking
Ejercicios
Compare BM25 vs búsqueda vectorial vs búsqueda híbrida en los documentos de muestra. Para cada una de las 5 consultas de prueba, registre qué enfoque devuelve el chunk más relevante en la posición #1. La búsqueda híbrida debería ganar en al menos 3 de 5.
Implemente un filtro de metadados. Agregue un campo "category" (categoría) a cada documento (security, billing, api, product). Antes de ejecutar la búsqueda vectorial, filtre los chunks para que solo incluyan la categoría relevante. Pruebe con "What encryption is used?" y verifique que solo busque en los chunks de la categoría de seguridad.
Construya un pipeline HyDE completo utilizando la función de generación simple de la Lección 06. Compare la calidad de la recuperación (relevancia de las top-3) entre la búsqueda directa de consultas y la búsqueda HyDE en las 5 consultas de prueba. HyDE debería mejorar los resultados para consultas vagas.
Implemente la estrategia de chunking padre-hijo en los documentos de muestra. Use child_size=30 y parent_size=100. Busque con los chunks hijos pero devuelva los chunks padres en el prompt. Compare las respuestas generadas con el chunking estándar con chunk_size=50.
Cree un conjunto de datos de evaluación: 10 preguntas con chunks de respuestas conocidos. Mida Recall@3, Recall@5 y Recall@10 para (a) solo búsqueda vectorial, (b) solo BM25, (c) búsqueda híbrida, (d) híbrida + reranking. Grafique los resultados e identifique dónde ayuda más el reranking.
Términos Clave
| Término | Lo que dice la gente | Lo que realmente significa |
|---|---|---|
| BM25 | "Búsqueda por palabras clave" | Un algoritmo de clasificación probabilístico que puntúa documentos por frecuencia de término, frecuencia inversa de documento y normalización de la longitud del documento |
| Búsqueda híbrida | "Lo mejor de ambos mundos" | Ejecutar búsqueda semántica (vectorial) y de palabras clave (BM25) en paralelo, luego fusionar los resultados con fusión de rangos |
| Fusión de Rango Recíproco | "Fusionar listas clasificadas" | Combinar múltiples listas clasificadas sumando 1/(k + rango) para cada documento en todas las listas |
| Reranking | "Puntuación de segunda pasada" | Usar un modelo cross-encoder más costoso para volver a puntuar un conjunto de candidatos de la recuperación inicial |
| Cross-encoder | "Modelo conjunto consulta-documento" | Un modelo que toma una consulta y un documento como una sola entrada, produciendo una puntuación de relevancia; más preciso que los bi-encoders pero demasiado lento para la búsqueda en todo el corpus |
| Bi-encoder | "Modelo de incrustación independiente" | Un modelo que incrusta consultas y documentos de forma independiente; rápido porque las incrustaciones se precalculan, pero menos preciso que los cross-encoders |
| HyDE | "Buscar con una respuesta falsa" | Generar una respuesta hipotética a la consulta, generar su embedding y buscar documentos reales similares a ella |
| Chunking padre-hijo | "Búsqueda pequeña, contexto grande" | Indexar chunks pequeños para una recuperación precisa pero devolver el chunk padre más grande para proporcionar suficiente contexto |
| Filtrado de metadados | "Acotar antes de buscar" | Filtrar documentos por atributos (fecha, origen, categoría) antes de ejecutar la búsqueda vectorial para reducir el espacio de búsqueda |
| Fidelidad | "¿Se mantuvo basado en los datos?" | Si la respuesta generada está respaldada por los documentos recuperados, en lugar de ser alucinada a partir de los datos de entrenamiento del modelo |
Lecturas Adicionales
- Robertson & Zaragoza, "The Probabilistic Relevance Framework: BM25 and Beyond" (2009) -- la referencia definitiva para BM25, explicando los fundamentos probabilísticos detrás de la fórmula
- Cormack et al., "Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods" (2009) -- el artículo original de RRF que demuestra que supera a los métodos de fusión más complejos
- Gao et al., "Precise Zero-Shot Dense Retrieval without Relevance Labels" (2022) -- el artículo de HyDE que demuestra que las incrustaciones de documentos hipotéticos mejoran la recuperación sin datos de entrenamiento
- Nogueira & Cho, "Passage Re-ranking with BERT" (2019) -- demostró que el reranking con cross-encoder sobre BM25 mejora significativamente la calidad de la recuperación
- Khattab et al., "DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines" (2023) -- trata la construcción de prompts y la selección de pesos como un problema de optimización sobre pipelines de recuperación; lea esto para "programar LLMs" en lugar de "diseñar prompts para LLMs".
- Edge et al., "From Local to Global: A Graph RAG Approach to Query-Focused Summarization" (Microsoft Research 2024) -- artículo de GraphRAG: extracción de entidad-relación + detección de comunidad de Leiden para resúmenes enfocados en consultas; la distinción entre recuperación global y local.
- Asai et al., "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection" (ICLR 2024) -- RAG de auto-evaluación con tokens de reflexión; la frontera de los agentes más allá del modelo estático recuperar-y-luego-generar.
- LangChain Query Construction blog -- cómo traducir consultas de lenguaje natural en consultas estructuradas de bases de datos (Text-to-SQL, Cypher) como paso previo a la recuperación.