Не окей, гугл: как сделать поисковик для работы с служебными презентациями

Привет, Хабр! Это снова команда «МосТрансПроекта». Мы постоянно работаем с информацией и знаниями, которые храним в служебных презентациях. Чтобы ими было удобней пользоваться и извлекать данные, мы решили создать удобный сервис хранения документов с поиском. Задача оказалась непростой, и в этой статье мы расскажем, как её решили. Текст будет интересен всем, кто занимается структурированием данных, поисковыми машинами и ИИ.

b45137eb5739d3aca80ff69b222b9f7d.png

В вечном поиске

Сегодня Москва активно развивается, в том числе в области транспорта. Проектами развития транспортной инфраструктуры в том числе занимаемся мы —  МосТрансПроект. Все наши знания и данные чаще всего мы храним в служебных презентациях, которые используются на ежедневных совещаниях. Такие презентации являются главным форматом хранения и повторного использования данных, и, по сути, основным способом коммуникаций для нас. 

Документы часто нужны специалистам, чтобы получить последние данные по проекту или посмотреть статус принятых решений. Но отыскать конкретную презентацию порой непросто: она может быть в единственном экземпляре на компьютере у одного сотрудника. А если он уволился или заболел? Или компьютер сломался?

Чтобы такого не происходило, мы решили создать хранилище всех презентаций с поиском. Проще всего залить документы на корпоративный сервер, но такой вариант подходит для пользователей настольных компьютеров и ноутбуков. В реальной жизни чаще всего оперативный доступ к данным нужен через телефон. Тогда мы завели канал в корпоративном мессенджере, куда стали заливать все финальные презентации с кратким описанием и хэштегом для поиска. Но в этом случае не было поиска внутри самих презентаций, потому что индексировалось только название файла.

Тогда мы решили сделать свой поисковик на основе бота в служебном корпоративном мессенджере. Бот мы назвали «Информатум». Каждая презентация через бот загружается на внутренний сервер, раскладывается на тексты, с которыми затем работает поисковик. В ответ на запрос бот выдает презентацию в форматах PDF и PPTX, а также фото слайда и часть текста с информацией по запросу пользователя.

PPTX нужен для тех случаев, когда из презентации необходимо извлечь отдельный график/справочную информацию. PDF — когда нужно быстро скачать и прочитать всю презентацию (PPTX, как правило, тяжелый и не всегда корректно отображается в телефоне). Сейчас в хранилище загружено более 800 комплектов материалов, в том числе, относительно старых (созданных в 2019–2020 гг.), сохранившихся только в PDF.

Как все устроено

Теперь — про изнанку «Информатума». Загружаемыефайлы сохраняются в бакете облачного хранилища S3 minio, в то время как текст презентации парсится и индексируется в поисковой системе ElasticSearch (ES).

Полный процесс сохранения файла включает в себя 4 этапа:

6411e3cf4b467871a0626ae22798ff7c.png

Парсинг презентаций происходит по-разному в зависимости от формата файла.

Для файлов PPTX текст извлекается на основе форм, расположенных на слайде. Если форма является текстовой, текст извлекается непосредственно из нее. Следует отметить, что текст обычно извлекается в порядке размещения форм на слайде, но это не всегда соответствует логическому порядку. Например, заголовок может находиться ниже основного текста. В некоторых случаях формы могут быть объединены в группы, что требует предварительного разделения на первичные формы. В таких ситуациях невозможно гарантировать порядок извлечения текста.

427096189f5a9ec046178dba4ef46d9c.pnga6663e585cbfab1402058438774fab33.png

PDF-файлы состоят из страниц, каждая из которых может содержать текстовые блоки, изображения и другие графические элементы. Текст извлекается постранично в виде строк, однако порядок его извлечения может зависеть от расположения текста на странице. В отличие от формата PPTX, PDF-файлы не имеют четкой структуры, что может приводить к неожиданному порядку извлечения текста, следуя порядку наложения элементов на странице. Если текст распределен по странице (что актуально для презентаций), порядок его чтения может оказаться нелогичным и зависеть от последовательности сохранения элементов при создании документа, а также от их наложения друг на друга.

Текст документа в ElasticSearch хранится по слайдам для более удобного поиска. Пример структуры документа:

{
    "_id": "Уникальный идентификатор документа",
    "filename": "Имя файла",
    "author": "Автор документа",
    "date_added": "Дата добавления документа",
    "pages_pdf": [
        {
            "page_number": 1,
            "text": "Здесь хранится текст слайда 1"
        }
        // и т.д. для каждой страницы
    ],
    "pages_pptx": [
        {
            "page_number": 1,
            "text": "Здесь хранится текст слайда 1"
       }
    ]
}

