Привет! Меня зовут Георгий Каляпин. Когда я начинал работать разработчиком, мне приходили разные маленькие заказы, а потом я стал искать их сам в чатах с фрилансерами. Проблема была в том, что чаты приходилось мониторить постоянно и в них встречалось много нецелевых вакансий.

Поэтому я решил создать чат-бот RemoteHunt — помощника в поиске фриланса. Он 24/7 просматривает тематические каналы и чаты, после чего сегментирует вакансии на категории и отправляет пользователю. Изначально бот задумывался как пет-проект, но в процессе разработки перерос в нечто большее.

В этой статье я расскажу о принципе работы чат-бота и трудностях, с которыми встретился. Не всё получилось идеально с первого раза, поэтому какие-то моменты буду исправлять или улучшать. С похожими задачами я встречался в рамках курса «Мидл Python-разработчик» в Практикуме, но я не хотел копировать готовые решения.

Принцип работы

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

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

RabbitMQ — это брокер сообщений, очередь. Все сообщения сначала прилетают туда, а потом отправляются в ChatGPT. Он слушает очередь и сортирует вакансии по категориям, после чего отправляет их обратно в RabbitMQ. Пример промта для GPT:

question = 'Я пришлю вакансию, а ты ответишь, к каким категориям она относится.\n\n' \

                'Вакансия:\n\n' \

                f'{text}.\n\n' \

                'Ответь номером и категорией, к каким из предложенных наиболее вероятно принадлежит вакансия ' \

                'описанная выше и ничего более:\n' \

                + "".join([f"{x + 1}) {categories[x].display_name}\n" for x in range(len(categories))]) + \

               '\nЕсли это не вакансия, скажи "нет".'

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

GPT нужно выносить в отдельный сервис, потому что он часто может не отвечать: если я бы реализовывал GPT через API, это всегда была бы 30–60-секундная задержка, и не факт, что GPT ответит. Он может быть просто перегружен и сказать «извини». Именно здесь пригождается RabbitMQ: если GPT не обработал сообщение, оно никуда не пропадает и ждёт своей очереди. 

Итак, ChatGPT рассортировал вакансии по категориям и отправил их обратно в RabbitMQ. Теперь вакансии готовы к отправке пользователям.

Пользователь взаимодействует с основным ботом, он в свою очередь подключён к 12 ботам с категориями. Через основной бот происходит подписка, оплата и выбор категорий — он не связан с рассылкой самих вакансий. 12 ботов — это, по сути, сами категории с вакансиями, которые видит пользователь. 

Одна вакансия может попасть сразу в несколько категорий. Например, есть вакансия копирайтера, в требованиях пишут, что посты должны быть с иллюстрациями. Бот это считывает и решает, что вакансия подходит для категорий «копирайтинг» и «дизайн». Я считаю, это нормально.

У бота есть Scheduler, или планировщик. Каждые 10 минут он мониторит подписки и сообщает пользователям с истёкшей подпиской, что пора купить новую. Также он каждый день отправляет админский отчёт о новых пользователях и подписках. Сейчас я работаю над тем, чтобы он каждый день отправлял вакансии в общий канал — по одной вакансии на каждую категорию.

Postgres хранит все данные: по пользователям, категориям, каналам, подпискам и оплатам. Content API нужен для доступа к базе, а админка реализована через Django-Admin. Payment App Service — это сервис, который отвечает за работу с подписками, именно с платёжной системой. А Payment Service — это уже сторонний сервис, через который происходит оплата.

Я решил сделать всё по уму и подключить «ЮКассу». Это удобно и для пользователей, и для меня. Пользователям не нужно кидать деньги кому-то на карту и гадать, подключат ли им подписку или нет. Всё полностью автоматизировано: чат-бот сам подключает подписку при успешной оплате. При этом все платежи проходят официально, без подозрительных переводов. 

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

Что можно улучшить

Scheduler я буду переписывать: я писал его быстро и с нуля, а уже потом вспомнил библиотеку Dagster, которая поможет сделать всё проще и более наглядно. Там уже готовый сервис с визуальным интерфейсом и расширенным функционалом. Например, если что-то идёт не так, он создаёт файл, где я могу посмотреть ошибки, а потом перезапустить всё со своего телефона. 

