человек общается с ИИ
человек общается с ИИ

Привет, Хабр! Меня зовут Гурциев Ричард, я магистрант 1-го курса AI Talent Hub. За первый семестр я с головой погрузился в крутой проект, цель которого — сделать этап трудоустройства проще и удобнее как для работодателей, так и для кандидатов. В этой статье я хочу поделиться своим опытом работы над проектом Advisor?

Перед тем как углубиться в этапы реализации проекта, следует ввести в курс дела.

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


Содержание:

  1. Данные

  2. RAG

  3. Deploy

Данные

Когда встал вопрос о том, где искать данные, первым на ум пришел HeadHunter (HH) — одна из самых известных и популярных платформ для работы с вакансиями и резюме. Изучив возможности площадки, оказалось, что у HH есть официальный API для получения данных о вакансиях! Но с резюме всё оказалось не так просто: чтобы их собрать, пришлось разработать парсер, так как URL-адрес для резюме был неочевиден. Написав скрипт, который успешно отправлял запросы и извлекал информацию, необходимо было приступать к следующему шагу — структурированию данных.

пример резюме кандидата
пример резюме кандидата

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

Подобная информация увеличивает объём текста, усложняя процесс анализа и отвлекая от ключевых факторов, таких как опыт работы, навыки и достижения. Поэтому ключевым моментом стал выбор полей. Такие параметры, как название вакансии/резюме, опыт работы, ключевые навыки, образование, языки, тип занятости и график работы, стали основой для построения базы знаний.

Определившись с основным контентом необходимо было привести данные к единой структуре. Очевидно, что обрабатывать такие данные вручную не очень хочется. Решением стало внедрение автоматизированной системы, способной быстро и точно анализировать данные. В её основе лежит фреймворк vLLM и модель Qwen/Qwen2.5-7B-Instruct, которая принимая инструкции, довольно неплохо справляется с обработкой естественного языка.

*Инструкция — это текстовый запрос, который передается модели, чтобы получить от нее ответ или выполнить задачу.

Таким образом, собранные резюме/вакансии принимают суммаризированный вид по тем основным полям, которые ранее были указаны.

результат обработки собранных данных
результат обработки собранных данных

После того как данные были собраны и обработаны, следующий ключевой шаг — это их хранение. И тут в игру вступает Qdrant — мощная векторная база данных, которая идеально подходит для хранения данных. Qdrant был выбран по нескольким причинам: он интуитивно понятен в использовании, и его можно легко развернуть в контейнерах. Это обеспечит гибкость и стабильность, необходимое для работы с данных. О процессах, связанных с Qdrant, мы подробно поговорим в блоке RAG.

RAG

Что такое RAG ? Если кратко, то RAG (Retrieval-Augmented Generation) — это форма общения с моделью, при котором ответ на запрос формируется языковой моделью, используя данные из внешнего хранилища, например, Qdrant, Milvus и прочее. Важно, что эти данные извлекаются на основе векторной схожести, что позволяет повысить точность и релевантность сгенерированного ответа. Более подробно можно ознакомиться тут.

Выбор и настройка ретривера (retriever)

Создание RAG начинается с выбора и настройки retriever — компонента, который отвечает за извлечение информации из базы данных.

Для извлечения релевантной информации необходимо было подобрать подходящий алгоритм, который обеспечил бы эффективное извлечение информации из базы, поэтому для поиска подходящего алгоритма было решено взять несколько моделей и рассмотреть их как отдельно, так и совместно. В качестве плотных векторов были выбраны: deepvk/USER-bge-m3, ruRoPEBert-e5-base-2k, rubert-tiny2, multilingual-e5-large и all-MiniLM-L6-v2. Для формирования разряженных векторов был взят bm25.

Проведённые эксперименты для оценки top@10 points показали, что среди алгоритмов поиска, использующих только один вид embeddings, лучшим оказался deepvk/USER-bge-m3. Результаты представлены ниже.

метрика c одним видом эмбеддингов
метрики c одним видом эмбеддингов

Однако хотелось рассмотреть и другие подходы, которые могли повысить полученные метрики.

Fusion Vs Matryoshka

Существует два основных подхода к созданию гибридной поисковой системы: "слияние" и метод "матрёшки". Первый подход предполагает комбинирование результатов, полученных различными методами поиска, исключительно на основе их оценок. Для этого обычно требуется нормализация, так как значения оценок разных методов могут значительно отличаться по диапазону. После нормализации используются показатели релевантности для вычисления итогового балла, который определяет порядок документов. Qdrant имеет встроенную поддержку метода взаимного ранжирования, который является стандартом де-факто в этой области.

взаимное ранговое слияние (изображение было взято отсюда)
взаимное ранговое слияние (изображение было взято отсюда)

Метрики, рассчитанные с использованием метода Fusion только ухудшили результат.

метрики c методом Fusion
метрики c методом Fusion

Ну что ж, попробуем применить иной подход)

Механизм поиска может выполнять роль механизма повторного ранжирования. Например, можно предварительно отобрать результаты с использованием разреженных векторов, а затем повторно ранжировать их с помощью плотных векторов. Если у вас есть несколько embeddings, то, по принципу матрёшки, можно начать с отбора кандидатов с плотными векторами наименьшей размерности и постепенно уменьшать их количество, повторно ранжируя с помощью более сложных многомерных векторов. Ансамбль таких подходов позволит комбинировать слияние и повторное ранжирование для достижения наилучших результатов.

ранжирование гибридного ансамбля (изображение было взято отсюда)
ранжирование гибридного ансамбля (изображение было взято отсюда)

