0Pricing
AI Engineering Academy · Lección

Por qué funciona la recuperación en dos etapas

Comprenda el equilibrio entre cobertura y precisión de la recuperación en una sola etapa, y cómo un recuperador rápido y aproximado seguido de un reordenador lento pero preciso combina lo mejor de ambos enfoques.

Por qué funciona la recuperación en dos etapas es una lección gratuita de AI Engineering Academy en CoddyKit. Esta es la lección 1 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 Engineering Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Engineering Academy incluye 4 lecciones en total.

El equilibrio entre cobertura y precisión en la recuperación

Todo sistema de recuperación afronta un equilibrio fundamental: la cobertura mide cuántos documentos relevantes encuentra (¿ha omitido alguno?), mientras que la precisión mide la exactitud de los resultados principales (¿cuántos de los documentos recuperados son realmente relevantes?). Maximizar ambas métricas simultáneamente resulta costoso desde el punto de vista computacional. Los recuperadores rápidos sacrifican precisión en favor de la cobertura; los rankers precisos sacrifican velocidad en favor de la exactitud.

Bi-Encoder frente a Cross-Encoder: la distinción fundamental

Los dos tipos de modelos en el núcleo de la recuperación en dos etapas se diferencian en cómo procesan la consulta y el documento. Un bi-encoder codifica la consulta y cada documento de forma independiente y mide la similitud entre sus vectores: es rápido, pero está limitado por la codificación independiente. Un cross-encoder recibe la consulta y el documento concatenados como una sola entrada, lo que permite una interacción profunda entre ambos: es muy preciso, pero tiene una complejidad O(n) sobre el conjunto de candidatos.

# Bi-encoder: compute query embedding ONCE, compare to all doc embeddings
# O(1) query encoding + O(n) dot products via ANN index = fast
query_vec = embed(query)  # done once
results = vector_index.search(query_vec, top_k=100)  # fast ANN search

# Cross-encoder: re-scores (query, doc) pairs jointly
# O(k) forward passes for k candidate documents = slow but accurate
for doc in results[:100]:
    score = cross_encoder.score(query, doc.text)  # joint scoring

Etapa 1: recuperación rápida y aproximada

La primera etapa es un recuperador rápido, normalmente un bi-encoder con un índice de vecinos más próximos aproximados o un índice BM25, que recupera un conjunto amplio de candidatos (50-200 documentos) con una cobertura alta, pero una precisión moderada. El objetivo no es ser preciso, sino no omitir documentos relevantes. Se busca de forma amplia y se aceptan algunos falsos positivos, sabiendo que la segunda etapa los depurará.

from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# Stage 1: retrieve 100 candidates (high recall, modest precision)
vectorstore = FAISS.from_documents(documents, OpenAIEmbeddings())
coarse_retriever = vectorstore.as_retriever(
    search_kwargs={'k': 100}  # large candidate set
)

candidates = coarse_retriever.invoke(query)
print(f'Stage 1: retrieved {len(candidates)} candidate documents')

Etapa 2: reordenamiento preciso con Cross-Encoder

La segunda etapa toma el conjunto de candidatos de la etapa 1 y vuelve a puntuar cada par (consulta, documento) utilizando un cross-encoder que lee ambos elementos conjuntamente. Como procesa solo entre 50 y 200 candidatos (no todo el corpus), puede asumir el coste de la codificación conjunta. La atención profunda del cross-encoder sobre la entrada concatenada le permite estimar la relevancia real con mucha más precisión que un bi-encoder.

from sentence_transformers import CrossEncoder

reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')

def rerank(query: str, candidates: list[str], top_k: int = 5) -> list[str]:
    # Score each (query, document) pair jointly
    pairs = [[query, doc] for doc in candidates]
    scores = reranker.predict(pairs)

    # Sort by score descending
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, _ in ranked[:top_k]]

candidate_texts = [doc.page_content for doc in candidates]
final_docs = rerank(query, candidate_texts, top_k=5)
print(f'Stage 2: selected top {len(final_docs)} documents after re-ranking')

Por qué funciona esta combinación

