Saltar al contenido
Lección 2 de 12

Embeddings a Fondo

8 min read

¿Qué Son los Embeddings?

Un embedding es una representación numérica de texto como un vector -- una lista de números de punto flotante que captura el significado semántico de la entrada. Dos fragmentos de texto que significan cosas similares producirán vectores que están cerca en el espacio de embeddings. Dos fragmentos que significan cosas diferentes producirán vectores que están lejos.

Esta es la base de todo en RAG. Cuando buscas documentos relevantes, no estás haciendo coincidencia de palabras clave. Estás comparando el significado de una consulta contra el significado del texto almacenado, medido por la distancia entre sus vectores.

Considera estas tres oraciones:

  1. "El gato se sentó en la alfombra."
  2. "Un felino descansó sobre el tapete."
  3. "Los ingresos trimestrales superaron las proyecciones."

Las oraciones 1 y 2 casi no tienen palabras en común, pero sus embeddings estarán muy cerca porque significan casi lo mismo. La oración 3 comparte algunos patrones estructurales pero su embedding estará lejos de ambas porque el significado es completamente diferente.

Esto es lo que hace a la búsqueda semántica tan poderosa. La búsqueda por palabras clave fallaría en conectar las oraciones 1 y 2. La búsqueda basada en embeddings las conecta sin esfuerzo.

Cómo el Texto se Convierte en un Vector

El proceso de generar un embedding implica pasar texto a través de una red neuronal (el modelo de embedding) que ha sido entrenada con cantidades masivas de datos de texto. Durante el entrenamiento, el modelo aprende a posicionar texto semánticamente similar cerca en un espacio de alta dimensionalidad.

La salida es un arreglo de longitud fija de números de punto flotante. Por ejemplo, text-embedding-3-small de OpenAI produce un vector de 1536 dimensiones. Esto significa que cada fragmento de texto -- ya sea una sola palabra o un párrafo completo -- se mapea a una lista de 1536 números.

# Una vista simplificada de como luce un embedding
embedding = [0.0023, -0.0142, 0.0381, ..., -0.0091]  # 1536 flotantes

Los números individuales en el vector no son interpretables por humanos. No puedes mirar la dimensión 47 y decir "esto representa el concepto de animales." El significado está distribuido a través de todas las dimensiones colectivamente. Pero matemáticamente, estos vectores codifican información semántica rica que puede ser comparada, agrupada y buscada.

Modelos de Embedding Populares

Elegir el modelo de embedding correcto es una de las decisiones más importantes en tu sistema RAG. Estas son las opciones principales:

APIs Comerciales

OpenAI text-embedding-3-small -- 1536 dimensiones, excelente relación calidad-costo. Esta es la opción predeterminada para la mayoría de los sistemas RAG en producción. Maneja bien texto en inglés y multilingüe, cuesta $0.02 por millón de tokens y tiene una ventana de contexto de 8191 tokens.

OpenAI text-embedding-3-large -- 3072 dimensiones, mayor precisión para tareas de recuperación complejas. Cuesta $0.13 por millón de tokens. Úsalo cuando la precisión de recuperación es crítica y el costo es secundario.

Cohere embed-v3 -- Disponible en múltiples tamaños (1024 dimensiones para la versión light, hasta 1024 para la versión completa). Fuerte soporte multilingüe con más de 100 idiomas. Ofrece tipos de entrada especializados para consultas de búsqueda vs. documentos.

Modelos de Código Abierto

BGE (BAAI General Embedding) -- Familia de modelos de la Academia de IA de Beijing. BGE-large-en-v1.5 es una opción sólida para inglés. BGE-m3 maneja bien casos multilingües. Gratis para ejecutar localmente.

E5 (EmbEddings from bidirEctional Encoder rEpresentations) -- La familia de embeddings de Microsoft. E5-large-v2 y E5-mistral-7b-instruct son de alto rendimiento. Las variantes ajustadas por instrucción permiten anteponer prefijos específicos de tarea.

GTE (General Text Embeddings) -- La oferta de Alibaba. GTE-large compite con modelos comerciales en muchos benchmarks. Bueno para despliegues autoalojados.

all-MiniLM-L6-v2 -- Un clásico de sentence-transformers. Solo 384 dimensiones y rápido de ejecutar. No es la mayor calidad, pero extremadamente práctico para prototipado y entornos con recursos limitados.

Cómo Elegir

Para la mayoría de los equipos que comienzan: usa text-embedding-3-small de OpenAI. Es barato, rápido y suficientemente bueno para la gran mayoría de los casos de uso. Cambia a modelos de código abierto cuando necesites evitar dependencias de API, reducir costos a escala, o manejar dominios especializados donde un modelo ajustado supera a los de propósito general.

Tradeoffs de Dimensionalidad

Más dimensiones generalmente capturan más matices pero tienen costos:

  • Almacenamiento: Un vector de 3072 dimensiones ocupa el doble de espacio que uno de 1536 dimensiones.
  • Velocidad de búsqueda: Más dimensiones significan computaciones de similitud más lentas, especialmente a escala.
  • Tamaño del índice: Los índices de bases de datos vectoriales crecen con la dimensionalidad.

