RAG sur une chaîne YouTube : du RSS aux réponses sourcées

Construire un RAG sur une chaîne YouTube, c'est répondre à « qu'a dit ce créateur au sujet de X, et dans quelle vidéo ? » — sur tout ce qu'il a publié. Le pipeline tient en quatre étapes : découvrir les vidéos via RSS, récupérer les transcriptions, découper et vectoriser, répondre avec attribution. Voici chaque étape en Python exécutable, y compris ce que la plupart des articles passent sous silence : la gestion du quota et ce à quoi ressemble honnêtement la citation sans horodatage.
Étape 1 : les IDs de vidéos depuis le flux RSS de la chaîne
YouTube fournit toujours un vrai flux RSS par chaîne, sans clé API :
import requests
import xml.etree.ElementTree as ET
CHANNEL_ID = "UCxxxxxxxxxxxxxxxxxxxxxx"
NS = "{http://www.youtube.com/xml/schemas/2015}"
feed = requests.get(
f"https://www.youtube.com/feeds/videos.xml?channel_id={CHANNEL_ID}",
timeout=30,
).text
video_ids = [el.text for el in ET.fromstring(feed).iter(f"{NS}videoId")]
Mise en garde : le flux ne renvoie que les ~15 mises en ligne les plus récentes. Pour une chaîne active, exécutez ce code régulièrement et accumulez les IDs dans un fichier ou une table ; le catalogue au-delà exige la YouTube Data API ou une liste manuelle établie une fois. Pour maintenir un index à jour, en revanche, le RSS est parfait — c'est aussi la colonne vertébrale du pipeline chaîne vers newsletter.
Étape 2 : récupérer les transcriptions en respectant le quota
import time
API_KEY = "yt_your_key_here"
def fetch(video_id: str) -> dict | None:
resp = requests.get(
"https://youtube2text.org/api/transcribe",
params={"url": f"https://www.youtube.com/watch?v={video_id}"},
headers={"x-api-key": API_KEY},
timeout=90,
)
if resp.status_code == 404:
return None # TRANSCRIPT_UNAVAILABLE — no captions, skip
if resp.status_code == 429:
time.sleep(resp.json()["error"].get("retryAfterSeconds", 30))
return fetch(video_id)
resp.raise_for_status()
return resp.json()["result"]
transcripts = [t for vid in video_ids if (t := fetch(vid))]
Faites le calcul sur votre formule avant de boucler sur une chaîne : le niveau gratuit offre 5 vidéos par mois, ce qui couvre une preuve de concept et rien d'autre. La formule basique (5,99 $) en donne 50, la pro (9,99 $) en donne 500 — la pro est le plancher réaliste pour indexer une chaîne plus ses mises en ligne courantes.
Étape 3 : découper et vectoriser
N'importe quelle base vectorielle convient ; Chroma garde l'exemple court. Les sous-titres automatiques manquent souvent de ponctuation, donc je découpe par nombre de mots plutôt que par phrases :
import chromadb
def chunk(text: str, size: int = 300, overlap: int = 50):
words = text.split()
for i in range(0, len(words), size - overlap):
yield " ".join(words[i:i + size])
client = chromadb.PersistentClient(path="./channel_index")
col = client.get_or_create_collection("channel")
for t in transcripts:
for i, piece in enumerate(chunk(t["content"])):
col.add(
ids=[f'{t["videoId"]}-{i}'],
documents=[piece],
metadatas=[{"videoId": t["videoId"], "title": t["title"]}],
)
Étape 4 : répondre avec citations — version honnête
L'API renvoie du texte brut sans horodatage, donc je vais être franc : vous ne pouvez pas citer « à 23:41 » à partir de ces données. Ce que vous pouvez faire — et c'est généralement suffisant — c'est l'attribution par vidéo au niveau du chunk : chaque extrait récupéré connaît son videoId et son title, donc les réponses citent la vidéo d'origine, avec un lien fonctionnel.
hits = col.query(query_texts=["what's their take on electric trucks?"], n_results=5)
context = "\n\n".join(
f'[{m["title"]}] (https://www.youtube.com/watch?v={m["videoId"]})\n{doc}'
for doc, m in zip(hits["documents"][0], hits["metadatas"][0])
)
prompt = (
"Answer using only the excerpts below. After each claim, "
f"cite the video title in brackets.\n\n{context}\n\nQuestion: ..."
)
Envoyez prompt au LLM de votre choix — un modèle local fonctionne très bien ici, même configuration que le pipeline de résumé Ollama.
Notes d'assemblage
Pour les adeptes des frameworks : tout ceci se transpose tel quel sur LlamaIndex ou LangChain si vous préférez ne pas coder le découpage à la main. Dans tous les cas, la couche transcription est identique, et c'est la seule partie avec un compteur — récupérez une clé sur youtube2text.org/app/keys et commencez avec le niveau gratuit pour valider le pipeline avant de le pointer vers 300 vidéos.