AI

Comment créer un pipeline RAG local avec Bright Data et ChromaDB

Apprenez à créer un pipeline RAG combinant le Web Unlocker de Bright Data avec ChromaDB pour accéder localement aux données web actuelles sans hallucinations.
19 min de lecture
ChromaDB with Bright Data

Dans cet article, vous apprendrez :

  • Ce que sont la génération augmentée par récupération (RAG) et ChromaDB, et ce que chacune apporte.
  • Pourquoi associer l’API Web Unlocker de Bright Data à ChromaDB est une approche pratique pour ancrer un modèle de langage dans des données réelles et actualisées.
  • Comment créer un pipeline de bout en bout qui collecte du contenu web, l’intègre localement et répond à des questions, le tout sur votre propre machine.

Avant d’aborder les outils et le code, il est utile de clarifier les concepts et de voir comment ils s’articulent dans un workflow RAG.

Qu’est-ce que la génération augmentée par récupération (RAG) ?

Un grand modèle de langage ne connaît que ce sur quoi il a été entraîné. Interrogez-le sur une page publiée la semaine dernière, un wiki interne ou un catalogue de produits de niche, et il devinera ou vous dira qu’il ne sait pas. La génération augmentée par récupération comble cette lacune.

L’idée est simple. Au lieu de s’appuyer sur la mémoire du modèle, vous stockez vos propres documents dans un index consultable. Lorsqu’une question arrive, vous récupérez d’abord les extraits de texte les plus pertinents, puis vous augmentez le prompt en y collant ces extraits comme contexte, et enfin vous laissez le modèle générer une réponse ancrée dans ce contenu. Le modèle fait toujours l’écriture, mais les faits proviennent de données que vous contrôlez.

Cela compte pour deux raisons. Les réponses restent actuelles car vous décidez ce qui entre dans l’index et quand le rafraîchir. Et les réponses restent précises car le modèle raisonne sur du texte source réel plutôt que de combler des lacunes à partir des données d’entraînement. Pour les bases de connaissances, les assistants support, les outils de recherche et tout ce qui repose sur des informations propriétaires ou évolutives, le RAG est devenu l’approche standard.

La partie récupération étant couverte, voyons où ces documents résident concrètement.

Qu’est-ce que ChromaDB ?

ChromaDB (souvent appelé simplement Chroma) est une base de données vectorielle open source conçue pour les applications IA. Une base de données classique fait correspondre des valeurs exactes ; une base de données vectorielle fait correspondre le sens. Elle y parvient en stockant des embeddings, qui sont des représentations numériques du texte où les idées similaires se retrouvent proches dans l’espace vectoriel. Lorsque vous l’interrogez, Chroma renvoie les extraits stockés dont les embeddings sont les plus proches de votre question, même sans correspondance exacte de mots.

Ce qui fait de Chroma un bon choix pour un premier projet RAG, c’est sa simplicité d’utilisation. Il s’installe avec un simple pip install, s’exécute en mode embarqué dans votre processus Python sans serveur séparé à gérer, et persiste tout sur disque via PersistentClient afin que votre index survive à un redémarrage. Vous pouvez brancher n’importe quel modèle d’embedding, des API OpenAI ou Google à un modèle local qui ne touche jamais le réseau. Pour un projet devant tourner sur un ordinateur portable et rester privé, cette combinaison est difficile à battre.

RAG vs Fine-Tuning : quelle est la différence ?

Les personnes novices dans ce domaine opposent souvent le RAG au fine-tuning comme s’il s’agissait d’options concurrentes. Ils résolvent des problèmes différents :

  • Le fine-tuning modifie le modèle lui-même. Vous le réentraînez sur des exemples pour qu’il adapte son ton, son format ou son comportement. C’est le bon outil quand vous voulez que le modèle agisse différemment, mais c’est lent, coûteux, et les connaissances intégrées deviennent obsolètes dès que vos données changent.
  • Le RAG laisse le modèle intact et change ce qu’il voit au moment de la question. Vous injectez du contexte pertinent dans le prompt à chaque requête. C’est le bon outil quand vous voulez que le modèle connaisse quelque chose de spécifique et d’actuel, et la mise à jour est aussi simple que l’ajout d’un document à votre index.

La règle générale retenue par la plupart des équipes : si vous avez besoin de nouvelles connaissances, optez pour le RAG ; si vous avez besoin d’un nouveau comportement, envisagez le fine-tuning. De nombreux systèmes en production utilisent les deux. Dans ce tutoriel, nous nous concentrons entièrement sur le RAG, car l’objectif est de répondre à des questions sur du contenu web qui évolue bien trop souvent pour réentraîner un modèle.