El diseño en dos etapas aprovecha una asimetría clave: la búsqueda ANN rápida de la primera etapa escala a millones de documentos en milisegundos, mientras que el cross-encoder preciso de la segunda etapa opera únicamente sobre el pequeño conjunto de candidatos. Así obtiene la escalabilidad de la búsqueda aproximada y la precisión de la puntuación conjunta exacta. La canalización completa es rápida y muy precisa, algo que ninguna de las dos etapas logra por sí sola.

Perfil de latencia de la recuperación en dos etapas

En una canalización típica de dos etapas: la etapa 1 (búsqueda ANN vectorial sobre 1 millón de documentos) tarda entre 5 y 20 ms; la etapa 2 (cross-encoder sobre 100 candidatos) tarda entre 100 y 500 ms, según la longitud de los documentos y el hardware. El presupuesto total de latencia es de 150-600 ms, aceptable para la mayoría de las aplicaciones. La aceleración mediante GPU en la etapa 2 puede reducir el reordenamiento a menos de 30 ms para documentos cortos, lo que hace que la canalización sea competitiva en latencia con la recuperación en una sola etapa para aplicaciones sensibles a la latencia.

import time

def two_stage_search(query, coarse_retriever, reranker, top_k=5):
    t0 = time.perf_counter()

    candidates = coarse_retriever.invoke(query)        # stage 1
    t1 = time.perf_counter()

    candidate_texts = [c.page_content for c in candidates]
    final_docs = rerank(query, candidate_texts, top_k)  # stage 2
    t2 = time.perf_counter()

    print(f'Stage 1 (retrieval): {(t1-t0)*1000:.1f}ms')
    print(f'Stage 2 (re-ranking): {(t2-t1)*1000:.1f}ms')
    print(f'Total: {(t2-t0)*1000:.1f}ms')
    return final_docs

Elección del tamaño adecuado del conjunto de candidatos

El tamaño del conjunto de candidatos de la primera etapa es un hiperparámetro crítico. Si es demasiado pequeño (por ejemplo, 10), es posible que se omitan documentos relevantes antes incluso de comenzar el reordenamiento. Si es demasiado grande (por ejemplo, 500), la latencia de la etapa 2 se dispara. La curva de cobertura en N —cuántos documentos relevantes se capturan con distintos valores de N— orienta esta elección. Los puntos óptimos habituales están entre 50 y 150 candidatos, donde la cobertura está cerca de saturarse, pero la latencia sigue siendo manejable.

def recall_at_n(coarse_retriever, test_queries, golden_relevant, n_values):
    for n in n_values:
        recalls = []
        for query, relevant in zip(test_queries, golden_relevant):
            # Temporarily set k to n
            coarse_retriever.search_kwargs['k'] = n
            results = coarse_retriever.invoke(query)
            retrieved_ids = {r.metadata.get('id') for r in results}
            relevant_found = len(set(relevant) & retrieved_ids)
            recalls.append(relevant_found / len(relevant))
        avg = sum(recalls) / len(recalls)
        print(f'N={n}: recall={avg:.3f}')

Primera etapa híbrida + segunda etapa con Cross-Encoder

La configuración más potente en dos etapas combina un recuperador híbrido (denso + BM25) como primera etapa con un cross-encoder como segunda etapa. La recuperación híbrida maximiza la cobertura de la primera etapa al combinar la coincidencia semántica y la de palabras clave; después, el cross-encoder selecciona con precisión los documentos más relevantes del conjunto combinado de candidatos. Esta configuración alcanza sistemáticamente una calidad de recuperación de vanguardia en las evaluaciones comparativas.

from langchain.retrievers import EnsembleRetriever

# Stage 1: hybrid retrieval for maximum recall
hybrid_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6],
)

# Stage 2: cross-encoder re-ranking for high precision
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

cross_encoder_model = HuggingFaceCrossEncoder(model_name='cross-encoder/ms-marco-MiniLM-L-6-v2')
compressor = CrossEncoderReranker(model=cross_encoder_model, top_n=5)

from langchain.retrievers import ContextualCompressionRetriever
two_stage = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=hybrid_retriever,
)

