Comment ça fonctionne
POST /api/institution/courses/[courseId]/rag-ingest
Body: { "mode": "full" | "incremental" }
↓
1. Liste les leçons publiées du cours
2. Pour chaque leçon, extrait le texte par type :
- rich_text → suppression HTML
- slides → jointure des éléments texte + notes du présentateur
- quiz → jointure question + explication par question
- pdf → document-extractor (PyPDF + Adobe extract fallback)
- video → LiveClassTranscription.transcriptionText (si approuvée)
- assignment → instructions
3. Chunking : 800 tokens, 200 de chevauchement, préserve la structure sémantique
4. Embeddings via Voyage AI (1024 dims, fallback OpenAI)
5. Crée ContentBlock + ContentEmbedding avec métadonnées :
{ source: "course_lesson", courseId, lessonId, lessonTitle,
moduleTitle, ingestionId }
6. Statut final dans CourseRagIngestion
Modes
mode: "full"
Supprime TOUS les ContentBlock + ContentEmbedding du cours et réingère tout.
Quand l'utiliser :
- Première ingestion du cours
- Après une réorganisation majeure (modules renommés, leçons réordonnées)
- Suspicion d'embeddings corrompus
Coût : proportionnel à la taille totale du cours. Typiquement $0.004 pour 30 leçons.
mode: "incremental"
Identifie les leçons modifiées après la dernière ingestion (updatedAt > lastIngestionAt), supprime uniquement les chunks de ces leçons et les réingère.
Quand l'utiliser :
- Modifications ponctuelles
- Auto-sync (recommandé pour la production)
- Ajout de nouvelles leçons
Coût : proportionnel au delta. Typiquement $0.0001 par leçon modifiée.
Auto-sync
Course.autoSyncRag: Boolean @default(false)
Lorsque true :
- Toute modification de leçon via l'API
/api/institution/courses/[id]/modules/[mid]/lessons/[lid]déclenche une réingestion incrémentale - S'exécute via Next.js
after()(ne bloque pas la requête de l'administrateur) - Échec silencieux enregistré dans
CourseRagIngestion.errorsJSON
Recommandé pour la production. Maintenez désactivé lors de la configuration initiale du cours pour éviter les embeddings gaspillés.
Statut et débogage
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": []
}
Fournisseur d'embeddings
| Fournisseur | Modèle | Dims | Coût/1K tokens |
|---|---|---|---|
| Voyage AI (principal) | voyage-3 | 1024 | $0.00005 |
| OpenAI (fallback) | text-embedding-3-large (avec dimensions: 1024) | 1024 | $0.00013 |
Fetch direct (AI SDK v3 ne supporte pas le paramètre dimensions) :
// Voyage AI
fetch("https://api.voyageai.com/v1/embeddings", {
body: JSON.stringify({
model: "voyage-3",
input: texts,
input_type: "document"
})
})
Retrieval en runtime
Durant le chat du tuteur, RetrievalAgent exécute :
const chunks = await retrieve({
query: reformulatedQuery,
filters: { tenantId, courseId },
k: 10,
tenantOnlyMode: true, // exclut le fallback vers le contenu global
boostByWeakAreas: studentModel.quizContext.weakAreas
});
tenantOnlyMode: true garantit que le tuteur ne cite JAMAIS le contenu d'un autre tenant — même si un contenu similaire existe dans la base de données.
Supprimer le RAG d'un cours
DELETE /api/institution/courses/[courseId]/rag-ingest
Supprime tous les ContentBlock + ContentEmbedding + CourseRagIngestion du cours. Utile pour archiver des cours sans occuper pgvector.
Limitations
- Images : ne sont pas converties en embeddings. Feuille de route : description automatique via vision LLM avant l'embedding.
- Équations mathématiques : extraites sous forme de texte de type LaTeX. Le RAG fonctionne mais la qualité dépend du balisage d'origine.
- Vidéos sans transcription : ne sont pas ingérées. Configurez la transcription automatique dans MediaAsset (Whisper → Google STT) au préalable.
- Taille maximale par cours : pgvector supporte des millions de vecteurs mais la latence de retrieval augmente. >10K chunks par cours peut devenir lent — envisagez de diviser en sous-cours.