Кейс 4. Интеллектуальная навигация по событиям / таймлайнам
Цель кейса
Показать, как с помощью эмбеддингов и inference можно реализовать поиск по смыслу в ленте событий или базе знаний, не прибегая к обучению моделей, сложному NLP-пайплайну или хрупким эвристикам. Кейс демонстрирует, как трансформеры в PHP превращаются из "ML‑экзотики" в обычный инженерный компонент.
Сценарий
Предположим, у нас есть лента событий или материалов, где каждое событие описано несколькими предложениями. Например:
"введены новые ограничения в отношении технологических корпораций"
"страны региона наращивают инвестиции в спутниковые программы"
"обострение конфликта на политической почве в нескольких провинциях"
Пользователь вводит запрос:
"космическая гонка среди стран региона"
Очевидная проблема: ни одно из слов запроса буквально не обязано встречаться в описании событий. Классический поиск по словам здесь быстро превращается в набор костылей: синонимы, морфология, разные языки, ручные словари и бесконечные исключения.
И это не ошибка данных. Это нормальное поведение человека – формулировать мысли иначе, чем их описывает система.
Идея решения
Вместо попытки угадать слова мы будем искать по смыслу. Не в философском, а в сугубо инженерном смысле:
описание события → вектор
запрос пользователя → вектор
дальше – обычный поиск ближайших значений
Эмбеддинги здесь выступают как универсальный индекс смысла. Мы переводим текст в геометрию, а дальше работаем уже с расстояниями.
Алгоритм выглядит так:
Берём массив событий
{id, title, description}Считаем эмбеддинг только по
description, так как заголовки часто слишком короткие и добавляют шумЭмбеддим пользовательский запрос
Считаем близость запроса к каждому событию
Сортируем и возвращаем
top‑Nрезультатов
Всё это - без обучения моделей и без сложной инфраструктуры.
Логика работы
Вынесем всю механику в отдельный класс SemanticEventSearch. Этот класс не претендует на идеальную архитектуру и используется исключительно в демонстрационных целях, поэтому мы сознательно опускаем вопросы абстракций и оптимизаций.
Класс SemanticEventSearch:
Снаружи мы задаём:
список событий через
setEvents()модель эмбеддингов через
setModel()текст запроса через
setQuery()количество результатов
topNчерез конструктор
Дальше вызываем run() и получаем отсортированный результат.
Детали реализации
При первом запуске поднимается embedder:
При этом используется каталог .transformers-cache. При первом обращении модель, токенизатор и веса скачиваются и сохраняются локально. Все последующие запуски используют кэш, что делает работу существенно быстрее и предсказуемее.
Для самих событий используется локальный файл embeddings.events.json. Это простой кэш эмбеддингов:
при запуске мы пытаемся его прочитать
проверяем, совпадает ли модель в кэше с текущей
если для каких-то событий эмбеддингов нет, считаем их и сохраняем обратно
Таким образом эмбеддинги событий считаются один раз и переиспользуются.
Поиск
После этого логика становится тривиальной:
эмбеддим запрос пользователя
нормализуем вектор (чтобы косинусная близость была стабильной)
считаем cosine similarity с каждым событием
сортируем по убыванию
берём
top‑N
Рендер результатов (render(query, results)) намеренно вынесен наружу. Класс поиска возвращает только данные, а не HTML или текстовый вывод.
Пример использования
Список событий:
Запускаем код:
Результат:
Мы получили совпадение не по словам, а по смыслу. Самое релевантное событие оказалось первым, хотя формулировки запроса и описания различаются.
Диаграмма пайплайна семантического поиска
Чтобы зафиксировать архитектуру решения целиком, полезно посмотреть на неё не через код, а через поток данных. В этом кейсе пайплайн предельно простой и именно поэтому хорошо масштабируется.

Диаграмма подчёркивает два ключевых момента:
Во‑первых, эмбеддинги событий считаются заранее и переиспользуются. Это превращает ML‑часть из дорогой операции в обычный кэшируемый ресурс.
Во‑вторых, запрос пользователя проходит тот же самый путь преобразования, что и данные. Благодаря этому мы сравниваем не слова, а представления в одном и том же семантическом пространстве.
Где это применять в продакшене
В целом это уже похоже на продуктовый подход, а не на эксперимент. К тому же вы можете легко заметить, что такой подход:
слабо привязан к конкретной формулировке запроса
хорошо работает на коротких описаниях
легко комбинируется с обычными фильтрами (дата, регион, тип события)
Ограничения и подводные камни
Важно не воспринимать семантический поиск как серебряную пулю.
Модели весят немало, и производительность нужно учитывать. Для некоторых задач обычный SQL с LIKE или полнотекстовый индекс будет проще и надёжнее. Но это уже нормальный инженерный разговор про trade‑off'ы, а не про магию и чёрные ящики.
Именно в таком виде трансформеры и эмбеддинги перестают быть "AI ради AI" и становятся частью поддерживаемой, объяснимой системы.
Чтобы самостоятельно протестировать этот код, установите примеры из официального репозитория GitHub или воспользуйтесь онлайн-демонстрацией для его запуска.
Last updated