MAATRIX / Блог / HR-отдел: ИИ для первичного отбора резюме на своём сервере

HR-отдел: ИИ для первичного отбора резюме на своём сервере

HR-отдел: ИИ для первичного отбора резюме на своём сервере

MAATRIX

Когда на вакансию приходит 200-300 резюме, а закрывать позицию нужно за две недели, рекрутер физически не успевает вдумчиво прочитать каждое. В ход идёт беглый просмотр по диагонали, и часть подходящих кандидатов отсеивается просто потому, что до их резюме дело дошло поздно вечером в пятницу. Соблазн загрузить весь пул резюме в ChatGPT велик — но резюме это персональные данные: ФИО, телефон, адрес, иногда паспортные сведения в приложенных документах. Отправлять их в чужое облако — риск и с точки зрения 152-ФЗ, и с точки зрения банальной утечки. Ниже — рабочая схема, как поднять ИИ-помощника для первичного отбора резюме на собственном сервере, где данные кандидатов не покидают периметр компании.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Почему это вообще стоит делать на своём сервере, а не в облачном сервисе

Внешние ATS с «ИИ-скорингом» и облачные чат-боты решают задачу быстро, но за это приходится платить доступом третьей стороны к данным кандидатов. У большинства HR-инструментов на рынке серверы физически находятся за пределами России, а обработка персональных данных граждан РФ по 152-ФЗ должна вестись с использованием баз, расположенных на территории России, либо у оператора должны быть соблюдены требования локализации. Это отдельная больная тема — разбор про то, где законно держать сервер с персональными данными.

Локальный вариант снимает часть этих вопросов сразу: резюме загружаются в базу на вашем сервере, обработка идёт локальной моделью, наружу не уходит ничего. Плюс это дешевле на больших объёмах — вы не платите за токены облачного API за каждое резюме, а платите один раз за сервер.

Минус тоже есть, и его нужно проговорить честно: локальная модель по качеству понимания текста чаще всего уступает топовым облачным (GPT-4-класса и выше), особенно на нестандартных резюме — с нестандартной структурой, на смеси языков, со странным форматированием. Для задачи «отфильтровать явно нерелевантные резюме и составить короткую сводку по релевантным» этого обычно достаточно. Для тонких оценочных суждений о личности кандидата — нет, и не должно быть, об этом ниже отдельно.

Архитектура: что разворачивать и зачем

Рабочий стек для этой задачи — три компонента:

  • Сервер — VPS или выделенный сервер с достаточным объёмом RAM (не GPU в первую очередь, объясню ниже).
  • AnythingLLM — open-source платформа для RAG (retrieval-augmented generation): она хранит документы в векторной базе, находит релевантные фрагменты по запросу и передаёт их локальной или облачной LLM для формирования ответа. Есть встроенный векторный стор (LanceDB), UI для загрузки документов и разделение на рабочие пространства (workspaces).
  • Локальная LLM — например, через Ollama: модели уровня Qwen2.5 14B или Llama 3.1 8B справляются с задачей «прочитай резюме, сравни с требованиями, дай короткую сводку» вполне прилично. Точные цифры по скорости и качеству у вас будут зависеть от модели и железа — не берите на веру чужие бенчмарки, тестируйте на своих реальных резюме перед тем как полагаться на систему.

Почему не обязательно GPU: для одного HR-отдела, который обрабатывает десятки-сотни резюме в день, а не тысячи одновременных запросов, инференс на CPU с достаточным объёмом RAM (32-64 ГБ) вполне тянет модели 7-14B с приемлемой скоростью ответа — счёт на секунды, а не на доли секунды, что для этой задачи не критично. Если объём вырастет на порядок, тогда есть смысл смотреть в сторону GPU.

Базовая установка на Ubuntu 24.04 через Docker:

mkdir -p /opt/anythingllm/storage
cd /opt/anythingllm

docker run -d -p 3001:3001 \
  --name anythingllm \
  --restart unless-stopped \
  -v /opt/anythingllm/storage:/app/server/storage \
  -e STORAGE_DIR="/app/server/storage" \
  mintplexlabs/anythingllm

Ollama на том же сервере:

curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:14b
ollama pull nomic-embed-text

nomic-embed-text — модель эмбеддингов, она превращает текст резюме в вектор для поиска. Отдельная от «читающей» модели — это нормально и даже правильно: эмбеддинги можно взять компактные и быстрые, а на генерацию сводок пустить более мощную модель.

В AnythingLLM в настройках LLM Provider указываете Ollama и адрес http://host.docker.internal:11434 (или IP сервера, если Ollama не в докере), в Embedding Provider — тоже Ollama с моделью nomic-embed-text. Подробный пошаговый разбор установки и частых проблем на этом шаге есть в статье про установку AnythingLLM на VPS — там же таблица по требованиям к RAM под разные объёмы документов, если сомневаетесь в конфигурации сервера, загляните в материал сколько RAM нужно для AnythingLLM.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть AnythingLLM

Загрузка резюме и структура базы

Первое практическое решение — как организовать workspaces. Вариант, который хорошо работает на практике: один workspace на одну открытую вакансию, а не общая свалка всех резюме компании. Причины:

  1. Требования к позиции «Backend-разработчик» и «Менеджер по продажам» не пересекаются, и смешивать их в одном поиске только шумит.
  2. Так проще выгрузить или удалить резюме после закрытия вакансии — что важно с точки зрения хранения персональных данных: их не нужно хранить дольше, чем есть законное основание.

Резюме приходят в разных форматах — PDF, DOCX, иногда просто текст письма. AnythingLLM умеет парсить PDF и DOCX «из коробки» через встроенные загрузчики документов, вручную конвертировать не нужно. Загрузка — через UI (drag-and-drop в workspace) или через API, если резюме приходят автоматически с почты или из формы на сайте:

curl -X POST http://localhost:3001/api/v1/document/upload \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@/path/to/resume_ivanov.pdf"

После загрузки документ нужно явно embed-нуть в workspace (либо это происходит автоматически при загрузке через UI, в зависимости от настройки Auto-Embed). Если после загрузки поиск не находит резюме — почти всегда причина именно в том, что документ загружен в общее хранилище, но не привязан (embedded) к конкретному workspace; это одна из самых частых ошибок на этом шаге.

Практический момент по именованию: если в системе будут сотни файлов вида resume.pdf, потеряться легко. Простое соглашение — переименовывать при загрузке в формат Фамилия_Имя_вакансия_дата.pdf — экономит часы поиска потом.

Поиск кандидатов по требованиям вакансии

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

Найди среди загруженных резюме кандидатов с опытом работы
с PostgreSQL от 3 лет и знанием Python. Перечисли имена файлов
и кратко укажи, где именно в резюме это упомянуто.

RAG-механизм находит в базе фрагменты резюме, семантически близкие к запросу (не только по точному совпадению слов — «PostgreSQL» найдёт и упоминания «работа с реляционными БД, в частности Postgres»), и модель формирует ответ со ссылками на конкретные документы.

Важный нюанс, который стоит держать в голове: семантический поиск — это не точный SQL-запрос. Он может пропустить кандидата, если тот описал релевантный опыт нетипичной формулировкой, и может ошибочно зацепить кандидата, у которого нужное слово встретилось в другом контексте. Поэтому первичный отбор через RAG — это сужение потока «с 300 резюме до 40 подходящих под грубые критерии», а не финальный список для собеседований.

Хорошая практика — формулировать в запросе не один критерий, а разбить требования вакансии на 3-5 обязательных пунктов и явно попросить модель отметить, какие из них не подтверждены в тексте резюме, а не просто выдать список «подходит/не подходит».

Короткая сводка по каждому кандидату

Это самая ценная часть для рекрутера — не бинарный вердикт, а компактная выжимка, которую можно прочитать за 20 секунд вместо 3 минут на чтение полного резюме. Шаблон промпта, который стоит зафиксировать как системный для workspace (в настройках workspace есть поле System Prompt):

Для каждого запрошенного резюме сформируй сводку строго по структуре:
- Имя файла резюме
- Ключевой опыт (2-3 пункта, только то, что явно написано в резюме)
- Совпадения с требованиями вакансии (по пунктам)
- Что не указано или не подтверждено в резюме
- Не делай вывод "рекомендую/не рекомендую" — только факты для решения человеком

Последняя строка тут не формальность, а сознательное решение. Дальше — почему.

Где ИИ должен остановиться: решение всегда за человеком

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

  • Модель не видит контекст, которого нет в тексте резюме. Формат резюме, стиль изложения, оформление — не показатель квалификации, но модель может неявно им придать вес просто потому, что «хорошо оформленные» резюме статистически чаще встречались в её обучающих данных рядом с положительными оценками.
  • Риск предвзятости при автоматическом отборе без контроля — реальный и задокументированный. Модели, обучавшиеся на реальных данных о найме, склонны воспроизводить исторические паттерны — в том числе неявные gender-, age- и name-based смещения, которые были в этих данных. Автоматический отсев по такому скорингу без проверки человеком — это не гипотетический, а вполне реализовавшийся на практике риск (нашумевший случай с внутренним HR-инструментом Amazon в своё время — как раз про это: модель обучилась на исторических решениях и начала занижать оценку резюме, где встречались маркеры женского пола).
  • Юридическая ответственность за решение об отказе кандидату лежит на компании, а не на алгоритме. Если отказ можно будет трактовать как дискриминационный, ссылка на «так решил ИИ» не снимает ответственности.

Практический вывод простой: система должна выдавать сводки и подсказки для сужения потока, но не должна автоматически отправлять отказы или автоматически двигать кандидата дальше по воронке без просмотра человеком. В AnythingLLM это естественным образом реализуется тем, что вывод модели — это текст в чате для рекрутера, а не действие в ATS; но если вы дальше интегрируете это через API в свою HR-систему — не автоматизируйте сам факт отказа, только этап «показать рекрутеру сокращённый список».

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть AnythingLLM

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Останутся ли резюме кандидатов доступны только внутри компании?

Да, если сервер настроен без внешнего публичного доступа к API AnythingLLM (закрыт firewall'ом или доступен только через VPN). Обязательно ограничьте порт 3001 — по умолчанию AnythingLLM не требует авторизации для API, если явно не включить multi-user режим с паролями. Как закрыть доступ посторонним — в статье как защитить локальную LLM от посторонних.

Можно ли использовать облачную модель (например, через API) вместо локальной, если данные всё равно шифруются в пути?

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

Сколько резюме может обработать один сервер?

Зависит от объёма RAM, размера модели и того, сколько запросов идёт параллельно. Для одного-двух рекрутеров, работающих не одновременно, конфигурации с 32-64 ГБ RAM обычно достаточно для моделей 7-14B. Точные цифры для вашего объёма стоит проверить тестовым прогоном перед тем, как полагаться на систему в бою.

Нужно ли удалять резюме после закрытия вакансии?

С точки зрения работы с персональными данными — да, хранить резюме отказавшихся кандидатов бессрочно без согласия не стоит. В AnythingLLM документ и его эмбеддинги удаляются через UI workspace или API — учитывайте это в регламенте HR-отдела.

Что делать, если модель путает кандидатов или искажает факты из резюме?

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

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.