Таким образом, мы рассматриваем материал или документ как уникальную единицу, описываемую файлами PDF и PPTX. Такая связь между файлами PDF и PPTX позволяет нам эффективно находить результаты запросов пользователя в документах, где PDF-документы сохранены в виде изображений (т.е. без текста).

Как устроен поиск

Для поиска взяли поисковую систему Elasticsearch. Это, по сути, база данных, в которую можно заливать данные и правильно их там искать. Мы понимали, что обычные базы данных с их «жестким» поиском — не самое оптимальное решение, ведь нам хотелось учитывать разные словоформы и возможные опечатки пользователей. Elasticsearch позволяет чуть более гибко искать нужную информацию. Нам нужно было как можно скорее выпустить первую версию продукта, чтобы собрать обратную связь от пользователей и понимать, куда развиваться дальше. Первая версия бота, которую мы сделали буквально на коленке за выходные, умела обрабатывать простейшие поисковые запросы. В последствие это все доводилось еще до ума.

Была очень сложная история с настройкой анализатора русского текста, чтобы он корректно все распознавал. В Elacticsearch есть библиотека русского языка, но она далека от совершенства.

Например, в русском языке символы «Ё» и «Е» часто используются взаимозаменяемо. В целях повышения точности поиска была произведена нормализация этих символов, что позволяет избежать ситуации, когда запросы, содержащие одну букву, не находят документы, содержащие другую букву. Данный фильтр применяется перед процессом токенизации текста, что обеспечивает более корректную обработку вводимых данных.

При настройке ElasticSearch было уделено особое внимание учету окончаний слов, что привело к внедрению стеммера:

"ru_stemmer": {
    "type": "stemmer",
    "language": "russian"
}

Использование стеммера для русского языка существенно улучшает полноту поиска, поскольку разные формы одного и того же слова (например, «бег», «бега», «бегал») приводятся к общему корню. Этот фильтр применяется к токенам текста после их разбиения на слова, что позволяет сократить количество уникальных токенов и повысить качество поиска.

Кроме того, был разработан анализатор текста:

"e_ru": {
    "tokenizer": "standard",
    "filter": [
        "lowercase",
        "ru_stemmer"
    ],
    "char_filter": ["e_char_filter"]
}

Данный анализатор разбивает текст на токены по пробелам и знакам препинания, а также приводит все токены к нижнему регистру для единообразия. Анализатор используется как при индексировании, так и при поиске, что способствует более эффективной обработке текстовых данных.

Другая проблема была связана с тем, что Elacticsearch изначально настроен только под отдельные взятые слова. А названия файлов презентаций, например, могут быть распознаны как единое слово, хотя там может быть два слова, разделенных нижним подчеркиванием. Нам нужно было сделать так, чтобы поиск умел искать в словах в названии файла. Сделали отдельный сервис внутри бота через команду /filename для поиска по названию файла. Учитывая, что названия файлов часто имеют формат «Название_файлаV15_(короткая).pdf», хранение таких данных в текстовом формате может быть бессмысленным. В связи с этим было принято решение использовать тип keyword для таких полей. Для полей данного типа применяется нормализатор, поскольку они не подлежат токенизации:

"lowercase_normalizer": {
    "type": "custom",
    "filter": ["lowercase"]
}

Применение нормализатора обеспечивает единообразие при сравнении значений, это важно для повышения качества поиска и обработки данных в системе.

Для поиска внутри документа мы используем два подхода: поиск по точному совпадению фразы и «примерный» поиск. Точный поиск ищет точное вхождение фразы, учитывая точный порядок слов хотя бы на одной странице документа. При примерном поиске перед обработкой слова разбиваются по токенам, отдельным элементам. В результате получается список слов, по которым будет осуществляться поиск. Для полученных токенов формируется условие поиска в виде словаря. В данном случае существует требование о наличии всех слов в тексте хотя бы на одной странице документа. Для этого создается множество условий, в рамках которых слова могут менять местами. Предполагается, что слова могут находиться на любом расстоянии друг от друга. Также применяется анализатор текста, который преобразует все слова в нижний регистр и отбрасывает окончания, что позволяет реализовать возможность поиска по различным формам слов.

