Zurück zum BlogIntegrationen

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

Kurzantwort

Das Transkript jedes Videos über eine gehostete API abrufen, den Text in Abschnitte teilen, die Abschnitte einbetten und die Vektoren speichern - und zur Abfragezeit die Frage einbetten, die nächstliegenden Abschnitte abrufen und sie einem LLM als fundierten Kontext mit Quellenangaben zum Ausgangsvideo übergeben.
01

Die Transkripte sammeln

Hol dir einen Schlüssel im Dashboard (100 kostenlose Credits, keine Kreditkarte nötig) und rufe das Transkript jedes Videos anhand seiner ID ab. Credits werden pro Anfrage berechnet, nicht pro Videominute - ein 3-stündiger Vortrag kostet also denselben 1 Credit wie ein 2-minütiger Clip, was wichtig wird, sobald du den kompletten Backkatalog eines Kanals einliest:

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 und title kommen zusammen mit dem Transkripttext zurück - genau darauf verweist Schritt 5 später als Quellenangabe.

02

Die Transkripte in Abschnitte teilen

Teile jedes Transkript vor dem Einbetten in überlappende Fenster - ein Embedding für das gesamte Video ist zu grob, um eine konkrete Antwort daraus abzurufen, und dafür ist keine zusätzliche Bibliothek nötig:

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,
  }))
);

Ein Fenster von 1.200 Zeichen mit 200 Zeichen Überlappung ist ein vernünftiger Standardwert für gesprochene Transkripte - verkleinere es bei kurzen, dichten Clips, vergrößere es bei langen vortragsartigen Inhalten, bei denen mehr umgebender Kontext hilft.

03

Einbetten und speichern

Bette jeden Abschnitt ein und behalte Video-ID und Titel zusammen mit dem Vektor - genau das macht aus einem abgerufenen Abschnitt später eine Quellenangabe:

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) }))
);

Das hält die Vektoren zur Übersichtlichkeit im Arbeitsspeicher. Ab einigen hundert Abschnitten solltest du das Array durch pgvector, Pinecone oder einen anderen Vektorspeicher ersetzen - der Abrufschritt unten braucht nur eine Funktion, die die Top-k nächstgelegenen Vektoren zurückgibt, nicht genau diese Struktur.

04

Die relevanten Abschnitte abrufen

Bette zur Abfragezeit die Frage mit demselben Modell ein und ordne die gespeicherten Abschnitte nach Kosinus-Ähnlichkeit:

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 ist ein guter Startwert - zu wenige Abschnitte, und dem Modell fehlt genug Kontext für eine vollständige Antwort; zu viele, und irrelevante Abschnitte verwässern die Antwort.

05

Eine fundierte Antwort generieren

Übergib die abgerufenen Abschnitte als nummerierten Kontext an das Modell und weise es an, nur daraus zu antworten und seine Quellen anzugeben - genau das macht die Antwort bis zu einem echten Video zurückverfolgbar, statt sich auf das allgemeine Wissen des Modells zu verlassen:

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;
}

Die Quellenangabe per Videotitel statt eines erfundenen Zeitstempels entspricht dem, was die Transkript-API tatsächlich liefert - lass das Modell keinen Zeitstempel erfinden, den es nie bekommen hat.

Den Index aktuell halten

Bei einem Kanal, der weiter veröffentlicht, prüfe regelmäßig mit dem kostenlosen /channel/latest-Endpoint (0 Credits) auf neue Uploads, überspringe bereits indizierte Video-IDs und rufe, teile und embedde nur die neuen. So bleiben die Einlesekosten proportional zum neuen Inhalt, statt bei jedem Lauf einen kompletten Re-Index zu erfordern.

Warum Transkripte sich gut für RAG eignen

Video ist eines der unhandlicheren Formate, um darüber Retrieval zu bauen - man kann ein Video nicht grep-en. Ein Transkript verwandelt es zurück in Text, mit dem dein bestehender Embedding- und Retrieval-Stack bereits umgehen kann, ohne einen eigenen Speech-to-Text-Job zu betreiben. Und weil eine API mit Preis pro Anfrage nicht nach Videolänge abrechnet, wird das Einlesen eines Rückstands an langformatigen Inhalten (Podcasts, Vorträge, mehrstündige Streams) nicht systematisch teurer als das Einlesen kurzer Clips - genau das Archiv, für dessen Durchsuchung eine RAG-Pipeline meist gebaut wird.

Die vollständige Endpoint-Referenz, Authentifizierung und Rate-Limits stehen in der API-Dokumentation. Hol dir einen Schlüssel mit 100 kostenlosen Credits im Dashboard, um das komplett durchzutesten.

RAG-Pipeline FAQ

Q01

Brauche ich eine echte Vektordatenbank, um das nachzubauen?

Nein - das In-Memory-Array in dieser Anleitung funktioniert bis zu einigen hundert Abschnitten problemlos und ist der schnellste Weg, die Pipeline komplett durchzutesten. Wechsle zu pgvector oder einem verwalteten Vektorspeicher, sobald du mehr indizierst oder der Index Neustarts überdauern soll.

Q02

Welches Embedding-Modell sollte ich verwenden?

text-embedding-3-small ist ein vernünftiger Standard - günstig und für die meisten Transkript-Retrievals gut genug. Wechsle nur zu text-embedding-3-large, wenn du Qualitätsprobleme beim Retrieval siehst, die es plausibel beheben würde; es kostet pro Aufruf mehr.

Q03

Was passiert, wenn ein Video gar kein Transkript hat?

Der Transkript-Endpoint gibt einen klaren Fehlercode zurück (TRANSCRIPT_DISABLED oder TRANSCRIPT_NOT_FOUND) statt eines leeren Strings - prüfe success: false und überspringe das Video, statt einen leeren String einzubetten, als wäre er echter Inhalt.

Q04

Was kostet es, sagen wir 200 Videos zu indizieren?

200 Credits für die Transkript-Abrufe, egal ob es 2-minütige Clips oder 2-stündige Videos sind - die 100 kostenlosen Signup-Credits decken für sich genommen schon rund die Hälfte eines 200-Video-Rückstands ab. Die Embedding-Kosten kommen separat dazu und werden von deinem Embedding-Anbieter abgerechnet.

Q05

Kann ich statt nur des Videotitels einen genauen Zeitstempel angeben?

Nicht mit diesem Endpoint - die öffentliche Transkript-API liefert reinen Text, keine zeitgestempelten Segmente, deshalb zitieren die Beispiele hier per Videotitel und -ID. Wenn eine zeitstempel-genaue Quellenangabe für deinen Anwendungsfall wichtig ist, halte die Abschnitte kleiner, damit die Quellenangabe auf Videoebene so präzise wie möglich bleibt.

Verwandte Themen