Мой блог
Как я автоматизировал CRM-систему с помощью AI-агентов, SQL и Python
За годы активного посещения отраслевых мероприятий, конференций и поддержания нетворкинга я накопил огромное количество деловых контактов. Со временем в моей CRM-системе сформировалась база почти из 10 000 человек, включающая подробную историю встреч, звонков, переписок и договоренностей. Однако само по себе накопление данных не приносит пользы, если вы не можете эффективно их задействовать. Наступил момент, когда вручную отслеживать статусы, выстраивать фильтры и вспоминать контекст каждого знакомства стало невозможно. Я поставил перед собой задачу автоматизировать максимум процессов в CRM, сохраняя при этом персональный подход к каждому человеку.
Что было в первой версии моей CRM
Первоначально мои данные существовали в разрозненном виде в нескольких независящих друг от друга источниках:
- База контактов после личных встреч на офлайн-мероприятиях и онлайн-созвонах;
- Подробные архивы встреч и звонков за период с 2022 по 2026 год;
- Telegram-архивы, содержащие десятки тысяч диалогов;
- Заметки, персональные карточки, теги и вручную выставленные статусы;
- Предыдущая версия CRM, в которой был настроен базовый функционал отправки сообщений.
Когда я объединил и очистил все эти массивы от явных дублей, у меня получилось 9 718 уникальных профилей. Главная сложность заключалась в том, что один и тот же человек мог оставлять следы в разных местах. Например, сначала он писал мне в Telegram, затем приходил на видеовстречу, а еще через год попадал в мои заметки под совершенно другим именем. Связать эти записи в единую цепочку, не потеряв происхождение каждого факта и исторический контекст, оказалось непростой технической задачей.
Почему я не стал переписывать CRM с нуля
Изначально у меня было желание полностью выкинуть старый код и написать идеально сконструированную CRM-систему с нуля. Однако я вовремя отказался от этой идеи. В старой системе уже безупречно работали критически важные механизмы: авторизация аккаунтов, корректная отправка сообщений, управление лимитами Telegram и обработка сетевых ошибок. Переписать все это с нуля означало потратить месяцы на повторное исправление сотен мелких багов и краевых случаев, о которых я мог уже и не помнить.