Pourquoi intégrer Bright Data dans un pipeline RAG + ChromaDB ?

Un système RAG ne vaut que ce que valent les documents qu’on lui fournit. Des données médiocres en entrée donnent des réponses médiocres en sortie, mais formulées avec assurance. Le vrai goulot d’étranglement dans la plupart des pipelines n’est donc pas la recherche vectorielle ou le modèle, c’est l’obtention de sources propres, fiables et à jour.

C’est simple quand vos données se trouvent déjà dans un dossier de PDF. Cela devient difficile dès que vos connaissances doivent provenir du web ouvert. Les pages publiques se cachent derrière des systèmes de détection de bots, rendent leur contenu en JavaScript, servent des résultats différents selon la région et lancent des CAPTCHA à tout ce qui semble automatisé. Maintenir ses propres scrapers face à tout cela est un travail à part entière, qui casse à chaque fois qu’un site cible modifie son balisage.

C’est là qu’intervient l’API Web Unlocker de Bright Data. Vous lui envoyez une URL cible, et elle vous renvoie le contenu de la page en gérant en coulisses les mesures anti-bot, la rotation de proxies, le rendu JavaScript et le géociblage. Vous pouvez même lui demander de renvoyer la page en Markdown propre, ce qui est idéal pour le RAG car cela supprime la navigation et le contenu générique pour ne laisser que du texte lisible à découper et intégrer. Pas d’automatisation de navigateur, pas de pool de proxies à gérer.

Combiné au stockage vectoriel local de Chroma et à un modèle exécuté localement, cela vous donne un pipeline actuel là où c’est nécessaire et privé partout ailleurs : seule l’étape de collecte de données accède au web, tandis que l’embedding, le stockage, la récupération et la génération restent sur votre machine.

Ce modèle est particulièrement utile pour :

  • Les assistants de recherche qui répondent à des questions sur les derniers articles, publications ou documentations plutôt que sur un instantané d’entraînement du modèle.
  • Les outils d’intelligence compétitive qui suivent les pages de produits, de prix ou de fonctionnalités sur un ensemble de sites concurrents.
  • Les bots de support interne ancrés dans la documentation en direct, afin que les réponses reflètent les docs actuelles plutôt qu’une version d’il y a des mois.
  • Les systèmes de surveillance de marché qui récupèrent de nouvelles annonces ou actualités et permettent à un analyste de les interroger en langage naturel.

En laissant l’infrastructure de données web de Bright Data gérer la collecte et Chroma gérer la récupération, vous obtenez un moteur RAG de qualité production sans écrire une seule ligne de code de scraping.

Comment créer un pipeline RAG local avec Bright Data et ChromaDB

Dans cette section guidée, vous allez créer un pipeline en trois étapes :

  1. Collecter le contenu web : Un script appelle l’API Web Unlocker de Bright Data pour récupérer un ensemble de pages et renvoyer chacune en Markdown.
  2. Intégrer et stocker : Un second script découpe ce contenu, génère des embeddings localement avec un modèle sentence-transformer, et écrit les vecteurs dans ChromaDB.
  3. Récupérer et générer : Un script de requête intègre votre question, extrait les extraits les plus pertinents de Chroma, et les transmet à un modèle local pour produire une réponse ancrée avec les sources.

Remarque : Il s’agit d’une conception parmi d’autres possibles. Vous pourriez remplacer le modèle local par un appel API pour des réponses de meilleure qualité, ajouter une étape de reclassement entre récupération et génération, ou pointer l’étape de collecte vers des centaines d’URL au lieu de trois. La structure reste la même.

Suivez les étapes ci-dessous pour créer un pipeline RAG entièrement local alimenté par l’API Web Unlocker de Bright Data et ChromaDB.

Prérequis

Pour suivre ce tutoriel, vous avez besoin de :

  • Un compte Bright Data avec une zone Web Unlocker active. Connectez-vous à votre tableau de bord, allez dans Paramètres du compte et copiez votre Token API (au format UUID). Notez également le nom de votre zone ; vous aurez besoin des deux.
  • Python 3.10+ installé localement.
  • Ollama installé, avec un modèle téléchargé pour l’étape de génération (ce tutoriel utilise llama3.1, mais tout modèle de chat convient). Si vous préférez un modèle hébergé, l’étape 5 indique où substituer un appel API.
Zone Web Unlocker Bright Data et token API

Étape 1 : Configuration du projet

Créez un répertoire de travail, configurez un environnement virtuel et installez les dépendances :

mkdir local-rag-pipeline && cd local-rag-pipeline
python -m venv venv
source venv/bin/activate        # Sur Windows : venv\Scripts\activate
pip install requests chromadb sentence-transformers

