Softwareentwickler nehmen einen spannenden Platz in dieser Welt ein. Unabhängig vom Tech-Stack oder der Branche haben wir die Aufgabe, Probleme zu lösen, die direkt zu den Zielen und Absichten unserer Arbeitgeber beitragen. Als Bonus können wir Technologie nutzen, um alle Herausforderungen zu bewältigen, die in unser Fadenkreuz geraten.
In diesem Beispiel wollte ich mich darauf konzentrieren, wie pgvector – eine Open-Source-Vektorähnlichkeitssuche für Postgres – verwendet werden kann, um in Unternehmensdaten vorhandene Datenähnlichkeiten zu identifizieren.
Nehmen wir als einfaches Beispiel an, dass die Marketingabteilung Unterstützung für eine Kampagne benötigt, die sie starten möchte. Das Ziel besteht darin, alle Salesforce- Konten zu erreichen, die in Branchen angesiedelt sind, die eng mit der Softwarebranche verbunden sind.
Am Ende möchten sie sich auf Konten in den drei ähnlichsten Branchen konzentrieren und dieses Tool in Zukunft nutzen, um Ähnlichkeiten für andere Branchen zu finden. Wenn möglich, möchten sie die Möglichkeit haben, die gewünschte Anzahl passender Branchen bereitzustellen, anstatt immer die ersten drei zurückzugeben.
Bei diesem Anwendungsfall geht es um die Durchführung einer Ähnlichkeitssuche. Obwohl es möglich ist, diese Übung manuell durchzuführen, fällt mir aufgrund der vorab trainierten Einbettungen, die bereits für mehrere Sprachen erstellt wurden, das Tool Wikipedia2Vec ein. Worteinbettungen – auch Vektoren genannt – sind numerische Darstellungen von Wörtern, die sowohl deren syntaktische als auch semantische Informationen enthalten. Durch die Darstellung von Wörtern als Vektoren können wir mathematisch bestimmen, welche Wörter anderen semantisch „näher“ sind.
In unserem Beispiel hätten wir auch ein einfaches Python-Programm schreiben können, um Wortvektoren für jede in Salesforce konfigurierte Branche zu erstellen.
Die pgvector
Erweiterung erfordert eine Postgres-Datenbank. Allerdings befinden sich die Unternehmensdaten für unser Beispiel derzeit in Salesforce. Glücklicherweise bietet Heroku Connect eine einfache Möglichkeit, die Salesforce-Konten mit Heroku Postgres zu synchronisieren und sie in einer Tabelle namens salesforce.account
zu speichern. Dann haben wir eine weitere Tabelle namens salesforce.industries
, die jede Branche in Salesforce (als VARCHAR-Schlüssel) zusammen mit dem zugehörigen Wortvektor enthält.
Mit den Salesforce-Daten und Wortvektoren in Postgres erstellen wir eine RESTful-API mit Java und Spring Boot. Dieser Dienst führt die erforderliche Abfrage durch und gibt die Ergebnisse im JSON-Format zurück.
Wir können die allgemeine Ansicht der Lösung wie folgt veranschaulichen:
Der Quellcode befindet sich in GitLab. Die Ausgabe eines git push heroku
-Befehls löst eine Bereitstellung in Heroku aus und führt eine RESTful-API ein, die das Marketingteam problemlos nutzen kann.
Sobald das High-Level-Design vorliegt, können wir mit der Entwicklung einer Lösung beginnen. Mit meinem Salesforce-Login konnte ich zum Bildschirm „Konten“ navigieren, um die Daten für diese Übung anzuzeigen. Hier ist ein Beispiel für die erste Seite mit Unternehmensdaten:
Zu diesem Zweck wollte ich Heroku verwenden, um die Anfrage des Marketingteams zu lösen. Ich habe mich bei meinem Heroku-Konto angemeldet und über die Schaltfläche „Neue App erstellen“ eine neue Anwendung namens similarity-search-sfdc
erstellt:
Nachdem ich die App erstellt hatte, navigierte ich zur Registerkarte „Ressourcen“ , um das Heroku-Postgres-Add-on zu finden. Ich habe „Postgres“ in das Add-On-Suchfeld eingegeben.
Nachdem ich Heroku Postgres aus der Liste ausgewählt hatte, entschied ich mich für den Plan Standard 0 , aber pgvector ist für Datenbankangebote der Standardstufe (oder höher) verfügbar, auf denen PostgreSQL 15 oder die Beta-Datenbank der Essential-Stufe ausgeführt wird .
Als ich das Add-on bestätigte, generierte Heroku eine DATABASE_URL
Verbindungszeichenfolge und stellte sie bereit. Ich habe dies im Abschnitt „Config Vars“ der Registerkarte „Einstellungen“ meiner App gefunden. Ich habe diese Informationen verwendet, um eine Verbindung zu meiner Datenbank herzustellen und die pgvector-Erweiterung wie folgt zu aktivieren:
CREATE EXTENSION vector;
Als nächstes habe ich nach dem Heroku Connect- Add-on gesucht und es gefunden. Ich wusste, dass mir dies eine einfache Möglichkeit bieten würde, eine Verbindung zu den Unternehmensdaten in Salesforce herzustellen.
Für diese Übung funktioniert der kostenlose Demo-Edition -Plan einwandfrei.
Zu diesem Zeitpunkt sah die Registerkarte „Ressourcen“ für die App similarity-search-sfdc
folgendermaßen aus:
Ich habe die Anweisungen zum Einrichten von Heroku Connect befolgt, um mein Salesforce-Konto mit Heroku Connect zu verknüpfen. Dann habe ich das Kontoobjekt für die Synchronisierung ausgewählt. Nach Abschluss konnte ich dieselben Salesforce-Kontodaten in Heroku Connect und in der zugrunde liegenden Postgres-Datenbank sehen.
Aus SQL-Sicht führte meine Vorgehensweise zur Erstellung einer salesforce.account
Tabelle mit dem folgenden Design:
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 );
Damit die Ähnlichkeitssuche wie erwartet funktionierte, musste ich Wortvektoren für jede Salesforce-Kontobranche generieren:
Da der primäre Anwendungsfall die Notwendigkeit anzeigte, Ähnlichkeiten für die Softwarebranche zu finden, müssten wir auch für diese Branche einen Wortvektor generieren.
Um die Dinge für diese Übung einfach zu halten, habe ich diese Aufgabe manuell mit Python 3.9 und einer Datei namens embed.py
ausgeführt, die so aussieht:
from wikipedia2vec import Wikipedia2Vec wiki2vec = Wikipedia2Vec.load('enwiki_20180420_100d.pkl') print(wiki2vec.get_word_vector('software').tolist())
Bitte beachten Sie, dass die Methode get_word_vector()
eine Darstellung der Branche in Kleinbuchstaben erwartet.
Beim Ausführen von python embed.py
wurde der folgende Wortvektor für das software
generiert:
[-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]
Um die Wortvektoren zu speichern, mussten wir mit dem folgenden SQL-Befehl eine industries
zur Postgres-Datenbank hinzufügen:
create table salesforce.industries ( name varchar not null constraint industries_pk primary key, embeddings vector(100) not null );
Nachdem die industries
erstellt wurde, fügen wir jeden der generierten Wortvektoren ein. Wir tun dies mit SQL-Anweisungen, die den folgenden ähneln:
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] ');
Bitte beachten Sie: Während wir einen Wortvektor mit der kleingeschriebenen Darstellung der Softwarebranche (Software) erstellt haben, muss die Spalte industries.name
mit dem großgeschriebenen Branchennamen (Software) übereinstimmen.
Sobald alle generierten Wortvektoren zur industries
hinzugefügt wurden, können wir unseren Fokus auf die Einführung einer RESTful-API verlagern.
Dies war der Punkt, an dem meine Leidenschaft als Softwareentwickler auf Hochtouren kam, weil ich alles hatte, um die anstehende Herausforderung zu lösen.
Als Nächstes habe ich mit Spring Boot 3.2.2 und Java (Temurin) 17 das similarity-search-sfdc
Projekt in IntelliJ IDEA mit den folgenden Maven-Abhängigkeiten erstellt:
<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>
Ich habe vereinfachte Entitäten sowohl für das Kontoobjekt als auch für das Branchenobjekt (Einbettung) erstellt, die mit den zuvor erstellten Postgres-Datenbanktabellen übereinstimmten.
@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; }
Mithilfe der JpaRepository-Schnittstelle habe ich die folgenden Erweiterungen hinzugefügt, um einen einfachen Zugriff auf die Postgres-Tabellen zu ermöglichen:
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> { }
Beachten Sie, dass in der Methode findSimilaritiesForIndustry()
die ganze schwere Arbeit zur Lösung dieses Anwendungsfalls stattfindet. Die Methode akzeptiert die folgenden Parameter:
industry
: die Branche, für die Ähnlichkeiten gefunden werden sollenlimit
: die maximale Anzahl von Branchenähnlichkeiten, nach denen bei der Abfrage von Konten gesucht werden soll
Beachten Sie den euklidischen Distanzoperator (<->) in unserer obigen Abfrage. Dies ist der integrierte Operator der Erweiterung zum Durchführen der Ähnlichkeitssuche.
Mit dem ursprünglichen Anwendungsfall „Software“ und einer Beschränkung auf die drei nächstgelegenen Branchen würde die ausgeführte Abfrage wie folgt aussehen:
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;
Von dort aus habe ich die AccountsService
Klasse erstellt, um mit den JPA-Repositorys zu interagieren:
@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"); } } }
Zuletzt ließ ich die AccountsController
Klasse einen RESTful-Einstiegspunkt bereitstellen und eine Verbindung zum AccountsService
herstellen:
@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); } } }
Nachdem der Spring Boot-Dienst bereit war, habe ich dem Projekt die folgende Procfile
hinzugefügt, um Heroku mehr über unseren Dienst zu informieren:
web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar
Aus Sicherheitsgründen habe ich die Datei system.properties
hinzugefügt, um anzugeben, welche Versionen von Java und Maven erwartet werden:
java.runtime.version=17 maven.version=3.9.5
Mit der Heroku-CLI habe ich meinem GitLab-Repository einen Remote-Dienst für den similarity-search-sfdc
-Dienst zur Heroku-Plattform hinzugefügt:
heroku git:remote -a similarity-search-sfdc
Ich habe außerdem den Buildpack-Typ für den Dienst similarity-search-sfdc
über den folgenden Befehl festgelegt:
heroku buildpacks:set https://github.com/heroku/heroku-buildpack-java
Abschließend habe ich den Dienst similarity-search-sfdc
mit dem folgenden Befehl für Heroku bereitgestellt:
git push heroku
Nun wurde die Registerkarte „Ressourcen“ für die App similarity-search-sfdc
wie folgt angezeigt:
Während die RESTful-API ausgeführt wurde, habe ich den folgenden cURL-Befehl ausgegeben, um die drei wichtigsten Salesforce-Branchen (und zugehörigen Konten) zu finden, die der Softwarebranche am nächsten kommen:
curl --location 'https://HEROKU-APP-ROOT-URL/accounts/similarities?industry=Software&limit=3'
Die RESTful-API gibt den 200 OK HTTP
zusammen mit der folgenden Nutzlast zurück:
[ { "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" } ]
Daher sind die Branchen Technologie , Medien und Elektronik in diesem Beispiel die Branchen, die der Softwarebranche am nächsten kommen.
Jetzt verfügt die Marketingabteilung über eine Liste von Kunden, an die sie sich für ihre nächste Kampagne wenden kann.
Vor Jahren habe ich mehr Zeit mit dem Multiplayer-Videospiel Team Fortress 2 verbracht, als ich zugeben möchte. Hier ist ein Screenshot von einer Veranstaltung im Jahr 2012, die viel Spaß gemacht hat:
Diejenigen, die mit diesem Aspekt meines Lebens vertraut sind, könnten Ihnen sagen, dass meine bevorzugte Spielerklasse der Soldat war. Dies liegt daran, dass der Soldat über das beste Gleichgewicht zwischen Gesundheit, Bewegung, Geschwindigkeit und Feuerkraft verfügt.
Ich bin der Meinung, dass Softwareentwickler die „Soldatenklasse“ der realen Welt sind, weil wir uns an jede Situation anpassen und uns darauf konzentrieren können, Lösungen bereitzustellen, die die Erwartungen auf effiziente Weise erfüllen.
Seit einigen Jahren konzentriere ich mich auf das folgende Leitbild, das meiner Meinung nach auf jeden IT-Experten anwendbar ist:
„Konzentrieren Sie Ihre Zeit auf die Bereitstellung von Features/Funktionalitäten, die den Wert Ihres geistigen Eigentums steigern. Nutzen Sie Frameworks, Produkte und Services für alles andere.“
- J. Vester
Im Beispiel für diesen Beitrag konnten wir Heroku Connect nutzen, um Unternehmensdaten mit einer Postgres-Datenbank zu synchronisieren. Nach der Installation der pgvector-Erweiterung haben wir aus diesen Salesforce-Konten Wortvektoren für jede einzelne Branche erstellt. Schließlich haben wir einen Spring Boot-Dienst eingeführt, der das Auffinden von Salesforce-Konten vereinfacht, deren Branche einer anderen Branche am nächsten kommt.
Wir haben diesen Anwendungsfall schnell mit vorhandenen Open-Source-Technologien, der Hinzufügung eines winzigen Spring Boot-Dienstes und dem Heroku PaaS gelöst – ganz im Einklang mit meinem Leitbild. Ich kann mir nicht vorstellen, wie viel Zeit ohne diese Frameworks, Produkte und Dienstleistungen erforderlich wäre.
Bei Interesse finden Sie den Original-Quellcode für diesen Artikel auf GitLab:
https://gitlab.com/johnjvester/similarity-search-sfdc
Ich wünsche Ihnen einen wirklich tollen Tag!