RAG avanzado con LangChain: re-ranking y consultas híbridas

By Carlos Montiel | Especialista en IA Empresarial
Publicado: 2026-07-28 | Por: Carlos Montiel | Lectura: ~4 minutos

Un RAG básico con similitud vectorial pura falla en producción más de lo que los demos sugieren. Esta guía cubre las dos técnicas que más elevan la precisión: búsqueda híbrida y re-ranking.

Por qué el RAG "naive" se queda corto

Un pipeline de recuperación basado solo en similitud coseno sobre embeddings tiene un punto ciego conocido: falla con términos exactos —códigos de producto, nombres propios, números de artículo— porque los embeddings capturan semántica, no coincidencia léxica. Un cliente que busca "error E-4021" puede no recuperar el documento correcto si el embedding lo asocia semánticamente con otros errores similares. La solución estándar en sistemas de producción es combinar recuperación léxica (BM25) con recuperación vectorial.

Búsqueda híbrida con EnsembleRetriever

LangChain resuelve esto con `EnsembleRetriever`, que combina múltiples retrievers y fusiona resultados usando Reciprocal Rank Fusion (RRF):

from langchain_community.retrievers import BM25Retriever from langchain.retrievers import EnsembleRetriever bm25_retriever = BM25Retriever.from_documents(documentos) bm25_retriever.k = 10 vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 10}) ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6], ) docs = ensemble_retriever.invoke("error E-4021 en el módulo de facturación")

Los pesos (`weights`) no son universales: en dominios técnicos con mucho vocabulario exacto (logs, códigos de error, SKUs), subir el peso de BM25 a 0.5-0.6 suele mejorar el recall. En dominios más conversacionales, el peso vectorial dominante funciona mejor.

Re-ranking con cross-encoders

La búsqueda híbrida mejora el recall (traer los documentos relevantes al conjunto de candidatos), pero no necesariamente el orden. Para eso se usa un re-ranker: un cross-encoder que evalúa el par (consulta, documento) directamente, mucho más preciso pero más costoso computacionalmente que la búsqueda vectorial, razón por la cual se aplica solo sobre los top-k candidatos, no sobre toda la base.

from langchain.retrievers import ContextualCompressionRetriever from langchain_community.document_compressors import FlashrankRerank compressor = FlashrankRerank(top_n=4) retriever_con_reranking = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=ensemble_retriever, ) docs_finales = retriever_con_reranking.invoke("error E-4021 en el módulo de facturación")

`FlashrankRerank` es una opción ligera que corre localmente sin llamada a API externa, útil cuando la latencia o el costo de un re-ranker gestionado (como Cohere Rerank) no se justifica para el volumen del proyecto. Para mayor precisión, `CohereRerank` vía `langchain-cohere` ofrece mejores resultados en benchmarks multilingües, relevante si el contenido está en español.

Integrando el pipeline completo en LCEL

El pipeline final combina recuperación híbrida, re-ranking y generación en una sola cadena declarativa:

from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser prompt = ChatPromptTemplate.from_template( "Usa únicamente el contexto para responder.\n\nContexto:\n{context}\n\nPregunta: {question}" ) def formatear(docs): return "\n\n".join(f"[Fuente: {d.metadata.get('source')}]\n{d.page_content}" for d in docs) rag_avanzado = ( {"context": retriever_con_reranking | formatear, "question": RunnablePassthrough()} | prompt | ChatOpenAI(model="gpt-4o-mini", temperature=0) | StrOutputParser() )

Incluir la fuente (`metadata.get("source")`) en el contexto formateado no es cosmético: permite que el modelo cite su fuente en la respuesta, lo que reduce alucinaciones percibidas y facilita auditoría en dominios regulados (financiero, salud, legal), algo que en nuestros proyectos con clientes de la región resulta ser un requisito no negociable.

Chunking: la variable que más impacto tiene

Ninguna técnica de re-ranking compensa un chunking mal hecho. Recomendamos `RecursiveCharacterTextSplitter` con `chunk_size` entre 500-800 tokens y `chunk_overlap` de 10-15% para texto general, pero para documentos estructurados (contratos, manuales técnicos) conviene un splitter consciente de la estructura, como `MarkdownHeaderTextSplitter`, que preserva la jerarquía de secciones como metadata en cada chunk, mejorando drásticamente la relevancia del contexto recuperado.

Evaluación: no optimices a ciegas

Cualquier cambio en el pipeline de recuperación debe medirse contra un conjunto de evaluación con preguntas y documentos "gold" conocidos, usando métricas como recall@k y MRR (Mean Reciprocal Rank) antes y después del cambio. LangSmith permite versionar estos datasets de evaluación y correr comparaciones automáticas, tema que cubrimos en detalle en el artículo dedicado a observabilidad.

Carlos Montiel
Arquitecto de Soluciones IA Empresarial
Especialista en LLMs, Agentes y Orquestación
guatemalia.com/#contacto · info@guatemalia.com

¿Necesitas implementar IA en tu empresa?

Carlos Montiel es arquitecto de soluciones IA empresarial. Implementa LLMs, Agentes, RAG y orquestadores en empresas de Guatemala y Latinoamérica. Contáctalo para una consultoría.

Contactar a Carlos Montiel

info@guatemalia.com