How to Build a RAG Pipeline with YouTube Transcripts
A working RAG pipeline over YouTube video transcripts: collect transcripts across a video list, chunk them, embed and store the vectors, retrieve the relevant chunks, and generate a cited answer - full code for every step, plus how to keep the index fresh.
00:09:00 · SEP 27, 2026
Réponse rapide
Récupérer les transcriptions
Obtenez une clé depuis le tableau de bord (100 crédits gratuits, sans carte bancaire) et récupérez la transcription de chaque vidéo par son identifiant. Les crédits sont facturés par requête, pas par minute de vidéo : une conférence de 3 heures coûte donc le même 1 crédit qu'un extrait de 2 minutes - ce qui compte dès que vous indexez tout le catalogue d'une chaîne :
async function fetchTranscript(videoId: string) {
const res = await fetch(
`https://getyoutubetranscript.com/api/v1/transcript?v=${videoId}`,
{ headers: { Authorization: `Bearer ${process.env.GETYOUTUBETRANSCRIPT_API_KEY}` } }
);
const json = await res.json();
if (!json.success) throw new Error(json.message);
return json.data as { video_id: string; title: string; transcript: string; word_count: number };
}
const videoIds = ['dQw4w9WgXcQ', 'jNQXAC9IVRw' /* ... */];
const videos = await Promise.all(videoIds.map(fetchTranscript));video_id et title reviennent avec le texte de la transcription - c'est ce que l'étape 5 utilise pour citer la réponse.
Découper les transcriptions
Découpez chaque transcription en fenêtres qui se chevauchent avant de générer les embeddings - un embedding sur la vidéo entière est trop grossier pour retrouver une réponse précise, et aucune bibliothèque n'est nécessaire pour cela :
function chunkText(text: string, size = 1200, overlap = 200) {
const chunks: string[] = [];
let start = 0;
while (start < text.length) {
const end = Math.min(start + size, text.length);
chunks.push(text.slice(start, end));
start += size - overlap;
}
return chunks;
}
const records = videos.flatMap((video) =>
chunkText(video.transcript).map((text, chunkIndex) => ({
videoId: video.video_id,
title: video.title,
chunkIndex,
text,
}))
);Une fenêtre de 1 200 caractères avec un chevauchement de 200 caractères est un bon point de départ pour des transcriptions orales - réduisez-la pour des extraits courts et denses, augmentez-la pour un contenu de type conférence où plus de contexte environnant aide.
Générer les embeddings et les stocker
Générez l'embedding de chaque morceau et conservez l'identifiant et le titre de la vidéo à côté du vecteur, puisque c'est ce qui transforme un morceau récupéré en citation par la suite :
import OpenAI from 'openai';
const openai = new OpenAI();
async function embed(text: string) {
const res = await openai.embeddings.create({ model: 'text-embedding-3-small', input: text });
return res.data[0].embedding;
}
const index = await Promise.all(
records.map(async (r) => ({ ...r, embedding: await embed(r.text) }))
);Cet exemple garde les vecteurs en mémoire par souci de clarté. Au-delà de quelques centaines de morceaux, remplacez le tableau par pgvector, Pinecone ou une autre base vectorielle - l'étape de récupération ci-dessous n'a besoin que d'une fonction renvoyant les k vecteurs les plus proches, pas exactement de cette structure.
Récupérer les morceaux pertinents
Au moment de la requête, générez l'embedding de la question avec le même modèle et classez les morceaux stockés par similarité cosinus :
function cosineSimilarity(a: number[], b: number[]) {
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] ** 2;
normB += b[i] ** 2;
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
async function retrieve(query: string, k = 5) {
const queryEmbedding = await embed(query);
return index
.map((r) => ({ ...r, score: cosineSimilarity(r.embedding, queryEmbedding) }))
.sort((a, b) => b.score - a.score)
.slice(0, k);
}k=5 est un bon point de départ - trop peu de morceaux et le modèle n'a pas assez de contexte pour répondre complètement, trop et des morceaux non pertinents commencent à diluer la réponse.
Générer une réponse fiable
Transmettez les morceaux récupérés au modèle sous forme de contexte numéroté et demandez-lui de répondre uniquement à partir de ce contenu, en citant ses sources - c'est ce qui garde la réponse traçable jusqu'à une vraie vidéo plutôt que jusqu'aux connaissances générales du modèle :
async function answer(query: string) {
const matches = await retrieve(query);
const context = matches
.map((m, i) => `[${i + 1}] From "${m.title}":\n${m.text}`)
.join('\n\n');
const completion = await openai.chat.completions.create({
model: 'gpt-4.1-mini',
messages: [
{
role: 'system',
content:
"Answer only using the numbered transcript excerpts below. Cite sources as [1], [2], etc. " +
"If the excerpts don't contain the answer, say so.",
},
{ role: 'user', content: `${context}\n\nQuestion: ${query}` },
],
});
return completion.choices[0].message.content;
}Citer par titre de vidéo, plutôt que d'inventer un horodatage, correspond à ce que l'API de transcription renvoie réellement - ne laissez pas le modèle fabriquer un horodatage qu'il n'a jamais reçu.
Maintenir l'index à jour
Pour une chaîne qui continue de publier, vérifiez les nouvelles vidéos selon un planning avec le endpoint gratuit /channel/latest (0 crédit), ignorez les identifiants de vidéo déjà indexés et ne récupérez, découpez et générez les embeddings que pour les nouvelles. Le coût d'ingestion reste ainsi proportionnel au nouveau contenu plutôt qu'une réindexation complète à chaque exécution.
Pourquoi les transcriptions fonctionnent bien pour le RAG
La vidéo est l'un des formats les plus pénibles pour construire de la recherche documentaire dessus - impossible de faire un grep dans une vidéo. Une transcription la retransforme en texte que votre pipeline d'embeddings et de récupération sait déjà traiter, sans exécuter vous-même un job de reconnaissance vocale. Et comme une API facturée à la requête ne mesure pas selon la durée de la vidéo, ingérer un ensemble de contenus longs (podcasts, conférences, streams de plusieurs heures) ne coûte pas systématiquement plus cher que d'ingérer des extraits courts - exactement le type d'archive qu'un pipeline RAG est généralement conçu pour explorer.
La référence complète des endpoints, l'authentification et les limites de débit se trouvent dans la documentation de l'API. Obtenez une clé avec 100 crédits gratuits depuis le tableau de bord pour tester cela de bout en bout.
FAQ sur le pipeline RAG
Ai-je besoin d'une vraie base de données vectorielle pour suivre ce guide ?
Non - le tableau en mémoire de ce guide fonctionne bien jusqu'à quelques centaines de morceaux et c'est le moyen le plus rapide de vérifier le pipeline de bout en bout. Passez à pgvector ou à une base vectorielle gérée une fois que vous indexez plus que cela, ou si vous avez besoin que l'index persiste entre les redémarrages.
Quel modèle d'embedding dois-je utiliser ?
text-embedding-3-small est un choix par défaut raisonnable - peu coûteux et suffisant pour la plupart des cas de récupération sur des transcriptions. Passez à text-embedding-3-large seulement si vous constatez des problèmes de qualité de récupération qu'il pourrait plausiblement résoudre ; il coûte plus cher par appel.
Que se passe-t-il si une vidéo n'a aucune transcription ?
Le endpoint de transcription renvoie un code d'erreur clair (TRANSCRIPT_DISABLED ou TRANSCRIPT_NOT_FOUND) plutôt qu'une chaîne vide - vérifiez success: false et ignorez cette vidéo plutôt que d'intégrer une chaîne vide comme s'il s'agissait d'un vrai contenu.
Combien coûte l'indexation de, disons, 200 vidéos ?
200 crédits pour les récupérations de transcriptions, qu'il s'agisse d'extraits de 2 minutes ou de vidéos de 2 heures - les 100 crédits gratuits à l'inscription couvrent à eux seuls environ la moitié d'un lot de 200 vidéos. Le coût des embeddings est séparé et facturé par votre fournisseur d'embeddings.
Puis-je citer un horodatage précis plutôt que juste le titre de la vidéo ?
Pas avec ce endpoint - l'API de transcription publique renvoie du texte brut, pas des segments horodatés, donc les exemples ici citent par titre et identifiant de vidéo. Si la citation au niveau de l'horodatage compte pour votre cas d'usage, gardez des morceaux plus petits pour que la citation au niveau vidéo reste aussi précise que possible.
À lire aussi
- YouTube API Quota Exceeded: Causes and Fixes
- YouTube Transcript API Rate Limit: What It Is and How to Handle 429s
- YouTube Transcript MCP Server: Setup Guide for Claude and Other AI Tools
- YouTube Transcripts in n8n: HTTP Request Workflow Guide
- Migrating from Supadata: A Field Guide
- How to Get YouTube Transcripts in Python
- Migrating from TranscriptAPI.com: A Field Guide
- Build an AI YouTube Video Summarizer with LangChain and Next.js
- The Best YouTube Transcript APIs in 2026
- GetYouTubeTranscript vs TranscriptAPI
- GetYouTubeTranscript vs youtubetotranscript.com
- GetYouTubeTranscript vs youtube-transcript.io
- GetYouTubeTranscript vs NoteGPT