A la fin de ce notebook, vous saurez : 1. Comprendre l’architecture Vector Store de SK 2. Generer des embeddings avec OpenAI 3. Utiliser InMemoryVectorStore pour le prototypage 4. Connecter Qdrant pour la production 5. Implementer un pipeline RAG complet
Prerequis
Python 3.10+
Notebooks 01-04 completes
Cle API OpenAI configuree (.env)
Acces Qdrant (optionnel, fourni)
Duree estimee : 50 minutes
Sommaire
Section
Contenu
Concepts cles
1
Introduction
Pourquoi les Vector Stores ?
2
Architecture SK
VectorStore, Collections, Records
3
Embeddings
OpenAITextEmbedding
4
InMemoryVectorStore
Prototypage rapide
5
Qdrant
Production-ready
6
RAG Pattern
Pipeline complet
7
Conclusion
Resume, exercices
Qu’est-ce qu’un Vector Store ? Une base de données optimisee pour stocker et rechercher des vecteurs (embeddings). C’est la fondation du RAG (Retrieval-Augmented Generation) qui permet aux LLMs d’acceder a vos données.
# Installationimport osfrom dotenv import load_dotenv# Chargement du fichier .env (cles API)load_dotenv("../.env")api_key = os.getenv("OPENAI_API_KEY")print(f"Configuration: API Key {'OK'if api_key else'MANQUANTE'}")print("Dependances installees")
Configuration: API Key OK
Dependances installees
1. Introduction aux Vector Stores
Pourquoi les Vector Stores ?
Les LLMs ont une limite de contexte et pas d’acces a vos données privees. Les Vector Stores resolvent ce problème. Le graphe Mermaid de la cellule suivante sépare les deux chemins qui convergent vers la recherche de similitude : l’indexation des documents et l’encodage de la requête.
Connecteurs SK disponibles
Connecteur
Type
Cas d’usage
InMemoryVectorStore
Local
Prototypage, tests
QdrantVectorStore
Cloud/Self-hosted
Production
AzureAISearchVectorStore
Azure
Enterprise
PineconeVectorStore
Cloud
Scalabilite
RedisVectorStore
Cache distribue
Performance
Le diagramme ci-dessous rend ce pipeline RAG vectoriel sous forme de graphe : l’indexation (Documents -> Vector Store) et la recherche (Query -> similitude -> contexte).
flowchart TD
D(["Documents"]) --> E["Embeddings"] --> VS[("Vector Store<br/>Qdrant ou autre")]
Q(["Query : Qu'est-ce que X ?"]) --> EQ["Embedding Query<br/>[0.2, 0.4, ...]"]
EQ --> SIM["Recherche de similitude"]
VS --> SIM
SIM --> TOPK["Top-K documents pertinents"]
TOPK --> CTX(["Contexte pour le LLM"])
classDef idx fill:#cfe2ff,stroke:#084298,color:#052c65
classDef gen fill:#fff3cd,stroke:#b8860b,color:#5c4400
classDef io fill:#d1e7dd,stroke:#0f5132,color:#0a3622
class D,E,VS idx
class EQ,SIM,TOPK gen
class Q,CTX io
Lecture. A l’indexation, chaque document est converti en embedding (vecteur dense) et stocke dans un vector store (Qdrant ici). A la requête, la question est encodee dans le même espace, puis une recherche de similitude (cosinus / dot product) ramene les Top-K passages les plus proches – qui forment le contexte injecte au LLM. Tout repose sur le fait que proximite vectorielle approxime proximite sémantique.
La même abstraction à trois niveaux, rendue sous forme de graphe : chaque niveau en encapsule un autre.
flowchart TB
subgraph VS["VectorStore — InMemory · Qdrant · Azure · Pinecone …"]
subgraph COL["VectorStoreRecordCollection — équivalent d'une « table »"]
RD["VectorStoreRecordDefinition<br/>schéma des records"]
end
end
style VS fill:#cfe2ff,stroke:#084298,color:#052c65
style COL fill:#d1e7dd,stroke:#0f5132,color:#052e16
style RD fill:#fff3cd,stroke:#b8860b,color:#5c4400
Lecture. L’encapsulation suit l’analogie SQL : le VectorStore est la connexion à la base, la Collection une table, la RecordDefinition le schéma de ses lignes. Changer de backend (InMemory → Qdrant) ne change que le niveau externe : le code SK reste identique.
3. Generation d’Embeddings
from semantic_kernel import Kernelfrom semantic_kernel.connectors.ai.open_ai import OpenAITextEmbedding# Configurationkernel = Kernel()# Service d'embeddingembedding_service = OpenAITextEmbedding( service_id="embedding", ai_model_id="text-embedding-3-small"# Modele recommande (peu couteux, performant))kernel.add_service(embedding_service)# Test de generation d'embeddingtest_texts = ["Semantic Kernel est un SDK pour l'IA","Python est un langage de programmation"]embeddings =await embedding_service.generate_embeddings(test_texts)print(f"Nombre de textes: {len(test_texts)}")print(f"Dimension des embeddings: {len(embeddings[0])}")print(f"Premier embedding (debut): {embeddings[0][:5]}...")
Nombre de textes: 2
Dimension des embeddings: 1536
Premier embedding (debut): [-0.03924561 0.04214478 -0.02653503 -0.03384399 0.00598907]...
Exercice 1 : Calcul de similarite cosinus entre embeddings
Objectif : Implementer une fonction qui calcule la similarite cosinus entre deux vecteurs d’embedding, puis compare la similarite de paires de textes.
La similarite cosinus est la metrique fondamentale pour evaluer la proximite sémantique entre textes dans un vector store. Comprendre cette metrique est essentiel pour debugger les résultats de recherche vectorielle.
Indices : - # Étape 1 : Implementer le produit scalaire (sum(a[i] * b[i])) - # Étape 2 : Calculer les normes L2 de chaque vecteur (sqrt(sum(x^2))) - # Étape 3 : Diviser le produit scalaire par le produit des normes - # Indice : Les vecteurs OpenAI sont déjà normalises, donc le produit scalaire seul suffit
import mathdef cosine_similarity(vec1: list[float], vec2: list[float]) ->float:""" Calcule la similarite cosinus entre deux vecteurs. # Etape 1 : Calculer le produit scalaire (dot product) # Etape 2 : Calculer les normes de chaque vecteur # Etape 3 : Retourner dot_product / (norm1 * norm2) # Indice : math.sqrt(sum(x**2 for x in vec)) donne la norme L2 # Indice : Retourner 0.0 si l'un des normes est nul (eviter division par zero) """# TODO etudiant : implementer la similarite cosinusreturn0.0# TODO etudiant : remplacer par le calcul# Test avec des vecteurs simplesvec_a = [1.0, 0.0, 0.0]vec_b = [0.0, 1.0, 0.0]vec_c = [1.0, 0.0, 0.0]print(f"Similarite a vs b (orthogonaux) : {cosine_similarity(vec_a, vec_b):.4f}") # Attendu : ~0.0print(f"Similarite a vs c (identiques) : {cosine_similarity(vec_a, vec_c):.4f}") # Attendu : 1.0# Test avec de vrais embeddings (decommentez apres implementation)# texts = ["Le chat dort", "Le felin sommeille", "La voiture roule"]# embs = await embedding_service.generate_embeddings(texts)# print(f"Similarite 'chat dort' vs 'felin sommeille' : {cosine_similarity(embs[0], embs[1]):.4f}")# print(f"Similarite 'chat dort' vs 'voiture roule' : {cosine_similarity(embs[0], embs[2]):.4f}")
Similarite a vs b (orthogonaux) : 0.0000
Similarite a vs c (identiques) : 0.0000
Interprétation : Generation d’Embeddings avec OpenAI
Sortie obtenue : 2 embeddings de dimension 1536 generes avec succes.
Normalisation : Les vecteurs sont normalises (norme L2 = 1) pour similarite cosinus
Determinisme : Même texte = même embedding (pas de randomness)
Dimension reduite : text-embedding-3-small peut etre reduit a 512/256 dimensions avec dimensions parameter
Calcul de similarite :
# Similarite cosinus entre deux embeddingsdef cosine_similarity(vec1, vec2): dot_product =sum(a * b for a, b inzip(vec1, vec2))# Déjà normalises, donc dot_product = cosine similarityreturn dot_product
Performance typique :
Latence : ~50-200ms pour 1-10 textes
Throughput : ~500-1000 textes/seconde (batch)
Cout : 100K mots = ~125K tokens = $0.0025
Notes techniques :
SK 1.39+ : OpenAITextEmbedding supporte dimensions parameter pour truncation
Les embeddings sont caches automatiquement par SK pour eviter regeneration
Pour multilingual, text-embedding-3-small supporte 100+ langues
4. InMemoryVectorStore
Pour le prototypage rapide sans infrastructure externe.
from dataclasses import dataclassfrom typing import Annotatedfrom semantic_kernel.connectors.in_memory import InMemoryStorefrom semantic_kernel.data.vector import ( vectorstoremodel, VectorStoreField, FieldTypes)# Definition du schema de record avec la nouvelle API SK 1.39+@vectorstoremodel@dataclassclass DocumentRecord:"""Schema d'un document dans le vector store."""id: Annotated[int, VectorStoreField(field_type=FieldTypes.KEY)] content: Annotated[str, VectorStoreField(field_type=FieldTypes.DATA)] title: Annotated[str, VectorStoreField(field_type=FieldTypes.DATA)] embedding: Annotated[list[float] |None, VectorStoreField( field_type=FieldTypes.VECTOR, dimensions=1536 ) ] =None# Creation du store en memoirememory_store = InMemoryStore()# Obtenir ou creer une collectioncollection = memory_store.get_collection( DocumentRecord, collection_name="documents")# Creer la collection (si elle n'existe pas)# SK 1.39+: ensure_collection_exists() au lieu de create_collection_if_not_exists()await collection.ensure_collection_exists()print("Collection 'documents' creee")print(f"Schema: id (key), content (data), title (data), embedding (vector 1536d)")
Collection 'documents' creee
Schema: id (key), content (data), title (data), embedding (vector 1536d)
Exercice 2 : Schema de record personnalise avec metadonnees
Objectif : Créer un schema de record vectoriel adapte a une base de connaissances d’articles scientifiques, avec des metadonnees (auteur, annee, catégorie) permettant le filtrage.
Le schema DocumentRecord utilise precedemment est minimal. En production, les records vectoriels contiennent des metadonnees riches pour filtrer les recherches (par date, auteur, domaine).
Indices : - # Étape 1 : Ajouter des champs DATA pour auteur (str), annee (int) et domaine (str) - # Étape 2 : Créer la collection avec ce nouveau schema - # Étape 3 : Inserer 2-3 records d’exemple avec ces metadonnees - # Indice : Chaque champ DATA supplementaire necessite une annotation VectorStoreField(field_type=FieldTypes.DATA)
@vectorstoremodel@dataclassclass ArticleRecord:""" Schema pour une base de connaissances d'articles scientifiques. # Etape 1 : Ajouter les champs auteur, annee et domaine comme VectorStoreField DATA # Etape 2 : Respecter le meme pattern que DocumentRecord pour id, content, embedding # Etape 3 : Le champ embedding doit rester list[float] | None avec dimensions=1536 """# TODO etudiant : completer le schema avec les 3 champs metadonnees manquantsid: Annotated[str, VectorStoreField(field_type=FieldTypes.KEY)] content: Annotated[str, VectorStoreField(field_type=FieldTypes.DATA)] title: Annotated[str, VectorStoreField(field_type=FieldTypes.DATA)]# TODO etudiant : ajouter auteur, annee, domaine embedding: Annotated[list[float] |None, VectorStoreField(field_type=FieldTypes.VECTOR, dimensions=1536) ] =Noneprint("Exercice a completer : ajoutez les champs auteur, annee et domaine au schema")
Exercice a completer : ajoutez les champs auteur, annee et domaine au schema
Interprétation : Schema de Record Vector Store
Architecture du schema : Annotation avec decorateurs pour définir la structure des données.
Composant
Type
Rôle
@vectorstoremodel
Decorateur classe
Marque la classe comme schema SK
@dataclass
Decorateur Python
Genere __init__, __repr__, etc.
VectorStoreField
Annotation
Specifie le type de champ
FieldTypes.KEY
Type champ
Identifiant unique (primary key)
FieldTypes.DATA
Type champ
Données textuelles ou metadonnees
FieldTypes.VECTOR
Type champ
Embedding vectoriel
Points cles :
Separation des concerns : Les champs KEY, DATA, et VECTOR ont des rôles distincts
Dimensions fixes : Le champ vector doit specifier dimensions=1536 pour text-embedding-3-small
Optional embedding : list[float] | None permet de créer des records sans embedding initial
Type safety : Annotated + dataclass assurent la validation des types au runtime
Type de clé et Qdrant : le champ KEY est un int (id de point). Qdrant n’accepte que des entiers non signés ou des UUID comme identifiants de point — une chaîne arbitraire ("doc1") est rejetée à l’upsert. Le int est le choix le plus simple pour ce notebook ; une clé métier en chaîne exigerait un champ KEY paysage + un mapping id_from_record.
Notes techniques :
SK 1.39+ utilise ensure_collection_exists() au lieu de create_collection_if_not_exists()
Le schema est immuable après creation de la collection
Les dimensions doivent correspondre au modèle d’embedding utilise
# Documents d'exempledocuments = [ {"id": 1,"title": "Introduction a Semantic Kernel","content": "Semantic Kernel est un SDK open-source de Microsoft pour integrer des LLMs dans vos applications." }, {"id": 2,"title": "Plugins SK","content": "Les plugins dans Semantic Kernel sont des collections de fonctions que le kernel peut invoquer." }, {"id": 3,"title": "Agents SK","content": "L'Agent Framework permet de creer des agents autonomes qui utilisent des plugins et collaborent entre eux." }, {"id": 4,"title": "RAG avec SK","content": "RAG (Retrieval-Augmented Generation) combine la recherche vectorielle avec la generation de texte par LLM." }]# Generer les embeddingscontents = [doc["content"] for doc in documents]embeddings =await embedding_service.generate_embeddings(contents)# Creer les recordsrecords = []for doc, emb inzip(documents, embeddings): record = DocumentRecord(id=doc["id"], title=doc["title"], content=doc["content"], embedding=list(emb) ) records.append(record)# Inserer dans la collection (SK 1.39+: upsert prend une liste)keys =await collection.upsert(records)print(f"Documents inseres: {keys}")
Documents inseres: [1, 2, 3, 4]
Interprétation : Pipeline d’Ingestion de Documents
Sortie obtenue : 4 documents inseres avec succes dans la collection InMemory.
Étape
Opération
Code cle
1. Extraction
Recuperer les contenus textuels
contents = [doc["content"] for doc in documents]
2. Embedding
Generer les vecteurs (batch)
generate_embeddings(contents)
3. Record creation
Associer metadata + vecteur
DocumentRecord(id, title, content, embedding)
4. Upsert
Inserer/Mettre a jour en base
collection.upsert(records)
Points cles :
Batch embedding : Generer tous les embeddings en une seule requête API (plus efficace et moins couteux)
Upsert semantics : Insere si nouveau, met a jour si existant (base sur la cle id)
Liste de records : SK 1.39+ accepte une liste complete, pas d’itération necessaire
Separation données/vecteurs : Le contenu original est conserve avec l’embedding pour affichage
Calcul des couts :
Modèle : text-embedding-3-small ($0.02/1M tokens)
4 documents x ~20 tokens = 80 tokens
Cout : ~$0.0000016 (negligeable)
Notes d’optimisation :
Pour de gros volumes, utiliser des batches de 100-500 documents
Les embeddings peuvent etre pre-calcules et stockes offline
Qdrant supporte l’ingestion parallele pour acceleration
# Recherche vectoriellequery ="Comment creer des agents avec Semantic Kernel ?"# Generer l'embedding de la requetequery_embedding = (await embedding_service.generate_embeddings([query]))[0]# Rechercher les documents similaires (SK 1.39+: search au lieu de vectorized_search)results =await collection.search( vector=list(query_embedding), vector_property_name="embedding", top=3, include_vectors=False)print(f"Query: {query}")print("\nResultats:")print("-"*60)# SK 1.39+: results.results est un async generatorasyncfor result in results.results:print(f"Score: {result.score:.4f}")print(f"Title: {result.record.title}")print(f"Content: {result.record.content}")print("-"*60)
Query: Comment creer des agents avec Semantic Kernel ?
Resultats:
------------------------------------------------------------
Score: 0.3709
Title: Plugins SK
Content: Les plugins dans Semantic Kernel sont des collections de fonctions que le kernel peut invoquer.
------------------------------------------------------------
Score: 0.4582
Title: Agents SK
Content: L'Agent Framework permet de creer des agents autonomes qui utilisent des plugins et collaborent entre eux.
------------------------------------------------------------
Score: 0.4796
Title: Introduction a Semantic Kernel
Content: Semantic Kernel est un SDK open-source de Microsoft pour integrer des LLMs dans vos applications.
------------------------------------------------------------
Interprétation : Recherche Vectorielle et Distance Cosinus
Sortie obtenue : 3 documents récupérés, triés par score croissant. Le score retourné par défaut est une distance cosinus (0 = vecteurs identiques), donc le premier résultat est le plus pertinent — c’est le contrat normal d’un top=3 : on retourne les meilleurs en tête, jamais le pire.
Document
Score (distance)
Pertinence
Interprétation
Plugins SK
0.3709
Plus proche (meilleur)
Distance la plus faible = le plus similaire à la requête
Agents SK
0.4582
Intermédiaire
Mentionne explicitement agents + plugins
Introduction SK
0.4796
Plus éloigné (moins pertinent)
Distance la plus grande des trois
Points clés :
Distance cosinus, et non similarité : le score est une distance — 0 = vecteurs identiques, et plus il monte, plus le document est éloigné de la requête (Cosine Distance = 1 − Cosine Similarity). Le tri interne est croissant (operator.le) pour servir le meilleur en tête. Inverser ce sens (croire 1 = identique) décrit une similarité, pas ce que retourne la collection par défaut.
Recherche sémantique : la requête « créer des agents » retrouve des documents pertinents sans correspondance de mots exacte.
Top-K : top=3 limite aux 3 meilleurs résultats, meilleur en tête (tri ascendant sur la distance).
Ordre contre-intuitif — une vraie limite d’embedding : pour la question « Comment créer des agents ? », c’est « Plugins SK » qui arrive en tête — alors que le corpus contient « Agents SK » (« L’Agent Framework permet de créer des agents autonomes »), qui répond littéralement à la question. Les embeddings courts capturent mal l’intention fine de la requête (le concept « agents » vs l’action « créer ») ; c’est pourquoi la production ajoute un reranking (cross-encoder) et une recherche hybride (vectorielle + mots-clés BM25) pour corriger ce genre d’inversion.
Paramètres de recherche :
Paramètre
Valeur utilisée
Impact
vector
Embedding de la question
Vecteur de référence
vector_property_name
“embedding”
Champ à comparer
top
3
Nombre de résultats
include_vectors
False
Ne pas retourner les embeddings (économie mémoire)
Notes techniques :
SK 1.39+ : search() remplace vectorized_search()
Les résultats sont un async generator (itération asynchrone)
distance_function par défaut sur le champ embedding : DistanceFunction.DEFAULT → cosinus distance (cf. semantic_kernel/connectors/in_memory.py : DEFAULT: cosine, tri operator.le, invert_score=False). Déclarer explicitement COSINE_SIMILARITY inverserait le sens du score.
Seuils indicatifs (distance cosinus — plus c’est bas, plus c’est pertinent) :
Distance < 0.4 : Très pertinent (similarité > 0.6)
Distance 0.4–0.6 : Pertinent (similarité 0.4–0.6)
Distance 0.6–0.8 : Potentiellement pertinent
Distance > 0.8 : Peu pertinent (similarité < 0.2)
5. Qdrant (Production)
Qdrant est un vector store production-ready. La connexion ci-dessous cible une instance locale (QDRANT_URL, par défaut http://localhost:6333), provisionnée par docker run -p 6333:6333 qdrant/qdrant. La clé d’API vient de l’environnement (QDRANT_API_KEY) — une instance sans authentification fonctionne aussi sans clé. Si l’instance n’est pas disponible, la cellule dégrade proprement en InMemoryStore (le except affiche le type d’exception pour distinguer un timeout d’un 401 ou d’un point-id invalide).
from semantic_kernel.connectors.qdrant import QdrantStorefrom qdrant_client import QdrantClientimport warningswarnings.filterwarnings("ignore", message="Api key is used with an insecure connection.")# Configuration Qdrant (depuis .env ou valeurs fournies)QDRANT_URL = os.getenv("QDRANT_URL", "http://localhost:6333")QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")# Connexion au client Qdrantqdrant_client = QdrantClient( url=QDRANT_URL, api_key=QDRANT_API_KEY)# Verification de la connexiontry: collections = qdrant_client.get_collections()print(f"Connexion Qdrant reussie !")print(f"Nombre de collections presentes: {len(collections.collections)}")print(f"Collection 'sk_demo' deja presente: {'sk_demo'in [c.name for c in collections.collections]}")exceptExceptionas e:print(f"Erreur de connexion: {type(e).__name__}: {e}")print("Continuez avec InMemoryStore pour les exemples suivants.")
Connexion Qdrant reussie !
Nombre de collections presentes: 113
Collection 'sk_demo' deja presente: True
# Creation du store Qdrant via SKtry: qdrant_store = QdrantStore( url=QDRANT_URL, api_key=QDRANT_API_KEY )# Collection Qdrant (SK 1.39+) qdrant_collection = qdrant_store.get_collection( DocumentRecord, collection_name="sk_demo" )# SK 1.41 : ensure_collection_exists() n'est PAS idempotent pour Qdrant : il appelle# create_collection() sans vérifier l'existence, donc un « sk_demo » résiduel d'une# exécution précédente lève un 409. On supprime d'abord une collection existante# (exécution idempotente, relançable), puis on crée.ifawait qdrant_collection.collection_exists():await qdrant_collection.ensure_collection_deleted()await qdrant_collection.ensure_collection_exists()# Inserer les memes documents keys =await qdrant_collection.upsert(records)print(f"Documents inseres dans Qdrant: {keys}")# Recherche qdrant_results =await qdrant_collection.search( vector=list(query_embedding), vector_property_name="embedding", top=3 )print(f"\nRecherche Qdrant pour: '{query}'")asyncfor result in qdrant_results.results:print(f" {result.score:.4f} - {result.record.title}")exceptExceptionas e:print(f"Qdrant non disponible: {type(e).__name__}: {e}")print("Les exemples utilisent InMemoryStore.")
Documents inseres dans Qdrant: [1, 2, 3, 4]
Recherche Qdrant pour: 'Comment creer des agents avec Semantic Kernel ?'
0.6290 - Plugins SK
0.5418 - Agents SK
0.5204 - Introduction a Semantic Kernel
Interprétation : InMemory vs Qdrant - Choix Architectural
Comparaison detaillee des implementations :
Caractéristique
InMemoryVectorStore
Qdrant
Persistance
Non (RAM seulement)
Oui (disque/cloud)
Scalabilite
~10K documents
Millions de documents
Performance recherche
O(n) lineaire
O(log n) avec HNSW
Infrastructure
Aucune
Docker/Cloud requis
Latence typique
<10ms (petits datasets)
10-50ms (optimise)
Cout
Gratuit (RAM locale)
$0.25-2/GB/mois (cloud)
Usage
Dev/Test/Prototypage
Production/Scale
Points clés :
Portée de cette exécution. Ce notebook tourne contre une instance Qdrant locale mono-nœud (http://localhost:6333) — un seul nœud, pas de shards ni de replicas. L’architecture sharded décrite ci-dessous est la cible de déploiement production, pas la topologie effective de ce démo.
Courbe de performance : InMemory devient lent au-dela de 10K documents (recherche lineaire)
HNSW advantage : Qdrant utilise Hierarchical Navigable Small World graphs pour recherche sous-lineaire
Même API SK : Le code reste identique, seul le backend change (portabilite garantie)
Persistance critique : InMemory perd tout au redemarrage, Qdrant survit aux crashes
Note sur la convention de score. Les deux moteurs trient par pertinence (meilleur en tête), mais le score ne se lit pas dans le même sens : InMemory (cellule 18) retourne une distance cosinus (0 = identique, plus bas = plus pertinent), tandis que Qdrant (cellule 22) retourne une similarité cosinus (plus haut = plus pertinent). Ne pas confondre les deux en comparant les chiffres bruts d’une cellule à l’autre.
Migration InMemory -> Qdrant :
Export : Sauvegarder les records InMemory en JSON
Setup Qdrant : Deployer via Docker ou cloud
Create collection : Schema identique avec ensure_collection_exists()
Batch upsert : Charger par batches de 100-1000 records
Validation : Tester quelques requêtes pour verifier coherence
Quand migrer vers Qdrant :
Dataset > 5K documents
Besoin de persistance
Latence recherche > 100ms avec InMemory
Production deployments
Filtres complexes sur metadonnees
Alternatives a Qdrant :
Vector DB
Avantage
Inconvenient
Pinecone
Fully managed, simple
Cout eleve
Weaviate
GraphQL, multimodal
Complexite setup
Milvus
Open-source, scale
Infrastructure lourde
Chroma
Leger, Python-native
Performance limitee
Le déploiement Qdrant en production décrit plus haut, rendu sous forme de graphe : un client SK adresse un cluster sharded via REST/gRPC.
Lecture. Le sharding horizontal répartit les documents sur plusieurs nœuds. Ce notebook exécute le cas mono-nœud local ; le cluster sharded est la cible de production. : la capacité croît linéairement (millions de docs) et la recherche reste sous-linéaire grâce à l’index HNSW. En production, on ajoute des replicas par shard pour la haute disponibilité.
6. RAG Pattern Complet
Assemblons tout pour un pipeline RAG fonctionnel.
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettingsfrom semantic_kernel.contents import ChatHistory# Ajouter le service de chatkernel.add_service(OpenAIChatCompletion(service_id="chat"))asyncdef rag_query(question: str, collection, embedding_service, kernel, top_k: int=3):"""Pipeline RAG complet."""# 1. Generer l'embedding de la question query_embedding = (await embedding_service.generate_embeddings([question]))[0]# 2. Rechercher les documents pertinents (SK 1.39+) results =await collection.search( vector=list(query_embedding), vector_property_name="embedding", top=top_k, include_vectors=False )# 3. Construire le contexte (SK 1.39+: async iteration) context_parts = []asyncfor result in results.results: context_parts.append(f"- {result.record.title}: {result.record.content}") context ="\n".join(context_parts)# 4. Construire le prompt augmente augmented_prompt =f"""Tu es un assistant qui repond en utilisant uniquement le contexte fourni.CONTEXTE:{context}QUESTION: {question}REPONSE (basee uniquement sur le contexte):"""# 5. Appeler le LLM (SK 1.39+: settings requis) chat_service = kernel.get_service(service_id="chat") history = ChatHistory() history.add_user_message(augmented_prompt) settings = OpenAIChatPromptExecutionSettings() response =await chat_service.get_chat_message_contents( chat_history=history, settings=settings )return {"question": question,"context": context,"answer": str(response[0]) }# Test du pipeline RAGresult =await rag_query( question="Comment les agents SK peuvent-ils utiliser des plugins ?", collection=collection, embedding_service=embedding_service, kernel=kernel)print("="*60)print(f"Question: {result['question']}")print("="*60)print(f"\nContexte utilise:\n{result['context']}")print("="*60)print(f"\nReponse:\n{result['answer']}")
============================================================
Question: Comment les agents SK peuvent-ils utiliser des plugins ?
============================================================
Contexte utilise:
- Agents SK: L'Agent Framework permet de creer des agents autonomes qui utilisent des plugins et collaborent entre eux.
- Plugins SK: Les plugins dans Semantic Kernel sont des collections de fonctions que le kernel peut invoquer.
- Introduction a Semantic Kernel: Semantic Kernel est un SDK open-source de Microsoft pour integrer des LLMs dans vos applications.
============================================================
Reponse:
Les agents SK, créés via l’Agent Framework, peuvent utiliser des plugins en invoquant les fonctions fournies par ces plugins. Dans Semantic Kernel, un plugin est une collection de fonctions que le kernel peut appeler ; les agents s’appuient donc sur ces fonctions pour accomplir des tâches et collaborer entre eux.
Exercice 3 : Evaluation de la qualite RAG
Objectif : Implementer une fonction qui evalue si la reponse RAG est coherente avec les documents recuperes.
Dans un système RAG en production, il est crucial de mesurer si la reponse du LLM est effectivement ancre dans le contexte fourni. Vous allez créer une fonction evaluate_rag_response qui compare la reponse avec les documents sources.
Indices : - # Étape 1 : Comparer les mots-cles de la reponse avec ceux du contexte - # Étape 2 : Calculer un score de couverture (fraction des documents sources utilises) - # Étape 3 : Detecter les informations de la reponse absentes du contexte - # Indice : Utilisez des ensembles de mots (set) pour la comparaison lexicale
def evaluate_rag_response(response: str, context_docs: list[str]) ->dict:""" Evalue la coherence entre une reponse RAG et les documents sources. # Etape 1 : Extraire les mots significatifs (ignorer les mots vides) # Etape 2 : Calculer le pourcentage de mots de la reponse presents dans le contexte # Etape 3 : Identifier les mots de la reponse absents du contexte (potentielles hallucinations) # Indice : Utilisez set() pour les operations ensemblistes (intersection, difference) # Indice : Filtrez les mots courts (< 4 caracteres) pour ignorer les articles/prepositions """# TODO etudiant : implementer l'evaluationprint("Exercice a completer")return {"coverage_score": 0.0, "hallucination_risk": "unknown", "ungrounded_words": []}# Test (decommentez apres implementation)# test_result = evaluate_rag_response(# response="Les agents SK utilisent des plugins pour collaborer.",# context_docs=[# "L'Agent Framework permet de creer des agents autonomes qui utilisent des plugins.",# "Les plugins dans Semantic Kernel sont des collections de fonctions."# ]# )# print(f"Score de couverture: {test_result['coverage_score']:.2f}")# print(f"Risque hallucination: {test_result['hallucination_risk']}")# print(f"Mots non ancres: {test_result['ungrounded_words']}")
Metadata filtering : Filtrer par date, auteur, type avant recherche
Chain-of-thought : Demander au LLM d’expliquer son raisonnement
Le même prompt augmenté, rendu sous forme de graphe — les blocs empilés qui composent l’entrée envoyée au LLM.
flowchart TB
SYS["System — « Tu es un assistant… »<br/>instructions de comportement"]
CTX["CONTEXTE — Top-K documents<br/>base de connaissances dynamique"]
Q["QUESTION — saisie utilisateur"]
REP["RÉPONSE — générée par le LLM"]
SYS --> CTX --> Q --> REP
classDef instr fill:#cfe2ff,stroke:#084298,color:#052c65
classDef ctx fill:#fff3cd,stroke:#b8860b,color:#5c4400
classDef q fill:#d1e7dd,stroke:#0f5132,color:#052e16
classDef rep fill:#f8d7da,stroke:#842029,color:#2c0b0e
class SYS instr
class CTX ctx
class Q q
class REP rep
Lecture. Le grounding consiste à injecter le CONTEXTE (documents récupérés par recherche vectorielle) entre les instructions système et la question : le LLM répond à partir de faits vérifiables plutôt que de sa mémoire, ce qui limite les hallucinations et rend les sources traçables. C’est ce prompt que le pipeline RAG assemble à chaque requête.
Conclusion
Resume des concepts
Concept
Description
Code cle
VectorStore
Abstraction de base
InMemoryVectorStore(), QdrantStore()
Collection
Groupe de records
store.get_collection(name, type)
Record
Document + embedding
@vectorstoremodel @dataclass
Embedding
Vecteur sémantique
OpenAITextEmbedding.generate_embeddings()
Search
Recherche similitude
collection.vectorized_search(vector, options)
RAG
Retrieval-Augmented Generation
Contexte + LLM
Points cles a retenir
InMemory pour dev, Qdrant pour prod - Même API, backend différent
Les embeddings capturent le sens - Pas juste les mots-cles
RAG = Search + Generate - Contexte pertinent pour le LLM
Le chunking est crucial - Decouper les longs documents
Les metadonnees enrichissent - Filtres et contexte additionnel
Exercices suggeres
RAG sur PDF : Ingerer un PDF et poser des questions
Filtres : Ajouter des filtres sur les metadonnees (date, auteur)