AI-матчинг резюме и вакансий под ключ: парсинг любых форматов из Telegram и векторный поиск пар
HR-агентство собирает резюме и вакансии в Telegram вперемешку форматов. Собрал под ключ систему: ИИ читает текст, PDF, DOCX и сканы, а векторный поиск находит пары кандидат↔вакансия.
Задача
HR-агентство работает через Telegram: резюме кандидатов падают в группы вперемешку — текстом, PDF, DOCX, фотографиями и сканами; вакансии собираются из своих и партнёрских каналов. Чтобы предложить кандидата нанимателю, рекрутер вручную открывает каждое резюме, вытаскивает роль, грейд, стек и опыт и сопоставляет с открытыми вакансиями.
На потоке это не масштабируется по трём причинам: разнобой форматов (фото и скан резюме вообще не попадают в поиск), ручное сопоставление каждого резюме с каждой вакансией, и дубли из пересекающихся каналов. Это типичная боль рекрутингового агентства или внутреннего HR с входящим потоком резюме из мессенджеров и бирж, где первичный разбор и матчинг съедают рабочую неделю.
Решение
Я собрал под ключ конвейер, который проходит весь путь от сообщения в Telegram до готовой пары «кандидат — вакансия» в дашборде. Один ответственный за весь стек: сбор, парсинг, ИИ, матчинг, фронтенд, инфраструктура.
Сбор. Userbot на Telethon слушает каналы с резюме и каналы с вакансиями, складывает входящие со статусом RAW, проверяет кэш и отсекает дубли из пересекающихся источников ещё до обработки.
Парсинг любых форматов. Тип контента определяется автоматически: текст идёт в LLM напрямую, текстовый слой PDF снимается через pdfplumber, DOCX — своим парсером, а фотографии и сканы (где текстового слоя нет) распознаются OCR через LLM Vision. Дальше LLM (OpenAI / Gemini) приводит сырой текст к единой структуре — роль, грейд, стек, опыт. На этом же шаге резюме анонимизируется: прямые контакты и идентификаторы вычищаются. Это и privacy-by-design, и причина, почему на скриншотах ниже данные обезличены. Один пост может содержать несколько вакансий — парсер их разделяет.
Матчинг — векторный, не по ключевым словам. Из структурированных полей генерируется эмбеддинг (1536d) и хранится в pgvector внутри PostgreSQL. Пары ищутся по косинусному расстоянию с порогом (по умолчанию 0.75, настраивается в районе 60–70%); фильтр отсекает неактивные вакансии и вакансии без контактов. Вектор берёт смысл, а не буквы: «Python-разработчик» и «бэкендер на FastAPI» по ключевым словам не совпадут, по смыслу — близки.
Фон и реалтайм. Тяжёлая обработка вынесена в Celery + Redis, дашборд получает новые матчи по WebSocket без перезагрузки. Статусы идут RAW → PROCESSED; при ошибке — ERROR с сохранением исходного файла для разбора, а не молчаливая потеря.
Дашборд. На React 19 — лента матчей со score, карточки «кандидат | вакансия» рядом, быстрая модерация горячими клавишами (tinder-mode ← / →) и отправка нанимателю в один шаг.
Девопс. Контейнеризация Docker + nginx, CI/CD на GitLab, 304 pytest-теста (инфраструктура и БД, Telegram и API, парсинг и OCR, матчинг и вектор, end-to-end).


Дизайн — под ключ, без отдельного дизайнера
ТЗ по дизайну не было — интерфейс я спроектировал и собрал сам, с AI-ассистированием. Цельная тёплая палитра (не дефолтный серый), консистентная иерархия на всех экранах, продуктовые паттерны: карточка матча в две колонки со score слева, модерация ленты горячими клавишами, бейджи статусов, тёмная тема, гибкие настройки (порог матчинга, парсинг, юзербот, почта).
Честная рамка: это дизайн внутреннего инструмента — админки/дашборда, не маркетингового лендинга. Но в своём жанре он цельный и юзабельный, и отдельного дизайнера нанимать не пришлось — UI входит в «под ключ» наравне с бэком и инфраструктурой.

Стек
Python · FastAPI · Celery · Redis · PostgreSQL + pgvector · Telethon · OpenAI / Gemini (LLM · эмбеддинги · Vision OCR) · React 19 · TypeScript · Vite · Tailwind v4 · shadcn/ui · Docker · nginx · GitLab CI/CD · pytest (304 теста)
Результат
- Любой формат резюме — текст, PDF, DOCX, фото, скан — приводится к единой структуре и попадает в поиск; раньше скан-фото не искалось вообще.
- Матчинг по смыслу (вектор), а не по совпадению ключевых слов; порог настраивается под задачу.
- Реалтайм-лента с модерацией в один клик (tinder-mode); дубли из пересекающихся каналов отсекаются на входе.
- 304 теста, контейнеризация, CI/CD — production-уровень, а не прототип «на коленке».

Оценочно, по индустриальным нормам 2026 (SHRM, McKinsey, LinkedIn): ручной первичный разбор и сопоставление одного резюме — около 5 минут, пачка из 100 резюме — 12–18 часов, ручной throughput — 6–10 разборов в день. Конвейер сводит разбор к секундам на резюме, а человеку остаётся модерация уже готовой ленты пар.
Отправка нанимателю — через подтверждение человеком: пара уходит кандидату только после проверки (human-in-the-loop). Сбор заточен под Telegram-источники, под другой канал (почта, биржи) добавляется коннектор.