Lors du premier lancement du pipeline, sentence-transformers téléchargera le modèle d’embedding (quelques centaines de mégaoctets). Ensuite, il se charge depuis le cache et fonctionne hors ligne.

Téléchargez un modèle pour l’étape de génération si ce n’est pas déjà fait :

ollama pull llama3.1

Étape 2 : Création de la structure du projet

Créez les dossiers dans lesquels le pipeline écrira :

mkdir -p data/raw data/chroma

La structure de votre projet ressemblera à ceci :

local-rag-pipeline/
├── data/
│   ├── raw/            # Markdown brut collecté depuis le web
│   └── chroma/         # Index ChromaDB persisté
├── collect.py          # Étape 1 : récupération des pages via Bright Data
├── ingest.py           # Étape 2 : découpage, embedding et stockage
└── rag.py              # Étape 3 : récupération et génération

Étape 3 : Collecte des données web avec Bright Data

Créez le fichier collect.py :

import json
import requests
from pathlib import Path

API_KEY = "your-brightdata-api-token-here"
ZONE = "web_unlocker1"
BASE_URL = "https://api.brightdata.com/request"
RAW_DATA_PATH = "data/raw/pages.json"

HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

TARGETS = [
    "https://en.wikipedia.org/wiki/Retrieval-augmented_generation",
    "https://en.wikipedia.org/wiki/Vector_database",
    "https://en.wikipedia.org/wiki/Large_language_model",
]


def fetch_pages():
    results = []
    for url in TARGETS:
        print(f"Fetching: {url}")
        response = requests.post(
            BASE_URL,
            headers=HEADERS,
            json={
                "zone": ZONE,
                "url": url,
                "format": "raw",
                "data_format": "markdown",
            },
            timeout=60,
        )
        response.raise_for_status()
        results.append({"url": url, "content": response.text})
        print(f"  -> {len(response.text)} chars")

    Path(RAW_DATA_PATH).parent.mkdir(parents=True, exist_ok=True)
    with open(RAW_DATA_PATH, "w") as f:
        json.dump(results, f, indent=2)

    print(f"Saved {len(results)} pages to {RAW_DATA_PATH}")


if __name__ == "__main__":
    fetch_pages()

Remplacez your-brightdata-api-token-here par votre token API réel et mettez à jour ZONE pour correspondre au nom de votre zone Web Unlocker.

Voici ce que fait chaque partie :

  • API_KEY et ZONE : Vos identifiants Bright Data. Le token API est le token au format UUID de vos paramètres de compte, pas un mot de passe de zone.
  • TARGETS : Les pages à ingérer. Les trois articles Wikipedia ici forment un corpus cohérent sur lequel poser des questions. Remplacez par vos propres URL ; c’est exactement là que le Web Unlocker justifie sa place, car les sites d’actualités, les pages produits et les applications JavaScript qui bloquent les requêtes ordinaires reviennent propres via l’API.
  • fetch_pages : Parcourt chaque URL et envoie une requête POST à l’endpoint Web Unlocker. L’option data_format: "markdown" indique à Bright Data de renvoyer du Markdown lisible plutôt que du HTML brut, ce qui vous évite une étape d’analyse. Les résultats sont écrits dans un seul fichier JSON pour l’étape suivante.

Remarque : Certaines pages peuvent renvoyer un message bad_endpoint si un site est restreint en mode d’accès immédiat de Bright Data. C’est un comportement attendu ; Bright Data expose l’erreur dans la réponse plutôt que d’échouer silencieusement. Contactez votre Gestionnaire de compte si vous avez besoin d’un accès complet à une cible restreinte.

Sortie terminal après l'exécution de collect.py

Étape 4 : Intégration du contenu dans ChromaDB

Créez le fichier ingest.py :

import json
import chromadb
from chromadb.utils import embedding_functions

RAW_DATA_PATH = "data/raw/pages.json"
CHROMA_PATH = "data/chroma"
COLLECTION_NAME = "web_knowledge"


def chunk_text(text, size=800, overlap=100):
    words = text.split()
    chunks, i = [], 0
    while i < len(words):
        chunks.append(" ".join(words[i:i + size]))
        i += size - overlap
    return chunks


