Vous avez entendu parler de RAG (Retrieval-Augmented Generation) et vous souhaitez créer un système de recherche intelligent pour vos documents ? Parfait ! Dans ce tutoriel complet, je vais vous guider pas à pas dans la mise en place d'un système RAG avec Qdrant et le filtrage avancé. En tant qu'auteur technique qui a déployé des centaines de systèmes RAG en production, je vous partage ici ma méthode éprouvée.
Prérequis : Ce dont vous aurez besoin
Pas de panique si vous débutez en développement. Voici la liste minimale pour suivre ce tutoriel :
- Python 3.9 ou supérieur installé sur votre machine
- Un compte S'inscrire ici pour obtenir votre clé API HolySheep
- Docker installé (pour Qdrant)
- 15 minutes de votre temps
Pourquoi Qdrant pour votre RAG ?
Pendant des années, j'ai testé différentes bases de données vectorielles. Pinecone, Weaviate, Chroma... Mais Qdrant s'est imposé pour une raison simple : sa flexibilité en matière de filtrage et de recherche à facettes est incomparable. Imaginez pouvoir rechercher "tous les documents commerciaux de 2024 en français" en une seule requête. C'est exactement ce que permet Qdrant.
Installation de l'environnement
Étape 1 : Créer un environnement virtuel Python
# Créer et activer un environnement virtuel
python -m venv rag-env
Sur Windows
rag-env\Scripts\activate
Sur macOS/Linux
source rag-env/bin/activate
Étape 2 : Installer les dépendances
# Installer les packages nécessaires
pip install qdrant-client openai dash vectorstores-helpers python-dotenv
Étape 3 : Lancer Qdrant avec Docker
# Télécharger et lancer Qdrant en arrière-plan
docker run -d --name qdrant \
-p 6333:6333 \
-p 6334:6334 \
qdrant/qdrant
Configuration de votre projet RAG
Créer le fichier .env
Créez un fichier .env à la racine de votre projet :
# HolySheep AI Configuration
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
HOLYSHEEP_BASE_URL=https://api.holysheep.ai/v1
Qdrant Configuration
QDRANT_HOST=localhost
QDRANT_PORT=6333
Initialisation du client Qdrant
Maintenant, créons notre premier script Python. Je vais commenter chaque ligne pour que vous compreniez parfaitement ce qui se passe.
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
from dotenv import load_dotenv
import os
Charger les variables d'environnement
load_dotenv()
Connexion à Qdrant (notre base de données vectorielle)
qdrant_client = QdrantClient(
host=os.getenv("QDRANT_HOST", "localhost"),
port=int(os.getenv("QDRANT_PORT", 6333))
)
print("✅ Connexion réussie à Qdrant !")
print(f"📍 Hôte: {os.getenv('QDRANT_HOST')}:{os.getenv('QDRANT_PORT')}")
Création d'une Collection avec Filtrage
Une collection dans Qdrant, c'est comme un groupe de documents apparentés. Vous pouvez créer plusieurs collections pour organiser vos données. Le point crucial ici : nous allons définir des schémas de métadonnées qui permettront ensuite le filtrage.
from qdrant_client.models import Filter, FieldCondition, MatchValue, Range
def creer_collection_avec_filtrage(client):
"""
Crée une collection optimisée pour le filtrage par:
- Catégorie (documents, emails, rapports)
- Année de publication
- Langue du document
"""
collection_name = "documents_entreprise"
# Supprimer si existe déjà (pour le tutoriel)
try:
client.delete_collection(collection_name)
print(f"🗑️ Collection '{collection_name}' supprimée")
except:
pass
# Créer la collection avec vecteurs de dimension 1536 (OpenAI ada-002)
client.create_collection(
collection_name=collection_name,
vectors_config=VectorParams(
size=1536, # Dimension standard pour embeddings OpenAI
distance=Distance.COSINE # Similarité cosinus pour les recherches
)
)
print(f"✅ Collection '{collection_name}' créée avec succès!")
print(f"📐 Vecteurs de dimension 1536 - Distance: COSINE")
return collection_name
Exécuter la création
collection = creer_collection_avec_filtrage(qdrant_client)
Insertion de Documents avec Métadonnées
C'est ici que la magie opère. Chaque document aura des métadonnées qui permettront le filtrage ultérieur. Ces métadonnées sont comme des tags ou des catégories que vous assignez à vos documents.
# Exemple de documents avec leurs métadonnées enrichies
documents_exemple = [
{
"id": 1,
"texte": "Notre rapport trimestriel montre une croissance de 25% des ventes.",
"titre": "Rapport Q3 2024 - Performance Commerciale",
"categorie": "rapport",
"annee": 2024,
"trimestre": 3,
"langue": "français",
"departement": "commercial"
},
{
"id": 2,
"texte": "Procédure interne : comment gérer les congés payés selon la législation française.",
"titre": "Guide RH - Gestion des Congés",
"categorie": "procedure",
"annee": 2024,
"trimestre": 1,
"langue": "français",
"departement": "rh"
},
{
"id": 3,
"texte": "Q3 2024 quarterly report shows 25% sales growth in the European market.",
"titre": "Q3 2024 - European Sales Report",
"categorie": "rapport",
"annee": 2024,
"trimestre": 3,
"langue": "anglais",
"departement": "commercial"
},
{
"id": 4,
"texte": "Note de service : réunion d'équipe prévue le 15 janvier 2025 à 14h.",
"titre": "Convocation Réunion Équipe",
"categorie": "note_service",
"annee": 2025,
"trimestre": 1,
"langue": "français",
"departement": "direction"
},
{
"id": 5,
"texte": "Budget prévisionnel 2025 : allocations par département et objectifs financiers.",
"titre": "Budget 2025 - Prévisions Financières",
"categorie": "budget",
"annee": 2025,
"trimestre": 1,
"langue": "français",
"departement": "finance"
}
]
def generer_embedding(texte):
"""
Génère un embedding via HolySheep AI.
HolySheep offre des tarifs imbattables: DeepSeek V3.2 à $0.42/MTok
avec une latence moyenne de seulement 35ms!
"""
import openai
# Configuration HolySheep - remplace OpenAI standard
client = openai.OpenAI(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
base_url="https://api.holysheep.ai/v1"
)
response = client.embeddings.create(
model="text-embedding-ada-002",
input=texte
)
return response.data[0].embedding
def inserer_documents_avec_metadonnees(client, collection_name, documents):
"""
Insère les documents dans Qdrant avec leurs métadonnées pour le filtrage.
"""
points = []
for doc in documents:
# Générer l'embedding du texte
embedding = generer_embedding(doc["texte"])
# Préparer les métadonnées pour Qdrant
payload = {
"titre": doc["titre"],
"texte": doc["texte"],
"categorie": doc["categorie"],
"annee": doc["annee"],
"trimestre": doc["trimestre"],
"langue": doc["langue"],
"departement": doc["departement"]
}
point = PointStruct(
id=doc["id"],
vector=embedding,
payload=payload
)
points.append(point)
# Insérer tous les points d'un coup
client.upsert(
collection_name=collection_name,
points=points
)
print(f"✅ {len(points)} documents insérés avec succès!")
return True
Insérer nos documents d'exemple
inserer_documents_avec_metadonnees(qdrant_client, collection, documents_exemple)
Recherche Avancée avec Filtres
Filtrer par catégorie
from qdrant_client.models import Filter, FieldCondition, MatchValue
def rechercher_par_categorie(client, collection_name, categorie):
"""
Recherche tous les documents d'une catégorie spécifique.
Exemple: tous les 'rapport' ou toutes les 'procedure'.
"""
# Définir le filtre : catégorie DOIT être égale à la valeur
filtre = Filter(
must=[
FieldCondition(
key="categorie",
match=MatchValue(value=categorie)
)
]
)
# Requête de recherche (vecteur nul pour récupérer sans similarity)
results = client.search(
collection_name=collection_name,
query_vector=[0.0] * 1536, # Vecteur nul car filtrage only
query_filter=filtre,
limit=10
)
print(f"📂 Documents de catégorie '{categorie}':")
for r in results:
print(f" - {r.payload['titre']} (Année: {r.payload['annee']})")
return results
Test: récupérer tous les rapports
rechercher_par_categorie(qdrant_client, collection, "rapport")
Filtrer par année et langue
from qdrant_client.models import Range
def rechercher_documents_avances(client, collection_name, annee=None,
langue=None, departement=None):
"""
Recherche avec FILTRAGE MULTIPLE sur plusieurs critères.
Combine année + langue + département en une seule requête!
"""
conditions = []
if annee:
conditions.append(
FieldCondition(
key="annee",
range=Range(gte=annee) # greater than or equal
)
)
if langue:
conditions.append(
FieldCondition(
key="langue",
match=MatchValue(value=langue)
)
)
if departement:
conditions.append(
FieldCondition(
key="departement",
match=MatchValue(value=departement)
)
)
filtre = Filter(must=conditions) if conditions else None
results = client.search(
collection_name=collection_name,
query_vector=[0.0] * 1536,
query_filter=filtre,
limit=10
)
print(f"🔍 Résultats filtrés:")
print(f" Année: {annee or 'toutes'}")
print(f" Langue: {langue or 'toutes'}")
print(f" Département: {departement or 'tous'}")
print(f" 📄 {len(results)} documents trouvés\n")
for r in results:
print(f" ✓ {r.payload['titre']}")
print(f" Langue: {r.payload['langue']} | Dept: {r.payload['departement']}")
return results
Test: documents en français de 2024
rechercher_documents_avances(
qdrant_client,
collection,
annee=2024,
langue="français"
)
Recherche Sémantique avec Filtres Combinés
C'est le scénario le plus courant en production : vous voulez rechercher un concept (via le vecteur) ET filtrer par des critères précis (métadonnées). Parfait pour les chatbots d'entreprise !
def recherche_semantique_filtree(client, collection_name, question,
langue=None, annee_min=None):
"""
RAG complet : recherche sémantique + filtrage metadata.
1. Convertit la question en vecteur (via HolySheep AI)
2. Filtre les résultats par critères métier
3. Retourne les chunks les plus pertinents
"""
# Étape 1: Embedding de la question
import openai
client_ai = openai.OpenAI(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
base_url="https://api.holysheep.ai/v1"
)
embedding_response = client_ai.embeddings.create(
model="text-embedding-ada-002",
input=question
)
question_vector = embedding_response.data[0].embedding
# Étape 2: Construire le filtre dynamique
conditions = []
if langue:
conditions.append(
FieldCondition(
key="langue",
match=MatchValue(value=langue)
)
)
if annee_min:
conditions.append(
FieldCondition(
key="annee",
range=Range(gte=annee_min)
)
)
filtre = Filter(must=conditions) if conditions else None
# Étape 3: Recherche hybride (vecteur + filtre)
results = client.search(
collection_name=collection_name,
query_vector=question_vector,
query_filter=filtre,
limit=3
)
print(f"❓ Question: « {question} »")
if langue:
print(f" 🔽 Filtré par: langue = '{langue}'")
if annee_min:
print(f" 🔽 Filtré par: année >= {annee_min}")
print(f" 📊 {len(results)} résultats trouvés\n")
# Afficher les résultats avec score de similarité
contexte = []
for i, r in enumerate(results, 1):
print(f"--- Résultat {i} (score: {r.score:.3f}) ---")
print(f"Titre: {r.payload['titre']}")
print(f"Texte: {r.payload['texte'][:100]}...")
print()
contexte.append(r.payload['texte'])
return "\n\n".join(contexte)
Test: chercher des infos sur les performances commerciales
contexte_trouve = recherche_semantique_filtree(
qdrant_client,
collection,
"performances commerciales et chiffres de ventes",
langue="français",
annee_min=2024
)
Faceted Search : Exploration par Facettes
La recherche à facettes permet à l'utilisateur d'explorer les données en temps réel, comme sur un site e-commerce. Il voit combien de documents correspondent à chaque filtre et peut les combiner.
def exploration_par_facettes(client, collection_name):
"""
Affiche les comptages par facette (catégorie, année, langue).
Permet à l'utilisateur de comprendre la structure des données.
"""
# Récupérer tous les points sans filtre
results = client.scroll(
collection_name=collection_name,
limit=100,
with_payload=True
)[0]
# Compteurs pour chaque facette
facet_categories = {}
facet_annees = {}
facet_langues = {}
facet_departements = {}
for point in results:
payload = point.payload
# Compter les catégories
cat = payload.get("categorie", "inconnu")
facet_categories[cat] = facet_categories.get(cat, 0) + 1
# Compter les années
annee = payload.get("annee", "inconnu")
facet_annees[annee] = facet_annees.get(annee, 0) + 1
# Compter les langues
langue = payload.get("langue", "inconnu")
facet_langues[langue] = facet_langues.get(langue, 0) + 1
# Compter les départements
dept = payload.get("departement", "inconnu")
facet_departements[dept] = facet_departements.get(dept, 0) + 1
print("=" * 50)
print("📊 EXPLORATION PAR FACETTES")
print("=" * 50)
print("\n🏷️ Par Catégorie:")
for cat, count in sorted(facet_categories.items(), key=lambda x: -x[1]):
print(f" {cat}: {count} documents")
print("\n📅 Par Année:")
for annee, count in sorted(facet_annees.items()):
print(f" {annee}: {count} documents")
print("\n🌐 Par Langue:")
for langue, count in sorted(facet_langues.items(), key=lambda x: -x[1]):
print(f" {langue}: {count} documents")
print("\n🏢 Par Département:")
for dept, count in sorted(facet_departements.items(), key=lambda x: -x[1]):
print(f" {dept}: {count} documents")
print("\n" + "=" * 50)
return {
"categories": facet_categories,
"annees": facet_annees,
"langues": facet_langues,
"departements": facet_departements
}
Exécuter l'exploration par facettes
facettes = exploration_par_facettes(qdrant_client, collection)
Génération RAG avec Contexte Filtré
Maintenant que nous savons rechercher et filtrer, combinons tout avec un vrai prompt RAG ! J'utilise HolySheep AI pour la génération car leurs tarifs sont exceptionnellement compétitifs : DeepSeek V3.2 à $0.42 par million de tokens, soit 85% moins cher que GPT-4.1 à $8.
def generer_reponse_rag(question, contexte, modele="deepseek-chat"):
"""
Génère une réponse RAG en utilisant le contexte récupéré.
HolySheep AI offre des tarifs imbattables:
- DeepSeek V3.2: $0.42/MTok (entrée) - $1.68/MTok (cache hit)
- GPT-4.1: $8/MTok - Gemini 2.5 Flash: $2.50/MTok
"""
import openai
client = openai.OpenAI(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
base_url="https://api.holysheep.ai/v1"
)
prompt = f"""Tu es un assistant d'entreprise helpful. Utilise UNIQUEMENT les informations fournies dans le contexte ci-dessous pour répondre à la question.
Si la réponse n'est pas dans le contexte, dis-le clairement.
---
CONTEXTE:
{contexte}
---
QUESTION: {question}
RÉPONSE:"""
response = client.chat.completions.create(
model=modele,
messages=[
{"role": "system", "content": "Tu es un assistant d'entreprise helpful."},
{"role": "user", "content": prompt}
],
temperature=0.3, # Réponses factuelles
max_tokens=500
)
return response.choices[0].message.content
Pipeline RAG complet
def pipeline_rag_complet(question, langue=None, annee_min=None):
"""
Pipeline RAG complet:
1. Recherche vectorielle avec filtres
2. Génération de la réponse
"""
print(f"\n{'='*60}")
print(f"🚀 PIPELINE RAG COMPLET")
print(f"{'='*60}")
# Étape 1: Récupérer le contexte pertinent
contexte = recherche_semantique_filtree(
qdrant_client,
collection,
question,
langue=langue,
annee_min=annee_min
)
if not contexte.strip():
print("⚠️ Aucun contexte trouvé pour cette question.")
return None
# Étape 2: Générer la réponse
print(f"\n🤖 Génération en cours via HolySheep AI...")
reponse = generer_reponse_rag(question, contexte)
print(f"\n💬 RÉPONSE GÉNÉRÉE:")
print("-" * 40)
print(reponse)
print("-" * 40)
return reponse
Tester le pipeline RAG complet
reponse = pipeline_rag_complet(
"Quel est le rapport sur les ventes et les performances commerciales?",
langue="français",
annee_min=2024
)
Erreurs courantes et solutions
Erreur 1 : "Connection refused" vers Qdrant
# ❌ ERREUR: docker ps montre qdrant arrêté
docker: Error response from daemon: driver failed programming external connectivity.
✅ SOLUTION 1: Redémarrer Qdrant proprement
docker stop qdrant
docker rm qdrant
docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant
✅ SOLUTION 2: Vérifier que les ports ne sont pas occupés
netstat -an | grep 6333
ou sur macOS
lsof -i :6333
Erreur 2 : "Invalid API key" avec HolySheep
# ❌ ERREUR: openai.AuthenticationError: Incorrect API key provided
✅ SOLUTION: Vérifier le format de la clé et l'URL base
1. Vérifiez que votre fichier .env contient bien HOLYSHEEP_API_KEY
2. Vérifiez que l'URL est exactement: https://api.holysheep.ai/v1
3. Regenerer la clé sur https://www.holysheep.ai/register
Test de vérification
import openai
client = openai.OpenAI(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
base_url="https://api.holysheep.ai/v1"
)
models = client.models.list()
print("✅ Connexion HolySheep réussie!")
Erreur 3 : "Vector size mismatch"
# ❌ ERREUR: qdrant_client.models.VectorSizeError: Vector size 1536 is invalid
✅ SOLUTION: Vérifier que la dimension du vecteur correspond à votre model
Chaque modèle d'embedding a une dimension différente:
- text-embedding-ada-002: 1536 dimensions
- text-embedding-3-small: 1536 dimensions (par défaut)
- text-embedding-3-large: 3072 dimensions (par défaut)
Si vous utilisez un modèle différent, spécifiez la dimension:
client.create_collection(
collection_name="ma_collection",
vectors_config=VectorParams(
size=3072, # ← ADAPTER à votre modèle
distance=Distance.COSINE
)
)
Ou spécifiez la dimension exacte pour text-embedding-3-small:
response = client.embeddings.create(
model="text-embedding-3-small",
input="texte",
dimensions=1536 # ← Forcer la dimension
)
Erreur 4 : Filtre ne renvoie aucun résultat
# ❌ ERREUR: La recherche avec filtre renvoie 0 résultats mais les documents existent
✅ SOLUTION: Vérifier la structure du payload dans les points
Qdrant est sensible aux types: "2024" (str) ≠ 2024 (int)
1. Vérifier le type dans la collection
results = client.scroll(collection_name="documents_entreprise", limit=1)
print(f"Type de 'annee': {type(results[0].payload['annee'])}")
2. Adapter le filtre au type correct
Si annee est un string:
filtre = Filter(must=[
FieldCondition(
key="annee",
match=MatchValue(value="2024") # ← string, pas int
)
])
Si annee est un integer:
filtre = Filter(must=[
FieldCondition(
key="annee",
range=Range(gte=2024) # ← integer
)
])
Erreur 5 : Timeout sur les requêtes de génération
# ❌ ERREUR: openai.APITimeoutError: Request timed out
✅ SOLUTION: HolySheep AI garantit <50ms de latence moyenne
Si timeout: vérifier la connectivité réseau
import openai
import httpx
Configurer un client avec timeout étendu
client = openai.OpenAI(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
base_url="https://api.holysheep.ai/v1",
timeout=httpx.Timeout(60.0, connect=10.0) # 60s lecture, 10s connexion
)
Test de latence
import time
start = time.time()
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Bonjour"}]
)
latence = (time.time() - start) * 1000
print(f"✅ Latence mesurée: {latence:.0f}ms")
Tableau comparatif des performances HolySheep
| Modèle | Prix entrada/MTok | Latence moy. | Cas d'usage |
|---|---|---|---|
| DeepSeek V3.2 | $0.42 | 35ms | Économique, tâches simples |
| Gemini 2.5 Flash | $2.50 | 45ms | Bon équilibre performance/prix |
| GPT-4.1 | $8.00 | 80ms | Qualité maximale, complexité élevée |
| Claude Sonnet 4.5 | $15.00 | 60ms | Analyses approfondies |
Conclusion : Votre système RAG est prêt !
Vous venez de construire un système RAG complet avec Qdrant permettant :
- ✅ L'insertion de documents avec métadonnées enrichies
- ✅ Le filtrage par catégorie, année, langue, département
- ✅ La recherche sémantique hybridée avec filtres
- ✅ L'exploration par facettes pour découvrir vos données
- ✅ La génération de réponses via HolySheep AI à moindre coût
Ce que j'apprécie particulièrement avec cette architecture, après des années de production, c'est la flexibilité des filtres Qdrant. Vous pouvez ajouter autant de facets que votre cas d'usage le demande sans impacter les performances.
HolySheep AI complète parfaitement cette stack avec des tarifs exceptionnels : DeepSeek V3.2 à $0.42/MTok avec une latence moyenne de 35ms, soit une économie de plus de 85% par rapport aux providers traditionnels. Les paiements via WeChat et Alipay facilitent également la gestion pour les équipes chinoises.
Prochaines étapes recommandées
- 🧪 Ajoutez plus de documents et testez des filtres complexes
- 📊 Implémentez le re-ranking avec Cohere pour améliorer la précision
- 🔄 Ajoutez un système de mise à jour temps réel des documents
- 📈 Surveillez les métriques de latence et ajustez les batch sizes
Si vous avez des questions ou souhaitez approfondir un aspect particulier de ce tutoriel, n'hésitez pas à me contacter via les commentaires.