Вместо этого я принял решение четко разделить систему на две составляющие:
- Старая CRM («Руки»): берет на себя всю рутинную исполнительскую работу — чтение и запись в БД, обработку событий, отправку сообщений, строгое соблюдение лимитов, повторные запросы при сбоях и фиксацию факта доставки.
- AI-агент («Мозг»): отвечает за стратегические решения — определяет, кого из базы пора активировать, оценивает важность контакта, анализирует изменения после последних переговоров и формирует следующее целевое действие.
Как система устроена сейчас
Текущая архитектура моей CRM состоит из пяти последовательных слоев, каждый из которых решает строго выделенную задачу.
1. Архив событий
В моей работе задействовано 4 Telegram-аккаунта, суммарный архив которых превышает 51 000 диалогов и 1,5 миллиона сообщений. Прогонять весь этот гигантский объем через LLM неразумно: это слишком дорого и медленно. Поэтому первичное сканирование я доверил обычным скриптам и базе данных SQLite.
Скрипт быстро и абсолютно бесплатно вычисляет базовые метрики коммуникации с помощью простейших запросов:
SELECT dialog_id, MAX(message_date) AS last_contact, COUNT(*) AS message_count
FROM messages
GROUP BY dialog_id;
Нейросеть на этом этапе вообще не привлекается — она подключается только тогда, когда требуется глубоко понять смысловое содержание конкретной беседы.
2. Единая карточка
На втором слое происходит сборка всех разрозненных фактов вокруг конкретной личности. Информация о контакте может храниться в самых разных формах:
- Имя в заметках после проведенного звонка;
- Никнейм (username) в Telegram;
- Цифровой Telegram ID;
- Номер телефона в WhatsApp;
- Имя и название компании с визитки;
- Данные из групповых чатов.
Универсального идентификатора не существует: юзернеймы меняются, имена повторяются, а телефоны указаны далеко не везде. Поэтому связывание происходит поэтапно: сначала по точному совпадению ID, затем по username, и лишь потом по комбинации «Имя + Компания». Автоматически спорные записи не объединяются — система помечает их и сохраняет источник каждого факта, чтобы при возникновении противоречий я мог лично принять решение.
3. Высчитываю все параметры
Каждому контакту я присвоил категорию отношений: Hot, Warm, Lukewarm, Cold или Archived. Кроме того, система динамически рассчитывает приоритет реактивации по шкале от 0 до 100.
Формула расчета выглядит следующим образом:
priority = 0.35 * recency + 0.20 * frequency + 0.25 * depth + 0.20 * resurgence - penalty
Где задействованы ключевые параметры:
- Recency: давность последнего содержательного контакта;
- Frequency: общая частота и количество созвонов/диалогов;
- Depth: глубина проработки отношений;
- Resurgence: появление нового входящего сигнала от человека;
- Penalty: штраф за оставленные без ответа сообщения.
Для затухания активности во времени используется формула полураспада: score = 2 ** (-days_since_event / half_life). На основе этих вычислений система формирует оперативные задачи. К примеру, при последнем пересчете моя база распределилась так: 16 контактов требуют немедленного письма, 77 нужно обработать в течение недели, 601 — запланировать на месяц, а остальные оставлены в покое до новых сигналов.
4. Автоматизация и подготовка контекста
Передавать всю базу в контекстное окно AI-агента невозможно. Сначала срабатывает жесткий детерминированный фильтр в БД:
SELECT * FROM people
WHERE reactivation_priority >= 75
ORDER BY reactivation_priority DESC
LIMIT 20;
Для отобранных лидов формируется сжатый датасет: кем приходится человек, история знакомства, ключевые обещания, даты звонков и причины попадания в очередь. Только после этого подключается LLM. Ее задача крайне локальна — решить, какое целевое действие будет логичным для конкретной карточки. Если данных недостает, модель возвращает статус needs_review.
5. Автоматизация коммуникации с лидами
AI-агент никогда не отправляет сообщения напрямую. Он лишь генерирует черновик и предлагаемый шаг. Дальше в дело вступает изолированный транспортный слой, проверяющий ограничения канала:
- Допустимо ли писать с данного аккаунта;
- Не превышены ли суточные лимиты;
- Не отправлялось ли аналогичное сообщение ранее;
- Есть ли уникальный ID доставленного сообщения.
Между отправками обязательно добавляется случайная задержка (jitter). При получении ошибки FloodWait транспорт выжидает паузу. Все аккаунты используют единый журнал расхода лимитов. В коде это реализовано следующим образом:
if not budget.can_send(account):
return "LIMIT_REACHED"
if already_contacted(person, campaign):
return "DUPLICATE"
result = send_with_jitter(message)
if result.message_id:
record_delivery(result.message_id)
else:
record_attempt()
Состояние ATTEMPT (попытка) и SENT (подтвержденная доставка) строго разделены в базе, что исключает дублирование рассылок.
Пример работы системы в реальности
Представим лид трехлетней давности. В старой CRM у него висел застывший статус «new». Ночной скрипт-сборщик фиксирует новый входящий сигнал в Telegram. SQL-код обновляет дату контакта и подтягивает приоритет реактивации выше 75 баллов. AI-агент получает очищенную карточку с историей договоренностей и генерирует персональное сообщение. После моей санкции транспортный слой отправляет текст, сохраняя ID доставки. Как только человек отвечает — автоматическая цепочка моментально останавливается.
Не получилось: мои ошибки и выводы
1. Не удалась попытка заставить LLM делать всё
Изначально я хотел давать нейросети верхнеуровневые промпты в духе «найди лучшего лида для предложения сервиса». Это оказалось невероятно дорогим и неэффективным путем. Модель тратила тысячи токенов на банальную сортировку, которую SQL делает мгновенно. Я пришел к строгому разделению: классический код считает, база фильтрует, модель оценивает смыслы, транспорт отправляет.
2. Избыточное доверие к статичным статусам
Ручные статусы в CRM устаревают сразу после их присвоения. Вычисляемый динамический скоринг тоже может ошибаться, но он всегда содержит понятную формулу, дату пересчета и исходные события.
3. Фокус только на исходящих рассылках
Сначала я проектировал систему под холодные исходящие касания. Однако позже сместил акцент: максимальную ценность представляют входящие обращения. Теперь они получают отдельную приоритетную очередь и жесткий SLA на ответ.
Что в итоге автоматизировано
Без моего ручного участия в системе выполняются следующие процессы:
- Выгрузка и сбор диалогов со всех аккаунтов;
- Связывание и дедупликация профилей;
- Расчет давности, глубины и «температуры» контакта;
- Формирование динамической очереди задач;
- Фиксация новых входящих сигналов;
- Подготовка сжатого контекста для AI;
- Генерация варианта следующего шага;
- Контроль лимитов Telegram и защита от повторов;
- Фиксация факта доставки и автоматическая остановка сценария при ответе.
За собой я оставил только ключевые точки контроля: утверждение стратегической цели, проверку рискованных действий и финальное одобрение текстов.
Сколько токенов это экономит
Благодаря переносу первичной обработки на SQLite и Python, около 1,7 млн сообщений и 51 000 диалогов обрабатываются без привлечения LLM. Нейросеть получает на вход лишь несколько отфильтрованных карточек из текущей очереди. Расходы на работу AI-агента зависят не от размера всей CRM, а исключительно от количества решений, принимаемых прямо сейчас.
Что бы я сделал иначе
Если бы я строил такую систему заново, я бы вообще не начинал с интерфейса. Оптимальный порядок разработки выглядит так:
- Неизменяемый архив сырых событий;
- Надежные сквозные идентификаторы контактов;
- Фиксация происхождения (провенанса) каждого факта;
- Вычисляемый скоринг с датой актуализации;
- Очередь целевых действий;
- Изолированный транспортный слой;
- И только в самом конце — пользовательский UI.
Вывод
Сначала я наполнил CRM богатой историей данных, и лишь затем построил поверх нее умную автоматизацию. В итоге архив хранит факты, SQL связывает и считает, AI-агент принимает смысловые решения, а транспорт гарантирует доставку. В ближайших планах — внедрить сквозную аналитику конверсий, чтобы агенты наглядно показывали, какие категории лидов и формулировки приносят максимальную финансовую отдачу.
Источник: habr.com