API comerciales de reordenamiento

Si desea la precisión de un cross-encoder sin gestionar su propio modelo, tanto Cohere Rerank como Jina AI Reranker ofrecen API de reordenamiento alojadas en la nube. Se envían una consulta y una lista de textos de documentos, y se reciben las puntuaciones de relevancia. Estas API utilizan modelos cross-encoder grandes (a menudo de más de 500 millones de parámetros) que superan a los cross-encoders pequeños autoalojados, a cambio de una latencia adicional de API (50-300 ms) y un precio por documento reordenado.

import cohere

co = cohere.Client('YOUR_API_KEY')

def cohere_rerank(query: str, documents: list[str], top_k: int = 5):
    response = co.rerank(
        model='rerank-english-v3.0',
        query=query,
        documents=documents,
        top_n=top_k,
    )
    return [
        {'text': documents[r.index], 'score': r.relevance_score}
        for r in response.results
    ]

final = cohere_rerank(query, candidate_texts, top_k=5)
for doc in final:
    print(f'Score {doc["score"]:.3f}: {doc["text"][:80]}')

Cuándo es excesiva la recuperación en dos etapas

La recuperación en dos etapas añade complejidad y latencia frente a la de una sola etapa. No siempre es necesaria. Para corpus pequeños, de menos de 10.000 documentos, un solo cross-encoder sobre el conjunto completo puede ser suficientemente rápido. En aplicaciones donde sea crítica una latencia inferior a 100 ms y las mejoras de precisión sean modestas, puede ser preferible la recuperación densa en una sola etapa. Utilice dos etapas cuando tenga un corpus grande, requisitos elevados de precisión y pueda asumir una latencia de recuperación de 200-500 ms.

Recuperación en tres etapas para una escala extrema

Para corpus de decenas de millones de documentos, a veces se utiliza una canalización de tres etapas: en la primera etapa se recuperan 10.000 candidatos con ANN, en la segunda se vuelven a clasificar 100 mediante un cross-encoder pequeño y rápido, y en la tercera se vuelven a clasificar 5 mediante un cross-encoder grande y potente. Cada etapa aplica un modelo más costoso y preciso a un conjunto más pequeño. Esta arquitectura se utiliza en motores de búsqueda y sistemas de preguntas y respuestas sobre documentos a gran escala.

Comprobación rápida

Compruebe su comprensión de por qué funciona la recuperación en dos etapas a partir de esta lección.

Resumen de la lección

En esta lección ha aprendido que los bi-encoders son rápidos, pero se limitan a codificar la consulta y el documento de forma independiente; los cross-encoders son precisos gracias a la codificación conjunta, pero demasiado lentos para buscar en todo el corpus; y la recuperación en dos etapas combina ambos: una primera etapa rápida para lograr una alta cobertura, seguida de una segunda etapa precisa para lograr una alta precisión. La primera etapa recupera muchos más candidatos de los necesarios para evitar omitir documentos relevantes. A continuación implementaremos la reclasificación con cross-encoder mediante Cohere y BGE.

Preguntas frecuentes

¿La lección «Por qué funciona la recuperación en dos etapas» es gratis?

Sí — el texto completo de «Por qué funciona la recuperación en dos etapas» 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 Engineering Academy, actualiza a CoddyKit PRO. El curso de AI Engineering Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Por qué funciona la recuperación en dos etapas»?

Comprenda el equilibrio entre cobertura y precisión de la recuperación en una sola etapa, y cómo un recuperador rápido y aproximado seguido de un reordenador lento pero preciso combina lo mejor de am… Practicas AI Engineering Academy 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 Engineering Academy?

No se requiere experiencia previa. AI Engineering Academy 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 1 de 4.

¿Cuánto tiempo toma la lección «Por qué funciona la recuperación en dos etapas»?

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 Engineering Academy?

Sí. Cada lección de AI Engineering Academy 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

  1. Por qué funciona la recuperación en dos etapas
  2. Reordenación con cross-encoders mediante Cohere y BGE
  3. Compresión contextual y filtrado de relevancia
  4. Medición del impacto de la reordenación
← Volver a AI Engineering Academy