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

Jawaban singkat

Ambil transkrip setiap video lewat API yang di-hosting, potong teksnya, embed setiap potongan, dan simpan vektornya - lalu saat ada pertanyaan, embed pertanyaan tersebut, ambil potongan yang paling relevan, dan berikan ke LLM sebagai konteks yang berdasar fakta lengkap dengan sitasi ke video sumbernya.
01

Kumpulkan transkripnya

Dapatkan API key dari dashboard (100 kredit gratis, tanpa kartu) lalu ambil transkrip setiap video berdasarkan id-nya. Kredit dikenakan per permintaan, bukan per menit video, jadi kuliah 3 jam biayanya sama saja 1 kredit dengan klip 2 menit - ini penting saat Anda meng-ingest seluruh arsip sebuah channel:

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 dan title ikut dikembalikan bersama teks transkripnya - itulah yang disitir kembali sebagai jawaban di langkah 5.

02

Potong transkrip menjadi beberapa bagian

Pecah setiap transkrip menjadi jendela-jendela yang saling tumpang tindih sebelum di-embed - embedding satu video utuh terlalu kasar untuk mengambil jawaban yang spesifik, dan tidak perlu library apa pun untuk ini:

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

Jendela 1.200 karakter dengan overlap 200 karakter adalah nilai default yang wajar untuk transkrip ucapan lisan - perkecil untuk klip pendek yang padat, perbesar untuk konten gaya kuliah panjang di mana konteks sekitar lebih membantu.

03

Embed dan simpan

Embed setiap potongan dan simpan video id serta title bersama vektornya, karena itulah yang mengubah potongan yang diambil menjadi sitasi nanti:

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

Ini menyimpan vektor di memori demi kejelasan. Setelah lebih dari beberapa ratus potongan, ganti array ini dengan pgvector, Pinecone, atau vector store lain - langkah pengambilan di bawah hanya butuh fungsi yang mengembalikan top-k vektor terdekat, bukan bentuk persis ini.

04

Ambil potongan yang relevan

Saat ada pertanyaan, embed pertanyaan itu dengan model yang sama lalu urutkan potongan tersimpan berdasarkan cosine similarity:

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 adalah titik awal yang wajar - terlalu sedikit potongan dan model tidak punya cukup konteks untuk menjawab lengkap, terlalu banyak dan potongan yang tidak relevan mulai mengencerkan jawabannya.

05

Hasilkan jawaban yang berdasar fakta

Berikan potongan yang diambil ke model sebagai konteks bernomor dan instruksikan agar menjawab hanya dari yang tersedia serta menyitir sumbernya - inilah yang membuat jawaban bisa dilacak kembali ke video asli, bukan pengetahuan umum model itu sendiri:

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

Menyitir berdasarkan judul video, bukan mengarang timestamp, sesuai dengan apa yang benar-benar dikembalikan API transkrip - jangan biarkan model mengarang timestamp yang tidak pernah diberikan kepadanya.

Jaga agar indeks tetap segar

Untuk channel yang terus mengunggah, cek unggahan baru secara terjadwal lewat endpoint gratis /channel/latest (0 kredit), lewati video id yang sudah terindeks, dan hanya ambil, potong, serta embed yang baru saja. Biaya ingest jadi tetap sebanding dengan konten baru, bukan re-index penuh setiap kali dijalankan.

Kenapa transkrip cocok untuk RAG

Video adalah salah satu format yang cukup merepotkan untuk dibangun sistem retrieval-nya - Anda tidak bisa meng-grep video. Transkrip mengubahnya kembali menjadi teks yang sudah bisa ditangani stack embedding dan retrieval yang sudah Anda punya, tanpa perlu menjalankan speech-to-text sendiri. Dan karena API per-permintaan tidak mengukur berdasarkan panjang video, meng-ingest arsip konten panjang (podcast, kuliah, siaran berjam-jam) tidak jadi lebih mahal secara sistematis dibanding meng-ingest klip pendek - padahal itulah arsip yang biasanya ingin dicari lewat pipeline RAG.

Referensi endpoint lengkap, autentikasi, dan rate limit ada di API docs. Dapatkan API key dengan 100 kredit gratis dari dashboard untuk mencoba semuanya dari awal sampai akhir.

FAQ pipeline RAG

Q01

Apakah saya butuh vector database sungguhan untuk mengikuti ini?

Tidak - array di memori pada panduan ini sudah cukup untuk beberapa ratus potongan dan merupakan cara tercepat untuk memverifikasi pipeline dari awal sampai akhir. Pindah ke pgvector atau vector store terkelola begitu Anda mengindeks lebih dari itu, atau butuh indeks yang tetap ada setelah restart.

Q02

Model embedding mana yang sebaiknya saya pakai?

text-embedding-3-small adalah default yang wajar - murah dan cukup baik untuk kebanyakan retrieval transkrip. Pindah ke text-embedding-3-large hanya jika Anda melihat masalah kualitas retrieval yang kemungkinan bisa diatasi olehnya; biayanya lebih mahal per panggilan.

Q03

Apa yang terjadi jika sebuah video sama sekali tidak punya transkrip?

Endpoint transkrip mengembalikan kode error yang jelas (TRANSCRIPT_DISABLED atau TRANSCRIPT_NOT_FOUND) alih-alih string kosong - cek success: false dan lewati video itu, jangan meng-embed string kosong seolah-olah itu konten sungguhan.

Q04

Berapa biaya untuk mengindeks, katakanlah, 200 video?

200 kredit untuk pengambilan transkripnya, terlepas apakah videonya klip 2 menit atau video 2 jam - 100 kredit gratis saat mendaftar sudah menutupi kira-kira separuh backlog 200 video itu sendiri. Biaya embedding terpisah dan ditagih oleh penyedia embedding Anda.

Q05

Bisakah saya menyitir timestamp yang tepat, bukan cuma judul video?

Tidak dengan endpoint ini - API transkrip publik mengembalikan teks polos, bukan segmen berwaktu, jadi contoh di sini menyitir berdasarkan judul dan id video. Jika sitasi setingkat timestamp penting untuk kasus Anda, buat potongannya lebih kecil agar sitasi setingkat video tetap sepresisi mungkin.

Terkait