Lab: ingestión
El primer lab con código de verdad
Sección titulada «El primer lab con código de verdad»Hasta acá fue conceptual. Ahora vamos a ejecutar la fase de indexación del pipeline RAG: leer los 4 markdowns de data/, partirlos en chunks, generar un embedding por chunk con Ollama, y guardar todo en Qdrant.
El comando es uno solo:
pnpm vercel:ingestSi todo está bien configurado del Setup, en menos de un minuto vas a ver tu primera collection vectorizada. Vamos a abrir la caja y mirar qué hace ese comando exactamente.
El archivo
Sección titulada «El archivo»El código vive en packages/01-vercel-ai-sdk/src/ingest.ts — 76 líneas honestas, sin abstracciones que escondan nada. Vamos por partes.
Stage 1 — Carga + chunking
Sección titulada «Stage 1 — Carga + chunking»const docs = await loadMarkdownDir(DATA_DIR);console.log(`Loaded ${docs.length} documents`);
const chunks = chunkDocs(docs);console.log(`Generated ${chunks.length} chunks`);loadMarkdownDir y chunkDocs viven en @rag-lab/shared — código compartido que los 4 frameworks reutilizan. La idea: que la única variable cambiante entre los packages 01-04 sea el framework, no la lógica de chunking ni la carga.
chunkDocs aplica el chunker estándar (800 chars, 100 de overlap). Vas a obtener entre 20 y 30 chunks para el corpus de ejemplo.
Stage 2 — Recrear la collection en Qdrant
Sección titulada «Stage 2 — Recrear la collection en Qdrant»async function recreateCollection(): Promise<void> { const { collections } = await qdrant.getCollections(); const exists = collections.some((c) => c.name === COLLECTION); if (exists) { await qdrant.deleteCollection(COLLECTION); } await qdrant.createCollection(COLLECTION, { vectors: { size: EMBED_DIM, distance: 'Cosine' }, });}Cada vez que corrés pnpm vercel:ingest, la collection rag_vercel se borra y recrea. Es la opción más simple para un curso: ingestión idempotente, sin estado raro entre runs.
Dos detalles que importan:
size: EMBED_DIM— la dimensión de los vectores (768 paranomic-embed-text). Tiene que matchear con el modelo de embeddings o Qdrant tira error al primer upsert.distance: 'Cosine'— la métrica que usa Qdrant para comparar vectores. Cosine es el default de la industria para texto.
Stage 3 — Embedding de todos los chunks
Sección titulada «Stage 3 — Embedding de todos los chunks»const embeddingModel = ollama.embeddingModel(OLLAMA_EMBED);const { embeddings } = await embedMany({ model: embeddingModel, values: chunks.map((c) => c.content),});console.log(`Embedded ${embeddings.length} chunks`);embedMany es la función estrella de Vercel AI SDK acá. Le mandás todos los chunks en un solo llamado, y devuelve un array paralelo de vectores. Internamente puede paralelizarlo o batchearlo — vos no te enterás.
ollama.embeddingModel(...) viene de @ai-sdk/openai-compatible, apuntando al endpoint /v1 de Ollama (el provider lo configuramos en las primeras líneas del archivo).
Stage 4 — Construir points y upsertar
Sección titulada «Stage 4 — Construir points y upsertar»const points = chunks.map((chunk, i) => ({ id: i, vector: embeddings[i]!, payload: { docId: chunk.docId, filename: chunk.filename, index: chunk.index, content: chunk.content, },}));
await qdrant.upsert(COLLECTION, { wait: true, points });Cada chunk se convierte en un point de Qdrant: id numérico, vector con el embedding, y payload con la metadata original (incluyendo el content para poder recuperarlo en query).
wait: true hace que el upsert espere a que Qdrant confirme la escritura. En un curso es lo correcto. En producción con miles de chunks, querés batchear y no bloquear.
Correlo
Sección titulada «Correlo»pnpm vercel:ingestEl archivo completo
Sección titulada «El archivo completo»¿Querés ver las 76 líneas enteras? Acá está el código tal como vive en main:
packages/01-vercel-ai-sdk/src/ingest.ts en GitHub →
Lo que viene
Sección titulada «Lo que viene»Tu corpus está indexado. Ahora viene la parte divertida: hacer preguntas. En la próxima página vas a ver el lab de query — cómo el sistema toma tu pregunta, encuentra los chunks relevantes y le pide al LLM que responda.