Cómo funciona
POST /api/institution/courses/[courseId]/rag-ingest
Body: { "mode": "full" | "incremental" }
↓
1. Lista las lecciones publicadas del curso
2. Para cada lección, extrae texto por tipo:
- rich_text → strip HTML
- slides → join text elements + speaker notes
- quiz → join question + explanation por pregunta
- pdf → document-extractor (PyPDF + Adobe extract fallback)
- video → LiveClassTranscription.transcriptionText (si aprobada)
- assignment → instructions
3. Chunking: 800 tokens, 200 overlap, preserva estructura semántica
4. Embeddings vía Voyage AI (1024 dims, fallback OpenAI)
5. Crea ContentBlock + ContentEmbedding con metadata:
{ source: "course_lesson", courseId, lessonId, lessonTitle,
moduleTitle, ingestionId }
6. Estado final en CourseRagIngestion
Modos
mode: "full"
Elimina TODOS los ContentBlock + ContentEmbedding del curso y reingerir todo.
Cuándo usarlo:
- Primera ingestión del curso
- Tras una reorganización mayor (módulos renombrados, lecciones reordenadas)
- Sospecha de embeddings corruptos
Costo: proporcional al tamaño total del curso. Típicamente $0.004 por 30 lecciones.
mode: "incremental"
Identifica las lecciones modificadas desde la última ingestión (updatedAt > lastIngestionAt), elimina solo los chunks de esas lecciones y las reingerir.
Cuándo usarlo:
- Ediciones puntuales
- Auto-sync (recomendado para producción)
- Adición de nuevas lecciones
Costo: proporcional al delta. Típicamente $0.0001 por lección modificada.
Auto-sync
Course.autoSyncRag: Boolean @default(false)
Cuando está en true:
- Cada edición de lección vía API
/api/institution/courses/[id]/modules/[mid]/lessons/[lid]dispara una reingestión incremental - Se ejecuta vía Next.js
after()(no bloquea la request del admin) - Los fallos silenciosos se registran en
CourseRagIngestion.errorsJSON
Recomendado para producción. Mantenlo desactivado durante la configuración inicial del curso para evitar embeddings desperdiciados.
Estado y debug
GET /api/institution/courses/[courseId]/rag-ingest
Response:
{
"ingestionId": "uuid",
"courseId": "uuid",
"mode": "incremental",
"status": "completed", // pending | running | completed | failed
"startedAt": "2026-05-23T10:00:00Z",
"completedAt": "2026-05-23T10:01:34Z",
"stats": {
"lessonsProcessed": 5,
"chunksCreated": 47,
"tokensEmbedded": 12450,
"costUsd": 0.0006
},
"errors": []
}
Proveedor de embeddings
| Proveedor | Modelo | Dims | Costo/1K tokens |
|---|---|---|---|
| Voyage AI (primary) | voyage-3 | 1024 | $0.00005 |
| OpenAI (fallback) | text-embedding-3-large (con dimensions: 1024) | 1024 | $0.00013 |
Direct fetch (AI SDK v3 no soporta el parámetro dimensions):
// Voyage AI
fetch("https://api.voyageai.com/v1/embeddings", {
body: JSON.stringify({
model: "voyage-3",
input: texts,
input_type: "document"
})
})
Retrieval en runtime
Durante el chat del tutor, RetrievalAgent ejecuta:
const chunks = await retrieve({
query: reformulatedQuery,
filters: { tenantId, courseId },
k: 10,
tenantOnlyMode: true, // excluye fallback hacia contenido global
boostByWeakAreas: studentModel.quizContext.weakAreas
});
tenantOnlyMode: true garantiza que el tutor NUNCA cite contenido de otro tenant — incluso si existe contenido similar en la base de datos.
Eliminar RAG del curso
DELETE /api/institution/courses/[courseId]/rag-ingest
Elimina todos los ContentBlock + ContentEmbedding + CourseRagIngestion del curso. Útil para archivar cursos sin ocupar espacio en pgvector.
Limitaciones
- Imágenes: no se convierten en embeddings. Roadmap: descripción automática vía vision LLM antes del embedding.
- Ecuaciones matemáticas: se extraen como texto LaTeX-like. El RAG funciona, pero la calidad depende del markup original.
- Videos sin transcripción: no se ingieren. Configura la auto-transcripción en MediaAsset (Whisper → Google STT) antes.
- Tamaño máximo por curso: pgvector soporta millones de vectores, pero la latencia de retrieval aumenta. Más de 10K chunks por curso puede volverse lento — considera dividir en sub-cursos.