initial commit: slides + practica_resueltos + README

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 11:50:55 +02:00
commit 0aff31e2d3
203 changed files with 23207 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
---
layout: cover
class: text-center
---
# Módulo 7: RAG
Retrieval Augmented Generation
<div class="pt-12">
<span class="text-xl opacity-80">
Extendiendo el conocimiento de los LLMs con tus datos
</span>
</div>
+25
View File
@@ -0,0 +1,25 @@
---
layout: default
---
# ¿Qué es RAG?
<v-clicks>
**Retrieval Augmented Generation** combina búsqueda de información con generación de texto
- **Problema**: Los LLMs tienen conocimiento limitado
- Datos de entrenamiento hasta cierta fecha
- No conocen información específica de tu empresa
- No pueden acceder a bases de datos en tiempo real
- **Solución RAG**:
- Buscar información relevante en tus documentos
- Proporcionar ese contexto al LLM
- Generar respuestas basadas en tus datos
<div class="mt-8 p-4 bg-blue-500/10 rounded">
💡 <b>En resumen</b>: Dale al LLM la información que necesita para responder correctamente
</div>
</v-clicks>
+28
View File
@@ -0,0 +1,28 @@
---
layout: default
---
# Flujo típico de RAG
<div class="mt-8">
```mermaid
graph LR
A[Pregunta del usuario] --> B[Convertir a embedding]
B --> C[Buscar en BD vectorial]
C --> D[Recuperar documentos relevantes]
D --> E[Construir prompt con contexto]
E --> F[LLM genera respuesta]
F --> G[Respuesta al usuario]
```
</div>
<v-clicks>
### Dos fases principales
1. **Indexación** (una vez): Convertir documentos a embeddings y almacenarlos
2. **Recuperación** (cada consulta): Buscar documentos relevantes y generar respuesta
</v-clicks>
+22
View File
@@ -0,0 +1,22 @@
---
layout: default
---
# Arquitecturas RAG
<v-clicks>
### 1. 2-Step RAG (Simple)
- Recuperación **siempre antes** de la generación · Flujo lineal y predecible
- Ideal para: FAQs, chatbots de documentación · ⚡ Alto control, baja flexibilidad
### 2. Agentic RAG
- Un **agente decide cuándo y cómo** recuperar información · Razona y usa herramientas
- Ideal para: Asistentes de investigación · ⚡ Bajo control, alta flexibilidad
### 3. Hybrid RAG
- Combina ambos con **validación intermedia** · Query + retrieval + answer validation
- Ideal para: Q&A de dominio específico · ⚡ Control y flexibilidad medios
</v-clicks>
+14
View File
@@ -0,0 +1,14 @@
---
layout: cover
class: text-center
---
# Embeddings
La base de la búsqueda semántica
<div class="pt-12">
<span class="px-2 py-1 rounded cursor-pointer opacity-80">
Convirtiendo texto en vectores numéricos
</span>
</div>
+27
View File
@@ -0,0 +1,27 @@
---
layout: default
---
# ¿Qué son los Embeddings?
<v-clicks>
**Embeddings** son representaciones numéricas de texto que capturan su significado semántico
- Vector de números (típicamente 768, 1536, o 3072 dimensiones)
- Textos con significado similar tienen embeddings cercanos
- Permite búsqueda por similitud semántica
### Ejemplo conceptual
```
"perro" → [0.2, 0.8, 0.1, ...]
"gato" → [0.3, 0.7, 0.2, ...] ← Similar a perro
"coche" → [0.9, 0.1, 0.8, ...] ← Muy diferente
```
<div class="mt-4 p-4 bg-green-500/10 rounded">
✨ La "magia" está en que textos con significados parecidos tienen vectores parecidos
</div>
</v-clicks>
+29
View File
@@ -0,0 +1,29 @@
---
layout: default
---
# Proveedores de Embeddings
LangChain soporta múltiples proveedores de embeddings:
<v-clicks>
### OpenAI
```typescript
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings({
model: "text-embedding-3-small", // o text-embedding-3-large
});
```
### Google (Vertex AI / Gemini)
```typescript
import { GoogleEmbeddings } from "@langchain/google";
const embeddings = new GoogleEmbeddings({
model: "text-embedding-004",
});
```
</v-clicks>
+36
View File
@@ -0,0 +1,36 @@
---
layout: default
---
# Más proveedores de Embeddings
<v-clicks>
### Mistral AI
```typescript
import { MistralAIEmbeddings } from "@langchain/mistralai";
const embeddings = new MistralAIEmbeddings({
model: "mistral-embed",
});
```
### Cohere
```typescript
import { CohereEmbeddings } from "@langchain/cohere";
const embeddings = new CohereEmbeddings({
model: "embed-multilingual-v3.0",
});
```
### Ollama (local)
```typescript
import { OllamaEmbeddings } from "@langchain/ollama";
const embeddings = new OllamaEmbeddings({
model: "llama3.2", // o cualquier modelo compatible
});
```
</v-clicks>
+27
View File
@@ -0,0 +1,27 @@
---
layout: default
---
# ¿Cómo elegir un proveedor?
<v-clicks>
### Criterios a considerar
- **Calidad**: OpenAI y Cohere son los más precisos
- **Coste**: Varía mucho entre proveedores
- **Latencia**: Los modelos locales (Ollama) son más rápidos
- **Privacidad**: Ollama permite procesamiento offline
- **Dimensiones**: Más dimensiones = más precisión pero más coste de almacenamiento
- **Idioma**: Algunos modelos funcionan mejor con español (Cohere multilingual)
### Recomendaciones
| Caso de uso | Proveedor recomendado |
|-------------|----------------------|
| Máxima calidad | OpenAI `text-embedding-3-large` |
| Mejor relación calidad/precio | OpenAI `text-embedding-3-small` |
| Privacidad/Local | Ollama |
| Multiidioma | Cohere `embed-multilingual-v3.0` |
</v-clicks>
+14
View File
@@ -0,0 +1,14 @@
---
layout: cover
class: text-center
---
# Bases de Datos Vectoriales
Almacenando y buscando embeddings
<div class="pt-12">
<span class="px-2 py-1 rounded cursor-pointer opacity-80">
Búsqueda eficiente por similitud
</span>
</div>
+27
View File
@@ -0,0 +1,27 @@
---
layout: default
---
# ¿Qué es una Base de Datos Vectorial?
<v-clicks>
**Vector Store** o **Base de Datos Vectorial** almacena embeddings y permite búsquedas por similitud
- Optimizadas para búsqueda por similitud coseno/euclidiana
- Indexación eficiente de vectores de alta dimensión
- Retornan los N documentos más similares a una consulta
### Tipos
1. **In-Memory**: Ideal para desarrollo y prototipos
- `MemoryVectorStore`
2. **Persistentes**: Para producción
- Qdrant, Pinecone, Weaviate, Chroma, etc.
<div class="mt-4 p-4 bg-yellow-500/10 rounded">
⚠️ En producción, siempre usa una BD vectorial persistente
</div>
</v-clicks>
+34
View File
@@ -0,0 +1,34 @@
---
layout: default
---
# MemoryVectorStore - Para desarrollo
<v-clicks>
**MemoryVectorStore** almacena embeddings en memoria (se pierden al cerrar)
```typescript
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
import { OpenAIEmbeddings } from "@langchain/openai";
const vectorStore = await MemoryVectorStore.fromTexts(
[
"LangChain es un framework para aplicaciones con LLMs",
"Los embeddings capturan el significado del texto",
"RAG combina búsqueda con generación de texto",
],
[{ id: 1 }, { id: 2 }, { id: 3 }], // metadata
new OpenAIEmbeddings()
);
// Buscar documentos similares
const results = await vectorStore.similaritySearch(
"¿Qué es RAG?",
2 // número de resultados
);
console.log(results);
```
</v-clicks>
+28
View File
@@ -0,0 +1,28 @@
---
layout: default
---
# Bases de Datos Vectoriales en Producción
<v-clicks>
### Opciones populares
| BD Vectorial | Características | Mejor para |
|--------------|----------------|------------|
| **Qdrant** | Open source, Rust, muy rápido | Autohosting, alta performance |
| **Pinecone** | Managed, fácil de usar | Startups, no querer gestionar infra |
| **Weaviate** | Open source, GraphQL | Búsquedas complejas |
| **Chroma** | Open source, Python-first | Prototipado rápido |
| **Milvus** | Open source, escalable | Grandes volúmenes de datos |
| **pgvector** | Extensión PostgreSQL | Ya usas PostgreSQL |
</v-clicks>
<v-click>
<div class="mt-4 p-4 bg-blue-500/10 rounded">
💡 <b>Recomendación</b>: Qdrant ofrece el mejor balance entre rendimiento, facilidad de uso y coste
</div>
</v-click>
+29
View File
@@ -0,0 +1,29 @@
---
layout: default
---
# Qdrant - Configuración
<v-clicks>
### Instalación
```bash
npm install @langchain/qdrant @qdrant/js-client-rest
```
### Iniciar Qdrant con Docker
```bash
docker run -p 6333:6333 qdrant/qdrant
```
### O usar Qdrant Cloud
Crear una cuenta gratuita en [qdrant.tech](https://qdrant.tech)
- 1GB de almacenamiento gratis
- Clusters gestionados
- Sin necesidad de infraestructura
</v-clicks>
+26
View File
@@ -0,0 +1,26 @@
---
layout: default
---
# Qdrant - Crear e insertar
```typescript
import { QdrantVectorStore } from "@langchain/qdrant";
import { OpenAIEmbeddings } from "@langchain/openai";
const vectorStore = await QdrantVectorStore.fromTexts(
[
"LangChain es un framework para aplicaciones con LLMs",
"Los embeddings capturan el significado del texto",
"RAG combina búsqueda con generación de texto",
],
[{ source: "doc1" }, { source: "doc2" }, { source: "doc3" }],
new OpenAIEmbeddings(),
{
url: "http://localhost:6333",
collectionName: "langchain_docs",
}
);
console.log("Documentos indexados correctamente");
```
+30
View File
@@ -0,0 +1,30 @@
---
layout: default
---
# Qdrant - Búsqueda
```typescript
import { QdrantVectorStore } from "@langchain/qdrant";
import { OpenAIEmbeddings } from "@langchain/openai";
// Conectar a colección existente
const vectorStore = await QdrantVectorStore.fromExistingCollection(
new OpenAIEmbeddings(),
{
url: "http://localhost:6333",
collectionName: "langchain_docs",
}
);
// Búsqueda por similitud
const results = await vectorStore.similaritySearch(
"¿Qué es RAG?",
3 // top 3 resultados
);
results.forEach((doc, i) => {
console.log(`${i + 1}. ${doc.pageContent}`);
console.log(` Metadata:`, doc.metadata);
});
```
+33
View File
@@ -0,0 +1,33 @@
---
layout: default
---
# Qdrant - Búsqueda con score
```typescript
// Búsqueda con puntuación de similitud
const resultsWithScore = await vectorStore.similaritySearchWithScore(
"embeddings y vectores",
3
);
resultsWithScore.forEach(([doc, score], i) => {
console.log(`${i + 1}. Score: ${score.toFixed(3)}`);
console.log(` ${doc.pageContent}`);
});
```
<v-click>
### Salida ejemplo
```
1. Score: 0.892
Los embeddings capturan el significado del texto
2. Score: 0.745
RAG combina búsqueda con generación de texto
3. Score: 0.621
LangChain es un framework para aplicaciones con LLMs
```
</v-click>
+14
View File
@@ -0,0 +1,14 @@
---
layout: cover
class: text-center
---
# Document Loaders y Splitters
Cargando y procesando documentos para RAG
<div class="pt-12">
<span class="px-2 py-1 rounded cursor-pointer opacity-80">
Del documento fuente al vector store
</span>
</div>
+28
View File
@@ -0,0 +1,28 @@
---
layout: default
---
# Document Loaders
<v-clicks>
**Document Loaders** transforman datos de distintas fuentes en objetos `Document` de LangChain
```typescript
// Estructura de un Document
interface Document {
pageContent: string; // contenido del texto
metadata: Record<string, any>; // información sobre la fuente
}
```
### Categorías principales
| Tipo | Ejemplos | Paquete |
|------|----------|---------|
| **Archivos locales** | PDF, DOCX, CSV, JSON, EPUB, PPTX | `@langchain/community` |
| **Web** | Cheerio, Playwright, Puppeteer | `@langchain/community` |
| **Servicios** | GitHub, Notion, Confluence, Jira | `@langchain/community` |
| **Audio** | OpenAI Whisper, AssemblyAI | `@langchain/community` |
</v-clicks>
+37
View File
@@ -0,0 +1,37 @@
---
layout: default
---
# PDFLoader
<v-clicks>
### Instalación
```bash
npm install @langchain/community pdf-parse
```
### Uso
```typescript
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
// Carga cada página como un Document independiente
const loader = new PDFLoader("./documentos/manual.pdf");
const docs = await loader.load();
console.log(`Cargadas ${docs.length} páginas`);
console.log(docs[0].pageContent.substring(0, 200));
console.log(docs[0].metadata);
// { source: './documentos/manual.pdf', pdf: {...}, loc: { pageNumber: 1 } }
```
### Opciones útiles
```typescript
// Cargar todo el PDF como un solo documento
const loader = new PDFLoader("./manual.pdf", { splitPages: false });
```
</v-clicks>
+39
View File
@@ -0,0 +1,39 @@
---
layout: default
---
# CheerioWebBaseLoader
<v-clicks>
Carga páginas web como documentos (sin ejecutar JavaScript)
### Instalación
```bash
npm install @langchain/community cheerio
```
### Uso
```typescript
import "cheerio";
import { CheerioWebBaseLoader } from
"@langchain/community/document_loaders/web/cheerio";
const loader = new CheerioWebBaseLoader(
"https://blog.langchain.dev/what-is-an-agent/",
{ selector: "article" } // selector CSS para filtrar contenido
);
const docs = await loader.load();
console.log(docs[0].pageContent.substring(0, 300));
console.log(docs[0].metadata);
// { source: 'https://blog.langchain.dev/...', title: '...' }
```
<div class="mt-4 p-4 bg-yellow-500/10 rounded">
⚠️ Para SPAs o páginas con JS dinámico, usa <b>Playwright</b> o <b>Puppeteer</b>
</div>
</v-clicks>
+33
View File
@@ -0,0 +1,33 @@
---
layout: default
---
# Otros Document Loaders útiles
<v-clicks>
### CSV
```typescript
import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";
const loader = new CSVLoader("./datos/productos.csv");
```
### DOCX (Microsoft Word)
```typescript
import { DocxLoader } from "@langchain/community/document_loaders/fs/docx";
const loader = new DocxLoader("./docs/informe.docx");
```
### Texto plano y Markdown
```typescript
import { TextLoader } from "@langchain/community/document_loaders/fs/text";
const loader = new TextLoader("./docs/README.md");
```
### JSON
```typescript
import { JSONLoader } from "@langchain/community/document_loaders/fs/json";
const loader = new JSONLoader("./datos/respuestas.json");
```
</v-clicks>
+36
View File
@@ -0,0 +1,36 @@
---
layout: default
---
# DirectoryLoader
<v-clicks>
Carga todos los archivos de un directorio, mapeando extensiones a loaders
```typescript
import { DirectoryLoader } from
"@langchain/community/document_loaders/fs/directory";
import { PDFLoader } from
"@langchain/community/document_loaders/fs/pdf";
import { TextLoader } from
"@langchain/community/document_loaders/fs/text";
import { CSVLoader } from
"@langchain/community/document_loaders/fs/csv";
const loader = new DirectoryLoader("./documentos/", {
".pdf": (path) => new PDFLoader(path),
".txt": (path) => new TextLoader(path),
".md": (path) => new TextLoader(path),
".csv": (path) => new CSVLoader(path),
});
const docs = await loader.load();
console.log(`Cargados ${docs.length} documentos`);
```
<div class="mt-4 p-4 bg-blue-500/10 rounded">
💡 Muy útil para indexar carpetas enteras de documentación de una sola vez
</div>
</v-clicks>
+31
View File
@@ -0,0 +1,31 @@
---
layout: default
---
# Text Splitters
<v-clicks>
Los documentos largos deben dividirse en **chunks** antes de crear embeddings
### RecursiveCharacterTextSplitter (recomendado)
```typescript
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000, // máximo de caracteres por chunk
chunkOverlap: 200, // solapamiento entre chunks
});
const chunks = await splitter.splitDocuments(docs);
```
Intenta mantener unidades de texto lógicas (párrafos → frases → palabras)
### ¿Por qué dividir?
- Los embeddings funcionan mejor con textos cortos y cohesivos
- Enviamos solo contexto relevante al LLM (ahorramos tokens)
- Mejora la precisión de la búsqueda por similitud
</v-clicks>
+21
View File
@@ -0,0 +1,21 @@
---
layout: default
---
# Estrategias de Chunking
<v-clicks>
### ¿Cómo elegir tamaño de chunk?
| Estrategia | chunkSize | chunkOverlap | Mejor para |
|------------|-----------|--------------|------------|
| **Fino** | 200-500 | 50 | FAQ, preguntas cortas |
| **Medio** | 500-1000 | 100-200 | Documentación técnica |
| **Grueso** | 1000-2000 | 200-400 | Artículos largos, informes |
<div class="mt-4 p-4 bg-blue-500/10 rounded">
💡 Experimenta con distintos tamaños — el óptimo depende del tipo de documento y las preguntas que esperas recibir
</div>
</v-clicks>
+36
View File
@@ -0,0 +1,36 @@
---
layout: default
---
# Splitters por tipo de contenido
<v-clicks>
`RecursiveCharacterTextSplitter.fromLanguage()` usa separadores adaptados a cada formato
### Markdown — respeta encabezados y listas
```typescript
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
const mdSplitter = RecursiveCharacterTextSplitter.fromLanguage("markdown", {
chunkSize: 1000, chunkOverlap: 200,
});
```
### HTML — respeta estructura de etiquetas
```typescript
const htmlSplitter = RecursiveCharacterTextSplitter.fromLanguage("html", {
chunkSize: 1000, chunkOverlap: 200,
});
```
### Código JS/TS — respeta funciones y clases
```typescript
const codeSplitter = RecursiveCharacterTextSplitter.fromLanguage("js", {
chunkSize: 1500, chunkOverlap: 200,
});
```
Otros lenguajes soportados: `python`, `java`, `go`, `rust`, `ruby`, `php`...
</v-clicks>
+31
View File
@@ -0,0 +1,31 @@
---
layout: default
---
# Extractores y parsers avanzados
<v-clicks>
### Unstructured.io
Servicio de extracción que soporta **+30 tipos de archivo** (PDF, DOCX, PPTX, imágenes, HTML...)
```typescript
import { UnstructuredLoader } from
"@langchain/community/document_loaders/fs/unstructured";
const loader = new UnstructuredLoader("./docs/informe.pdf", {
apiKey: process.env.UNSTRUCTURED_API_KEY,
apiUrl: "https://api.unstructuredapp.io/general/v0/general",
});
const docs = await loader.load();
```
- Extrae texto, tablas, imágenes con OCR
- API cloud o self-hosted
- Ideal para documentos complejos con layouts mixtos
<div class="mt-4 p-4 bg-blue-500/10 rounded">
💡 Unstructured es especialmente útil para PDFs escaneados o con tablas complejas donde <code>pdf-parse</code> se queda corto
</div>
</v-clicks>
+25
View File
@@ -0,0 +1,25 @@
---
layout: default
---
# Otros extractores a conocer
<v-clicks>
Cuando `pdf-parse` no es suficiente (PDFs escaneados, tablas complejas, layouts mixtos):
| Extractor | Descripción | JS/TS | Tablas | OCR |
|-----------|-------------|-------|--------|-----|
| **Docling** (IBM) | Comprensión avanzada de layout | ❌ Solo Python | ✅ | ✅ |
| **Azure Doc Intelligence** | Servicio cloud de Microsoft | ✅ | ✅ | ✅ |
| **Amazon Textract** | Servicio cloud de AWS | ❌ Solo Python | ✅ | ✅ |
| **LlamaParse** | Parser de LlamaIndex, API cloud | ✅ via API | ✅ | ✅ |
### Docling — mención especial
- Proyecto open source de IBM con excelente calidad de extracción
- Integración oficial solo con LangChain Python (`langchain-docling`)
- Desde Node.js: consumir su API REST o usar como preproceso a markdown.
</v-clicks>
+14
View File
@@ -0,0 +1,14 @@
---
layout: cover
class: text-center
---
# Ejemplo Práctico: RAG con PDFs
Búsqueda semántica sobre documentos
<div class="pt-12">
<span class="px-2 py-1 rounded cursor-pointer opacity-80">
Del PDF a respuestas inteligentes
</span>
</div>
+34
View File
@@ -0,0 +1,34 @@
---
layout: default
---
# Paso 1: Cargar el PDF y dividir en chunks
```typescript
import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
// Cargar PDF (cada página → un Document)
const loader = new PDFLoader("./docs/manual-usuario.pdf");
const docs = await loader.load();
console.log(`📥 ${docs.length} páginas cargadas`);
// Dividir en chunks pequeños para mejor búsqueda
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
const chunks = await splitter.splitDocuments(docs);
console.log(`📄 Dividido en ${chunks.length} chunks`);
```
<v-click>
<div class="mt-4 p-3 bg-blue-500/10 rounded text-sm">
- **chunkSize**: tamaño máximo de cada trozo en caracteres
- **chunkOverlap**: solapamiento entre chunks para no perder contexto
</div>
</v-click>
+34
View File
@@ -0,0 +1,34 @@
---
layout: default
---
# Paso 2: Indexar en Qdrant
```typescript
import { QdrantVectorStore } from "@langchain/qdrant";
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings();
// Crear colección e insertar chunks con sus embeddings
const vectorStore = await QdrantVectorStore.fromDocuments(
chunks,
embeddings,
{
url: "http://localhost:6333",
collectionName: "manuales",
}
);
console.log(`${chunks.length} chunks indexados en Qdrant`);
```
<v-click>
<div class="mt-4 p-3 bg-green-500/10 rounded text-sm">
`fromDocuments` hace todo: genera embeddings + crea la colección + inserta los vectores
</div>
</v-click>
+33
View File
@@ -0,0 +1,33 @@
---
layout: default
---
# Paso 3: Buscar documentos relevantes
```typescript
// Conectar a colección existente
const vectorStore = await QdrantVectorStore.fromExistingCollection(
new OpenAIEmbeddings(),
{ url: "http://localhost:6333", collectionName: "manuales" }
);
// Búsqueda semántica
const pregunta = "¿Cómo resetear la contraseña?";
const resultados = await vectorStore.similaritySearch(pregunta, 3);
resultados.forEach((doc, i) => {
console.log(`\n--- Resultado ${i + 1} ---`);
console.log(doc.pageContent.substring(0, 200));
console.log("Fuente:", doc.metadata.source);
});
```
<v-click>
<div class="mt-4 p-3 bg-yellow-500/10 rounded text-sm">
`similaritySearch(query, k)` devuelve los **k** chunks más similares semánticamente a la query
</div>
</v-click>
+37
View File
@@ -0,0 +1,37 @@
---
layout: default
---
# Paso 4: RAG - Búsqueda + Generación
```typescript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
const llm = new ChatOpenAI({ model: "gpt-4.1-mini" });
// Buscar contexto relevante
const pregunta = "¿Cómo resetear la contraseña?";
const docs = await vectorStore.similaritySearch(pregunta, 3);
const contexto = docs.map(d => d.pageContent).join("\n\n");
// Generar respuesta con contexto
const respuesta = await llm.invoke([
new SystemMessage(
`Responde basándote SOLO en este contexto:\n\n${contexto}`
),
new HumanMessage(pregunta),
]);
console.log(respuesta.content);
```
<v-click>
<div class="mt-4 p-3 bg-purple-500/10 rounded text-sm">
Esto es RAG: **recuperar** contexto relevante y pasárselo al LLM para que **genere** la respuesta
</div>
</v-click>
+27
View File
@@ -0,0 +1,27 @@
---
layout: default
---
# Búsquedas simples
```typescript
import { QdrantVectorStore } from "@langchain/qdrant";
import { OpenAIEmbeddings } from "@langchain/openai";
// Conectar a la colección
const vectorStore = await QdrantVectorStore.fromExistingCollection(
new OpenAIEmbeddings(),
{ url: "http://localhost:6333", collectionName: "manuales" }
);
// Buscar
const pregunta = "¿Cómo resetear la contraseña?";
const resultados = await vectorStore.similaritySearch(pregunta, 3);
resultados.forEach((doc, i) => {
console.log(`\n--- Resultado ${i + 1} ---`);
console.log(doc.pageContent);
console.log("Fuente:", doc.metadata.source);
console.log("Página:", doc.metadata.loc?.pageNumber);
});
```
+33
View File
@@ -0,0 +1,33 @@
---
layout: default
---
# RAG completo: Búsqueda + Generación
```typescript
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
const llm = new ChatOpenAI({ model: "gpt-4.1-mini" });
// Buscar contexto relevante
const pregunta = "¿Cómo resetear la contraseña?";
const docs = await vectorStore.similaritySearch(pregunta, 3);
const contexto = docs.map(d => d.pageContent).join("\n\n");
// Crear prompt con contexto
const prompt = PromptTemplate.fromTemplate(`
Responde la pregunta basándote SOLO en el siguiente contexto:
Contexto:
{contexto}
Pregunta: {pregunta}
Respuesta:`);
const chain = prompt.pipe(llm);
const respuesta = await chain.invoke({ contexto, pregunta });
console.log(respuesta.content);
```
+27
View File
@@ -0,0 +1,27 @@
---
layout: default
---
# Retrievers: Abstracción de búsqueda
<v-clicks>
**Retriever** es una interfaz estándar para recuperar documentos
```typescript
// Convertir vector store en retriever
const retriever = vectorStore.asRetriever({
k: 3, // número de documentos a recuperar
});
// Usar el retriever
const docs = await retriever.invoke("¿Cómo resetear la contraseña?");
```
### Ventajas
- Interfaz unificada para cualquier fuente de datos
- Fácil de intercambiar (Qdrant → Pinecone)
- Compatible con chains predefinidas de LangChain
</v-clicks>
+32
View File
@@ -0,0 +1,32 @@
---
layout: default
---
# Reranking: mejorando la relevancia
<v-clicks>
La búsqueda vectorial recupera documentos **similares**, pero no siempre los más **relevantes**. Un **reranker** reordena los resultados usando un modelo especializado.
```typescript
import { CohereRerank } from "@langchain/cohere";
const reranker = new CohereRerank({
apiKey: process.env.COHERE_API_KEY,
model: "rerank-v4.0-fast", // multilingüe por defecto
topN: 3,
});
// .compressDocuments() → devuelve los topN Document[] listos para usar en chains
const rerankedDocs = await reranker.compressDocuments(docs, query);
// .rerank() → devuelve TODOS los índices + scores (más control manual)
const results = await reranker.rerank(docs, query);
// [{ index: 2, relevanceScore: 0.95 }, { index: 0, relevanceScore: 0.72 }, ...]
```
<div class="mt-3 p-3 bg-green-500/10 rounded">
✨ Patrón común: recuperar ~10 docs con vector search → reranking → top 3 al LLM
</div>
</v-clicks>
+20
View File
@@ -0,0 +1,20 @@
---
layout: default
---
# Proveedores de Reranking
<v-clicks>
| Proveedor | Paquete | Free tier | Self-hosted | Multiidioma |
|-----------|---------|-----------|-------------|-------------|
| **Cohere** | `@langchain/cohere` | Trial API key | ❌ | ✅ `rerank-v4.0-pro/fast` |
| **MixedBread AI** | `@langchain/community` | 100 queries/mes | BYOC (enterprise) | ✅ |
| **IBM watsonx** | `@langchain/community` | Lite plan | ❌ | ✅ |
<div class="mt-4 p-4 bg-blue-500/10 rounded">
💡 El reranker también ayuda a <b>filtrar duplicados</b>: los chunks redundantes quedan con scores muy similares y se pueden descartar
</div>
</v-clicks>
+24
View File
@@ -0,0 +1,24 @@
---
layout: default
---
# Reranking self-hosted
<v-clicks>
### BAAI/bge-reranker-v2-m3
- Modelo open source (Apache 2.0), 0.6B params, multilingüe
- Funciona en **CPU** — no necesitas GPU
- Primera descarga ~600MB, luego se cachea
### Runtimes para servir el modelo
| Runtime | Descripción |
|---------|-------------|
| **Infinity** | Runtime ligero y sencillo de configurar. Soporta embeddings y reranking. Ideal para empezar |
| **TEI** (Text Embeddings Inference) | Runtime de HuggingFace, más optimizado. Tiene imágenes Docker separadas para CPU y GPU |
Ambos se despliegan con Docker y exponen una API REST compatible
</v-clicks>
+36
View File
@@ -0,0 +1,36 @@
---
layout: default
---
# Consumir reranker self-hosted desde Node.js
<v-clicks>
No hay integración oficial con LangChain.js, pero la API REST es muy sencilla:
```typescript
async function rerank(query: string, docs: string[], topN = 3) {
const res = await fetch("http://localhost:7997/rerank", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, documents: docs }),
});
const results = await res.json();
// [{ index: 2, relevance_score: 0.95 }, { index: 0, relevance_score: 0.72 }, ...]
return results
.sort((a, b) => b.relevance_score - a.relevance_score)
.slice(0, topN);
}
// Uso con documentos de LangChain
const searchResults = await vectorStore.similaritySearch(query, 10);
const ranked = await rerank(query, searchResults.map(d => d.pageContent));
const topDocs = ranked.map(r => searchResults[r.index]);
```
<div class="mt-4 p-4 bg-blue-500/10 rounded">
💡 Misma API para Infinity y TEI — cambiar de runtime no requiere cambiar código
</div>
</v-clicks>
+38
View File
@@ -0,0 +1,38 @@
---
layout: two-cols-header
---
# Mejores prácticas RAG
::left::
<v-clicks>
### Chunking
- Tamaño típico: 500-1500 caracteres
- Overlap: 10-20% del tamaño del chunk
- Experimenta con diferentes estrategias
### Embeddings
- Usa el mismo modelo para indexar y buscar
- Considera modelos multiidioma para español
- `text-embedding-3-small` buen balance
</v-clicks>
::right::
<v-clicks>
### Búsqueda
- Recupera 3-5 docs (más no siempre es mejor)
- Usa metadata para filtrar resultados
- Considera hybrid search (keyword + semántica)
### Prompts
- Instruye al LLM a basarse SOLO en el contexto
- Pide citas/referencias cuando sea posible
- Maneja casos sin información relevante
</v-clicks>
+38
View File
@@ -0,0 +1,38 @@
---
layout: two-cols-header
---
# Técnicas avanzadas de RAG
::left::
<v-clicks>
### Parent Document Retriever
- Busca con chunks pequeños
- Retorna documentos padres completos
- Mejor contexto sin perder precisión
### Multi-Query Retriever
- Genera múltiples variaciones de la pregunta
- Busca con todas las variaciones
- Combina resultados
</v-clicks>
::right::
<v-clicks>
### Contextual Compression
- Filtra partes irrelevantes de docs
- Reduce tokens enviados al LLM
- Mejora relevancia
### Self-Query Retriever
- Extrae filtros de metadata de la pregunta
- "Docs de 2023 sobre Python" → `{year: 2023, topic: "Python"}`
</v-clicks>
+24
View File
@@ -0,0 +1,24 @@
---
layout: default
---
# Recursos adicionales
<v-clicks>
### Documentación oficial
- [LangChain RAG Tutorial](https://docs.langchain.com/oss/javascript/tutorials/rag)
- [Qdrant LangChain Integration](https://qdrant.tech/documentation/frameworks/langchain/)
- [OpenAI Embeddings Guide](https://platform.openai.com/docs/guides/embeddings)
### Herramientas útiles
- [Qdrant Cloud](https://qdrant.tech) - BD vectorial gestionada
- [LangSmith](https://smith.langchain.com) - Debugging y evaluación
- [Embedding comparisons](https://huggingface.co/spaces/mteb/leaderboard) - MTEB Leaderboard
### Alternativas a explorar
- **LlamaIndex**: Framework especializado en RAG
- **Haystack**: Pipeline flexible para búsqueda
- **txtai**: Búsqueda semántica ligera
</v-clicks>