Поиск осуществляется по всем документам, находящимся в индексе. В случае успешного совпадения формируется JSON-структура, содержащая информацию о документе и тексте внутри него. Каждая страница документа, а также весь документ в целом имеют параметр _score, который обозначает оценку совпадения полученного результата с запросом. Результаты сортируются по убыванию оценки, что позволяет выводить наиболее релевантные документы первыми.

В ElasticSearch есть параметр _score — представляющий собой оценку, основанную на алгоритме BM25. Этот алгоритм учитывает частоту встречаемости термина в документе, обратную частоту документа и длину документа для нормализации.

Функция возвращает результат выполнения запроса к клиенту, который включает найденные документы и соответствующую информацию о них. Поиск осуществляется отдельно и независимо по полям текста pptx и pdf, что позволяет получить более точную оценку. Затем результаты поиска по точному совпадению фразы и приблизительному совпадению с учетом форм слова и перестановок объединяются в один итоговый результат.

Такой подход снижает вероятность «потери» документа в случае, если PDF-файл был загружен в виде изображений без текстового слоя. Он также учитывает ситуации, когда текст в PDF-документе (который является неструктурированным и может содержать случайные символы) был распознан менее точно по сравнению с текстом из файлов формата PPTX.

Пример запроса с примерным поиском по документу:

must_conditions = []
# Добавляем условие для точного совпадения фразы 
must_conditions.append({
    "match_phrase": {
        f"pages_{pages_key}.text": text
    }
})
# поисковый запрос
query={
    "nested": {
        "path": f"pages_{pages_key}",
        "query": {
            "bool": {
                "must": must_conditions
            }
        },
        "inner_hits": {
            "highlight": {
                "fields": {
                    f"pages_{pages_key}.page_number": {},
                    f"pages_{pages_key}.text": {
                        "pre_tags": [""],
                        "post_tags": [""],
                    },
                },
            }
        }
    }
},
sort=[
    {"_score": {"order": "desc"}}
],
track_scores=True

Для поиска по названиям файла (которые являются типом keyword) используется другой подход. Запрос пользователя разбивается на слова с использованием пробелов и символов нижнего подчеркивания, в результате чего формируется список строк. Для каждого слова, кроме пустых, создается запрос типа wildcard, который указывает на то, что искомое слово должно присутствовать в строке названия файла.

Далее формируется основной запрос, содержащий логическую конструкцию, которая требует выполнения всех заданных условий. В результате выполнения функции возвращается результат запроса к системе Elasticsearch. 

Пример такого запроса:

# Создаем массив запросов для каждого слова с использованием wildcard
must_queries = [
    {
        "wildcard": {
            search_type: f"*{word}*"
        }
    }
    for word in words if word  # убираем пустые строки, если такие есть
]
# Формируем запрос
query = {
    "bool": {
        "must": must_queries
    }
}

Бот для прокачки

«Информатум» активно наполняется данными и пользуется популярностью в МосТрансПроекте. Количество пользователей растет ежемесячно.

Недавно, проведя глубинное интервью с пользователями бота, мы выяснили, что сотрудники ищут, как правило, не конкретную презентацию, а все документы и материалы по какой-то теме для погружения в контекст. Им нужно быстро понять, что происходит в конкретной области. Такая потребность возникает, в частности, у новых сотрудников. Выходит, что бот, который изначально задумывался как сугубо сервисная штука, стал решать  HR-задачи (ускоряя адаптацию новых сотрудников) и повышать эффективность работы всего Института. Уже подсчитано, что благодаря запуску «Информатума» с мая по декабрь 2024 года сотрудники сэкономили более 100 часов рабочего времени. Для подсчёта использовали особую методику: кому интересно — пишите в комментариях, расскажем.

Еще на старте проекта мы подумали про кибербез, поскольку в «Информатум» загружаются стратегически важные презентации с довольно чувствительной информацией. Сейчас мы контролируем, кто получает доступ к боту. Отслеживаем, кто уволился из Института, и отключаем их от бота. Это реализовано через единый сервис авторизации в корпоративных ботах МосТрансПроекта (ЕСАБ), про него расскажем в отдельной статье.

Человек за ботом

В 2025 году поиск в «Информатуме» будет работать на основе ИИ. Мы уже развернули в тестовом режиме локальную LLM нейросеть, которая ищет по внутренней базе знаний, где лежат статьи. Когда закончим тест, прикрутим ИИ-модуль к боту.

Что даст ИИ «Информатуму»?