def main():
    with open(RAW_DATA_PATH) as f:
        pages = json.load(f)

    client = chromadb.PersistentClient(path=CHROMA_PATH)
    embed_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
        model_name="all-MiniLM-L6-v2"
    )
    collection = client.get_or_create_collection(
        name=COLLECTION_NAME,
        embedding_function=embed_fn,
    )

    documents, metadatas, ids = [], [], []
    for page in pages:
        for idx, chunk in enumerate(chunk_text(page["content"])):
            documents.append(chunk)
            metadatas.append({"source": page["url"], "chunk": idx})
            ids.append(f"{page['url']}#{idx}")

    collection.upsert(documents=documents, metadatas=metadatas, ids=ids)

    print(f"Indexed {len(documents)} chunks from {len(pages)} pages")
    print(f"Collection now holds {collection.count()} chunks")


if __name__ == "__main__":
    main()

Cette étape effectue le travail essentiel de transformation du texte brut en quelque chose de consultable :

  • chunk_text : Découpe chaque page en fenêtres chevauchantes d’environ 800 mots. Le découpage est important car vous voulez récupérer des passages ciblés, pas des pages entières, et le chevauchement de 100 mots évite qu’une phrase à cheval sur une limite soit coupée en deux.
  • SentenceTransformerEmbeddingFunction : Charge all-MiniLM-L6-v2, un modèle d’embedding petit et rapide qui s’exécute localement. Chroma l’appelle automatiquement chaque fois que vous ajoutez ou interrogez des documents, vous n’avez donc jamais à manipuler les vecteurs manuellement.
  • get_or_create_collection : Ouvre la collection persistante sur disque, la créant lors du premier lancement et la réutilisant ensuite.
  • collection.upsert : Écrit les extraits, leurs métadonnées et des ID stables dans Chroma. L’utilisation d’upsert plutôt qu’add signifie que vous pouvez relancer le script après avoir collecté du contenu frais sans vous heurter à des erreurs d’ID en double.

Étape 5 : Construction de l’étape de récupération et de génération

Créez le fichier rag.py :

import sys
import requests
import chromadb
from chromadb.utils import embedding_functions

CHROMA_PATH = "data/chroma"
COLLECTION_NAME = "web_knowledge"
OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL = "llama3.1"

PROMPT_TEMPLATE = """You are a research assistant. Answer the question using only the context below.
If the context does not contain the answer, say you don't have enough information.

Context:
{context}

Question: {question}

Answer:"""


def retrieve(question, n_results=4):
    client = chromadb.PersistentClient(path=CHROMA_PATH)
    embed_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
        model_name="all-MiniLM-L6-v2"
    )
    collection = client.get_collection(
        name=COLLECTION_NAME, embedding_function=embed_fn
    )
    results = collection.query(query_texts=[question], n_results=n_results)
    chunks = results["documents"][0]
    sources = [m["source"] for m in results["metadatas"][0]]
    return chunks, sources


def generate(question, chunks):
    prompt = PROMPT_TEMPLATE.format(context="\n\n".join(chunks), question=question)
    response = requests.post(
        OLLAMA_URL,
        json={"model": MODEL, "prompt": prompt, "stream": False},
        timeout=120,
    )
    response.raise_for_status()
    return response.json()["response"]


def main(question):
    chunks, sources = retrieve(question)
    answer = generate(question, chunks)

    print("\n=== Answer ===")
    print(answer.strip())
    print("\n=== Sources ===")
    for src in dict.fromkeys(sources):   # dédoublonnage, conservation de l'ordre
        print(f"  - {src}")


if __name__ == "__main__":
    main(" ".join(sys.argv[1:]))

Voici le flux :

  • retrieve : Intègre votre question avec le même modèle utilisé lors de l’ingestion (cette cohérence est ce qui rend la recherche par similarité fonctionnelle) et demande à Chroma les quatre extraits les plus proches. Elle renvoie à la fois le texte et l’URL source de chaque extrait.
  • generate : Construit un prompt qui ancre le modèle dans le contexte récupéré et lui demande d’admettre quand la réponse n’est pas disponible, ce qui est la garde la plus efficace contre les hallucinations. Il appelle ensuite un modèle local via l’API REST d’Ollama.
  • main : Assemble les deux et affiche la réponse accompagnée de la liste dédoublonnée des sources, afin que chaque réponse soit traçable jusqu’aux pages dont elle provient.

Si vous préférez un modèle hébergé, c’est la seule fonction qui change. Remplacez l’appel requests.post à Ollama par un appel à l’API Anthropic ou OpenAI en passant le même prompt ; la partie récupération reste exactement identique.

Étape 6 : Exécution du pipeline

Exécutez les trois étapes dans l’ordre. D’abord, collectez les pages :

python collect.py

Ensuite, découpez-les et intégrez-les dans Chroma :

python ingest.py
Exécution de l'ingestion RAG

Posez maintenant une question :

python rag.py "What problem does retrieval-augmented generation solve?"