Я до сих пор сомневаюсь, правильно ли я поступил в плане реализации Redis, кэширующего сервиса. На курсе учили, что Redis должен находиться в API. Это значит, что запрос от пользователя всегда идёт в API, а тот уже или идёт в базу данных Postgres, или достаёт информацию из кэша, то есть из Redis.

Я реализовал Redis в самом боте, а не внутри API, — мне показалось, что это более удачное решение. Это значит, что бот сразу обращается к Redis и пытается там найти, например, данные о пользователе: кто он, какие у него подписки и так далее. Только в случае, если бот не получает ответа, он уже обращается к API.

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

Буду рад услышать ваши мнения в комментариях: правильно ли я поступил или стоило прислушаться к курсу «Мидл Python-разработчик» в Практикуме. Или, возможно, есть ещё какой-то вариант, который я упустил.

Схема, предложенная на курсе
Схема, предложенная на курсе
Схема реализации чат-бота
Схема реализации чат-бота

Советы тем, кто хочет сделать похожий проект

Если проект именно такого формата, как у меня, нужно понимать, что привлечение аудитории — это отдельная тема. Проект может быть очень классным, но туда всё равно придётся нагонять трафик, и он будет платным. К этому нужно быть готовым.

Я не советую забивать на какие-то части системы и делать абы как, если что-то непонятно. Всё-таки проект делается в относительно короткие сроки, а работать он будет долго. Лучше потратить несколько дополнительных дней сейчас, чем в будущем разбираться со всплывающими проблемами. 

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

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

Планы на будущее

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

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

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

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


  1. Fompi
    14.08.2023 11:38

    Довольно простой и интересный проект. Будут в будущем статьи, как ты его улучшал и сколько было расходов и доходов?


    1. GeorgyKalyapin Автор
      14.08.2023 11:38
      +1

      Да, со временем будут появляться новые статьи


  1. savostin
    14.08.2023 11:38

    А почему бот, а не сайт?


    1. GeorgyKalyapin Автор
      14.08.2023 11:38

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


      1. savostin
        14.08.2023 11:38
        +1

        По своему опыту знаю, что компании рассматривают вакансии неделями. Ваши секунды реально что-то решают?


        1. GeorgyKalyapin Автор
          14.08.2023 11:38

          Имхо, любое сэкономленное время что-то решает.

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


          1. savostin
            14.08.2023 11:38

            Действительно, как-то пропустил этот момент. Пардон.


  1. avost
    14.08.2023 11:38

    В данном случае непонятно зачем вам вообще разделение на "бот" и "апи". А по архитектуре могу предположить, что схема из курса спроектирована для возможности расширения и масштабирования, чего ваш вариант не даёт или приводит к бОльшей связности. Например, если в систему добавится новый клиент, помимо сущности "бот" (например, добавим сущность "админка"), то по схеме из курса она будет связана только с "апи", а по вашей, если только с "апи" то кто-то будет получать неконстстентные данные, то есть придётся и админке ходить ещё и в редис и решать вопрос с синхронизацией кешей.
    То есть в текущих условиях ваша схема - норм (только разделение на "бот" и "апи" лишнее получается). А если расширяться, то схема из курса спроектирована лучше :).


    1. GeorgyKalyapin Автор
      14.08.2023 11:38
      +2

      C апи общаются все сервисы. А ботов в системе на данный момент 12 штук. В моем конкретном случае редис снимает нагрузку на апи в этих случаях:

      • Бот не ходит к апи на каждое сообщение от пользователя, что бы поднянуть его данные, он ходит в апи один раз и далее сохраняет у себя в кеше, т.е. пока происходит непосредственно переписка между юзером и ботом - данные юзера хранятся в кеше.

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

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

      Спасибо за фидбек.


  1. Roninon
    14.08.2023 11:38

    У бота есть Scheduler, или планировщик. Каждые 10 минут он мониторит подписки и сообщает пользователям с истёкшей подпиской, что пора купить новую.

    А подписка кратна одному дню? Или можно оформить подписку только на 10 минут? Или на 40 минут?

    Я это к тому, что шедулер каждые 10 мин, наверно слишком часто, как мне кажется.


    1. GeorgyKalyapin Автор
      14.08.2023 11:38
      +2

      Тестовая подписка занимает 2 дня. Платная от 1 недели - до месяца.

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


  1. minaton
    14.08.2023 11:38

    Боты на каких библиотеках сделаны?


    1. GeorgyKalyapin Автор
      14.08.2023 11:38
      +2

      aiogram 3