Inicio / Inteligencia Artificial / AI-First Full Stack: Construye Apps con IA / Embeddings y Vector Databases

Embeddings y Vector Databases

Generación de embeddings, similitud coseno, Pinecone, Qdrant, pgvector y búsqueda semántica.

Intermedio
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Embeddings y Vector Databases

Los embeddings son representaciones numéricas de texto (vectores) donde la distancia entre vectores refleja similitud semántica. Las vector databases almacenan y buscan estos vectores eficientemente. Juntos son la base de la búsqueda semántica y RAG.


¿Qué son los embeddings?

Un embedding convierte texto en un vector de números (típicamente 768-3072 dimensiones):

"TypeScript es genial" → [0.023, -0.156, 0.891, ..., 0.045]  (1536 dims)
"JavaScript mola"      → [0.019, -0.148, 0.887, ..., 0.041]  (similares!)
"Receta de paella"     → [-0.512, 0.334, 0.021, ..., -0.287] (muy diferente)

Similitud coseno

function cosineSimilarity(a: number[], b: number[]): number {
  const dotProduct = a.reduce((sum, ai, i) => sum + ai * b[i], 0);
  const magnitudeA = Math.sqrt(a.reduce((sum, ai) => sum + ai * ai, 0));
  const magnitudeB = Math.sqrt(b.reduce((sum, bi) => sum + bi * bi, 0));
  return dotProduct / (magnitudeA * magnitudeB);
}

// Resultado: -1 (opuestos) a 1 (idénticos)
// > 0.8 = muy similar
// > 0.6 = relacionado
// < 0.4 = no relacionado

Generación de Embeddings

OpenAI Embeddings

import OpenAI from 'openai';

const openai = new OpenAI();

async function getEmbedding(text: string): Promise<number[]> {
  const response = await openai.embeddings.create({
    model: 'text-embedding-3-small', // 1536 dims, $0.02/1M tokens
    input: text,
  });
  return response.data[0].embedding;
}

// Batch (más eficiente)
async function getEmbeddings(texts: string[]): Promise<number[][]> {
  const response = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: texts,
  });
  return response.data.map(d => d.embedding);
}

Modelos de embeddings

Modelo Dimensiones Costo (1M tokens) Uso
text-embedding-3-small 1536 $0.02 General, buena relación costo/calidad
text-embedding-3-large 3072 $0.13 Máxima precisión
Cohere embed-v3 1024 $0.10 Multilingüe excelente
Voyage voyage-3 1024 $0.06 Código y texto técnico

Vector Databases

pgvector (PostgreSQL)

La opción más simple si ya usas Postgres:

-- Instalar extensión
CREATE EXTENSION vector;

-- Crear tabla
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  embedding vector(1536), -- dimensiones del modelo
  metadata JSONB DEFAULT '{}',
  created_at TIMESTAMP DEFAULT NOW()
);

-- Crear índice para búsqueda rápida
CREATE INDEX ON documents
  USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 100);

-- Buscar similares
SELECT id, content, metadata,
       1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 5;

pgvector con Prisma

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Insertar documento con embedding
async function insertDocument(content: string, embedding: number[]) {
  await prisma.$executeRaw`
    INSERT INTO documents (content, embedding)
    VALUES (${content}, ${embedding}::vector)
  `;
}

// Búsqueda semántica
async function search(queryEmbedding: number[], limit = 5) {
  return prisma.$queryRaw`
    SELECT id, content, metadata,
           1 - (embedding <=> ${queryEmbedding}::vector) AS similarity
    FROM documents
    ORDER BY embedding <=> ${queryEmbedding}::vector
    LIMIT ${limit}
  `;
}

Pinecone

import { Pinecone } from '@pinecone-database/pinecone';

const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY! });
const index = pinecone.index('my-app');

// Insertar (upsert)
await index.upsert([
  {
    id: 'doc-1',
    values: embedding, // number[]
    metadata: { title: 'Intro a React', source: 'docs', category: 'frontend' },
  },
]);