Модели ИИ способны решать задачи, в которых есть много данных и нет четко поставленных правил. К этому еще добавляется нечеткий или иногда неясный запрос пользователя. По сути, такие модели в какой-то мере воспроизводят когнитивные способности человека. Например, при помощи нейронных сетей для векторизации и ранжирования мы можем эффективно вести семантический поиск по большому набору документов. Важное преимущество такого подхода — люди могут отправлять запросы с ошибками и это почти не повлияет на результат

Использование больших языковых моделей открыло новый этап в разработке ИИ-сервисов. Теперь мы можем решать сложные задачи по генерации текста, используя промты на естественном языке.

В «Информатуме» мы объединили семантический поиск и LLM для создания системы RAG (Retrieval Augmented Generation). Эта система по запросу пользователя ищет наиболее релевантные документы и затем при помощи LLM формирует из них ответ пользователю. Такая система дает следующие преимущества:

— Пользователям не надо будет ставить атрибуты и теги при загрузке презентаций: просто отправил документ в бот и забыл. ИИ, как мы думаем, решит проблему и так называемых «пустых» PDF, сохраненных в виде картинок: нейросеть сможет их распознавать и вынимать оттуда тексты. Проблема, слава богу, не масштабная: все новые презентации сохраняются сразу в двух форматах, PDF и PPTX, это обязательный стандарт Института.

— Система позволит делать краткую выжимку из загруженных презентации в 5–10 предложениях. Это актуально для объемных презентаций, которые некогда читать, при этом нужно быстро погрузиться в контекст.

— LLM сможет не просто обрабатывать простые запросы пользователя и исправлять опечатки, а отвечать на очень сильно свободно интерпретируемый запрос от пользователя и возвращать ответ с более сложной логикой. Например, вы спрашиваете какие планы по развитию метрополитена были с 2015 года, и информация на эту тему есть в презентации, где по какой-то причине нет слова «метро», но есть схемы с линиями. LLM должна понять и использовать это в выдаче.

— Будет доступен поиск по нечеткому запросу пользователя. Можно будет, к примеру, задавать вопросы в любой формулировке, не помня, что конкретно было в презентации.

На изображении ниже представлена схема работы RAG. Работает это так: мы предварительно обрабатываем все презентации и затем производим по ним семантический поиск. Мы хотим найти те презентации, в которых есть похожий контекст из запроса пользователя. Последний этап — формирование ответа из найденных презентаций на базе LLM:

46ea6045ca99d1c4a9615f4fefcdfa50.png

  Допустим «хочу знать все, что касается станции Липовая роща» или «какой пассажиропоток был на метро «Чистые пруды» в апреле 2023 года». Человек не обязан помнить точное название презентации и его содержание, но у него должна быть возможность найти все по этому ключу. Нейросеть будет собирать ответы из всех имеющихся данных, на которых она обучена составлять какие-то новые предложения.

— ИИ модуль можно будет научить понимать всякие технические аббревиатуры и сокращения (например ПС — подвижной состав, ПОДД — проект организации дорожного движения).

— ИИ сможете также ранжировать ответы по важности, группировать документы по темам

— Для улучшения безопасности хотим сделать систему отслеживания. Каждая скачанная презентация будет оставлять цифровой след. Возможно, что будем проставлять водяные знаки на документах, чтобы отследить путь презентации

— В будущем хотим делать подписку, которая позволит сотрудникам через пуши и письма отслеживать новые документы в «Информатуме» по конкретной теме, Уведомления будут приходить, если презентация была обновлена

— Думаем над созданием информационной экосистемы, объединяющей разнообразные текстовые материалы: от презентаций и статей до дайджестов и писем. Система будет включать интеллектуальный поиск и карту для удобного визуального представления данных с геопривязкой (про наш картографический сервис КИП ТК мы рассказывали в одной из прошлых статей). Это должно существенно облегчить работу командам, занимающимся транспортными решениями, позволяя мгновенно получать доступ ко всей необходимой информации по выбранному объекту. После этого хотим предложить расширить систему на весь транспортный комплекс, учитывая растущий интерес коллег к нашему проекту «Информатум».

У нас еще много разнообразных планов по развитию бота. Мы очень гордимся, что сделали его сами, бесплатно, не привлекая сторонних подрядчиков и не покупая никакой софт, силами небольшой команды.

Над проектом «Информатум» работали: Николай Кустов, Руслан Габдрахманов, Олеся Гуц, Дарья Архипова, Георгий Гаврилин, Ростислав Осленко.

Расскажите в комментариях, а вы работаете c данными в виде презентаций?

© Habrahabr.ru