Инженеры-программисты занимают захватывающее место в этом мире. Независимо от технологического стека или отрасли, перед нами стоит задача решать проблемы, которые напрямую способствуют достижению целей и задач наших работодателей. В качестве бонуса мы можем использовать технологии для смягчения любых проблем, попадающих в поле нашего зрения.
В этом примере я хотел сосредоточиться на том, как pgvector — программа поиска сходства векторов с открытым исходным кодом для Postgres — может использоваться для выявления сходства данных, существующих в корпоративных данных.
В качестве простого примера предположим, что отделу маркетинга требуется помощь для кампании, которую они планируют запустить. Цель — охватить все учетные записи Salesforce , работающие в отраслях, тесно связанных с индустрией программного обеспечения.
В конце концов, они хотели бы сосредоточиться на аккаунтах в трёх наиболее похожих отраслях с возможностью использовать этот инструмент в будущем для поиска сходств для других отраслей. Если возможно, они хотели бы иметь возможность предоставить желаемое количество соответствующих отраслей, а не всегда возвращать тройку лидеров.
Этот вариант использования сосредоточен на выполнении поиска по сходству. Хотя это упражнение можно выполнить вручную, на ум приходит инструмент Wikipedia2Vec из-за предварительно обученных вложений, которые уже созданы для нескольких языков. Вложения слов, также известные как векторы, представляют собой числовые представления слов, которые содержат как их синтаксическую, так и семантическую информацию. Представляя слова в виде векторов, мы можем математически определить, какие слова семантически «ближе» к другим.
В нашем примере мы также могли бы написать простую программу на Python для создания векторов слов для каждой отрасли, настроенных в Salesforce.
Расширению pgvector
требуется база данных Postgres. Однако корпоративные данные для нашего примера в настоящее время находятся в Salesforce. К счастью, Heroku Connect предоставляет простой способ синхронизации учетных записей Salesforce с Heroku Postgres, сохраняя их в таблице под названием salesforce.account
. Затем у нас будет еще одна таблица под названием salesforce.industries
, которая содержит каждую отрасль в Salesforce (в виде ключа VARCHAR) вместе со связанным с ней вектором слов.
Используя данные Salesforce и векторы слов в Postgres, мы создадим RESTful API с использованием Java и Spring Boot. Этот сервис выполнит необходимый запрос и вернет результаты в формате JSON.
Мы можем проиллюстрировать общий вид решения следующим образом:
Исходный код будет находиться в GitLab. Выполнение команды git push heroku
инициирует развертывание в Heroku, предоставляя RESTful API, который маркетинговая команда может легко использовать.
Имея общий проект, мы можем приступить к созданию решения. Используя свой логин в Salesforce, я смог перейти на экран «Учетные записи» и просмотреть данные для этого упражнения. Вот пример первой страницы данных предприятия:
Для этой цели я планировал использовать Heroku для решения запроса маркетинговой команды. Я вошел в свою учетную запись Heroku и использовал кнопку «Создать новое приложение» , чтобы создать новое приложение под названием similarity-search-sfdc
:
После создания приложения я перешел на вкладку «Ресурсы» и нашел надстройку Heroku Postgres. Я набрал «Postgres» в поле поиска дополнений.
Выбрав Heroku Postgres из списка, я выбрал план Standard 0 , но pgvector доступен в предложениях баз данных уровня Standard (или выше), работающих под управлением PostgreSQL 15 или бета-версии базы данных Essential-уровня .
Когда я подтвердил дополнение, Heroku сгенерировал и предоставил строку подключения DATABASE_URL
. Я нашел это в разделе Config Vars на вкладке Settings моего приложения. Я использовал эту информацию для подключения к своей базе данных и включения расширения pgvector следующим образом:
CREATE EXTENSION vector;
Затем я поискал и нашел дополнение Heroku Connect . Я знал, что это даст мне простой способ подключения к корпоративным данным в Salesforce.
Для этого упражнения бесплатный план Demo Edition отлично подойдет.
На этом этапе вкладка «Ресурсы» приложения similarity-search-sfdc
выглядела следующим образом:
Я следовал инструкциям « Настройка Heroku Connect », чтобы связать свою учетную запись Salesforce с Heroku Connect. Затем я выбрал объект «Учетная запись» для синхронизации. После завершения я смог увидеть одни и те же данные учетной записи Salesforce в Heroku Connect и в базовой базе данных Postgres.
С точки зрения SQL то, что я сделал, привело к созданию таблицы salesforce.account
следующего вида:
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 );
Чтобы поиск по сходству работал должным образом, мне нужно было сгенерировать векторы слов для каждой отрасли учетной записи Salesforce:
Поскольку основной вариант использования указывал на необходимость найти сходства в индустрии программного обеспечения, нам также нужно будет создать вектор слов для этой отрасли.
Чтобы упростить это упражнение, я выполнил эту задачу вручную, используя Python 3.9 и файл embed.py
, который выглядит следующим образом:
from wikipedia2vec import Wikipedia2Vec wiki2vec = Wikipedia2Vec.load('enwiki_20180420_100d.pkl') print(wiki2vec.get_word_vector('software').tolist())
Обратите внимание: метод get_word_vector()
предполагает представление отрасли в нижнем регистре.
Запуск python embed.py
сгенерировал следующий вектор слов для 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]
Чтобы сохранить векторы слов, нам нужно было добавить таблицу industries
в базу данных Postgres, используя следующую команду SQL:
create table salesforce.industries ( name varchar not null constraint industries_pk primary key, embeddings vector(100) not null );
После создания таблицы industries
мы вставим каждый из сгенерированных векторов слов. Мы делаем это с помощью операторов SQL, подобных следующим:
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] ');
Обратите внимание: хотя мы создали вектор слов с строчными буквами названия отрасли программного обеспечения (программное обеспечение), столбец industries.name
должен соответствовать названию отрасли, написанному с заглавной буквы (программное обеспечение).
Как только все сгенерированные векторы слов будут добавлены в таблицу industries
, мы можем переключить внимание на внедрение RESTful API.
Это был момент, когда моя страсть как инженера-программиста резко возросла, потому что у меня было все для решения стоящей задачи.
Затем, используя Spring Boot 3.2.2 и Java (temurin) 17, я создал проект similarity-search-sfdc
в IntelliJ IDEA со следующими зависимостями Maven:
<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>
Я создал упрощенные сущности как для объекта «Учетная запись », так и для объекта « Промышленность » (встраивание), которые соответствовали таблицам базы данных Postgres, созданным ранее.
@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; }
Используя интерфейс JpaRepository, я добавил следующие расширения, обеспечивающие легкий доступ к таблицам 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> { }
Обратите внимание, что вся тяжелая работа по решению этого варианта использования будет выполняться в методе findSimilaritiesForIndustry()
. Метод будет принимать следующие параметры:
industry
: отрасль, в которой нужно найти сходствоlimit
: максимальное количество отраслевых сходств для поиска при запросе учетных записей.
Обратите внимание на оператор евклидова расстояния (<->) в нашем запросе выше. Это встроенный в расширение оператор поиска по сходству.
С исходным вариантом использования в отрасли «Программное обеспечение» и ограничением, установленным для трех ближайших отраслей, выполняемый запрос будет выглядеть следующим образом:
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;
На основе этого я создал класс AccountsService
для взаимодействия с репозиториями 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"); } } }
Наконец, я попросил класс AccountsController
предоставить точку входа RESTful и подключиться к 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); } } }
Когда сервис Spring Boot был готов, я добавил в проект следующий Procfile
, позволяющий Heroku узнать больше о нашем сервисе:
web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar
На всякий случай я добавил файл system.properties
, чтобы указать, какие версии Java и Maven ожидаются:
java.runtime.version=17 maven.version=3.9.5
Используя интерфейс командной строки Heroku, я добавил в свой репозиторий GitLab удаленный сервис для службы similarity-search-sfdc
на платформе Heroku:
heroku git:remote -a similarity-search-sfdc
Я также установил тип пакета сборки для службы similarity-search-sfdc
с помощью следующей команды:
heroku buildpacks:set https://github.com/heroku/heroku-buildpack-java
Наконец, я развернул сервис similarity-search-sfdc
в Heroku, используя следующую команду:
git push heroku
Теперь вкладка «Ресурсы» для приложения similarity-search-sfdc
появилась, как показано ниже:
При работающем RESTful API я ввел следующую команду cURL, чтобы найти три ведущие отрасли Salesforce (и связанные с ними учетные записи), наиболее близкие к индустрии программного обеспечения :
curl --location 'https://HEROKU-APP-ROOT-URL/accounts/similarities?industry=Software&limit=3'
RESTful API возвращает статус ответа 200 OK HTTP
вместе со следующей полезной нагрузкой:
[ { "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" } ]
В результате в этом примере отрасли технологий , средств массовой информации и электроники являются наиболее близкими к индустрии программного обеспечения .
Теперь у отдела маркетинга есть список аккаунтов, с которыми они могут связаться для проведения следующей кампании.
Несколько лет назад я провел больше времени, чем мне хотелось бы признать, играя в многопользовательскую видеоигру Team Fortress 2 . Вот скриншот с очень веселого мероприятия 2012 года:
Те, кто знаком с этим аспектом моей жизни, могут сказать вам, что по умолчанию я выбрал класс игрока — солдат. Это потому, что у солдата лучший баланс здоровья, движения, скорости и огневой мощи.
Я чувствую, что инженеры-программисты — это «солдатский класс» реального мира, потому что мы можем адаптироваться к любой ситуации и сосредоточиться на предоставлении решений, которые эффективно отвечают ожиданиям.
Вот уже несколько лет я сосредоточен на следующей формулировке миссии, которая, по моему мнению, может быть применима к любому ИТ-специалисту:
«Сосредоточьте свое время на предоставлении функций/функциональность, которые увеличивают ценность вашей интеллектуальной собственности. Используйте платформы, продукты и услуги для всего остального».
- Дж. Вестер
В примере для этой статьи мы смогли использовать Heroku Connect для синхронизации корпоративных данных с базой данных Postgres. После установки расширения pgvector мы создали векторы слов для каждой уникальной отрасли из этих учетных записей Salesforce. Наконец, мы представили службу Spring Boot, которая упростила процесс поиска учетных записей Salesforce, отрасль которых наиболее близка к другой отрасли.
Мы быстро решили этот вариант использования с помощью существующих технологий с открытым исходным кодом, добавления крошечного сервиса Spring Boot и Heroku PaaS – полностью следуя моей формулировке миссии. Я не могу себе представить, сколько времени потребовалось бы без этих фреймворков, продуктов и услуг.
Если вам интересно, вы можете найти исходный код этой статьи на GitLab:
https://gitlab.com/johnjvester/similarity-search-sfdc
Хорошего дня!