GPT Diffusion

RAG que funciona: patrones reales vs tutoriales basura

2026-04-28 · Tutoriales #rag#agentes#arquitectura#llm#open-source

TL;DR

  • El RAG básico (embed → search → generate) funciona en demos y poco más.
  • Lo que marca la diferencia: chunking inteligente, reranking, y evaluación.
  • Si tu RAG no tiene evaluación automática, estás debugueando a ciegas.

El problema del RAG básico

El 90% de tutoriales de RAG siguen este patrón:

  1. Parte tu documento en chunks de 512 tokens
  2. Embed cada chunk con un modelo de embeddings
  3. Guarda en una vector DB
  4. Cuando el usuario pregunta, busca los K chunks más similares
  5. Pasa esos chunks al LLM como contexto

Esto funciona para demos. En producción falla por:

  • Chunks irrelevantes: El embedding captura similitud semántica, no relevancia para la pregunta.
  • Contexto roto: Partir texto en 512 tokens corta ideas a mitad.
  • Sin evaluación: No sabes si las respuestas son correctas.
  • Sin feedback loop: El sistema no mejora con el uso.

Patrón 1: Chunking inteligente

No uses tamaño fijo

El chunking por tamaño fijo es la peor opción. Usa chunking semántico:

from semantic_text_splitter import TextSplitter

splitter = TextSplitter(max_chunk_size=1000)
chunks = splitter.split_text(document)

O mejor aún, chunking por estructura:

  • Markdown: un chunk por sección (## headers).
  • Código: un chunk por función/clase.
  • HTML: un chunk por sección semántica.
  • PDF: un chunk por página o sección con heading.

Metadata en cada chunk

Cada chunk debe llevar metadata:

chunk = {
    "content": "texto del chunk...",
    "source": "docs/api-reference.md",
    "section": "Authentication",
    "chunk_type": "code_example",
    "last_updated": "2026-05-01",
}

Esto permite filtrado previo al embedding search: “solo busca en la sección de Authentication”.

Embedding search solo captura similitud semántica. Añade keyword search (BM25):

# En lugar de solo embedding search
results = vector_db.search(query_embedding, top_k=20)

# Usa hybrid: embedding + BM25
embedding_results = vector_db.search(query_embedding, top_k=20)
bm25_results = bm25_index.search(query_keywords, top_k=20)

# Combina con Reciprocal Rank Fusion
combined = reciprocal_rank_fusion(
    [embedding_results, bm25_results],
    k=60  # constante RRF
)

Hybrid search mejora recall un 15-30% sobre embedding solo.

Patrón 3: Reranking

El paso que más marca la diferencia. Después de recuperar 20-50 candidatos, reordena con un modelo de reranking:

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-12-v2")

# Rerankear top candidates
pairs = [(query, chunk["content"]) for chunk in candidates]
scores = reranker.predict(pairs)

# Ordenar por score y quedarse con top 5
ranked = sorted(zip(candidates, scores), key=lambda x: -x[1])[:5]

Impacto: Reranking mejora precisión en top-5 entre un 20-40%. Es el single biggest improvement que puedes hacer.

Coste: El reranking es rápido (<10ms por query) y barato (modelo local).

Patrón 4: Query transformation

El usuario pregunta cosas ambiguas. Transforma la query antes de buscar:

Multi-query

Genera 3-5 variaciones de la pregunta y busca con todas:

from openai import OpenAI
client = OpenAI()

def generate_queries(original_query):
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role": "user",
            "content": f"Genera 3 variantes de esta pregunta para búsqueda: {original_query}"
        }]
    )
    return [original_query] + response.choices[0].message.content.split('\n')

HyDE (Hypothetical Document Embedding)

El LLM genera una respuesta hipotética, y buscas embeddings de ESA respuesta:

hypothetical_answer = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": f"Responde brevemente: {query}"}]
).choices[0].message.content

# Buscar usando la respuesta hipotética, no la pregunta
results = vector_db.search(embed(hypothetical_answer), top_k=10)

Funciona porque la respuesta hipotética es semánticamente más cercana a los documentos reales que la pregunta.

Patrón 5: Evaluación

Sin evaluación, no sabes si tu RAG funciona. Implementa méctricas automatizadas:

Dataset de test

Crea 50-100 pares pregunta-respuesta-correcta:

{
  "question": "¿Cómo reseteo la contraseña?",
  "expected_answer": "Ve a Settings > Security > Reset Password",
  "relevant_chunks": ["docs/security.md#password-reset"],
  "source": "docs/security.md"
}

Métricas automáticas

def evaluate_rag(test_cases):
    results = {"faithfulness": [], "relevance": [], "correctness": []}

    for case in test_cases:
        answer = rag_pipeline.query(case["question"])

        # Faithfulness: ¿la respuesta se basa en los chunks recuperados?
        results["faithfulness"].append(
            check_faithfulness(answer, retrieved_chunks)
        )

        # Relevance: ¿los chunks recuperados son relevantes?
        results["relevance"].append(
            check_relevance(case["question"], retrieved_chunks)
        )

        # Correctness: ¿la respuesta es correcta?
        results["correctness"].append(
            check_correctness(answer, case["expected_answer"])
        )

    return {k: sum(v)/len(v) for k, v in results.items()}

LLM-as-judge para evaluación

Usa un modelo mejor como juez:

def check_faithfulness(answer, chunks):
    prompt = f"""Dada esta respuesta:
{answer}

Y estos chunks de referencia:
{chunks}

¿La respuesta está fielmente basada en los chunks? Responde solo: YES o NO.
Si la respuesta incluye información no presente en los chunks, responde NO."""

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content.strip() == "YES"

Patrón 6: Context window vs RAG

En 2026, con modelos de 128K-1M tokens de contexto, ¿tiene sentido RAG?

Sí, para:

  • Knowledge bases que cambian frecuentemente
  • Documentación que no cabe ni en 1M tokens
  • Coste: meter 100K tokens de contexto en cada llamada es caro

No, para:

  • Documentos estáticos pequeños (<50K tokens)
  • Prototipos rápidos
  • Cuando la latencia del retrieval empeora el UX

El approach híbrido: usa contexto largo para el documento principal y RAG para el knowledge base externo.

Stack recomendado

ComponenteHerramienta¿Por qué
Embeddingstext-embedding-3-small (OpenAI) o bge-m3 (local)Calidad/precio
Vector DBQdrant o ChromaOpen-source, fácil de usar
Rerankercross-encoder/ms-marco-MiniLM-L-12-v2Rápido, local
FrameworkSin framework. Código customMás control, menos abstracción

Evita frameworks de RAG (LangChain, LlamaIndex) para producción. Añaden complejidad sin aportar valor. El código custom es más simple, más mantenible y más fácil de debuguear.

Conclusión

RAG que funciona = hybrid search + reranking + evaluación. Todo lo demás son optimizaciones menores.

Si solo implementas una mejora, que sea reranking. Es el ROI más alto por esfuerzo invertido.


Fuentes: Pinecone learning center, Qdrant docs,经验 propia con RAG en producción.

Cargando comentarios...