// Buscar
const results = await index.query({
  vector: queryEmbedding,
  topK: 5,
  includeMetadata: true,
  filter: { category: { $eq: 'frontend' } }, // Filtros por metadata
});

results.matches.forEach(match => {
  console.log(`${match.id}: ${match.score} - ${match.metadata?.title}`);
});

Qdrant

import { QdrantClient } from '@qdrant/js-client-rest';

const qdrant = new QdrantClient({ url: 'http://localhost:6333' });

// Crear colección
await qdrant.createCollection('documents', {
  vectors: { size: 1536, distance: 'Cosine' },
});

// Insertar
await qdrant.upsert('documents', {
  points: [
    {
      id: 1,
      vector: embedding,
      payload: { content: 'Texto del documento', source: 'docs' },
    },
  ],
});

// Buscar
const results = await qdrant.search('documents', {
  vector: queryEmbedding,
  limit: 5,
  filter: {
    must: [{ key: 'source', match: { value: 'docs' } }],
  },
});

Comparación de Vector DBs

Base de datos Tipo Ventaja principal Mejor para
pgvector Extensión PostgreSQL Sin infra adicional Apps con Postgres existente
Pinecone SaaS managed Cero mantenimiento, escalable Producción sin ops
Qdrant Self-hosted / Cloud Filtros avanzados, rápido Control total
ChromaDB In-process (Python) Simple para prototipos Desarrollo local
Weaviate Self-hosted / Cloud Búsqueda híbrida nativa Apps multimodales

Pipeline completo: Texto → Embedding → Búsqueda

class SemanticSearch {
  private openai: OpenAI;

  constructor() {
    this.openai = new OpenAI();
  }

  async embed(text: string): Promise<number[]> {
    const response = await this.openai.embeddings.create({
      model: 'text-embedding-3-small',
      input: text,
    });
    return response.data[0].embedding;
  }

  async indexDocument(content: string, metadata: Record<string, any>) {
    const embedding = await this.embed(content);
    await db.query(
      `INSERT INTO documents (content, embedding, metadata)
       VALUES ($1, $2::vector, $3)`,
      [content, JSON.stringify(embedding), JSON.stringify(metadata)]
    );
  }

  async search(query: string, limit = 5) {
    const queryEmbedding = await this.embed(query);

    const results = await db.query(
      `SELECT content, metadata,
              1 - (embedding <=> $1::vector) AS similarity
       FROM documents
       ORDER BY embedding <=> $1::vector
       LIMIT $2`,
      [JSON.stringify(queryEmbedding), limit]
    );

    return results.rows.filter(r => r.similarity > 0.5);
  }
}

// Uso
const search = new SemanticSearch();

// Indexar
await search.indexDocument(
  'React hooks permiten usar estado en componentes funcionales',
  { source: 'docs', topic: 'react' }
);

// Buscar
const results = await search.search('¿cómo manejo estado en React?');
// Encuentra el documento aunque las palabras sean diferentes!

Optimizaciones

1. Batch embeddings

// ❌ Lento: una llamada por documento
for (const doc of documents) {
  const embedding = await getEmbedding(doc.content);
}

// ✅ Rápido: batch de hasta 2048 textos
const embeddings = await getEmbeddings(documents.map(d => d.content));

2. Caché de embeddings

import { createHash } from 'crypto';

const embeddingCache = new Map<string, number[]>();

async function getCachedEmbedding(text: string): Promise<number[]> {
  const hash = createHash('md5').update(text).digest('hex');

  if (embeddingCache.has(hash)) {
    return embeddingCache.get(hash)!;
  }

  const embedding = await getEmbedding(text);
  embeddingCache.set(hash, embedding);
  return embedding;
}

3. Reducción de dimensiones

// text-embedding-3-small soporta dimensiones reducidas
const response = await openai.embeddings.create({
  model: 'text-embedding-3-small',
  input: text,
  dimensions: 512, // Reduce de 1536 a 512 (más rápido, menos storage)
});
🔒

Ejercicio práctico disponible

Implementa similitud coseno y búsqueda vectorial

Desbloquear ejercicios
// Implementa similitud coseno y búsqueda vectorial
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes