Les ingénieurs logiciels occupent une place passionnante dans ce monde. Indépendamment de la pile technologique ou du secteur, nous sommes chargés de résoudre des problèmes qui contribuent directement aux buts et objectifs de nos employeurs. En prime, nous pouvons utiliser la technologie pour atténuer tous les défis qui se présentent à nous.
Pour cet exemple, je voulais me concentrer sur la façon dont pgvector – une recherche de similarité vectorielle open source pour Postgres – peut être utilisé pour identifier les similitudes de données qui existent dans les données d'entreprise.
À titre d'exemple simple, supposons que le service marketing ait besoin d'aide pour une campagne qu'il envisage de lancer. L’objectif est d’atteindre tous les comptes Salesforce appartenant à des secteurs étroitement liés à l’industrie du logiciel.
En fin de compte, ils aimeraient se concentrer sur les comptes des trois secteurs les plus similaires, avec la possibilité d’utiliser cet outil à l’avenir pour trouver des similitudes avec d’autres secteurs. Si possible, ils aimeraient avoir la possibilité de fournir le nombre souhaité d'industries correspondantes plutôt que de toujours renvoyer les trois premières.
Ce cas d'utilisation consiste à effectuer une recherche de similarité. Bien qu'il soit possible de réaliser cet exercice manuellement, l'outil Wikipedia2Vec vient à l'esprit en raison des intégrations pré-entraînées qui ont déjà été créées pour plusieurs langues. Les incorporations de mots, également appelées vecteurs, sont des représentations numériques de mots qui contiennent à la fois leurs informations syntaxiques et sémantiques. En représentant les mots sous forme de vecteurs, nous pouvons déterminer mathématiquement quels mots sont sémantiquement « plus proches » des autres.
Dans notre exemple, nous aurions également pu écrire un simple programme Python pour créer des vecteurs de mots pour chaque secteur configuré dans Salesforce.
L'extension pgvector
nécessite une base de données Postgres. Cependant, les données d'entreprise de notre exemple résident actuellement dans Salesforce. Heureusement, Heroku Connect offre un moyen simple de synchroniser les comptes Salesforce avec Heroku Postgres, en les stockant dans une table appelée salesforce.account
. Ensuite, nous aurons une autre table appelée salesforce.industries
qui contient chaque secteur dans Salesforce (sous forme de clé VARCHAR), ainsi que son vecteur de mots associé.
Avec les données Salesforce et les vecteurs de mots dans Postgres, nous allons créer une API RESTful utilisant Java et Spring Boot. Ce service effectuera la requête nécessaire et renverra les résultats au format JSON.
Nous pouvons illustrer la vue de haut niveau de la solution comme ceci :
Le code source résidera dans GitLab. L'émission d'une commande git push heroku
déclenchera un déploiement dans Heroku, introduisant une API RESTful que l'équipe marketing peut facilement utiliser.
Une fois la conception de haut niveau en place, nous pouvons commencer à créer une solution. À l'aide de ma connexion Salesforce, j'ai pu accéder à l'écran Comptes pour afficher les données de cet exercice. Voici un exemple de la première page de données d'entreprise :
Pour cet effort, j'avais prévu d'utiliser Heroku pour résoudre la demande de l'équipe marketing. Je me suis connecté à mon compte Heroku et j'ai utilisé le bouton Créer une nouvelle application pour créer une nouvelle application appelée similarity-search-sfdc
:
Après avoir créé l'application, j'ai accédé à l'onglet Ressources pour trouver le module complémentaire Heroku Postgres. J'ai tapé « Postgres » dans le champ de recherche des modules complémentaires.
Après avoir sélectionné Heroku Postgres dans la liste, j'ai choisi le plan Standard 0 , mais pgvector est disponible sur les offres de bases de données de niveau Standard (ou supérieur) exécutant PostgreSQL 15 ou la base de données bêta de niveau Essential .
Lorsque j'ai confirmé le module complémentaire, Heroku a généré et fourni une chaîne de connexion DATABASE_URL
. J'ai trouvé cela dans la section Config Vars de l'onglet Paramètres de mon application. J'ai utilisé ces informations pour me connecter à ma base de données et activer l'extension pgvector comme ceci :
CREATE EXTENSION vector;
Ensuite, j'ai recherché et trouvé le module complémentaire Heroku Connect . Je savais que cela me donnerait un moyen simple de me connecter aux données de l'entreprise dans Salesforce.
Pour cet exercice, le plan gratuit Demo Edition fonctionne très bien.
À ce stade, l'onglet Ressources de l'application similarity-search-sfdc
ressemblait à ceci :
J'ai suivi les instructions « Configuration de Heroku Connect » pour lier mon compte Salesforce à Heroku Connect. Ensuite, j'ai sélectionné l'objet Compte pour la synchronisation. Une fois terminé, j'ai pu voir les mêmes données de compte Salesforce dans Heroku Connect et dans la base de données Postgres sous-jacente.
D'un point de vue SQL, ce que j'ai fait a abouti à la création d'une table salesforce.account
avec la conception suivante :
create table salesforce.account ( createddate timestamp, isdeleted boolean, name varchar(255), systemmodstamp timestamp, accountnumber varchar(40), industry varchar(255), sfid varchar(18), id serial primary key, _hc_lastop varchar(32), _hc_err text );
Pour que la recherche de similarité fonctionne comme prévu, je devais générer des vecteurs de mots pour chaque secteur de compte Salesforce :
Étant donné que le cas d'utilisation principal indiquait la nécessité de trouver des similitudes avec l'industrie du logiciel, nous aurions également besoin de générer un vecteur de mots pour cette industrie.
Pour simplifier les choses pour cet exercice, j'ai exécuté manuellement cette tâche en utilisant Python 3.9 et un fichier appelé embed.py
, qui ressemble à ceci :
from wikipedia2vec import Wikipedia2Vec wiki2vec = Wikipedia2Vec.load('enwiki_20180420_100d.pkl') print(wiki2vec.get_word_vector('software').tolist())
Veuillez noter que la méthode get_word_vector()
attend une représentation en minuscules de l'industrie.
L'exécution de python embed.py
a généré le vecteur de mot suivant pour le mot software
:
[-0.40402618050575256, 0.5711150765419006, -0.7885153293609619, -0.15960034728050232, -0.5692323446273804, 0.005377458408474922, -0.1315757781267166, -0.16840921342372894, 0.6626015305519104, -0.26056772470474243, 0.3681095242500305, -0.453583300113678, 0.004738557618111372, -0.4111144244670868, -0.1817493587732315, -0.9268549680709839, 0.07973367720842361, -0.17835664749145508, -0.2949991524219513, -0.5533796548843384, 0.04348105192184448, -0.028855713084340096, -0.13867013156414032, -0.6649054884910583, 0.03129105269908905, -0.24817068874835968, 0.05968991294503212, -0.24743635952472687, 0.20582349598407745, 0.6240783929824829, 0.3214546740055084, -0.14210252463817596, 0.3178422152996063, 0.7693028450012207, 0.2426985204219818, -0.6515568494796753, -0.2868216037750244, 0.3189859390258789, 0.5168254971504211, 0.11008890718221664, 0.3537853956222534, -0.713259220123291, -0.4132286608219147, -0.026366405189037323, 0.003034653142094612, -0.5275223851203918, -0.018167126923799515, 0.23878540098667145, -0.6077089905738831, 0.5368344187736511, -0.1210874393582344, 0.26415619254112244, -0.3066694438457489, 0.1471938043832779, 0.04954215884208679, 0.2045321762561798, 0.1391817331314087, 0.5286830067634583, 0.5764685273170471, 0.1882934868335724, -0.30167853832244873, -0.2122340053319931, -0.45651525259017944, -0.016777794808149338, 0.45624101161956787, -0.0438646525144577, -0.992512047290802, -0.3771328926086426, 0.04916151612997055, -0.5830298066139221, -0.01255014631897211, 0.21600870788097382, -0.18419665098190308, 0.1754663586616516, -0.1499166339635849, -0.1916201263666153, -0.22884036600589752, 0.17280352115631104, 0.25274306535720825, 0.3511175513267517, -0.20270302891731262, -0.6383468508720398, 0.43260180950164795, -0.21136239171028137, -0.05920517444610596, 0.7145522832870483, 0.7626600861549377, -0.5473887920379639, 0.4523043632507324, -0.1723199188709259, -0.10209759324789047, -0.5577948093414307, -0.10156919807195663, 0.31126976013183594, 0.3604489266872406, -0.13295558094978333, 0.2473849356174469, 0.278846800327301, -0.28618067502975464, 0.00527254119515419]
Afin de stocker les vecteurs de mots, nous devions ajouter une table industries
à la base de données Postgres à l'aide de la commande SQL suivante :
create table salesforce.industries ( name varchar not null constraint industries_pk primary key, embeddings vector(100) not null );
Une fois la table industries
créée, nous insérerons chacun des vecteurs de mots générés. Nous faisons cela avec des instructions SQL similaires aux suivantes :
INSERT INTO salesforce.industries (name, embeddings) VALUES ('Software','[-0.40402618050575256, 0.5711150765419006, -0.7885153293609619, -0.15960034728050232, -0.5692323446273804, 0.005377458408474922, -0.1315757781267166, -0.16840921342372894, 0.6626015305519104, -0.26056772470474243, 0.3681095242500305, -0.453583300113678, 0.004738557618111372, -0.4111144244670868, -0.1817493587732315, -0.9268549680709839, 0.07973367720842361, -0.17835664749145508, -0.2949991524219513, -0.5533796548843384, 0.04348105192184448, -0.028855713084340096, -0.13867013156414032, -0.6649054884910583, 0.03129105269908905, -0.24817068874835968, 0.05968991294503212, -0.24743635952472687, 0.20582349598407745, 0.6240783929824829, 0.3214546740055084, -0.14210252463817596, 0.3178422152996063, 0.7693028450012207, 0.2426985204219818, -0.6515568494796753, -0.2868216037750244, 0.3189859390258789, 0.5168254971504211, 0.11008890718221664, 0.3537853956222534, -0.713259220123291, -0.4132286608219147, -0.026366405189037323, 0.003034653142094612, -0.5275223851203918, -0.018167126923799515, 0.23878540098667145, -0.6077089905738831, 0.5368344187736511, -0.1210874393582344, 0.26415619254112244, -0.3066694438457489, 0.1471938043832779, 0.04954215884208679, 0.2045321762561798, 0.1391817331314087, 0.5286830067634583, 0.5764685273170471, 0.1882934868335724, -0.30167853832244873, -0.2122340053319931, -0.45651525259017944, -0.016777794808149338, 0.45624101161956787, -0.0438646525144577, -0.992512047290802, -0.3771328926086426, 0.04916151612997055, -0.5830298066139221, -0.01255014631897211, 0.21600870788097382, -0.18419665098190308, 0.1754663586616516, -0.1499166339635849, -0.1916201263666153, -0.22884036600589752, 0.17280352115631104, 0.25274306535720825, 0.3511175513267517, -0.20270302891731262, -0.6383468508720398, 0.43260180950164795, -0.21136239171028137, -0.05920517444610596, 0.7145522832870483, 0.7626600861549377, -0.5473887920379639, 0.4523043632507324, -0.1723199188709259, -0.10209759324789047, -0.5577948093414307, -0.10156919807195663, 0.31126976013183594, 0.3604489266872406, -0.13295558094978333, 0.2473849356174469, 0.278846800327301, -0.28618067502975464, 0.00527254119515419] ');
Veuillez noter que bien que nous ayons créé un vecteur de mots avec la représentation en minuscules de l'industrie du logiciel (logiciel), la colonne industries.name
doit correspondre au nom de l'industrie en majuscule (logiciel).
Une fois que tous les vecteurs de mots générés ont été ajoutés au tableau industries
, nous pouvons nous concentrer sur l'introduction d'une API RESTful.
C’est à ce moment-là que ma passion en tant qu’ingénieur logiciel est passée à la vitesse supérieure car j’avais tout en place pour résoudre le défi à relever.
Ensuite, en utilisant Spring Boot 3.2.2 et Java (temurin) 17, j'ai créé le projet similarity-search-sfdc
dans IntelliJ IDEA avec les dépendances Maven suivantes :
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.pgvector</groupId> <artifactId>pgvector</artifactId> <version>0.1.4</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
J'ai créé des entités simplifiées pour l'objet Account et l'objet Industry (intégration), qui correspondent aux tables de la base de données Postgres créées précédemment.
@AllArgsConstructor @NoArgsConstructor @Data @Entity @Table(name = "account", schema = "salesforce") public class Account { @Id @Column(name = "sfid") private String id; private String name; private String industry; } @AllArgsConstructor @NoArgsConstructor @Data @Entity @Table(name = "industries", schema = "salesforce") public class Industry { @Id private String name; }
En utilisant l'interface JpaRepository, j'ai ajouté les extensions suivantes pour permettre un accès facile aux tables Postgres :
public interface AccountsRepository extends JpaRepository<Account, String> { @Query(nativeQuery = true, value = "SELECT sfid, name, industry " + "FROM salesforce.account " + "WHERE industry IN (SELECT name " + " FROM salesforce.industries " + " WHERE name != :industry " + " ORDER BY embeddings <-> (SELECT embeddings FROM salesforce.industries WHERE name = :industry) " + " LIMIT :limit)" + "ORDER BY name") Set<Account> findSimilaritiesForIndustry(String industry, int limit); } public interface IndustriesRepository extends JpaRepository<Industry, String> { }
Notez que la méthode findSimilaritiesForIndustry()
est l'endroit où tout le gros du travail aura lieu pour résoudre ce cas d'utilisation. La méthode acceptera les paramètres suivants :
industry
: l'industrie pour trouver des similitudes pourlimit
: le nombre maximum de similitudes sectorielles à rechercher lors de la recherche de comptes
Notez l'opérateur de distance euclidienne (<->) dans notre requête ci-dessus. Il s'agit de l' opérateur intégré à l'extension pour effectuer la recherche de similarité.
Avec le cas d'utilisation original du secteur « Logiciel » et une limite fixée aux trois secteurs les plus proches, la requête en cours d'exécution ressemblerait à ceci :
SELECT sfid, name, industry FROM salesforce.account WHERE industry IN (SELECT name FROM salesforce.industries WHERE name != 'Software' ORDER BY embeddings <-> (SELECT embeddings FROM salesforce.industries WHERE name = 'Software') LIMIT 3) ORDER BY name;
À partir de là, j'ai construit la classe AccountsService
pour interagir avec les référentiels JPA :
@RequiredArgsConstructor @Service public class AccountsService { private final AccountsRepository accountsRepository; private final IndustriesRepository industriesRepository; public Set<Account> getAccountsBySimilarIndustry(String industry, int limit) throws Exception { List<Industry> industries = industriesRepository.findAll(); if (industries .stream() .map(Industry::getName) .anyMatch(industry::equals)) { return accountsRepository .findSimilaritiesForIndustry(industry, limit); } else { throw new Exception( "Could not locate '" + industry + "' industry"); } } }
Enfin, j'ai demandé à la classe AccountsController
de fournir un point d'entrée RESTful et de se connecter au AccountsService
:
@RequiredArgsConstructor @RestController @RequestMapping(value = "/accounts") public class AccountsController { private final AccountsService accountsService; @GetMapping(value = "/similarities") public ResponseEntity<Set<Account>> getAccountsBySimilarIndustry(@RequestParam String industry, @RequestParam int limit) { try { return new ResponseEntity<>( accountsService .getAccountsBySimilarIndustry(industry, limit), HttpStatus.OK); } catch (Exception e) { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } }
Le service Spring Boot étant prêt, j'ai ajouté le Procfile
suivant au projet, permettant à Heroku d'en savoir plus sur notre service :
web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar
Pour être sûr, j'ai ajouté le fichier system.properties
pour spécifier quelles versions de Java et Maven sont attendues :
java.runtime.version=17 maven.version=3.9.5
À l'aide de la CLI Heroku, j'ai ajouté une télécommande à mon référentiel GitLab pour le service similarity-search-sfdc
sur la plateforme Heroku :
heroku git:remote -a similarity-search-sfdc
J'ai également défini le type de buildpack pour le service similarity-search-sfdc
via la commande suivante :
heroku buildpacks:set https://github.com/heroku/heroku-buildpack-java
Enfin, j'ai déployé le service similarity-search-sfdc
sur Heroku à l'aide de la commande suivante :
git push heroku
Désormais, l'onglet Ressources de l'application similarity-search-sfdc
apparaît comme indiqué ci-dessous :
Avec l'API RESTful en cours d'exécution, j'ai émis la commande cURL suivante pour localiser les trois principaux secteurs Salesforce (et les comptes associés) les plus proches du secteur du logiciel :
curl --location 'https://HEROKU-APP-ROOT-URL/accounts/similarities?industry=Software&limit=3'
L'API RESTful renvoie un état de réponse 200 OK HTTP
ainsi que la charge utile suivante :
[ { "id": "001Kd00001bsP80IAE", "name": "CleanSlate Technology Group", "industry": "Technology" }, { "id": "001Kd00001bsPBFIA2", "name": "CMG Worldwide", "industry": "Media" }, { "id": "001Kd00001bsP8AIAU", "name": "Dev Spotlight", "industry": "Technology" }, { "id": "001Kd00001bsP8hIAE", "name": "Egghead", "industry": "Electronics" }, { "id": "001Kd00001bsP85IAE", "name": "Marqeta", "industry": "Technology" } ]
En conséquence, les secteurs de la technologie , des médias et de l'électronique sont les secteurs les plus proches du secteur du logiciel dans cet exemple.
Désormais, le service marketing dispose d'une liste de comptes qu'il peut contacter pour sa prochaine campagne.
Il y a des années, j'ai passé plus de temps que je ne voudrais l'admettre à jouer au jeu vidéo multijoueur Team Fortress 2 . Voici une capture d'écran d'un événement en 2012 qui était très amusant :
Ceux qui connaissent cet aspect de ma vie pourraient vous dire que ma classe de joueur par défaut était le soldat. En effet, le soldat a le meilleur équilibre entre santé, mouvement, vitesse et puissance de feu.
J'ai l'impression que les ingénieurs logiciels sont la « classe de soldats » du monde réel car nous pouvons nous adapter à n'importe quelle situation et nous concentrer sur la fourniture de solutions qui répondent aux attentes de manière efficace.
Depuis quelques années maintenant, je me concentre sur l'énoncé de mission suivant, qui, selon moi, peut s'appliquer à tout professionnel de l'informatique :
« Concentrez votre temps sur la fourniture de caractéristiques/fonctionnalités qui augmentent la valeur de votre propriété intellectuelle. Tirez parti des frameworks, des produits et des services pour tout le reste.
-J.Vester
Dans l'exemple de cet article, nous avons pu exploiter Heroku Connect pour synchroniser les données d'entreprise avec une base de données Postgres. Après avoir installé l'extension pgvector, nous avons créé des vecteurs de mots pour chaque secteur unique à partir de ces comptes Salesforce. Enfin, nous avons introduit un service Spring Boot, qui a simplifié le processus de localisation des comptes Salesforce dont le secteur était le plus proche d'un autre secteur.
Nous avons résolu ce cas d'utilisation rapidement avec les technologies open source existantes, l'ajout d'un petit service Spring Boot et le Heroku PaaS – en respectant pleinement mon énoncé de mission. Je ne peux pas imaginer combien de temps il faudrait sans ces cadres, produits et services.
Si vous êtes intéressé, vous pouvez trouver le code source original de cet article sur GitLab :
https://gitlab.com/johnjvester/similarity-search-sfdc
Passez une très bonne journée !