RAG en tres pasos
La idea cabe en una frase: en lugar de que el modelo responda desde su memoria de entrenamiento, le entregas los fragmentos relevantes de tu documentación y le pides que responda usando esa información y solo esa. El proceso se descompone en tres fases bien diferenciadas.
Trocear, embeber, recuperar
El ciclo completo: trocear el contenido en fragmentos, convertir cada fragmento en embedding y recuperar los más cercanos a la pregunta para entregarlos al modelo como contexto.
Durante la indexación, divides los documentos en fragmentos (chunks), calculas su embedding y los guardas en una base de datos vectorial. Cuando llega una pregunta, la conviertes en embedding y recuperas los fragmentos más cercanos. El último paso envía al LLM la pregunta, los fragmentos recuperados y una instrucción que le obligue a atenerse a ese contexto.
Indexar y consultar: qué pasa en cada mitad
Embeddings: el texto convertido en geometría
Un embedding es una lista de números que representa el significado de un texto. Modelos como all-MiniLM-L6-v2 generan vectores de 384 dimensiones; text-embedding-3-small de OpenAI produce 1536 y text-embedding-3-large llega a 3072. Dos textos con significado parecido acaban en puntos vecinos del espacio, de modo que la pregunta «¿cómo renuevo el certificado SSL?» queda cerca del fragmento que lo explica aunque no compartan ninguna palabra literal. Elegir entre modelo local y API es la misma encrucijada que la comparativa entre IA local y SaaS: control de datos frente a comodidad.
Vectores de 384 dimensiones con all-MiniLM-L6-v2
all-MiniLM-L6-v2 genera vectores de 384 dimensiones rápidos de calcular en CPU; nomic-embed-text ofrece 768 con mejor recall en español. La dimensión se fija al crear la colección.
Similitud coseno: el ángulo como medida de parecido
Para comparar dos vectores se usa casi siempre la similitud coseno, que mide el ángulo entre ellos y, con ello, su parecido de significado. Hay dos condiciones que conviene fijar desde el minuto cero: indexar y consultar con el mismo modelo, porque dimensiones distintas no caben en la misma colección, y elegir un modelo multilingüe si tu contenido mezcla idiomas, porque los entrenados solo con inglés degradan al procesar español.
Los modelos locales son una alternativa madura a las APIs: la librería sentence-transformers funciona incluso en CPU y Ollama sirve modelos de embedding como nomic-embed-text, de modo que el contenido no sale de tu infraestructura. La elección se decide por privacidad y volumen de llamadas, no por moda.
Búsqueda semántica frente a búsqueda léxica
Un buscador léxico devuelve documentos que contienen las palabras exactas de la consulta. Si el usuario escribe «no me llega el correo» y la documentación habla de «fallos en la entrega SMTP», el índice léxico puede quedarse en blanco. La búsqueda semántica compara significados mediante embeddings, así que ese desajuste de vocabulario deja de ser un obstáculo.
Cuando el sinónimo mata la búsqueda léxica
Si el usuario escribe «no me llega el correo», un buscador léxico no devuelve la entrada que dice «fallos de entrega de email»: la búsqueda vectorial sí, porque compara significado.
El punto ciego de los embeddings: códigos y siglas
Ahora bien, los embeddings tienen un punto ciego: los identificadores exactos. Códigos de error, nombres de funciones o referencias de producto no reciben tratamiento especial dentro del vector, y una consulta que contiene «ERR_0x41» puede devolver fragmentos genéricos. Los sistemas que funcionan en producción combinan los dos mundos: recuperación densa para el significado y recuperación léxica para los términos exactos.
Qdrant soporta vectores dispersos junto a los densos dentro de la misma colección, de modo que la parte léxica no exige mantener un segundo sistema. Las versiones recientes incorporan una Query API que encadena varias búsquedas y fusiona sus listas con estrategias como Reciprocal Rank Fusion.
Qué aporta Qdrant exactamente
Qdrant es una base de datos vectorial open source escrita en Rust y publicada bajo licencia Apache 2.0. Cada punto guarda un vector junto a un payload de metadatos en JSON: URL de origen, fecha de modificación, sección, permisos. Sobre esos metadatos puedes filtrar dentro de la propia búsqueda, lo que separa una demo de un sistema real: «recupera solo fragmentos de la categoría soporte modificados este año».
Puntos, payload y filtros
Cada punto guarda el vector junto a un payload (título, categoría, URL). Los filtros por payload combinan similitud semántica con restricciones duras sin servicios extra.
Debajo trabaja un índice HNSW (Hierarchical Navigable Small World), una estructura de grafos por capas que resuelve la búsqueda aproximada de vecinos con coste que crece de forma logarítmica respecto al tamaño de la colección. En la práctica, consultar colecciones de millones de vectores se mide en milisegundos. Cuando la memoria aprieta, Qdrant ofrece tres tipos de cuantización (escalar, por producto y binaria) que reducen el consumo de RAM a cambio de una pérdida de precisión configurable.
El arranque no necesita más: imagen oficial de Docker (qdrant/qdrant), API REST en el puerto 6333 y gRPC en el 6334. Con crecimiento de volumen o tráfico, el modo distribuido añade sharding y réplicas; si prefieres no operar servidores, existe Qdrant Cloud como alternativa gestionada. Para una primera prueba, un contenedor local y una colección bastan.
Arranque en dos minutos con la imagen oficial
El pipeline en una pantalla
Snippet mínimo en Python para upsert y búsqueda
Con el cliente oficial de Python, el esqueleto completo cabe en unas veinte líneas. Este ejemplo declara una colección para vectores de 384 dimensiones, sube un fragmento y ejecuta una búsqueda:
Colección de 384 dims y upsert en Python
Con el cliente oficial de Python, crear la colección, calcular los embeddings y hacer upsert de veinte entradas cabe en unas veinte líneas de código.
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, PointStruct, VectorParams
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="docs",
vectors_config=VectorParams(size=384, distance=Distance.COSINE),
)
client.upsert(
collection_name="docs",
points=[
PointStruct(
id=1,
vector=embedding_del_fragmento, # calculado con tu modelo
payload={"url": "https://ejemplo.com/doc", "texto": "..."},
)
],
)
hits = client.search(
collection_name="docs",
query_vector=embedding_de_la_pregunta,
limit=5,
)
Ejercicio de arranque con resultados inmediatos: indexa veinte documentos reales, escribe diez preguntas que tus usuarios harían de verdad y anota en cuántas el fragmento correcto aparece entre los cinco primeros resultados. Ese porcentaje es tu línea base, y cualquier cambio posterior (otro modelo de embeddings, otro tamaño de chunk, búsqueda híbrida) se mide contra ella.
Medir la recuperación, no solo las respuestas
Evaluar un RAG leyendo las respuestas del LLM es cómodo y engañoso: dos respuestas pueden sonar igual de bien y apoyarse en fragmentos de calidad muy distinta. Las métricas clásicas de los buscadores (precisión, recall, MRR) funcionan igual aquí que en cualquier motor de búsqueda. Con recall@5, por ejemplo, cuentas en qué fracción de las preguntas el fragmento correcto aparece entre los cinco primeros resultados.
Recall@k antes de juzgar al LLM
Evalúa primero si el sistema recupera el fragmento correcto: dos respuestas pueden sonar igual de bien y apoyarse en fragmentos distintos. Sin métrica de recuperación, juzgar al modelo es ruido.
El conjunto de prueba no necesita ser grande para ser útil. Veinte o treinta preguntas representativas detectan la mayoría de regresiones cuando cambias de modelo o de estrategia de chunking. Lo que importa es que las preguntas provengan de búsquedas reales, no de la imaginación de quien construyó el sistema.
El test vive junto al índice, no en un documento aparte
Los fallos típicos (chunks que cortan ideas por la mitad, índices que envejecen, ausencia de métricas) están desgranados en tres errores comunes al montar RAG en WordPress.
De local a producción
Para prototipar, un contenedor en tu propia máquina llega sobrado: la imagen oficial arranca en segundos y una colección de tamaño moderado convive con el resto de tus herramientas sin pelea por los recursos. Si quieres cerrar el círculo con todo en local, incluido el LLM, en IA local en WordPress: Ollama, Qdrant y los plugins que lo unen todo monto ese stack alrededor de WordPress.
Persistencia, copias y límites de memoria
En producción: volumen persistente para la colección, copia de seguridad programada y un límite de memoria por consulta para que una petición grande no tire el contenedor.
Ordena la migración en dos fases: consolida primero la calidad de búsqueda con datos reales y decide la infraestructura después. Cambiar de modelo de embeddings obliga a reindexar el corpus completo, así que cada retraso en descubrirlo se paga con horas de recálculo.
De prototipo a servicio: volumen, copias y límites
Por dónde empezar
La barrera para montar un RAG decente ya no está en el hardware ni en el precio de los modelos: está en la disciplina con los datos. Ninguna base vectorial arregla una documentación desordenada ni un chunking improvisado. Qdrant resuelve la parte de infraestructura con una API sencilla, filtros reales en la búsqueda y soporte híbrido sin segundo sistema.
Veinte entradas y una colección: la primera tarde
Descarga nomic-embed-text, crea una colección, embebe tus últimas veinte entradas y consulta con preguntas que sabes que están ahí. Ese es el circuito completo en una tarde.
La disciplina con los datos manda sobre la herramienta
Plan para esta semana: lanza el contenedor de Docker, elige un modelo de embeddings (multilingüe si tu contenido no está solo en inglés), indexa un puñado de documentos reales y construye el test de veinte preguntas. Cuando la recuperación te convenza, añade el generador. El orden importa más de lo que parece: un LLM excelente no salva un índice que busca mal. Y si el índice crece más de lo que tu máquina aguanta, un VPS para tu RAG deja Qdrant y el generador corriendo sin depender de tu portátil.
