← todos los posts
rag·12 de julio de 2026·10 min

Cómo diseñar una arquitectura RAG para producción sin alucinaciones

Aprendé a diseñar una arquitectura RAG para producción paso a paso: chunking, bases vectoriales, re-ranking y evaluación para evitar alucinaciones.

Querés armar un asistente para que tu equipo de soporte responda tickets con la documentación interna, pero el modelo inventa funciones que no existen. Databricks dice que los agentes multi-paso le ganan a RAG por 20 puntos en tareas complejas. Esos agentes razonan bien en varios pasos, pero una buena arquitectura rag sigue siendo la opción directa para mandar IA a producción hoy con datos propios y costos controlados. En este post te muestro cómo diseñar una arquitectura rag para producción, qué tradeoffs vas a enfrentar y cómo armar cada componente con el ejemplo de ese asistente de soporte.

El mecanismo y la arquitectura rag: desarmando la magia

RAG le entrega al modelo información actual desde una base externa en vez de dejarlo responder sólo con su pre-entrenamiento, tal como propuso el paper original de Lewis et al. en arXiv. El sistema reduce las alucinaciones porque ancla el contexto: le pedís al modelo que responda sólo con los fragmentos provistos.

El proceso tiene dos fases:

  1. Retrieval (Recuperación): Tu consulta se convierte en un vector numérico (embedding) para buscar los fragmentos más relevantes en tu base. La calidad del embedding define si captura la similitud semántica con sinónimos o paráfrasis en tu dominio. La base vectorial aporta eficiencia: maneja el throughput (consultas por segundo), la latencia (tiempo de respuesta), el costo por query, la escala y la resiliencia.
  2. Generation (Generación): El LLM recibe los fragmentos recuperados junto con la consulta original. Con ese contexto extendido, genera la respuesta anclada en tus datos reales.

La columna vertebral: tu base de conocimiento y estrategias de chunking

Tu base de conocimiento no es una carpeta llena de PDFs. Requiere una estrategia clara de datos para procesar esa documentación de soporte.

1. Chunking: el arte de cortar bien

Dividir los documentos en fragmentos o chunks define la calidad del sistema. Si el chunk es enorme, metés ruido y diluís lo importante. Si es muy chico, perdés el contexto de la frase. Tenés que buscar un equilibrio donde cada bloque sea coherente por sí solo.

Estrategia Pros Contras Casos de Uso Ideales Complejidad Evaluación de Efectividad
Fijo con Overlap Simple de implementar, funciona con texto no estructurado Puede cortar frases o ideas a la mitad, pierde contexto semántico Blogs, artículos genéricos, texto plano Baja Recall de chunks, longitud promedio del contexto
Por Estructura Preserva contexto semántico (sección, párrafo) Más complejo para automatizar en contenido inconsistente Documentación técnica, manuales, libros Media Context precision, faithfulness, human review
Semántico Chunks basados en el significado, no en el tamaño Requiere modelos de lenguaje para identificar límites, más lento Conversaciones, resúmenes, documentos complejos Alta Context recall, coherencia semántica
Recursivo Prueba diferentes tamaños hasta encontrar óptimo Iterativo, puede ser lento para indexación masiva Cualquier tipo de documento, adaptable Media Métricas Ragas (context recall, precision)
# Ejemplo de chunking con overlap (pseudocódigo en Python)
# Este es un punto de partida. En producción, considerá la semántica y la evaluación.

def chunk_document_fixed_overlap(text: str, chunk_size: int = 500, overlap_size: int = 100) -> list[str]:
    chunks = []
    start = 0
    while start < len(text):
        end = min(start + chunk_size, len(text))
        chunk = text[start:end]
        chunks.append(chunk)
        # Mover el inicio para el siguiente chunk, considerando el solapamiento
        start += (chunk_size - overlap_size)
        # Asegurar que no se cree un chunk vacío al final si el solapamiento es muy grande
        if start >= len(text) and len(chunks[-1]) < chunk_size // 2: # Heurística para evitar chunks residuales muy pequeños
            if len(chunks) > 1: # Si hay al menos un chunk anterior, unir el último con el anterior
                chunks[-2] = chunks[-2] + chunks.pop()
            else: # Si es el único chunk, dejarlo como está
                pass
    return chunks

# Para un chunking más avanzado, librerías como LangChain o LlamaIndex ofrecen RecursiveCharacterTextSplitter
# que es muy útil para probar diferentes separadores (\n\n, \n, ' ', '') y tamaños.

2. La base de datos vectorial: ¿dónde guardo los embeddings?

Para escalar a millones de documentos y sostener baja latencia, servicios como Pinecone o Weaviate resuelven varios frentes:

  1. Indexación distribuida: dividen grandes volúmenes de datos entre varios nodos.
  2. Búsqueda ANN: usan algoritmos de vecinos cercanos optimizados para alta dimensionalidad.
  3. Infraestructura gestionada: resuelven alta disponibilidad y escalado sin sumar mantenimiento operativo.
  4. Tolerancia a fallos: mantienen la continuidad del servicio ante caídas.
Característica pgvector (PostgreSQL) Pinecone/Weaviate (Managed) Chroma (Embeddings local)
Complejidad Baja/Media (si ya usás PG) Baja Muy Baja
Escalabilidad Media (depende del PG) Alta (cloud nativo) Baja (ideal para dev/POC)
Costo Bajo/Medio (infra existente) Medio/Alto (por uso) Muy Bajo (solo almacenamiento)
Control Alto Bajo Alto
Latencia Media Baja Baja (para datasets chicos)
Uso Típico Prototipos, datasets medios Producción, big data Desarrollo, testing, apps pequeñas

El cerebro del sistema: retrieval y re-ranking

Tener buenos chunks no alcanza si los recuperás mal. El retrieval necesita modelos de embedding precisos para el dominio de tus datos.

1. Búsqueda inicial (Similarity Search): buscás por cercanía vectorial con modelos como all-MiniLM-L6-v2 o las opciones de OpenAI y Cohere. La elección balancea costo, velocidad y precisión.

2. Re-ranking: filtrando el ruido para la precisión

La primera búsqueda suele traer texto parecido que no responde la duda puntual. Un re-ranker ordena esos resultados con un modelo cross-encoder liviano:

  • Mecanismo: toma la consulta del usuario junto con cada fragmento recuperado. Evalúa el par completo y calcula un puntaje de relevancia directo antes de pasárselo al LLM.
  • Tradeoff: suma unos milisegundos de latencia, pero sube la precisión y evita que el modelo procese información irrelevante. En sistemas corporativos donde el dato exacto importa, este paso es indispensable.

La voz: generación con LLMs y control de alucinaciones

Con los fragmentos reordenados, armás el prompt para el modelo.

Ingeniería de prompt para RAG:

Delimitá las instrucciones para controlar las respuestas:

  1. Instrucciones claras: pedile que responda usando únicamente el contexto provisto y que avise si el dato no aparece.
  2. Contexto delimitado: encerrá el texto en etiquetas como <context>...</context> para separar datos de instrucciones.
  3. Manejo de incertidumbre: exigí que declare cuándo no sabe la respuesta para no perder confiabilidad.
Pregunta del usuario: {query}

Contexto:
<context>
{chunk1}
{chunk2}
{chunk3}
</context>

Basándote estrictamente en el contexto proporcionado, respondé la pregunta. Si la información no está disponible en el contexto, decí "No tengo suficiente información para responder a eso basándome en los documentos proporcionados."

Tradeoffs del LLM: podés usar un modelo de frontera costoso o uno abierto más chico, como vimos al comparar modelos generalistas vs. especializados. Para muchas consultas de soporte alcanza con un modelo mediano. Si hacés fine-tuning, lográs que respete el formato y use menos tokens por consulta, bajando costos y tiempos de espera.

Orquestación: armar el rompecabezas en producción

Un RAG productivo funciona como un conjunto de microservicios coordinados.

graph TD
    A[Usuario/Aplicación] --> B(API Gateway/Load Balancer)
    B --> C{Servicio de Orquestación RAG}
    C --> D[Servicio de Embedding]
    C --> E[Servicio de Vector Store]
    C --> G[Servicio de Re-ranking]
    C --> H["Servicio LLM (OpenAI, HuggingFace, etc.)"]
    H --> I["Cache de Respuestas (Redis)"]
    I -- Cache Hit --> A
    H -- Cache Miss --> C
    C --> A

    subgraph Pipeline de Indexación
        J["Fuente de Datos (DB, S3, Web)"] --> K(Extracción y Limpieza)
        K --> L(Chunking Estratégico)
        L --> D
        D --> E
        E -- Metadata --> K
    end

    style C fill:#f9f,stroke:#333,stroke-width:2px
    style E fill:#f9f,stroke:#333,stroke-width:2px

Para operar esta arquitectura necesitás contemplar cuatro áreas:

  • Observabilidad: guardá logs, métricas y trazas de latencia en cada paso, aplicando distributed tracing con OpenTelemetry para ubicar cuellos de botella entre el vector store y el LLM. Podés medir alucinaciones con un segundo LLM evaluando la fidelidad (faithfulness) contra el contexto o mediante reglas heurísticas.
  • Escalabilidad: controlá si la base vectorial aguanta la carga y si tu proveedor de LLM tiene límites de tasa (rate limits). Medí la calidad del embedding con benchmarks del dominio, recall de consultas reales o pruebas A/B.
  • Seguridad: anonimizá datos personales (PII) antes de indexar, aplicá filtros por permisos de usuario sobre la metadata del chunk y asegurá el cifrado en tránsito y en reposo.
  • Pipeline de indexación: armá un proceso ETL continuo que resuelva la idempotencia, la concurrencia y la actualización constante de los documentos.