Étape 7 : Inspection des résultats

La requête renvoie une réponse ancrée suivie des sources sur lesquelles elle s’est appuyée :

=== Answer ===
Retrieval-augmented generation addresses the fact that a language model only
knows what it was trained on. By retrieving relevant passages from an external
index at query time and adding them to the prompt, the model can answer using
current, specific information it was never trained on, which also reduces
hallucination because the response is anchored to real source text.

=== Sources ===
  - https://en.wikipedia.org/wiki/Retrieval-augmented_generation
  - https://en.wikipedia.org/wiki/Large_language_model
Réponse RAG avec sources citées dans le terminal

Suggestion de capture d’écran : la sortie terminal d’une requête rag.py, montrant la réponse générée et la liste des sources en dessous.

Essayez quelques questions supplémentaires pour évaluer la qualité de la récupération :

python rag.py "How does a vector database differ from a relational database?"
python rag.py "What are common limitations of large language models?"

Si une réponse semble insuffisante, deux paramètres méritent d’être ajustés en premier. Augmenter n_results dans rag.py fournit plus de contexte au modèle, ce qui aide pour les questions larges au prix d’un prompt plus long. Ajuster size et overlap dans ingest.py modifie la façon dont le texte est découpé ; des extraits plus petits affinent les recherches précises, des extraits plus grands préservent davantage de contexte environnant. Relancez ingest.py après tout changement de découpage pour que l’index en tienne compte.

L’ensemble du processus s’est déroulé sans une seule ligne de code de scraping ou de proxy. Bright Data a livré du Markdown propre depuis le web, Chroma a géré les embeddings et la recherche par similarité localement, et un modèle local a produit la réponse, le tout ancré dans les pages que vous avez choisies de collecter.

Aller plus loin

Ce pipeline est une base fonctionnelle, et vous pouvez l’étendre dans plusieurs directions :

  • Remplacez le modèle local par une API hébergée comme Anthropic ou OpenAI lorsque vous avez besoin d’un raisonnement plus puissant ou d’un contexte plus long, en laissant toute la partie récupération intacte.
  • Ajoutez une étape de découverte avec l’API SERP de Bright Data afin que le pipeline puisse trouver des pages pertinentes à ingérer à partir d’une requête de recherche, plutôt que de travailler à partir d’une liste d’URL fixe.
  • Extrayez des enregistrements structurés plutôt que du texte libre en utilisant l’API Web Scraper de Bright Data, qui couvre plus de 120 domaines. Le tutoriel RAG agentique présente ce modèle de bout en bout.
  • Utilisez le filtrage par métadonnées de Chroma (l’argument where sur query) pour limiter la récupération à une source, une date ou une catégorie spécifique.
  • Ajoutez une couche de reclassement ou de recherche hybride si vos documents sont remplis de noms propres et de codes avec lesquels la recherche purement sémantique peine.
  • Planifiez des actualisations périodiques, ou exposez l’ensemble à un assistant via un serveur MCP afin qu’un outil comme Claude Desktop puisse collecter et interroger à la demande.
  • Passez de Chroma local à un stockage vectoriel géré comme Pinecone ou pgvector une fois que votre collection dépasse les capacités d’une seule machine ; la logique d’ingestion et de récupération se transporte avec un minimum de modifications.

Les possibilités sont pratiquement infinies.

Conclusion

Dans cet article, vous avez construit un pipeline RAG local fonctionnel en combinant l’API Web Unlocker de Bright Data avec ChromaDB.

Chroma gère l’embedding, le stockage et la recherche par similarité localement, de sorte que votre index et vos requêtes ne quittent jamais votre machine. Un modèle local transforme le contexte récupéré en une réponse ancrée avec citations de sources. Et Bright Data supprime la partie la plus difficile de tout le processus : collecter du contenu de page frais et propre depuis le web ouvert sans gérer de proxies, écrire des scrapers ou lutter contre les systèmes anti-bot.

Contrairement à un assistant sans code, cette stack vous donne un contrôle total sur chaque couche : quelles pages vous collectez, comment vous les découpez et les intégrez, combien vous en récupérez, et quel modèle rédige la réponse. Elle s’intègre naturellement dans toute plateforme de données ou IA plus grande et évolue selon vos besoins.

Pour créer des pipelines plus riches, explorez la suite complète d’outils de données web de Bright Data, notamment le Web Unlocker pour les pages protégées par des bots, l’API SERP pour les données de recherche, et des jeux de données prêts à l’emploi pour les cas d’usage courants.

Inscrivez-vous gratuitement à Bright Data dès aujourd’hui et commencez à ancrer vos modèles dans de vraies données web.