Módulo 1: ¿Qué son Embeddings?
Mini-Proyecto: Primer Embedding - Similarity Calculator
Descripción del proyecto
En este mini-proyecto construirás tu primer sistema funcional usando embeddings: un Similarity Calculator que permite buscar documentos similares a un query usando OpenAI embeddings.
Este proyecto consolida TODOS los conceptos del Módulo 1: definición de embeddings, propiedades vectoriales, espacios vectoriales, casos de uso, comparación con keywords, y arquitectura. Al final tendrás una herramienta CLI production-ready que podrás expandir en módulos futuros.
Duración estimada: 30-40 minutos
Objetivos del proyecto
Al completar este proyecto, habrás:
- ✅ Configurado OpenAI API correctamente
- ✅ Generado embeddings de múltiples documentos
- ✅ Calculado cosine similarity con numpy
- ✅ Implementado búsqueda de top-K más similares
- ✅ Creado una CLI funcional con manejo de errores
- ✅ Guardado embeddings en caché (optimización)
Especificaciones técnicas
Funcionalidades obligatorias:
- Indexación: Cargar 10+ documentos y generar embeddings
- Query: Usuario ingresa query, sistema retorna top-3 más similares
- Scoring: Mostrar cosine similarity score para cada resultado
- Caché: Guardar embeddings en archivo para reutilizar
- Error handling: Manejar API errors, archivos faltantes, etc.
Stack tecnológico:
- Python 3.10+
- OpenAI API (text-embedding-3-small)
- NumPy (cálculos vectoriales)
- python-dotenv (environment variables)
- JSON (persistencia simple)
Paso 1: Setup del proyecto
1.1: Estructura de archivos
embeddings-similarity-calculator/
├── .env # API key (NO commitear)
├── .gitignore # Ignorar .env
├── requirements.txt # Dependencias
├── documents.txt # Corpus (10 documentos)
├── embeddings_cache.json # Embeddings guardados
└── similarity_calculator.py # Main script
1.2: Crear carpeta del proyecto
mkdir embeddings-similarity-calculator
cd embeddings-similarity-calculator
1.3: Crear virtualenv (recomendado)
# Crear virtualenv
python -m venv venv
# Activar
# macOS/Linux:
source venv/bin/activate
# Windows:
venv\Scripts\activate
1.4: Instalar dependencias
Crear requirements.txt:
openai==1.12.0
numpy==1.26.3
python-dotenv==1.0.0
Instalar:
pip install -r requirements.txt
1.5: Configurar OpenAI API key
Crear .env:
# .env
OPENAI_API_KEY=sk-proj-tu-api-key-aqui
Crear .gitignore:
# .gitignore
.env
venv/
__pycache__/
*.pyc
embeddings_cache.json
⚠️ IMPORTANTE: NUNCA commitees .env con tu API key.
Paso 2: Crear corpus de documentos
Crear documents.txt:
Python es un lenguaje de programación de alto nivel
JavaScript es el lenguaje principal para desarrollo web
TypeScript agrega tipos estáticos a JavaScript
React es una biblioteca para construir interfaces de usuario
Vue.js es un framework progresivo para construir UIs
Django es un framework web de Python para backend
FastAPI es un framework moderno y rápido para APIs en Python
Node.js permite ejecutar JavaScript en el servidor
Docker es una plataforma para containerizar aplicaciones
Kubernetes orquesta contenedores en producción
Cada línea = 1 documento.
Paso 3: Implementar Similarity Calculator
Crear similarity_calculator.py:
"""
Similarity Calculator - Mini-Proyecto Módulo 1
Sistema de búsqueda semántica usando OpenAI embeddings
"""
import os
import json
import numpy as np
from openai import OpenAI
from dotenv import load_dotenv
from typing import List, Dict, Tuple
# Cargar environment variables
load_dotenv()
# Configuración
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
EMBEDDING_MODEL = "text-embedding-3-small"
DOCUMENTS_FILE = "documents.txt"
CACHE_FILE = "embeddings_cache.json"
class SimilarityCalculator:
"""
Sistema de búsqueda semántica con embeddings
"""
def __init__(self):
"""Inicializar OpenAI client y estructuras de datos"""
if not OPENAI_API_KEY:
raise ValueError("OPENAI_API_KEY no encontrado en .env")
self.client = OpenAI(api_key=OPENAI_API_KEY)
self.documents: List[str] = []
self.embeddings: Dict[int, List[float]] = {}
def load_documents(self, filepath: str) -> None:
"""
Cargar documentos desde archivo
Args:
filepath: Path al archivo de documentos
"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
self.documents = [line.strip() for line in f if line.strip()]
print(f"✅ Cargados {len(self.documents)} documentos")
except FileNotFoundError:
raise FileNotFoundError(f"Archivo {filepath} no encontrado")
except Exception as e:
raise Exception(f"Error al cargar documentos: {e}")
def get_embedding(self, text: str) -> List[float]:
"""
Generar embedding para un texto
Args:
text: Texto a convertir en embedding
Returns:
Lista de floats (embedding vector)
"""
try:
response = self.client.embeddings.create(
model=EMBEDDING_MODEL,
input=text
)
return response.data[0].embedding
except Exception as e:
print(f"❌ Error al generar embedding: {e}")
raise
def generate_embeddings(self, use_cache: bool = True) -> None:
"""
Generar embeddings para todos los documentos
Args:
use_cache: Si True, intenta cargar de caché primero
"""
# Intentar cargar de caché
if use_cache and self.load_cache():
print("✅ Embeddings cargados de caché")
return
print(f"🔄 Generando embeddings para {len(self.documents)} documentos...")
for idx, doc in enumerate(self.documents):
try:
embedding = self.get_embedding(doc)
self.embeddings[idx] = embedding
print(f" [{idx+1}/{len(self.documents)}] Generado")
except Exception as e:
print(f" ❌ Error en documento {idx}: {e}")
# Guardar en caché
self.save_cache()
print("✅ Embeddings generados y guardados en caché")
def save_cache(self) -> None:
"""Guardar embeddings en archivo JSON"""
try:
cache_data = {
"documents": self.documents,
"embeddings": {str(k): v for k, v in self.embeddings.items()}
}
with open(CACHE_FILE, 'w', encoding='utf-8') as f:
json.dump(cache_data, f)
except Exception as e:
print(f"⚠️ Error al guardar caché: {e}")
def load_cache(self) -> bool:
"""
Cargar embeddings desde caché
Returns:
True si se cargó exitosamente, False si no
"""
try:
if not os.path.exists(CACHE_FILE):
return False
with open(CACHE_FILE, 'r', encoding='utf-8') as f:
cache_data = json.load(f)
# Verificar que documentos coincidan
if cache_data["documents"] != self.documents:
print("⚠️ Documentos cambiaron, regenerando embeddings...")
return False
# Cargar embeddings
self.embeddings = {
int(k): v for k, v in cache_data["embeddings"].items()
}
return True
except Exception as e:
print(f"⚠️ Error al cargar caché: {e}")
return False
@staticmethod
def cosine_similarity(vec_a: List[float], vec_b: List[float]) -> float:
"""
Calcular cosine similarity entre dos vectores
Args:
vec_a: Primer vector
vec_b: Segundo vector
Returns:
Similaridad (0.0 a 1.0)
"""
vec_a = np.array(vec_a)
vec_b = np.array(vec_b)
dot_product = np.dot(vec_a, vec_b)
norm_a = np.linalg.norm(vec_a)
norm_b = np.linalg.norm(vec_b)
return dot_product / (norm_a * norm_b)
def search(self, query: str, top_k: int = 3) -> List[Tuple[int, str, float]]:
"""
Buscar documentos más similares al query
Args:
query: Query del usuario
top_k: Cantidad de resultados a retornar
Returns:
Lista de tuplas (doc_id, documento, similarity)
"""
# Generar embedding del query
print(f"\n🔍 Buscando: '{query}'")
query_embedding = self.get_embedding(query)
# Calcular similaridades
similarities = []
for doc_id, doc_embedding in self.embeddings.items():
sim = self.cosine_similarity(query_embedding, doc_embedding)
similarities.append((doc_id, self.documents[doc_id], sim))
# Ordenar por similaridad (descendente)
similarities.sort(key=lambda x: x[2], reverse=True)
# Retornar top-K
return similarities[:top_k]
def print_results(self, results: List[Tuple[int, str, float]]) -> None:
"""
Imprimir resultados formateados
Args:
results: Lista de resultados [(doc_id, doc, similarity)]
"""
print("\n📊 Resultados:")
print("=" * 80)
for rank, (doc_id, doc, similarity) in enumerate(results, 1):
# Barra de progreso visual
bar_length = int(similarity * 50) # 50 chars max
bar = "█" * bar_length + "░" * (50 - bar_length)
print(f"\n{rank}. [Doc {doc_id}] Similarity: {similarity:.4f}")
print(f" {bar} {similarity:.1%}")
print(f" \"{doc}\"")
print("\n" + "=" * 80)
def main():
"""Función principal - CLI interactivo"""
print("=" * 80)
print("🚀 SIMILARITY CALCULATOR - Embeddings Deep Dive Guide")
print("=" * 80)
try:
# Inicializar
calculator = SimilarityCalculator()
# Cargar documentos
calculator.load_documents(DOCUMENTS_FILE)
# Generar embeddings (usa caché si existe)
calculator.generate_embeddings(use_cache=True)
# Loop interactivo
print("\n" + "=" * 80)
print("💬 Ingresa tu query (o 'exit' para salir)")
print("=" * 80)
while True:
query = input("\nQuery: ").strip()
if query.lower() in ['exit', 'quit', 'salir']:
print("\n👋 ¡Hasta luego!")
break
if not query:
print("⚠️ Query vacío. Intenta de nuevo.")
continue
# Buscar
results = calculator.search(query, top_k=3)
# Mostrar resultados
calculator.print_results(results)
except KeyboardInterrupt:
print("\n\n👋 Interrupted. ¡Hasta luego!")
except Exception as e:
print(f"\n❌ Error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
Paso 4: Ejecutar el proyecto
Primera ejecución (sin caché):
python similarity_calculator.py
Output esperado:
================================================================================
🚀 SIMILARITY CALCULATOR - Embeddings Deep Dive Guide
================================================================================
✅ Cargados 10 documentos
🔄 Generando embeddings para 10 documentos...
[1/10] Generado
[2/10] Generado
[3/10] Generado
[4/10] Generado
[5/10] Generado
[6/10] Generado
[7/10] Generado
[8/10] Generado
[9/10] Generado
[10/10] Generado
✅ Embeddings generados y guardados en caché
================================================================================
💬 Ingresa tu query (o 'exit' para salir)
================================================================================
Query: python para backend
🔍 Buscando: 'python para backend'
📊 Resultados:
================================================================================
1. [Doc 5] Similarity: 0.8923
████████████████████████████████████████████░░░░░░ 89.2%
"Django es un framework web de Python para backend"
2. [Doc 6] Similarity: 0.8756
███████████████████████████████████████████░░░░░░░ 87.6%
"FastAPI es un framework moderno y rápido para APIs en Python"
3. [Doc 0] Similarity: 0.8234
█████████████████████████████████████████░░░░░░░░░ 82.3%
"Python es un lenguaje de programación de alto nivel"
================================================================================
Query: contenedores
🔍 Buscando: 'contenedores'
📊 Resultados:
================================================================================
1. [Doc 8] Similarity: 0.9012
█████████████████████████████████████████████░░░░░ 90.1%
"Docker es una plataforma para containerizar aplicaciones"
2. [Doc 9] Similarity: 0.8678
███████████████████████████████████████████░░░░░░░ 86.8%
"Kubernetes orquesta contenedores en producción"
3. [Doc 7] Similarity: 0.6823
██████████████████████████████████░░░░░░░░░░░░░░░░ 68.2%
"Node.js permite ejecutar JavaScript en el servidor"
================================================================================
Query: exit
👋 ¡Hasta luego!
Segunda ejecución (con caché):
python similarity_calculator.py
Output:
================================================================================
🚀 SIMILARITY CALCULATOR - Embeddings Deep Dive Guide
================================================================================
✅ Cargados 10 documentos
✅ Embeddings cargados de caché
================================================================================
💬 Ingresa tu query (o 'exit' para salir)
================================================================================
Query:
Nota: Esta vez NO generó embeddings (cargó de embeddings_cache.json). Ahorro de tiempo y costos API.
Paso 5: Testing manual
Queries de prueba:
Query: framework para interfaces
# Debería retornar: React, Vue.js, (relacionados)
Query: lenguaje tipado
# Debería retornar: TypeScript, (relacionados)
Query: ejecutar en servidor
# Debería retornar: Node.js, FastAPI, Django
Query: orquestación
# Debería retornar: Kubernetes, Docker
Query: desarrollo web moderno
# Debería retornar: React, Vue.js, JavaScript
Validaciones técnicas
Checklist de funcionalidad:
- Sistema carga documentos correctamente
- Genera embeddings (1536 dims cada uno)
- Calcula cosine similarity correctamente
- Retorna top-3 ordenados por similarity
- Guarda caché en JSON
- Carga caché en ejecuciones subsecuentes
- Maneja errors (API key inválida, archivo faltante)
- CLI interactivo funciona
- Scores están en rango [0.0, 1.0]
- Resultados son semánticamente correctos
Mejoras opcionales (Extra Credit)
Nivel 1: Funcionalidades adicionales
# 1. Normalizar embeddings
def normalize_embedding(self, embedding: List[float]) -> List[float]:
"""Normalizar embedding a magnitud 1.0"""
embedding_array = np.array(embedding)
return (embedding_array / np.linalg.norm(embedding_array)).tolist()
# 2. Mostrar estadísticas
def print_stats(self):
"""Imprimir estadísticas del corpus"""
print(f"\n📈 Estadísticas:")
print(f" Documentos: {len(self.documents)}")
print(f" Embeddings: {len(self.embeddings)}")
print(f" Dimensiones: 1536")
print(f" Modelo: {EMBEDDING_MODEL}")
Nivel 2: Comparar con BM25
# Agregar búsqueda keyword simple
def keyword_search(self, query: str, top_k: int = 3):
"""Búsqueda BM25 simplificada (keyword matching)"""
query_words = set(query.lower().split())
scores = []
for idx, doc in enumerate(self.documents):
doc_words = set(doc.lower().split())
matches = len(query_words.intersection(doc_words))
scores.append((idx, doc, matches))
scores.sort(key=lambda x: x[2], reverse=True)
return scores[:top_k]
# Comparar ambos métodos:
semantic_results = calculator.search(query, top_k=3)
keyword_results = calculator.keyword_search(query, top_k=3)
print("\n🔹 Semantic Search:")
calculator.print_results(semantic_results)
print("\n🔹 Keyword Search:")
# print keyword results...
Nivel 3: Hybrid search
def hybrid_search(self, query: str, alpha: float = 0.5, top_k: int = 3):
"""
Hybrid search: Semantic + Keyword
Args:
query: Query del usuario
alpha: Peso de keyword (1-alpha = peso de semantic)
top_k: Cantidad de resultados
"""
# Semantic scores
query_emb = self.get_embedding(query)
semantic_scores = {}
for doc_id, doc_emb in self.embeddings.items():
sim = self.cosine_similarity(query_emb, doc_emb)
semantic_scores[doc_id] = (sim + 1) / 2 # Normalizar a [0, 1]
# Keyword scores
query_words = set(query.lower().split())
keyword_scores = {}
for doc_id, doc in enumerate(self.documents):
doc_words = set(doc.lower().split())
matches = len(query_words.intersection(doc_words))
keyword_scores[doc_id] = matches
# Normalizar keyword scores
max_keyword = max(keyword_scores.values()) if keyword_scores else 1
keyword_normalized = {
k: v / max_keyword for k, v in keyword_scores.items()
}
# Hybrid scores
hybrid_scores = []
for doc_id in range(len(self.documents)):
semantic_score = semantic_scores.get(doc_id, 0)
keyword_score = keyword_normalized.get(doc_id, 0)
hybrid_score = alpha * keyword_score + (1 - alpha) * semantic_score
hybrid_scores.append((doc_id, self.documents[doc_id], hybrid_score))
# Ordenar y retornar top-K
hybrid_scores.sort(key=lambda x: x[2], reverse=True)
return hybrid_scores[:top_k]
Troubleshooting
Problema 1: "OPENAI_API_KEY no encontrado"
Causa: .env no existe o no tiene la key.
Solución:
# Crear .env:
echo "OPENAI_API_KEY=sk-proj-tu-key-aqui" > .env
# Verificar que está cargando:
python -c "from dotenv import load_dotenv; import os; load_dotenv(); print(os.getenv('OPENAI_API_KEY'))"
Problema 2: "FileNotFoundError: documents.txt"
Causa: Archivo no existe.
Solución:
# Crear documents.txt con el contenido del Paso 2
Problema 3: API rate limit error
Causa: Demasiadas llamadas a OpenAI API.
Solución:
# Agregar delay entre llamadas:
import time
for idx, doc in enumerate(self.documents):
embedding = self.get_embedding(doc)
self.embeddings[idx] = embedding
time.sleep(0.1) # 100ms delay
Problema 4: Similarity scores todos ~0.60-0.70
Causa: Corpus muy heterogéneo o query muy genérico.
Solución: Normal. Scores >0.80 indican alta similaridad. Scores 0.60-0.70 son "algo relacionados".
Criterios de éxito
Funcionalidad (70 puntos):
- (15 pts) Sistema carga documentos
- (20 pts) Genera embeddings correctamente
- (15 pts) Calcula cosine similarity
- (10 pts) Retorna top-3 ordenados
- (10 pts) Caché funciona
Código (20 puntos):
- (5 pts) Código limpio y organizado
- (5 pts) Type hints consistentes
- (5 pts) Docstrings en funciones
- (5 pts) Error handling robusto
Usabilidad (10 puntos):
- (5 pts) CLI intuitivo
- (5 pts) Output legible y formateado
Resumen del proyecto
Has construido:
- ✅ Sistema de búsqueda semántica funcional
- ✅ Integración con OpenAI API
- ✅ Cálculo de cosine similarity con numpy
- ✅ Caché de embeddings (optimización)
- ✅ CLI interactivo
Skills adquiridos:
- Consumir APIs de embeddings
- Manipulación de vectores con numpy
- Persistencia de datos (JSON)
- Error handling robusto
- Arquitectura de búsqueda semántica
Este proyecto es la base para:
- Módulo 2: Arquitectura técnica profunda
- Módulo 4: Semantic search desde cero con numpy
- Módulo 6: Embeddings en producción (batch, cache)
- Módulo 8: Proyecto integrador completo
Recursos adicionales
- OpenAI Embeddings API Docs - Documentación oficial
- NumPy Docs - NumPy para cálculos vectoriales
- Python dotenv - Environment variables
- JSON in Python - Serialización
- Cosine Similarity Explained - Matemáticas
Próximos pasos
Módulo 2: ¿Cómo funcionan Embeddings?
Profundizarás en:
- Transformer encoders (arquitectura detallada)
- Tokenización (BPE, WordPiece)
- Self-attention mechanisms
- Pooling strategies (código real)
- OpenAI API avanzado (batching, rate limiting)
Este proyecto sienta las bases. Los siguientes módulos construyen sobre esto.
Felicitaciones! 🎉
Has completado el Módulo 1: ¿Qué son Embeddings?
Lo que lograste:
- ✅ Entendiste qué son embeddings y por qué son críticos
- ✅ Aprendiste propiedades vectoriales y cosine similarity
- ✅ Comprendiste espacios vectoriales de alta dimensionalidad
- ✅ Identificaste casos de uso reales (RAG, search, recommendations)
- ✅ Comparaste embeddings vs keyword search
- ✅ Conociste arquitectura high-level de Transformers
- ✅ Construiste tu primer sistema funcional
Estás listo para Módulo 2: Arquitectura profunda de embeddings. 🚀
Módulo 1 completado - Embeddings Deep Dive Guide De conceptos fundamentales a implementación práctica