Un caso concreto: asistente de soporte para SaaS

Imaginá que sos el CTO de una empresa SaaS y necesitás un asistente interno para que el equipo de soporte resuelva tickets consultando manuales técnicos, FAQs y casos viejos.

Solución RAG propuesta:

  1. Fuentes de datos: documentación interna (Confluence, Markdown), tickets resueltos (Zendesk, JIRA) y FAQs públicas.
  2. Pipeline de indexación: un job diario procesa el texto, divide los manuales por sección y los tickets por respuesta con overlap. Genera vectores con sentence-transformers y los guarda en Pinecone con metadata (ID, fecha, tipo de documento y permisos).
  3. Servicio de RAG (FastAPI): expone un endpoint /ask que ejecuta este flujo:
    1. Recibe la consulta del agente de soporte.
    2. Vectoriza la pregunta con el modelo de embeddings.
    3. Pide a Pinecone entre 10 y 20 chunks aplicando filtros según los permisos del usuario.
    4. Reordena los fragmentos con un cross-encoder como cross-encoder/ms-marco-TinyBERT-L2-v2 y se queda con los 3 a 5 mejores.
    5. Envía la pregunta con los chunks a un modelo generalista de frontera o a un modelo mediano abierto con fine-tuning para soporte.
    6. Devuelve la respuesta al agente junto con los enlaces a los documentos fuente.

Tradeoffs de este caso:

  • Costo: Pinecone y un modelo de frontera suben la factura mensual. Se justifica por el tiempo ganado por ticket, aunque podés arrancar con un modelo más económico.
  • Latencia: el re-ranking y la inferencia tardan entre 1 y 3 segundos. Para un agente humano es un tiempo cómodo; para un chatbot en vivo con clientes puede sentirse lento.
  • Mantenimiento: necesitás monitorear el pipeline ETL, evaluar el recall del embedding periódicamente con preguntas de prueba y verificar la fidelidad de las respuestas con validadores automáticos.

Qué hacer el lunes: pasos accionables para tu primer RAG en producción

  1. Definí el caso de uso y tus KPIs: marcá objetivos como bajar los tiempos de primera respuesta, subir la precisión del equipo de soporte o reducir alucinaciones.
  2. Empezá simple, pero pensando en grande: arrancá con pgvector para validar rápido y proyectá la migración a Pinecone o Weaviate cuando crezca el volumen.
  3. Experimentá con chunking y embeddings: probá distintos tamaños de bloque y solapamiento con librerías como Ragas, LlamaIndex o LangChain midiendo context precision y context recall.
  4. Implementá evals desde el día uno: armá un set de prueba con preguntas reales y medí la fidelidad de las respuestas contra el contexto recuperado, sumando validaciones en tu pipeline como hacemos en CI/CD para agentes de IA.
    # Pseudocódigo para evaluar 'Faithfulness' (ejemplo conceptual)
    def evaluate_faithfulness(question: str, context: str, answer: str, llm_evaluator) -> float:
        # Instruir a un LLM evaluador para que determine si cada afirmación en la 'answer'
        # está directamente soportada por el 'context'.
        prompt = f"""
        Evalúa la siguiente respuesta basada estrictamente en el contexto proporcionado.
        Para cada afirmación en la respuesta, indica si está soportada por el contexto (SÍ/NO).
        Si alguna afirmación no está soportada, la respuesta no es fiel.
    
        Pregunta: {question}
        Contexto: {context}
        Respuesta: {answer}
    
        Ejemplo de formato de salida: Soporte: SÍ/NO. Razonamiento: <explicación>
        """
        evaluation_result = llm_evaluator.generate(prompt)
        # Procesar evaluation_result para obtener una puntuación (e.g., % de afirmaciones soportadas)
        # En la práctica, se usarían librerías como Ragas o LlamaIndex para esto.
        return parse_faithfulness_score(evaluation_result)
    
  5. Considerá un re-ranker: sumá un paso de reordenamiento para filtrar fragmentos irrelevantes, achicar el prompt y gastar menos en tokens.
  6. Planificá tu pipeline de indexación: programá un ETL que actualice documentos modificados y borre los obsoletos de tu base vectorial.
  7. No olvides la observabilidad y la seguridad: agregá métricas de latencia por paso, protegé los accesos a nivel de chunk y cifrá la información confidencial.