initial commit: slides ready + upgraded
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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");
|
||||
```
|
||||
@@ -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);
|
||||
});
|
||||
```
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
});
|
||||
```
|
||||
@@ -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);
|
||||
```
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user