RAG desde Cero (Parte 3): Vectores y Embeddings, qué hay bajo el capó
Serie RAG desde Cero - Post 3/3
Cuando ejecutaste el chatbot de la Parte 2, viste algo como esto:
Tres preguntas que probablemente no te respondiste en su momento:
- ¿Qué significa exactamente 0.861?
- ¿Por qué el Artículo 28 es más relevante que el 37 o el 7?
- ¿Cómo encontró "huelga" si la pregunta decía "¿tengo derecho a no trabajar para protestar?"?
Este post responde las tres. No hay código, es teoría pura. Pero es la teoría que hace que todo lo que construiste tenga sentido.
Qué es un vector
Un vector es una lista ordenada de números. Eso es todo.
Madrid tiene coordenadas geográficas: [40.4, -3.7]. Dos números que representan una posición en el espacio. Lo importante no son los números en sí, sino que permiten medir distancias: Madrid está cerca de Toledo ([39.9, -4.0]) y lejos de Tokio ([35.7, 139.7]).
Un embedding hace exactamente lo mismo, pero con texto y en muchas más dimensiones. Cuando ejecutaste crear_embedding("derecho a la huelga") en la Parte 1, el modelo devolvió una lista de 1536 números. Esos 1536 números representan una posición del texto en un espacio de 1536 dimensiones.
La promesa del embedding: textos con significado similar ocupan posiciones cercanas en ese espacio. "huelga" y "paro laboral" estarán cerca. "huelga" y "artículo 56 sobre la Corona" estarán lejos.
Ahora ya sabes qué devolvía crear_embedding() cada vez que lo llamabas.
Similitud coseno: cómo se mide "cerca"
Si dos textos son vectores en un espacio multidimensional, "cercano" podría medirse de varias formas. El sistema usa similitud coseno.
La intuición: en vez de medir la distancia entre dos puntos, medimos el ángulo entre dos flechas. Si dos vectores apuntan en la misma dirección (ángulo = 0°), la similitud es 1. Si apuntan en direcciones opuestas (ángulo = 180°), la similitud es -1. Si son perpendiculares (ángulo = 90°), la similitud es 0.
La fórmula:
Donde es el producto escalar (cada par de componentes multiplicados y sumados) y , son las longitudes (normas) de cada vector.
Para ver esto en acción, imaginemos que nuestros embeddings tuvieran solo 3 dimensiones en vez de 1536:
A = embedding("¿Tengo derecho a la huelga?") → [0.8, 0.5, 0.1]
B = embedding("Art. 28: derecho a la huelga") → [0.9, 0.4, 0.2]
C = embedding("Art. 56: el Rey") → [0.1, -0.3, 0.9]
similitud(A, B):
Producto escalar: (0.8 × 0.9) + (0.5 × 0.4) + (0.1 × 0.2) = 0.72 + 0.20 + 0.02 = 0.94
Norma de A:
Norma de B:
Similitud: ✅
similitud(A, C):
Producto escalar: (0.8 × 0.1) + (0.5 × -0.3) + (0.1 × 0.9) = 0.08 - 0.15 + 0.09 = 0.02
Norma de C:
Similitud: ✅
El Artículo 28 obtiene score 0.985 porque apunta casi en la misma dirección que la pregunta. El Artículo 56 obtiene 0.022 porque apuntan en direcciones completamente distintas. Los scores reales que viste (0.861, 0.714...) siguen exactamente este cálculo, pero con 1536 componentes en vez de 3.
¿Por qué coseno y no distancia euclidiana?
La distancia euclidiana penaliza los textos largos: un artículo largo tiene un vector con norma mayor, y por tanto está "más lejos" de cualquier pregunta corta, aunque traten el mismo tema. El coseno normaliza por longitud, lo que importa es la dirección, no el tamaño. Dos textos sobre el mismo tema apuntan en la misma dirección aunque uno sea más largo que el otro.
Cómo aprende un modelo de embeddings
Que "huelga" y "paro laboral" acaben cerca en el espacio vectorial no es casualidad. Es consecuencia de cómo se entrenan estos modelos.
La idea central viene de la lingüística de los años 50:
"You shall know a word by the company it keeps." , J.R. Firth, 1957
La hipótesis distribucional: el significado de una palabra está definido por las palabras que aparecen cerca de ella en el texto. "huelga" y "paro laboral" aparecen en contextos similares (junto a "trabajadores", "sindicatos", "convenio", "protesta"...), así que deben significar algo parecido. Nadie le dice esto al modelo explícitamente, lo infiere estadísticamente de grandes cantidades de texto.
Word2Vec, la intuición
Word2Vec (Mikolov et al., 2013) fue uno de los primeros modelos que operacionalizó esta idea a escala.
El entrenamiento es sorprendentemente simple: dado un texto, el modelo aprende a predecir qué palabras aparecen cerca de otras. Si en millones de documentos "huelga" y "sindicato" aparecen juntas con frecuencia, el modelo ajusta sus vectores internos para que esas dos palabras queden cerca en el espacio. Repite esto para cada par de palabras en todo el corpus de entrenamiento.
El resultado: vectores que capturan analogías semánticas sin que nadie las haya programado. El famoso rey - hombre + mujer ≈ reina emerge sola del proceso de entrenamiento.
Limitación de Word2Vec: cada palabra tiene un único vector, independientemente del contexto. "banco" siempre tiene el mismo vector, ya sea "banco financiero" o "banco de madera".
De Word2Vec a transformers
Attention Is All You Need (Vaswani et al., 2017) cambió el paradigma. Los transformers generan vectores contextuales: el vector de una palabra cambia según el texto que la rodea.
text-embedding-3-small es un modelo transformer. Cuando le pides el embedding de "el derecho a la huelga fue suspendido", genera vectores distintos para "huelga" que cuando procesa "ejerció su derecho a la huelga". El contexto importa.
Eso es lo que permite que la pregunta "¿tengo derecho a no trabajar para protestar?" encuentre el Artículo 28 sobre la huelga: el modelo no busca la palabra exacta, sino el concepto codificado en el vector de toda la frase.
Por qué 1536 dimensiones
Más dimensiones permiten representar más matices semánticos. Pero tienen un coste: más memoria, búsquedas más lentas, mayor coste de generación.
1536 es el valor que OpenAI eligió para text-embedding-3-small. No es arbitrario, es el punto donde el modelo alcanza buena cobertura semántica a bajo coste. Para comparar:
| Modelo | Dimensiones | MTEB Score | Coste | Cuándo usarlo |
|---|---|---|---|---|
text-embedding-3-small | 1536 | 62.3 | €0.02/1M tokens | Prototipo, aprendizaje |
text-embedding-3-large | 3072 | 64.6 | €0.13/1M tokens | Producción con OpenAI |
multilingual-e5-large-instruct | 1024 | mejor en español | Gratis (self-hosted) | Producción con control total |
BGE-M3 | 1024 | 63.0 | Gratis (self-hosted) | 100+ idiomas, muy versátil |
MTEB (Massive Text Embedding Benchmark) es el benchmark estándar para comparar modelos de embeddings en tareas de recuperación, clasificación y similitud semántica.
Para la Constitución Española: text-embedding-3-small es suficiente. Es texto legal estructurado, en un único idioma, y el vocabulario no es especialmente ambiguo. El modelo general lo maneja bien.
Para producción con texto legal técnico (jurisprudencia compleja, contratos con terminología sectorial muy específica): multilingual-e5-large-instruct tiene mejor rendimiento en español y puede correr en tu propio servidor, sin enviar datos a terceros.
Chunking revisitado: por qué importa la calidad del chunk
En la Parte 1 tomamos una decisión de chunking que parecía obvia: un artículo = un chunk. Ahora podemos justificarla matemáticamente.
Un embedding representa exactamente un punto en el espacio vectorial. Si el chunk mezcla varias ideas distintas, el vector resultante quedará en algún punto intermedio, difuso, sin orientación clara. Una pregunta sobre derechos laborales buscará un punto específico del espacio; un vector difuso que mezcla derechos laborales con competencias del Senado no va a estar cerca de ese punto.
La regla: un chunk, una idea. Cuanto más coherente semánticamente sea el chunk, mejor será su embedding y mejor funcionará la recuperación.
Los tres errores más comunes:
1. Chunk demasiado pequeño: pierde contexto. "Se reconoce el derecho" sin saber cuál, el vector resultante no representa nada útil.
2. Chunk demasiado grande: mezcla ideas. El Artículo 149, que enumera 32 materias de competencia estatal, produce un vector difuso que no representa bien ninguna de ellas.
3. Cortar por caracteres sin respetar semántica: peor de los dos mundos. Un chunk puede terminar a mitad de frase, generando representaciones sin sentido.
La Constitución tenía la estructura perfecta: 169 artículos numerados, cada uno con una idea central bien delimitada. Por eso no necesitamos overlap.
Chunking con overlap, cuándo sí importa
En documentos sin estructura semántica clara, PDFs de notas, emails, transcripciones de reuniones, no tienes unidades naturales como los artículos. Si divides por tamaño fijo, una idea clave puede quedar cortada justo en la frontera entre dos chunks, sin aparecer completa en ninguno de ellos.
El overlap resuelve esto: cada chunk comparte el 10-20% final con el inicio del siguiente.
Chunk 1: [========== 500 tokens ==========]
Chunk 2: [========== 500 tokens ==========]
↑ ~100 tokens repetidos
Es redundancia controlada: un fragmento importante que queda al final del Chunk 1 también aparece al principio del Chunk 2, completo y con contexto. Sacrificas algo de espacio de almacenamiento a cambio de mejor recall en la recuperación.
Limitaciones
Ninguna tecnología funciona perfectamente. Estas son las limitaciones que debes conocer antes de poner un sistema de embeddings en producción.
1. Dominio específico. text-embedding-3-small es un modelo de propósito general. Para texto legal técnico, contratos complejos, jurisprudencia con conceptos muy específicos, un modelo fine-tuneado en ese dominio puede rendir notablemente mejor. El modelo general no ha visto suficiente texto legal durante el entrenamiento para capturar todos sus matices.
2. Similitud semántica ≠ veracidad. Un score alto significa que los textos tratan del mismo tema, no que uno responda correctamente al otro. Un artículo que menciona "huelga" en un contexto de restricciones puede tener un score alto frente a una pregunta sobre el derecho a la huelga, aunque su contenido sea contrario a lo que el usuario busca.
3. Dimensiones opacas. No podemos interpretar qué codifica cada una de las 1536 dimensiones del vector. Si el sistema recupera artículos inesperados, no hay una forma directa de saber por qué, a diferencia de una búsqueda por palabras clave donde el match es explícito. Esto complica el debugging.
4. Embeddings estáticos. Los embeddings se generan una vez y no se actualizan solos. Si la Constitución se reforma, hay que regenerar los embeddings del texto modificado y re-indexarlos en Qdrant manualmente. En sistemas con datos que cambian frecuentemente, esto puede ser un cuello de botella operacional.
Más allá: técnicas que atacan estas limitaciones
Los embeddings básicos son la base, pero el estado del arte va bastante más lejos. Aquí un vistazo a las técnicas más relevantes, cada una de ellas merece un post propio.
Reranking. El pipeline de recuperación tiene dos etapas. Un bi-encoder (como el que hemos usado) genera embeddings independientes para la query y los documentos, y recupera rápido los top-20 candidatos. Un cross-encoder luego reordena esos candidatos evaluando query y documento juntos, puede capturar interacciones más finas, pero es mucho más lento. El resultado es un sistema más preciso sin sacrificar demasiada velocidad.
HyDE (Hypothetical Document Embeddings). En vez de embeddear directamente la pregunta, el modelo genera primero una respuesta hipotética a esa pregunta, y luego embeddeamos esa respuesta. La lógica: una respuesta hipotética se parece más a los documentos relevantes que la pregunta original, porque está en el mismo registro lingüístico y usa el mismo vocabulario que los documentos indexados.
RAG-Fusion / Query Expansion. Una sola query captura solo un ángulo del significado. Con RAG-Fusion, el sistema genera múltiples variantes de la pregunta, lanza varias búsquedas en paralelo y fusiona los resultados con un algoritmo de ranking. Mitiga el problema de que una pregunta formulada de una manera específica pueda no capturar todos los documentos relevantes.
Lo que ya sabes
Si llegaste hasta aquí, ahora entiendes qué pasó realmente cuando construiste el chatbot de la Parte 2:
- Los scores (0.861, 0.714...) son similitudes coseno: ángulo entre el vector de tu pregunta y el vector de cada artículo.
- El Artículo 28 ganó porque su vector apunta casi en la misma dirección que el vector de "¿tengo derecho a no trabajar para protestar?"
- Eso ocurrió porque ambos textos aparecen en contextos similares en el corpus de entrenamiento del modelo, la hipótesis distribucional en acción.
- Dividir por artículos (chunking semántico) garantizó vectores limpios y precisos.
La próxima vez que veas un score, sabes exactamente lo que está midiendo.
Recursos
- The Illustrated Word2Vec, Jay Alammar, La mejor explicación visual de cómo aprenden los modelos de embeddings
- Dot products and duality, 3Blue1Brown, Visualización del producto escalar y la similitud coseno
- Word2Vec paper, Mikolov et al., 2013, El paper original
- Attention Is All You Need, Vaswani et al., 2017, La arquitectura transformer
- multilingual-e5-large-instruct, Wang et al., 2024, Alternativa self-hosted para español
- BGE-M3, Chen et al., 2024, Modelo multilingüe versátil
- OpenAI Embeddings Guide, Referencia oficial
- MTEB Leaderboard, Benchmark estándar de modelos de embeddings
¿Tienes dudas sobre alguno de los conceptos? Contáctame o conectemos en LinkedIn.