Агент для email

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Агент для email

1. Цель задачи

Разработать автоматизированного email-агента, который подключается к почтовому ящику, читает входящие письма, классифицирует их на 5 заданных типов и генерирует ответные сообщения через LLM API. Проект позволяет освоить интеграцию с почтовыми протоколами (IMAP/SMTP), построение конвейера обработки текста и вызов внешних AI-моделей.

Ключевой результат Работающий CLI‑ или веб‑сервис, который корректно обрабатывает не менее 5 писем разных типов (по одному на категорию) и отправляет осмысленные ответы.


2. Исходные данные

Что нужноОткуда взять
Аккаунт электронной почты (реальный или тестовый)Gmail / Яндекс / тестовый IMAP-сервер (см. ниже)
Тестовые письма 5 типов (например: запрос, жалоба, спам, уведомление, прочее)Создать вручную или использовать публичные датасеты (Enron subset)
API‑ключ LLM (OpenAI / Anthropic / локальная модель)OpenAI API / Ollama + Mistral
Набор меток (5 категорий)Определить самостоятельно, зафиксировать в коде

Если нет реального инструмента — симулируем:

  1. Установить [Вики/Mailpit|Mailpit(Вики/TLS|https://github.com/axllent/mailpit) (локальный Вики/SMTP|SMTP+Вики/IMAP|IMAP сервер) или использовать Вики/pytest|pytest‑фикстуру Вики/aiosmtpd|aiosmtpd.
  2. Написать скрипт, который генерирует 5 тестовых писем (MIME‑формат) и отправляет их через SMTP на локальный сервер.
  3. Для классификации без LLM — использовать простой регулярный классификатор или предобученную модель из Hugging Face (например, distilbert-base-uncased-finetuned-sst-2-english), дообучив на 5 категориях (синтетические данные).
  4. Если нет доступа к SMTP-отправке — сохранять сгенерированные ответы в JSON.

3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияPython 3.10+Основной язык разработки
Почтовые протоколыimaplib, smtplib / aiosmtpdЧтение и отправка писем
Разбор MIMEemail (stdlib)Извлечение текста/вложений
КлассификацияLLM API (OpenAI) / Hugging Face transformersОпределение типа письма
Генерация ответаLLM API (OpenAI) / AnthropicСоздание текста ответа
ОркестрацияFastAPI / CLI на ClickТочка входа и управление
Логированиеstructlog / loguruОтслеживание работы агента
Тестированиеpytest, responses (для API)Юнит‑тесты и интеграционные тесты
Хранение результатовSQLite / JSON‑файлыСохранение обработанных писем

4. Этапы выполнения

Этап 1: Настройка окружения и подключение к почте (2 часа)

Действия

  1. Создать виртуальное окружение (python -m venv .venv) и установить зависимости (см. стек).
  2. Развернуть локальный почтовый сервер (Mailpit) для тестов:
    docker run --name mailpit -d -p 1025:1025 -p 8025:8025 axllent/mailpit
    
  3. Реализовать модуль mail_client.py с функциями:
    • connect_imap(host, port, user, password) – устанавливает IMAP‑соединение.
    • fetch_unseen() – получает список непрочитанных писем (UID).
    • fetch_message(uid) – возвращает объект email.message.Message.
  4. Написать скрипт генерации тестовых писем (generate_test_emails.py), который через SMTP отправляет 5 писем с разными темами и телами на адрес test@localhost. Пример категорий:
    categories = ["Запрос", "Жалоба", "Спам", "Уведомление", "Прочее"]
    
  5. Протестировать: запустить генератор, затем запустить fetch_unseen() и убедиться, что письма читаются.

Ожидаемый результат этапа Рабочее IMAP-соединение, прочитано не менее 3 тестовых писем, их заголовки и тело выводятся в консоль.


Этап 2: Разбор писем и извлечение содержимого (1 час)

Действия

  1. Реализовать функцию parse_email(msg):
    • Извлечь From, Subject, Date, Body (plain text или извлечение из HTML с помощью BeautifulSoup).
    • Обработать вложения (извлечь текст из pdf/docx, если нужно – опционально).
    • Вернуть словарь {uid, sender, subject, body, attachments, date}.
  2. Учесть кодировки (UTF‑8, base64, quoted-printable) – использовать email.policy.default.
  3. Написать юнит‑тест на парсинг одного MIME‑письма со вложением.

Ожидаемый результат этапа Модуль парсинга корректно извлекает данные из 5 тестовых писем (проверено pytest).


Этап 3: Классификация писем (3 часа)

Действия

  1. Определить 5 категорий и составить примеры для каждой (записать в YAML/JSON).
  2. Выбрать метод классификации:
    • Вариант A (LLM): Вызов OpenAI API с промптом, который просит отнести письмо к одной из 5 категорий.
    • Вариант B (ML): Дообучить distilbert на синтетическом датасете (500 примеров, сгенерированных через GPT).
  3. Реализовать класс Classifier с методом classify(email_text) -> category.
    • Для варианта A: использовать openai.ChatCompletion.create() с temperature=0, парсить ответ.
    • Для варианта B: загрузить модель, токенизировать, применить softmax, взять топ‑1.
  4. Интегрировать классификатор в конвейер: после парсинга вызывать classify() для каждого нового письма.
  5. Протестировать на 5 подготовленных письмах – каждое должно попасть в свою категорию (точность 100% на тестовом наборе).

Ожидаемый результат этапа Консольный вывод: Письмо от <sender>: категория = <category> для всех 5 писем.


Этап 4: Генерация ответа и отправка (2 часа)

Действия

  1. Реализовать функцию generate_reply(email_data, category):
    • Промпт: "Напиши вежливый ответ на письмо [текст] в категории [категория]".
    • Вызвать LLM API, получить сгенерированный текст.
    • (Опционально) добавить проверку длины и фильтр грубых слов.
  2. Реализовать send_reply(sender, subject, body, original_msg_id):
    • Создать MIME-ответ с правильными заголовками In-Reply-To, References.
    • Отправить через SMTP (localhost:1025 для Mailpit).
  3. Для отладки сначала сохранять ответы в JSON, не отправляя реально.
  4. Написать тест, который проверяет, что ответ действительно отправляется (mock SMTP).

Ожидаемый результат этапа Агент генерирует и отправляет (или сохраняет) корректный ответ для каждого из 5 типов писем.


Этап 5: Оркестрация, обработка ошибок и логирование (2 часа)

Действия

  1. Организовать главный цикл в main.py (или FastAPIэндпоинт /process):
    • Подключение к IMAP, получение непрочитанных, обработка, отправка ответов.
    • Обработка исключений: недоступность IMAP, ошибка LLM, лимиты API (повторные попытки с exponential backoff).
  2. Добавить логирование:
    • structlog / loguru – пишем время, UID, категорию, успех отправки.
    • Отдельный лог ошибок.
  3. Настроить управление сессиями: не повторять одну и ту же операцию (запоминать processed_uids в SQLite).
  4. Запустить полный тестовый прогон:
    • Сгенерировать 5 новых писем.
    • Запустить агента.
    • Проверить, что все ответы отправлены и категории верны.

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


5. Критерии приемки (Definition of Done)

  • IMAP-соединение устанавливается и письма читаются корректно (не менее 3 писем).
  • Парсер корректно извлекает текст из plain text и HTML (через BeautifulSoup).
  • Классификатор относит каждое из 5 тестовых писем к ожидаемой категории (100% accuracy на тесте).
  • Генератор ответа создаёт осмысленный текст (не пустой, не содержит грубых слов, длина от 50 символов).
  • Ответ отправляется (или сохраняется) без ошибок, заголовки In-Reply-To и References присутствуют.
  • Обработаны граничные случаи: письмо без темы, пустое тело, вложение (проигнорировано, но не упало).
  • Проект запускается одной командой (например, python -m email_agent) или через Docker Compose.
  • Все зависимости зафиксированы в requirements.txt / pyproject.toml.
  • Покрытие тестами – не менее 60% ключевых модулей (классификатор, парсер, клиент).

6. Ожидаемый результат

Основной артефакт Репозиторий с кодом email-агента, включающий:

  • mail_client.pyIMAP/SMTP клиент
  • parser.py – разбор писем
  • classifier.py – классификация (и модель, если используется локальная)
  • responder.py – генерация и отправка ответов
  • main.py – точка входа (CLI или FastAPI)
  • tests/ – юнит‑тесты
  • generate_test_emails.py – скрипт для тестовых писем
  • README.md – инструкция по запуску, описание 5 категорий

Дополнительные результаты

  • Логи обработанных писем в logs/email_agent.log
  • JSON-файл с историей (опционально)
  • Dockerfile и docker-compose.yml (опционально)

7. Возможные сложности и их решение

СложностьРешение
IMAP-сервер не отвечает / требует SSLИспользовать локальный Mailpit. Для удалённого – настроить OAuth2 (Gmail). Для тестов – mock IMAP через aiosmtpd.
LLM API нестабилен / превышение лимитаВнедрить tenacity retry + fallback (заранее заготовленные шаблоны ответов).
MIME-письмо с вложением не парситсяИспользовать email.policy.default и email.iterators.typed_subpart_iterator.
Классификатор путает категорииУточнить промпт (few-shot examples) или дообучить модель на 50–100 примерах на категорию.
Отправка письма не проходитПроверить SMTP-настройки. Для Gmail – включить «ненадёжные приложения» или App Password.
Проект не переиспользуем (захардкожены пароли)Вынести настройки в .env (python-dotenv) и config.yaml.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Настройка окружения и подключение к почте2 часа
Этап 2: Разбор писем и извлечение содержимого1 час
Этап 3: Классификация писем3 часа
Этап 4: Генерация ответа и отправка2 часа
Этап 5: Оркестрация, обработка ошибок и логирование2 часа
Итого10 часов

Примечание: Время может увеличиться в 1,5–2 раза при первом выполнении (из‑за изучения документации).


9. Связанные вопросы из базы знаний

ВопросТема
101Работа с IMAP через imaplib
205Разбор MIME-писем в Python
312Использование OpenAI API для классификации текста
415Обработка ошибок и retry в сетевых запросах
523Тестирование кода, работающего с внешними API (mocking)
601Логирование в production-grade приложениях
708FastAPI – создание эндпоинта для фоновой обработки
815Docker Compose для локального тестового окружения
890Оценка качества классификации (precision/recall)

10. Чек-лист самопроверки

  • Я проверил, что IMAP-клиент читает письма с локального или тестового сервера, а код не падает при отсутствии соединения.
  • Я убедился, что классификатор корректно обрабатывает все 5 тестовых писем (по одному на категорию).
  • Я проверил, что ответ генерируется, не содержит ничего лишнего и отправляется (или сохраняется).
  • Я написал минимум 3 теста (на парсер, классификатор, отправку) и они проходят.
  • Я задокументировал в README, как запустить проект, какие переменные окружения нужны, и какие категории используются.