Cas d'Usage - Python ML & Recherche Vectorielle
Guide pratique : À quoi servent les embeddings et la recherche vectorielle dans Aaperture
🎯 Concept de Base
Qu'est-ce qu'un embedding ?
Un embedding est une représentation numérique d'un texte sous forme de vecteur (liste de nombres). Des textes similaires ont des vecteurs proches dans l'espace vectoriel.
Exemple :
- "Consultation psychologique" →
[0.1, 0.3, -0.2, 0.5, ...](384 nombres) - "Thérapie de couple" →
[0.12, 0.28, -0.18, 0.48, ...](très proche) - "Facture d'électricité" →
[-0.5, 0.1, 0.8, -0.3, ...](très différent)
Pourquoi utiliser Qdrant ?
Qdrant est une base de données vectorielle qui permet de :
- Stocker des millions de vecteurs efficacement
- Rechercher rapidement les vecteurs les plus similaires
- Filtrer par métadonnées (orgId, userId, etc.)
📋 Cas d'Usage Concrets dans Aaperture
1. 🔍 Recherche Sémantique dans les Sessions
Problème actuel :
- La recherche textuelle classique ne trouve que les mots exacts
- "Consultation" ne trouve pas "Thérapie" ou "Séance"
Solution avec embeddings :
// 1. Indexer toutes les sessions existantes (une fois)
const sessions = await db.selectFrom("sessions")
.selectAll()
.where("orgId", "=", orgId)
.execute();
// Générer les embeddings pour chaque session
const texts = sessions.map(s => `${s.title} ${s.notes || ""}`);
const embeddings = await workersQueueService.enqueueMlEmbedTexts({
v: 1,
orgId,
entityId: "batch-sessions",
texts,
});
// Indexer dans Qdrant
await workersQueueService.enqueueMlQdrantUpsert({
v: 1,
orgId,
entityId: "index-sessions",
collectionName: `sessions-${orgId}`,
points: sessions.map((session, idx) => ({
id: session.id,
vector: embeddings.embeddings[idx],
payload: {
sessionId: session.id,
title: session.title,
orgId,
createdAt: session.createdAt,
},
})),
});
// 2. Rechercher des sessions similaires
const queryEmbedding = await workersQueueService.enqueueMlEmbedText({
v: 1,
orgId,
entityId: "query-embedding",
text: "Consultation pour anxiété",
});
const results = await workersQueueService.enqueueMlQdrantSearch({
v: 1,
orgId,
entityId: "search-sessions",
collectionName: `sessions-${orgId}`,
queryVector: queryEmbedding.embedding,
limit: 10,
scoreThreshold: 0.7, // Seulement les résultats très similaires
filter: { orgId }, // Filtrer par organisation
});
// results.results contient les sessions les plus similaires !
Résultat :
- L'utilisateur tape "Consultation anxiété"
- Le système trouve automatiquement :
- "Séance pour troubles anxieux"
- "Thérapie contre l'anxiété"
- "Consultation stress et anxiété"
- Même si les mots exacts ne correspondent pas !
2. 👥 Recommandations de Contacts Similaires
Cas d'usage :
- "Trouve-moi des contacts qui ont des besoins similaires à ce client"
// Indexer les contacts avec leurs descriptions
const contacts = await db.selectFrom("contacts")
.selectAll()
.where("orgId", "=", orgId)
.execute();
const contactTexts = contacts.map(c =>
`${c.firstName} ${c.lastName} - ${c.notes || ""} - ${c.tags?.join(" ") || ""}`
);
// Générer et indexer
const embeddings = await workersQueueService.enqueueMlEmbedTexts({
v: 1,
orgId,
entityId: "batch-contacts",
texts: contactTexts,
});
await workersQueueService.enqueueMlQdrantUpsert({
v: 1,
orgId,
entityId: "index-contacts",
collectionName: `contacts-${orgId}`,
points: contacts.map((contact, idx) => ({
id: contact.id,
vector: embeddings.embeddings[idx],
payload: {
contactId: contact.id,
name: `${contact.firstName} ${contact.lastName}`,
orgId,
},
})),
});
// Rechercher des contacts similaires
const targetContact = contacts.find(c => c.id === targetContactId);
const targetEmbedding = await workersQueueService.enqueueMlEmbedText({
v: 1,
orgId,
entityId: "target-embedding",
text: `${targetContact.firstName} ${targetContact.lastName} - ${targetContact.notes}`,
});
const similarContacts = await workersQueueService.enqueueMlQdrantSearch({
v: 1,
orgId,
entityId: "find-similar-contacts",
collectionName: `contacts-${orgId}`,
queryVector: targetEmbedding.embedding,
limit: 5,
filter: {
orgId,
contactId: { $ne: targetContactId }, // Exclure le contact lui-même
},
});
Résultat :
- "Montre-moi des clients similaires à Jean Dupont (anxiété, thérapie cognitive)"
- Le système trouve automatiquement d'autres contacts avec des profils similaires
3. 📄 Classification Automatique de Documents
Cas d'usage :
- Classifier automatiquement les documents uploadés (facture, contrat, questionnaire, etc.)
// Après OCR d'un document
const documentText = await extractTextFromDocument(documentId);
// Générer l'embedding du document
const docEmbedding = await workersQueueService.enqueueMlEmbedText({
v: 1,
orgId,
entityId: documentId,
text: documentText,
});
// Rechercher dans une collection de documents types connus
const documentTypes = await workersQueueService.enqueueMlQdrantSearch({
v: 1,
orgId,
entityId: "classify-document",
collectionName: "document-types",
queryVector: docEmbedding.embedding,
limit: 1,
scoreThreshold: 0.8,
});
if (documentTypes.results.length > 0) {
const detectedType = documentTypes.results[0].payload.type; // "INVOICE", "CONTRACT", etc.
await updateDocumentType(documentId, detectedType);
}
Résultat :
- Un document est uploadé
- Le système détecte automatiquement : "C'est une facture" ou "C'est un contrat"
- Plus besoin de sélectionner manuellement le type !
4. 💡 Suggestions Intelligentes
Cas d'usage :
- "Quelles sessions sont similaires à celle-ci ?"
- "Quels contacts pourraient bénéficier de ce type de service ?"
// Quand l'utilisateur consulte une session
const currentSession = await getSession(sessionId);
// Trouver des sessions similaires pour suggestions
const similarSessions = await workersQueueService.enqueueMlQdrantSearch({
v: 1,
orgId,
entityId: "suggest-sessions",
collectionName: `sessions-${orgId}`,
queryVector: currentSession.embedding, // Stocké lors de l'indexation
limit: 3,
filter: {
orgId,
sessionId: { $ne: sessionId }, // Exclure la session actuelle
},
});
// Afficher : "Sessions similaires : ..."
🚀 Workflow Complet : Indexation Initiale
Étape 1 : Créer la collection (une fois par organisation)
await workersQueueService.enqueueMlQdrantCreateCollection({
v: 1,
orgId: "org-123",
entityId: "collection-sessions",
collectionName: "sessions-org-123",
vectorSize: 384, // Dimension des embeddings (all-MiniLM-L6-v2)
distance: "Cosine",
});
Étape 2 : Indexer les données existantes (migration)
// Par batch de 100 pour éviter la surcharge
const batchSize = 100;
for (let i = 0; i < sessions.length; i += batchSize) {
const batch = sessions.slice(i, i + batchSize);
// Générer les embeddings
const texts = batch.map(s => `${s.title} ${s.notes || ""}`);
const embeddings = await workersQueueService.enqueueMlEmbedTexts({
v: 1,
orgId,
entityId: `batch-${i}`,
texts,
});
// Indexer dans Qdrant
await workersQueueService.enqueueMlQdrantUpsert({
v: 1,
orgId,
entityId: `upsert-${i}`,
collectionName: `sessions-${orgId}`,
points: batch.map((session, idx) => ({
id: session.id,
vector: embeddings.embeddings[idx],
payload: {
sessionId: session.id,
title: session.title,
orgId,
},
})),
});
}
Étape 3 : Indexer les nouvelles données (en temps réel)
// Quand une nouvelle session est créée
const newSession = await createSession({ title, notes, ... });
// Générer l'embedding
const embedding = await workersQueueService.enqueueMlEmbedText({
v: 1,
orgId,
entityId: newSession.id,
text: `${newSession.title} ${newSession.notes || ""}`,
});
// Indexer immédiatement
await workersQueueService.enqueueMlQdrantUpsert({
v: 1,
orgId,
entityId: `index-${newSession.id}`,
collectionName: `sessions-${orgId}`,
points: [{
id: newSession.id,
vector: embedding.embedding,
payload: {
sessionId: newSession.id,
title: newSession.title,
orgId,
},
}],
});
💰 Avantages vs OpenAI Embeddings
Co ûts
| Solution | Coût par 1000 embeddings |
|---|---|
| OpenAI (text-embedding-3-small) | ~$0.02 |
| sentence-transformers (local) | $0.00 ✅ |
Économie : Pour 100,000 sessions indexées :
- OpenAI : ~$2,000
- Local : $0 ✅
Performance
- Latence : Local est plus rapide (pas de réseau)
- Privacy : Les données ne quittent jamais votre infrastructure
- Scalabilité : Pas de limites de rate limiting
🎯 Résumé
Les embeddings + Qdrant permettent de :
- ✅ Recherche intelligente : Trouver du contenu même sans mots exacts
- ✅ Recommandations : Suggérer des sessions/contacts similaires
- ✅ Classification automatique : Détecter le type de document
- ✅ Économies : $0 vs $0.02 par 1000 embeddings
- ✅ Privacy : Tout reste local
Workflow typique :
- Créer une collection (une fois)
- Indexer les données existantes (migration)
- Indexer les nouvelles données (temps réel)
- Rechercher quand nécessaire
C'est comme avoir un Google Search interne pour votre CRM, mais qui comprend le sens et pas juste les mots ! 🚀