Construire des systèmes de recommandation qui semblent « instantanés » est l'une des tâches les plus difficiles dans l'architecture logicielle moderne. Les approches traditionnelles souffrent souvent de pics de latence lors du calcul des scores de similarité au moment de la requête. Vespa, une plateforme open source de recherche et d'analyse de données, offre un avantage unique en supportant nativement la recherche vectorielle au sein de son moteur de traitement de requêtes. Cela permet une personnalisation en temps réel sans avoir besoin de services d'inférence séparés et lents.
Pourquoi choisir Vespa pour la recherche vectorielle ?
Contrairement aux bases de données vectorielles pures qui peuvent avoir du mal avec le filtrage complexe ou les jointures de données structurées, Vespa combine recherche, similarité vectorielle et analyse dans un système unique. Pour la personnalisation, c'est crucial, car vous ne voulez rarement recommander des éléments basés uniquement sur la similarité sémantique. Vous devez filtrer par inventaire, emplacement de l'utilisateur ou statut d'abonnement, tout en attribuant simultanément des scores aux éléments en fonction des embeddings de préférence de l'utilisateur.
Le mécanisme de base consiste à stocker les embeddings d'éléments dans Vespa et à calculer les scores de similarité au moment de la requête en utilisant des algorithmes efficaces de plus proches voisins. Vespa prend en charge plusieurs méthodes d'indexation, y compris HNSW (Hierarchical Navigable Small World) pour les vecteurs de haute dimension, garantissant des temps de réponse de l'ordre de la milliseconde même avec des millions d'éléments.
Définition du modèle de données
Pour implémenter un modèle d'interaction utilisateur-élément, nous définissons d'abord le schéma. Nous stockerons un vecteur d'embedding pour chaque élément (par exemple, un produit ou un article) et des métadonnées pour le filtrage. Voici un exemple de fichier .sd (Définition de schéma) :
schema items {
document items {
field title type string {
indexing: index | summary
}
field category type string {
indexing: index
}
field embedding type tensor[x(768)] {
indexing: index
}
}
indexes {
vector embedding {
dimension: 768
distance-metric: euclidean
max-posting-list-size: 100
}
}
}
Remarquez la définition de l'index vector. Nous spécifions la dimension (768, typique pour les embeddings de type BERT) et la métrique de distance. La distance euclidienne est standard pour les embeddings normalisés, mais la similarité cosinus peut être obtenue en normalisant les vecteurs avant l'ingestion.
Requête avec personnalisation
La puissance de Vespa apparaît lors de la phase de requête. Nous pouvons combiner la similarité vectorielle avec la logique métier. Supposons que nous ayons un profil utilisateur avec son propre embedding, généré à partir de ses interactions passées. Nous pouvons envoyer une requête qui récupère les éléments les plus proches du vecteur de l'utilisateur, tout en excluant les éléments en rupture de stock.
{
"query": {
"root": {
"id": "root",
"timeout": "50ms",
"inputs": {
"query.user_embedding": [0.1, 0.5, -0.2, ... 765 more values]
}
}
},
"yql": "select * from items where userembedding(query.user_embedding) and category in filter('category')"
}
Ici, userembedding() est un opérateur de requête intégré de Vespa qui calcule la similarité entre le vecteur d'élément stocké et le vecteur de requête de l'utilisateur. Le résultat est un score qui peut être utilisé pour le classement. En ajoutant des filtres directement dans YQL (Vespa Query Language), nous nous assurons que seuls les éléments éligibles sont pris en compte et que la recherche vectorielle est élaguée tôt, améliorant ainsi les performances.
Gestion des mises à jour en temps réel
Un défi clé dans la personnalisation est de garder le modèle à jour. Vespa permet l'indexation en temps réel de nouveaux éléments et d'embeddings d'utilisateurs. Lorsqu'un utilisateur interagit avec un nouvel élément, vous pouvez mettre à jour son embedding de profil et le pousser vers le point d'accès update de Vespa. Comme l'index vectoriel est mis à jour de manière incrémentale, les requêtes suivantes refléteront les préférences les plus récentes de l'utilisateur sans redémarrer le service.
De plus, Vespa prend en charge la recherche distribuée sur plusieurs nœuds. À mesure que votre catalogue grandit, vous pouvez étendre l'index vectoriel en augmentant le nombre de groupes de contenu. La couche de requête distribue automatiquement la recherche vectorielle entre les nœuds et fusionne les résultats, maintenant une faible latence.
Exemple pratique : Recommandations e-commerce
Considérez un site e-commerce avec 10 millions de produits. Nous utilisons un réseau neuronal à deux tours pour générer des embeddings pour les utilisateurs et les produits. La tour utilisateur produit un vecteur de 128 dimensions basé sur leur historique de navigation, tandis que la tour produit produit un vecteur de 128 dimensions basé sur les attributs du produit.
Dans Vespa, nous stockons les vecteurs de produits. Au moment de la requête, le frontend envoie le vecteur 128-dim de l'utilisateur. Vespa exécute la requête YQL, filtre les articles en stock dans la région de l'utilisateur et les classe par similarité cosinus. Les 50 meilleurs résultats sont retournés en moins de 10 ms, prêts à être affichés dans la section « Recommandé pour vous ».
Conclusion
Vespa fournit une solution robuste et évolutive pour implémenter la personnalisation en temps réel via la recherche vectorielle. En intégrant la similarité vectorielle avec le filtrage structuré dans un seul moteur de requête, les développeurs peuvent construire des systèmes de recommandation réactifs qui équilibrent la pertinence avec les règles métier. Que vous construisiez un flux d'actualités ou une plateforme e-commerce, la capacité de Vespa à gérer des opérations vectorielles à grande échelle avec une faible latence en fait un choix puissant pour les applications modernes pilotées par les données.