Retour au blogIntégrations

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érez la transcription de chaque vidéo via une API hébergée, découpez le texte en morceaux, générez des embeddings pour ces morceaux et stockez les vecteurs - puis, au moment de la requête, générez l'embedding de la question, récupérez les morceaux les plus proches et transmettez-les à un LLM comme contexte fiable avec des citations vers la vidéo source.
01

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.

02

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.

03

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.

04

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.

05

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

Q01

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.

Q02

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.

Q03

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.

Q04

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.

Q05

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