Остановимся на ансамбле, который объединяет результаты матрёшечных вложений, плотных и разреженных векторов. Для данного алгоритма были выбраны модели на основе результатов 1-го исследования: deepvk/USER-bge-m3, Tochka-AI/ruRoPEBert-e5-base-2k и bm25.

ранжирование методом матрёшки
ранжирование методом матрёшки

Удалось выбить следующие результаты:

ndcg

precision@10

map@10

recall@10

mrr@10

dcg@10

0.991

1.0

0.77

0.77

1.0

4.54

Сравнивая результаты 1-го и 3-го эксперимента стало ясно, что использование метода матрёшки улучшило показатели поиска для MAP@10 и Recall@10, однако привело к незначительному снижению NDCG.

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

Мультиагентная система

После выбора алгоритма поиска мы приступаем к следующему этапу системы RAG — мультиагентным системам.

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

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

графовая сеть
графовая сеть

Графовая сеть начинается с узла classifier, который выполняет классификацию входного текста. Если текст не содержит информации о кандидате или вакансии, то маркой other текст классифицируется и срабатывает узел no_info, возвращая пользователю ответ: "Извините, по вашему запросу нет информации". Если текст не классифицируется как other, то в этом случается срабатывает узел extract, который извлекает из текста сущности для создания суммаризированного резюме или вакансии. Далее узел retriever на основе полученного текста выполняет ранжирование релевантных резюме или вакансий из векторной базы данных, а после узел answer оформляет эти данные в нужном формате и возвращает их пользователю. Как именно пользователь их получает станет ясно в блоке Deploy.

Deploy

Архитектура проекта
Архитектура проекта

Кратко обсудим клиентскую часть, которая состоит из backend и frontend секций. Разберем их поочередно.

Backend

Бэкенд был реализован с использованием FastAPI. С помощью этого фреймворка были созданы два endpoint: POST-запрос для отправки файла с резюме или вакансией, а также GET-запрос для получения всех отобранных кандидатов со стороны RAG.

Frontend

UI проекта
UI проекта
резюме на позицию frontend-разработчик
резюме на позицию frontend-разработчик

UI был разработан с использованием фреймворка Angular. В приложении предусмотрен выбор категории (резюме/вакансия), деятельности (например, frontend, backend и devops) и загрузка файлов формата (.txt, .pdf, .docs). После отправки запроса пользователь через некоторое время получает ранжированный список релевантных вакансий, которые требуют особого внимания кандидата.

Заключение

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

Проект Advisor будет в дальнейшем модифицироваться. Планируется попробовать использовать bert-like модели и обучить их на задачу NER, поскольку это на порядок повысит скорость ответа со стороны RAG системы. Также стоит обратить внимание на vLLM, которая, судя по этой статье, способна увеличить скорость обработки данных.

По поводу результата... Думаю, что стоит уделить внимание обработке данных и попробовать переранжировать результат RAG-системы, учитывая дополнительный вес поля с малым контентом.

Жду ваши вопросы и замечания!

To be discussed

Что следовало бы доработать или заменить? В чем причина?

Комментарии (10)


  1. sshikov
    19.01.2025 10:09

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

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

    И ровно тоже самое можно сказать про все остальные ключевые слова, и вообще про все, что в резюме написано. Потому что автор резюме считает, что пару лет поработал с SQL, и достаточно опытен, а мне, как нанимающему, нужно знание трех разных СУБД, и достаточно глубокое, и я резонно считаю, что такой опыт можно получить минимум за 5-10 лет. И это не значит, что кандидат плох - просто у нас с ним разный взгляд на оценку его опыта и знаний, и для выравнивания всего этого как раз нужно интервью, т.е. беседа двух естественных интеллектов.


    1. r1char9 Автор
      19.01.2025 10:09

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


      1. sshikov
        19.01.2025 10:09

        Не, я понимаю, что это в конце концов прототип, и все такое. Просто вот эта проблема, которую я попытался озвучить - она самая сложно формализуемая. Причем с обоих сторон - и когда кандидат вакансии подбирает, и наоборот, когда HR подбирает кандидатов. Т.е. все навыки - они ко всему прочему еще и взаимосвязаны, и если у кандидата, условно, написано React, это в 99% случаев означает, что он таки знает javascript и html, но возможно не наоборот. Т.е. набор навыков зачастую можно расширить, включив подразумеваемые.


        1. shtek
          19.01.2025 10:09

          У того же hh появился уровень подтверждения навыков, для того же SQL или git, python и тд. Можно это учитывать в проекте. Что не полностью, но частично закроет ваш вопрос. Можно добавить что-то, по аналогии с Class Weight. Хотя как тут его прикрутить, сходу не могу придумать.


          1. r1char9 Автор
            19.01.2025 10:09

            Спасибо за новодку, обязательно посмотрю.


          1. sshikov
            19.01.2025 10:09

            На мой взгляд - ну очень частично. Просто масштабы разных навыков ну очень уж разные.


      1. larmirs
        19.01.2025 10:09

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


  1. ciiz
    19.01.2025 10:09

    Хотел тоже поиграться с данными на HH, так и не понял как посмотреть резюме?
    Регистрироваться как работодатель?


    1. r1char9 Автор
      19.01.2025 10:09

      Думаю, это не имеет значения, поскольку резюме может посмотреть и кандидат. Для получения резюме я отдельно парсил страницы и выделял нужный текст с помощью BeautifulSoup. Перед этим создавал "пользователя" с использованием библиотеки fake_useragent.


      1. ciiz
        19.01.2025 10:09

        я хотел посмотреть резюме других кандидатов, что то типа хотел глянуть предложения по зарплатам и ожиданиями соискателей.