En la práctica, 1536 dimensiones es el punto óptimo para la mayoría de las aplicaciones. Bajar de 384 dimensiones comienza a degradar la calidad de recuperación. Subir por encima de 3072 raramente proporciona mejoras significativas para los casos de uso típicos de RAG.

Los modelos embedding-3 de OpenAI soportan un parámetro dimensions que te permite truncar la salida a una dimensionalidad menor. Esto es útil para experimentación:

from openai import OpenAI

client = OpenAI()

# 1536 dimensiones completas
response = client.embeddings.create(
    model="text-embedding-3-small",
    input="Que es retrieval-augmented generation?"
)
full_embedding = response.data[0].embedding
print(f"Dimensiones completas: {len(full_embedding)}")  # 1536

# Truncado a 512 dimensiones
response = client.embeddings.create(
    model="text-embedding-3-small",
    input="Que es retrieval-augmented generation?",
    dimensions=512
)
truncated_embedding = response.data[0].embedding
print(f"Dimensiones truncadas: {len(truncated_embedding)}")  # 512

Métricas de Similitud

Una vez que tienes dos vectores, necesitas una forma de medir qué tan similares son. Las dos métricas más comunes son:

Similitud Coseno

Mide el ángulo entre dos vectores, ignorando la magnitud. Retorna un valor entre -1 y 1, donde 1 significa dirección idéntica (mismo significado) y 0 significa ortogonal (sin relación).

import numpy as np

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

La similitud coseno es la opción predeterminada para la mayoría de los modelos de embedding porque los embeddings típicamente se normalizan durante el entrenamiento, haciendo la magnitud irrelevante.

Producto Punto

Un cálculo más simple que no normaliza por magnitud. Si tus embeddings ya están normalizados (vectores unitarios), el producto punto y la similitud coseno producen resultados idénticos.

def dot_product(a, b):
    return np.dot(a, b)

En la práctica, la mayoría de las bases de datos vectoriales te permiten elegir la métrica al momento de crear el índice. Usa similitud coseno a menos que tengas una razón específica para usar producto punto o distancia euclidiana.

Generando Embeddings: Código Práctico

Con OpenAI

from openai import OpenAI

client = OpenAI()  # Usa la variable de entorno OPENAI_API_KEY

def get_embeddings(texts: list[str], model: str = "text-embedding-3-small") -> list[list[float]]:
    """Genera embeddings para una lista de textos."""
    response = client.embeddings.create(
        model=model,
        input=texts
    )
    return [item.embedding for item in response.data]

# Embed una sola consulta
query_embedding = get_embeddings(["Como funciona la fotosintesis?"])[0]

# Embed multiples documentos en lote
documents = [
    "La fotosintesis convierte la luz solar en energia quimica en las plantas.",
    "La bolsa de valores cerro al alza el martes.",
    "Los cloroplastos son los organelos donde ocurre la fotosintesis.",
]
doc_embeddings = get_embeddings(documents)

# Comparar consulta con cada documento
for i, doc_emb in enumerate(doc_embeddings):
    similarity = np.dot(query_embedding, doc_emb)
    print(f"Documento {i}: similitud = {similarity:.4f}")

Con Sentence Transformers (Código Abierto)

from sentence_transformers import SentenceTransformer
import numpy as np

# Cargar modelo localmente (se descarga en la primera ejecucion)
model = SentenceTransformer("BAAI/bge-large-en-v1.5")

# Generar embeddings
query = "Como funciona la fotosintesis?"
documents = [
    "La fotosintesis convierte la luz solar en energia quimica en las plantas.",
    "La bolsa de valores cerro al alza el martes.",
    "Los cloroplastos son los organelos donde ocurre la fotosintesis.",
]

query_embedding = model.encode(query, normalize_embeddings=True)
doc_embeddings = model.encode(documents, normalize_embeddings=True)

# Calcular similitudes
similarities = np.dot(doc_embeddings, query_embedding)
for i, sim in enumerate(similarities):
    print(f"Documento {i}: similitud = {sim:.4f}")

Consejos para Embeddings en Producción

  • Agrupa tus solicitudes. Enviar 100 textos en una sola llamada API es mucho más barato y rápido que 100 llamadas individuales.
  • Almacena embeddings en caché. Nunca re-generes el embedding de texto que no ha cambiado. Almacena el embedding junto con el texto fuente en tu base de datos.
  • Normaliza consistentemente. Si normalizas embeddings al almacenarlos, asegúrate de también normalizar al momento de la consulta.
  • Usa el mismo modelo. Siempre usa el mismo modelo de embedding para documentos y consultas. Mezclar modelos produce puntajes de similitud sin sentido.
  • Haz benchmark con tus datos. Los rankings del leaderboard MTEB son útiles, pero el mejor modelo para tu caso de uso depende de tu dominio específico, idioma y patrones de consulta. Siempre prueba con datos reales.

Los embeddings son el puente entre el lenguaje humano y la similitud matemática. En la siguiente lección, aprenderás dónde almacenar estos vectores y cómo buscarlos a escala.