В данном примере разберем, как пользоваться API поиска в известном поисковом сервисе Algolia.
Идея
Идея была предельно проста, необходимо было сделать бот, который при введении имени препарата находит информацию о том, через сколько можно употреблять алкоголь. Сразу скажу, что бот был запилен и успешно работает, помогая (я надеюсь) сохранить здоровье граждан. Если что, то Алкобот доктора Знаева тут
Задача
На входе имеем имя препарата, по нему хотим найти релевантную запись из базы данных и отправить пользователю. В принципе всё довольно просто, никаких ракетных технологий. Но проблема в том, что пользователи очень часто опечатываются или вводят название неправильно. Для решения этой задачи лучше всего подходил поисковый движок с fuzzy поиском. Решено было воспользоваться Algolia, так как в неё удобно добавлять данные и она предоставляет удобный довольно простой API для использования.
Вот кусочек данных в формате JSON
{
"drugs": [ // по этому полю будем искать
"Допегит",
"Метилдопа"
],
"threashold": "12 часов", // это показываем пользователю
"consequences": "Усиливает снижение давления", // и это показываем
"objectID": "ff26ba646e8ba_dashboard_generated_id" // сгенерированный Algolia айди
}
Данные
Данные собирали долго и мучительно. Профессиональный нарколог несколько недель собирал информацию с РЛС (Регистр Лекарственных Средств) и отправлял ее мне.
Индекс
Теперь необходимо создать проект и индекс. Тут всё довольно просто, нажимаем Create New, выбираем бесплатную версию (она допускает до 10 тыс запросов в месяц, больше чем достаточно). Далее выбираем дата центр. Algolia переместила свои датацентры из России, но мои лежат в Европе, пока проблем не было. Далее нажимаем все нужные галочки и нажимаем Создать
Вам сразу предложат создать первый индекс, что конечно же надо сделать. Не думаю, что есть смысл загружать каждый шаг, там в общем-то всё очень просто. Далее надо выбрать как будем вносить данные, я выбрал ручной способ.
Далее необходимо взять наши JSON объекты и вставить их во всплывающее окно.
При создании объектов таким образом, каждая запись получит id в формате ff26ba646e8ba_dashboard_generated_id. Выглядит так себе, но жить можно.
Теперь индекс надо настроить, что бы поиск происходил не по всем данным, а только по массиву drugs
Для этого в интерфейсе индекса идём во вкладку Configuration и добавляем поле для индексации (кнопка Add searchable Attribute)
Код
Что бы получить доступ к индексу нам понадобятся id нашего приложения и ключи. Я использую ключ администратора, так как иногда пишу в базу. Идём в Settings -> Api Keys, там лежат наши ключи.
Теперь перейдём к коду. Сервер крутиться на node.js, примеры будут на нём же.
Итак, импортируем библиотеку и инициализируем индекс
const algoliasearch = require('algoliasearch');
const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_ADMIN_API_KEY);
const alco = client.initIndex('alco'); // alco называется мой индекс в Algolia
Поиск
По названию препарата ищем запись
exports.findDrug = async (drug, user = {}) => {
return alco.search(drug, { userToken: `${user.id}`, typoTolerance: 'strict', });
}
Эта функция вернёт объект в котором есть поле hits, это массив с результатами поиска. У Algolia отличная документация с примерами кода. Вот ссылка на нашу search функцию
Тут у нас есть три варианта
Ничего не найдено. Показываем сообщение, что ничего не найдено.
Найден один вариант. Берем единственный вариант, вынимаем нужные значения и формируем ответ пользователю.
Найдено несколько вариантов. Чуть сложней. Я не парюсь, беру первые два совпадения и выдергиваю из них названия и айдишник записи и формирую из них кнопку
if (hits.length > 1) {
const ret = [];
const firstMatches = hits.splice(0, Math.min(2, hits.length));
const matches = firstMatches.map(({ objectID, _highlightResult }) => ({ objectID, match: utils.fetchDrugNameFromAliases(_highlightResult.drugs) }));
const buttons = matches.map(({ objectID, match }) => ([{ text: match, callback_data: `/match&${objectID}` }]));
options.reply_markup = JSON.stringify({ inline_keyboard: buttons });
return [FEW_RESULTS_FOUND, options];
}
_highlightResult содержит массив drugs, потому что в индексе это поле так же массив, у каждой записи будет степень совпадения, по ней и фильтруем
none
(0)partial
(some)full
(all)
Результат выглядит у пользователя вот так :
Поиск по ID
В каждой кнопке зашит id записи и при нажатии надо вытянуть именно её.
exports.find = async (drugId) => {
return alco.getObject(drugId);
}
Итого
Вот таким нехитрым способом мы интегрировали высококлассный поиск в наше скромное приложение. Не пришлось поднимать никаких баз данных, кластеров или прочего. Вообще Algolia очень мощная штука, у них там и AI встроен для рекомендаций и вообще всё кашерно.
Всем спасибо. Ваш Ö.