<!-- Canonical: https://lucioduran.com/es/blog/rag-pipeline-architecture-chunking-reranking-evaluation
Language: es
Content-SHA256: 1da670c97c54d42e7d9eb9fbad3d8e1e59eec9fa60fffd9070768c1fdc3e7bca
-->

AI/ML

# Arquitectura de Pipelines RAG: Estrategias de Chunking, Búsqueda Híbrida, Reranking y Frameworks de Evaluación

2026-04-1616 min lectura

[Lucio Durán](<https://lucioduran.com/es>)
Engineering Manager & AI Solutions Architect

También disponible en: [English](<https://lucioduran.com/blog/rag-pipeline-architecture-chunking-reranking-evaluation>)

## Por Qué la Mayoría de los Pipelines RAG Fallan

El tutorial RAG por defecto, dividir documentos, generar embeddings, recuperar top-k, insertar en el prompt, funciona para demos. Falla en producción por razones predecibles:

- **El chunking destruye contexto.** Los cortes de tamaño fijo rompen oraciones a mitad de pensamiento, separan encabezados de su contenido y pierden la estructura del documento.
- **La búsqueda vectorial pierde keywords.** Los embeddings capturan similitud semántica pero fallan en coincidencias exactas de entidades, acrónimos y terminología de dominio específica.
- **La recuperación top-k es ruidosa.** Recuperar 5 chunks por similitud coseno frecuentemente devuelve 2-3 resultados irrelevantes que diluyen el contexto del prompt.
- **Sin evaluación no hay iteración.** Sin métricas sistemáticas, los equipos optimizan por intuición y despliegan pipelines que se degradan silenciosamente.

Este artículo cubre la arquitectura que aborda cada uno de estos modos de falla.

## Ingesta y Preprocesamiento de Documentos

Antes del chunking, el preprocesamiento de documentos determina la calidad del pipeline más de lo que la mayoría de los equipos creen.

### Normalización de Formatos

Diferentes formatos fuente requieren diferentes estrategias de extracción:

```python
# Extracción de PDF con detección de layout
from unstructured.partition.pdf import partition_pdf

elements = partition_pdf(
    filename="report.pdf",
    strategy="hi_res",           # OCR + detección de layout
    infer_table_structure=True,  # Extraer tablas como HTML
    extract_images_in_pdf=True,  # Extraer imágenes embebidas
)

# Separar tipos de elementos para procesamiento diferenciado
tables = [e for e in elements if e.category == "Table"]
narratives = [e for e in elements if e.category == "NarrativeText"]
titles = [e for e in elements if e.category == "Title"]
```

Decisiones clave en esta etapa:

- **Tablas**: Convertir a markdown o HTML, nunca dividir entre chunks. Un fragmento de tabla es peor que no tener tabla.
- **Bloques de código**: Mantener intactos. Dividir la definición de una función entre chunks hace que ambas mitades sean inútiles.
- **Imágenes**: Extraer captions y texto circundante. Para diagramas, considerar descripciones con modelos de visión.
- **Encabezados**: Preservar la jerarquía, se convierten en metadata que mejora el retrieval.

### Extracción de Metadata

Adjuntar metadata en el momento de la ingesta, no después:

```python
chunk_metadata = {
    "source": "engineering-handbook-v3.pdf",
    "section": "Capítulo 4: Operaciones de Base de Datos",
    "page_numbers": [42, 43],
    "document_type": "handbook",
    "last_updated": "2026-03-15",
    "audience": "backend-engineers",
}
```

Esta metadata habilita el retrieval filtrado, "encontrar chunks sobre operaciones de base de datos del handbook de ingeniería", lo cual mejora dramáticamente la precisión.

## Estrategias de Chunking

El chunking es donde la mayoría de los pipelines pierden calidad silenciosamente. La estrategia correcta depende de la estructura del documento.

### Chunking de Tamaño Fijo

La línea base. Dividir cada N tokens con M tokens de overlap.

```python
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=512,
    chunk_overlap=50,
    separators=["\n\n", "\n", ". ", " ", ""],
    length_function=len,  # o tiktoken para conteo preciso de tokens
)
chunks = splitter.split_text(document_text)
```

**Cuándo funciona**: Texto uniforme sin estructura, transcripciones, logs en texto plano, historiales de chat.

**Cuándo falla**: Documentos estructurados donde dividir en posiciones arbitrarias destruye el significado.

### Chunking Semántico

Agrupa oraciones por similitud de embedding, dividiendo donde los temas cambian:

```python
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

chunker = SemanticChunker(
    OpenAIEmbeddings(model="text-embedding-3-small"),
    breakpoint_threshold_type="percentile",
    breakpoint_threshold_amount=85,  # Dividir en el percentil 85 de distancia
)
chunks = chunker.split_text(document_text)
```

El chunking semántico produce chunks de tamaño variable que respetan los límites temáticos. El tradeoff es velocidad, generar embeddings de cada oración durante la ingesta es 10-50x más lento que la división por tamaño fijo.

### Chunking Consciente de la Estructura del Documento

El enfoque más efectivo para contenido estructurado. Usa la jerarquía del documento para definir los límites de los chunks:

```python
def structure_aware_chunk(elements, max_tokens=512):
    """Dividir por estructura del documento, respetando encabezados y secciones."""
    chunks = []
    current_chunk = []
    current_tokens = 0
    current_header_chain = []

    for element in elements:
        if element.category == "Title":
            # Nueva sección, volcar chunk actual
            if current_chunk:
                chunks.append({
                    "content": "\n".join(current_chunk),
                    "headers": list(current_header_chain),
                    "token_count": current_tokens,
                })
                current_chunk = []
                current_tokens = 0
            current_header_chain.append(element.text)

        elif element.category == "Table":
            # Las tablas van en su propio chunk, nunca se dividen
            if current_chunk:
                chunks.append({
                    "content": "\n".join(current_chunk),
                    "headers": list(current_header_chain),
                    "token_count": current_tokens,
                })
                current_chunk = []
                current_tokens = 0
            chunks.append({
                "content": element.metadata.text_as_html,
                "headers": list(current_header_chain),
                "token_count": count_tokens(element.text),
                "type": "table",
            })

        else:
            elem_tokens = count_tokens(element.text)
            if current_tokens + elem_tokens > max_tokens and current_chunk:
                chunks.append({
                    "content": "\n".join(current_chunk),
                    "headers": list(current_header_chain),
                    "token_count": current_tokens,
                })
                current_chunk = []
                current_tokens = 0
            current_chunk.append(element.text)
            current_tokens += elem_tokens

    if current_chunk:
        chunks.append({
            "content": "\n".join(current_chunk),
            "headers": list(current_header_chain),
            "token_count": current_tokens,
        })

    return chunks
```

### Encabezados Contextuales en los Chunks

Anteponer la jerarquía de secciones antes del embedding es una hipótesis que debe evaluarse contra una referencia con las demás condiciones iguales:

```
## Capítulo 4: Operaciones de Base de Datos > Estrategias de Backup > Recuperación Point-in-Time

Para realizar recuperación point-in-time, configurar el archivado continuo de WAL...
```

El embedding ahora captura tanto el contenido como su posición en la jerarquía del documento.

## Estrategia de Embeddings

### Selección del Modelo

El modelo de embedding determina el techo de la calidad de retrieval.

| Modelo | Dimensiones | MTEB Score | Latencia (p50) | Costo |
| --- | --- | --- | --- | --- |
| text-embedding-3-small | 1536 | 62.3 | 12ms | $0.02/1M tokens |
| text-embedding-3-large | 3072 | 64.6 | 18ms | $0.13/1M tokens |
| voyage-3 | 1024 | 67.1 | 15ms | $0.06/1M tokens |
| bge-m3 (self-hosted) | 1024 | 66.8 | 8ms | Costo de infraestructura |
| Cohere embed-v4 | 1024 | 67.5 | 14ms | $0.10/1M tokens |

**Recomendación práctica**: Para workloads solo en inglés, ` voyage-3 ` o ` Cohere embed-v4 ` entregan la mejor calidad de retrieval. Para deployments multilingües o sensibles al costo, ` bge-m3 ` self-hosted en una sola GPU es difícil de superar.

### Modelos de Interacción Tardía (ColBERT)

ColBERT produce embeddings por token en vez de un único vector, permitiendo matching más granular:

```python
from ragatouille import RAGPretrainedModel

rag = RAGPretrainedModel.from_pretrained("colbert-ir/colbertv2.0")
rag.index(
    collection=documents,
    index_name="knowledge-base",
    max_document_length=512,
)

results = rag.search(query="configuración de backup PITR", k=10)
```

ColBERT supera consistentemente a los modelos de vector único en calidad de retrieval pero requiere 10-50x más almacenamiento por documento. Usalo cuando la calidad de retrieval sea el cuello de botella y el almacenamiento sea barato.

## Arquitectura de Búsqueda Híbrida

La búsqueda vectorial sola no es suficiente. Combinar búsqueda semántica y léxica cubre los puntos ciegos de cada método.

### BM25 + Vector con Reciprocal Rank Fusion

```python
from rank_bm25 import BM25Okapi
import numpy as np

def hybrid_search(query, corpus_chunks, embeddings_index, k=20, alpha=0.5):
    """
    Combinar BM25 y búsqueda vectorial usando Reciprocal Rank Fusion.
    alpha controla el balance: 0.5 = peso igual.
    """
    # Búsqueda vectorial
    query_embedding = embed(query)
    vector_scores = embeddings_index.search(query_embedding, k=k * 2)

    # Búsqueda BM25
    tokenized_corpus = [chunk.split() for chunk in corpus_chunks]
    bm25 = BM25Okapi(tokenized_corpus)
    bm25_scores = bm25.get_scores(query.split())
    bm25_top = np.argsort(bm25_scores)[-k * 2:][::-1]

    # Reciprocal Rank Fusion
    rrf_scores = {}
    rrf_k = 60  # Constante RRF estándar

    for rank, doc_id in enumerate(vector_scores.ids):
        rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + alpha / (rrf_k + rank + 1)

    for rank, doc_id in enumerate(bm25_top):
        rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + (1 - alpha) / (rrf_k + rank + 1)

    # Ordenar por score fusionado
    ranked = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
    return ranked[:k]
```

### Por Qué Funciona la Búsqueda Híbrida

Considerá la consulta: "¿Cuál es el SLA del cluster Redis en EU-WEST-1?"

- **Búsqueda vectorial** encuentra chunks sobre SLAs y clusters Redis en general, semánticamente similares pero potencialmente de la región incorrecta.
- **BM25** encuentra chunks que contienen la cadena exacta "EU-WEST-1", coincidencia léxica.
- **Híbrida** surfacea el chunk que menciona tanto la política de SLA como la región específica.

### Búsqueda Híbrida Nativa en Base de Datos

PostgreSQL con pgvector 0.8+ soporta ambas en una sola consulta:

```sql
-- Búsqueda híbrida con pgvector + tsvector
WITH vector_results AS (
    SELECT id, content, 1 - (embedding <=> $1::vector) AS vector_score
    FROM documents
    WHERE metadata->>'department' = 'engineering'
    ORDER BY embedding <=> $1::vector
    LIMIT 40
),
text_results AS (
    SELECT id, content, ts_rank(search_vector, plainto_tsquery($2)) AS text_score
    FROM documents
    WHERE search_vector @@ plainto_tsquery($2)
      AND metadata->>'department' = 'engineering'
    ORDER BY text_score DESC
    LIMIT 40
),
combined AS (
    SELECT
        COALESCE(v.id, t.id) AS id,
        COALESCE(v.content, t.content) AS content,
        COALESCE(v.vector_score, 0) * 0.5 + COALESCE(t.text_score, 0) * 0.5 AS hybrid_score
    FROM vector_results v
    FULL OUTER JOIN text_results t ON v.id = t.id
)
SELECT * FROM combined ORDER BY hybrid_score DESC LIMIT 10;
```

## Reranking

El reranking es la mejora de mayor impacto que podés hacer a un pipeline RAG. Los embeddings de bi-encoder son rápidos pero imprecisos. Los rerankers cross-encoder son lentos pero precisos. La arquitectura usa ambos:

1. **Recuperar** 50-100 candidatos con búsqueda híbrida rápida (~10ms)
2. **Rerankear** los top 20-30 con un cross-encoder (latencia a medir)
3. **Seleccionar** los top 5-8 para el contexto del LLM

### Reranking con Cross-Encoder

```python
from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-v2-m3", max_length=512)

def rerank(query, candidates, top_k=5):
    """Rerankear candidatos usando cross-encoder."""
    pairs = [[query, candidate["content"]] for candidate in candidates]
    scores = reranker.predict(pairs)

    for i, candidate in enumerate(candidates):
        candidate["rerank_score"] = float(scores[i])

    ranked = sorted(candidates, key=lambda x: x["rerank_score"], reverse=True)
    return ranked[:top_k]
```

### API de Rerank de Cohere

Para equipos que prefieren infraestructura gestionada:

```python
import cohere

co = cohere.ClientV2()

results = co.rerank(
    model="rerank-v3.5",
    query="configuración de backup PITR para PostgreSQL",
    documents=[chunk["content"] for chunk in candidates],
    top_n=5,
    return_documents=True,
)

reranked = [
    {"content": r.document.text, "relevance_score": r.relevance_score}
    for r in results.results
]
```

### Medición de Impacto

En nuestro pipeline de producción (2.3M chunks, 10K consultas de prueba):

| Configuración | Recall@5 | MRR | Relevancia de Respuesta |
| --- | --- | --- | --- |
| Solo vectorial | 0.62 | 0.54 | 0.71 |
| Híbrida (BM25 + vector) | 0.74 | 0.63 | 0.78 |
| Híbrida + reranker | 0.83 | 0.76 | 0.89 |
| Híbrida + reranker + encabezados contextuales | 0.87 | 0.81 | 0.91 |

El reranker solo representa una mejora de 12 puntos en recall. Combinado con búsqueda híbrida y encabezados contextuales, el pipeline recupera el 87% de los chunks relevantes en los top 5 resultados.

## Construcción del Prompt

Después del retrieval y reranking, cómo construís el prompt del LLM importa más de lo que la mayoría de los equipos creen.

### Gestión de la Ventana de Contexto

```python
def build_rag_prompt(query, retrieved_chunks, max_context_tokens=6000):
    """Construir prompt con inyección de contexto consciente del presupuesto de tokens."""
    context_parts = []
    token_count = 0

    for chunk in retrieved_chunks:
        chunk_tokens = count_tokens(chunk["content"])
        if token_count + chunk_tokens > max_context_tokens:
            break
        context_parts.append(
            f"[Fuente: {chunk['metadata']['source']}, "
            f"Sección: {chunk['metadata'].get('section', 'N/A')}]\n"
            f"{chunk['content']}"
        )
        token_count += chunk_tokens

    context = "\n\n---\n\n".join(context_parts)

    return f"""Respondé la siguiente pregunta basándote en el contexto proporcionado.
Si el contexto no contiene suficiente información para responder completamente, decilo explícitamente.
Citá el documento fuente para cada afirmación.

Contexto:
{context}

Pregunta: {query}

Respuesta:"""
```

### Grounding con Citaciones

Forzá al modelo a citar fuentes requiriendo referencias inline:

```
Para cada afirmación factual en tu respuesta, incluí una cita en el formato [Fuente: nombre_archivo, Sección: nombre_sección].
Si no podés encontrar evidencia de soporte en el contexto para una afirmación, prefijala con [Sin soporte].
```

Esto hace que la detección de alucinaciones sea trivial, cualquier afirmación marcada como ` [Sin soporte] ` o sin cita es sospechosa.

## Framework de Evaluación

Un pipeline RAG sin evaluación es una demo, no un producto. La evaluación ocurre en tres niveles.

### Nivel 1: Calidad de Retrieval

Medida independientemente del LLM:

```python
def evaluate_retrieval(test_queries, ground_truth, retriever, k=5):
    """Evaluar retrieval con métricas estándar de IR."""
    recalls, mrrs, ndcgs = [], [], []

    for query, relevant_ids in zip(test_queries, ground_truth):
        retrieved = retriever.search(query, k=k)
        retrieved_ids = [r["id"] for r in retrieved]

        # Recall@k
        hits = len(set(retrieved_ids) & set(relevant_ids))
        recalls.append(hits / len(relevant_ids))

        # MRR
        for rank, rid in enumerate(retrieved_ids, 1):
            if rid in relevant_ids:
                mrrs.append(1.0 / rank)
                break
        else:
            mrrs.append(0.0)

    return {
        "recall@k": np.mean(recalls),
        "mrr": np.mean(mrrs),
    }
```

### Nivel 2: Calidad de Generación con RAGAS

RAGAS evalúa el pipeline completo, retrieval y generación juntos:

```python
from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)

results = evaluate(
    dataset=eval_dataset,  # Preguntas + respuestas ground truth + contextos
    metrics=[
        faithfulness,        # ¿La respuesta se mantiene fiel al contexto recuperado?
        answer_relevancy,    # ¿La respuesta es relevante a la pregunta?
        context_precision,   # ¿Los chunks recuperados son relevantes?
        context_recall,      # ¿Se recuperaron todos los chunks necesarios?
    ],
)

print(results)
# {'faithfulness': 0.89, 'answer_relevancy': 0.91,
#  'context_precision': 0.82, 'context_recall': 0.87}
```

**Faithfulness** es la métrica más importante, mide alucinación. Un score de faithfulness por debajo de 0.85 significa que el pipeline está generando afirmaciones no respaldadas por el contexto recuperado.

### Nivel 3: Evaluación Humana

Automatizar la recolección, evaluar manualmente:

```python
def sample_for_human_eval(production_queries, n=200):
    """Muestrear consultas estratificadas por dificultad y tema."""
    # Clasificar consultas por dificultad estimada
    easy = [q for q in production_queries if q["retrieval_confidence"] > 0.8]
    medium = [q for q in production_queries if 0.5 < q["retrieval_confidence"] <= 0.8]
    hard = [q for q in production_queries if q["retrieval_confidence"] <= 0.5]

    sample = (
        random.sample(easy, min(80, len(easy))) +
        random.sample(medium, min(80, len(medium))) +
        random.sample(hard, min(40, len(hard)))
    )

    return sample
```

Los evaluadores humanos califican en tres ejes: **correctitud** (factualmente precisa), **completitud** (cubre la pregunta en su totalidad) y **fundamentación** (cita contexto, no alucina).

## Arquitectura de Producción

### Vista General del Pipeline

```
Documentos → Preprocesamiento → Chunking → Embedding → Vector Store
                                                            ↓
Consulta → Expansión de Query → Búsqueda Híbrida → Reranker → Constructor de Prompt → LLM → Respuesta
                                                                                              ↓
                                                                                        Logger de Evaluación
```

### Expansión de Consultas

Reescribir la consulta del usuario antes del retrieval para mejorar el recall:

```python
def expand_query(query, llm):
    """Generar reformulaciones alternativas para mejorar el retrieval."""
    expansion_prompt = f"""Generá 3 reformulaciones alternativas de esta consulta de búsqueda.
Cada una debe capturar la misma intención pero usar terminología diferente.
Devolvé solo las consultas, una por línea.

Consulta: {query}"""

    alternatives = llm.generate(expansion_prompt).strip().split("\n")
    return [query] + alternatives[:3]
```

Ejecutar búsqueda híbrida sobre todas las consultas expandidas y fusionar resultados con RRF. Esto consistentemente agrega 3-5% de mejora en recall.

### Capa de Caché

Cachear en dos niveles para reducir latencia y costo:

1. **Caché de embeddings**: Hashear el texto de entrada, cachear el vector de embedding. Evita re-generar embeddings de consultas idénticas.
2. **Caché semántico**: Para consultas con similitud coseno \> 0.95 respecto a una consulta cacheada, devolver la respuesta cacheada. Reduce llamadas al LLM en un 20-40% en casos de uso de soporte donde preguntas similares se repiten.

### Monitoreo

Rastrear estas métricas en producción:

- **Latencia de retrieval** (p50, p95, p99), objetivo \< 100ms para híbrida + rerank
- **Score de faithfulness** sobre una muestra rotativa, alertar si cae por debajo de 0.85
- **Tasa de retrieval vacío**, consultas donde ningún chunk supera el umbral de relevancia
- **Señales de feedback del usuario**, thumbs up/down, eventos de copia, preguntas de seguimiento

## Cuándo RAG No Es Suficiente

RAG tiene límites bien definidos:

- **Razonamiento multi-hop**: Cuando la respuesta requiere sintetizar información de 5+ documentos con pasos inferenciales entre ellos, el retrieval de RAG frecuentemente pierde documentos intermedios.
- **Razonamiento temporal**: "¿Qué cambió entre Q3 y Q4?" requiere recuperar y comparar dos conjuntos de documentos específicos en el tiempo, el retrieval top-k estándar no está diseñado para esto.
- **Cómputo sobre datos**: "¿Cuál es el SLA promedio en todas las regiones?" requiere una consulta estructurada, no recuperación de texto.

Para estos casos, considerá **RAG agéntico**, un agente que planifica pasos de retrieval, ejecuta múltiples búsquedas y sintetiza resultados programáticamente, o enfoques híbridos que combinen RAG con consultas SQL y llamadas a APIs.

## Implementación reproducible

[RAG Evaluation Workbench](<https://lucioduran.com/es/projects/rag-evaluation-workbench>) compara BM25, recuperación densa e híbrida con el mismo generador local, prompt y presupuesto. La ficha reúne el corpus con licencia, las consultas etiquetadas, los resultados crudos, los intervalos y los comandos para reproducirlos.

En esa muestra, recuperar todo el contexto relevante no evitó los errores de respuesta ni las fallas de abstención. El experimento evalúa recuperación y generación por separado; no valida las estrategias de chunking, los rerankers ni los objetivos de producción de esta guía.

### Fragmentación: evidencia completa y costo del índice

[Chunking Ablation Suite](<https://lucioduran.com/es/projects/chunking-ablation-suite>) compara ocho configuraciones fijas, estructurales y semánticas con el mismo MiniLM. En 36 consultas de evaluación, structure-96 recuperó toda la evidencia en 33 casos; las configuraciones semánticas, en 12. El repositorio conserva fragmentos, vectores, rankings y costos por tipo de documento. También muestra una ablación de umbral que no cambia ningún límite. Ponerle «semántico» al algoritmo no resuelve la evaluación.

### Citas: una fuente relevante todavía puede no respaldar la afirmación

[Citation Grounding Checker](<https://lucioduran.com/es/projects/citation-grounding-checker>) compara reglas conservadoras, FLAN-T5-small y DeBERTa NLI sobre pares de afirmación y pasaje. Publica veredictos, precisión por clase, abstenciones y enlaces exactos a la evidencia. En sus 36 casos sintéticos de evaluación, DeBERTa acertó 31 y se abstuvo en uno. Las etiquetas no tienen revisión humana independiente; esa cifra describe el fixture publicado.

### Reranking: medir también cuándo el router no aporta

[Reranker Budget Router](<https://lucioduran.com/es/projects/reranker-budget-router>) asigna presupuestos de 0, 4 o 12 pares y congela las políticas antes de evaluar. Las seis políticas ajustadas omitieron todo reranking, igualaron la calidad del baseline never y agregaron overhead. Always-12 mejoró una consulta. Las trazas permiten inspeccionar esa oportunidad perdida: agregar un router no garantiza que la arquitectura mejore.

## Referencias y alcance

Las fuentes siguientes describen métodos de recuperación y evaluación. Este artículo es una guía técnica, no un informe de benchmarks realizados por el autor. Las mejoras y los costos de despliegue requieren mediciones sobre la carga concreta.

- [Lewis et al. Retrieval Augmented Generation](<https://arxiv.org/abs/2005.11401>).
- [Es et al. Ragas](<https://arxiv.org/abs/2309.15217>).
- [Elasticsearch Reciprocal Rank Fusion](<https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion>).

## Implementación reproducible: actualización e integridad del índice

[Incremental RAG Index](<https://lucioduran.com/es/projects/incremental-rag-index>) mantiene versiones completas en SQLite, reutiliza embeddings por hash y registra cambios en un diario. Compara reconstrucción completa e incremental en cinco escenarios, con un adaptador determinista y otro MiniLM local, e interrumpe el proceso en cuatro puntos de publicación por adaptador.

En ambos experimentos coincidieron documentos, vectores, raíces y rankings. Editar, agregar y borrar calculó dos embeddings frente a seis de la reconstrucción completa. Los cortes anteriores al commit conservaron la versión vieja; el posterior conservó la nueva y su reintento no creó otra. Los documentos borrados desaparecen del índice actual, pero permanecen en versiones históricas. La evidencia demuestra coherencia sobre seis documentos sintéticos, no calidad semántica ni rendimiento a escala.

## Preguntas frecuentes

### ¿Qué tamaño de chunk debería usar para RAG?

No hay un tamaño universal. Compará tamaños candidatos con consultas etiquetadas y registrá calidad de recuperación, truncamiento del contexto y calidad de generación.

### ¿La búsqueda híbrida es siempre mejor que solo vectorial?

No. La recuperación híbrida puede ayudar con términos exactos y similitud semántica, pero también puede introducir ruido. Compará los métodos sobre las mismas consultas reservadas.

### ¿Cómo evalúo un pipeline RAG de forma sistemática?

Usá un enfoque de tres capas: (1) métricas de retrieval como recall@k, MRR y NDCG sobre un set de consultas etiquetadas, (2) métricas de generación como faithfulness y answer relevance usando RAGAS, y (3) evaluación humana end-to-end sobre un set dorado rotativo de 200+ consultas.

### ¿Cuándo debería usar un reranker en vez de mejorar los embeddings?

Medí embeddings y reranking como cambios separados. Incorporá un reranker si la mejora observada justifica su latencia y su costo para esa carga.

## Tips

1. Compará estrategias de chunking con consultas etiquetadas. El tamaño y el overlap son parámetros iniciales que hay que ajustar, no valores óptimos universales.
2. Combiná BM25 y recuperación densa con RRF, y compará recall@k y NDCG contra cada método por separado. La mejora depende del corpus y de las consultas.
3. Evaluá un cross-encoder sobre los candidatos recuperados y medí relevancia, latencia y costo juntos. Este artículo no establece una mejora porcentual ni una latencia fija.
4. **Trampa de evaluación:** No uses LLM-as-judge exclusivamente. Combinalo con métricas de retrieval (recall@k, MRR) y evaluación humana sobre un set dorado de 200 consultas. Los jueces LLM tienen puntos ciegos en precisión factual.

- rag
- retrieval-augmented-generation
- chunking
- reranking
- hybrid-search
- embeddings
- llm
- evaluation
- ragas
- ai-agents

[Volver al Blog](<https://lucioduran.com/es/blog>)
