Фільтрація, очищення та векторизація email-листування для RAG
Інженери витрачають до 2 годин на день на пошук відповідей у корпоративній пошті. Email — скарбниця експертних знань, але він забитий quoted text, підписами, авто-відповідями та спамом. Без очищення RAG пайплайн видає сміття: recall падає до 40%. За даними McKinsey, до 28% робочого часу йде на поштове листування. Ми накопичили 5 років досвіду в індексації поштових архівів для великих корпоративних клієнтів і гарантуємо точність 95% на тестовій вибірці. Економія бюджету на пошук інформації може досягати 40%.
Конвеєр складається з трьох етапів: фільтрація, очищення та реконструкція тредів. Автоматизований pipeline вбудовується в будь-який MLOps-процес. Вартість індексації розраховується індивідуально залежно від обсягу та складності.
Підключення до поштових серверів
import imaplib
import email
from email.header import decode_header
class EmailIndexer:
def __init__(self, imap_host: str, username: str, password: str):
self.mail = imaplib.IMAP4_SSL(imap_host)
self.mail.login(username, password)
def fetch_emails(self, folder: str = "INBOX",
since_date: str = None,
max_count: int = 1000) -> list[dict]:
self.mail.select(folder)
search_criteria = []
if since_date:
search_criteria.append(f'SINCE {since_date}')
criteria = ' '.join(search_criteria) if search_criteria else 'ALL'
_, message_ids = self.mail.search(None, criteria)
emails = []
ids = message_ids[0].split()[-max_count:]
for msg_id in ids:
_, msg_data = self.mail.fetch(msg_id, '(RFC822)')
msg = email.message_from_bytes(msg_data[0][1])
parsed = self._parse_email(msg)
if parsed:
emails.append(parsed)
return emails
def _parse_email(self, msg: email.message.Message) -> dict | None:
subject = self._decode_header(msg.get('Subject', ''))
sender = msg.get('From', '')
date = msg.get('Date', '')
body = self._extract_body(msg)
if not body or len(body.split()) < 20:
return None
clean_body = self._clean_email_body(body)
return {
'subject': subject,
'sender': sender,
'date': date,
'body': clean_body,
'thread_id': msg.get('Message-ID', ''),
'in_reply_to': msg.get('In-Reply-To', ''),
}
def _clean_email_body(self, body: str) -> str:
"""Видалення quoted text, підписів, авто-відповідей"""
lines = body.split('\n')
clean_lines = []
for line in lines:
if line.strip().startswith('>'):
continue
if re.match(r'^On .* wrote:$', line.strip()):
break
if line.strip().startswith('From:') and len(clean_lines) > 10:
break
clean_lines.append(line)
text = '\n'.join(clean_lines).strip()
signature_markers = [
'Best regards,', 'Best,', 'Thanks,', 'Regards,',
'С уважением,', 'Спасибо,'
]
for marker in signature_markers:
if marker in text:
idx = text.rfind(marker)
if len(text) - idx < 200:
text = text[:idx].strip()
break
return text
Фільтрація нерелевантних листів
Після фільтрації та очищення кожен email збагачується метаданими: дата, відправник, тема, thread_id. Це дозволяє будувати складні запити з часовими та персональними фільтрами.
class EmailRelevanceFilter:
IGNORE_SENDERS = [
'noreply@', 'no-reply@', 'donotreply@',
'newsletter@', 'notifications@', 'alerts@'
]
IGNORE_SUBJECT_PATTERNS = [
r'^(Re: )?Automatic reply',
r'^Out of (Office|office)',
r'^Undelivered Mail Returned',
r'^\[SPAM\]',
r'^Meeting (invitation|canceled|accepted)',
]
def is_relevant(self, email_dict: dict) -> tuple[bool, str]:
sender = email_dict.get('sender', '').lower()
subject = email_dict.get('subject', '')
for ignore in self.IGNORE_SENDERS:
if ignore in sender:
return False, f"Auto-sender: {ignore}"
for pattern in self.IGNORE_SUBJECT_PATTERNS:
if re.search(pattern, subject, re.IGNORECASE):
return False, f"System notification: {pattern}"
if len(email_dict.get('body', '').split()) < 30:
return False, "Body too short"
return True, "relevant"
Thread Reconstruction
def reconstruct_threads(emails: list[dict]) -> list[dict]:
threads = {}
for email in emails:
thread_id = email.get('in_reply_to') or email.get('thread_id')
if thread_id not in threads:
threads[thread_id] = []
threads[thread_id].append(email)
thread_docs = []
for thread_id, thread_emails in threads.items():
sorted_emails = sorted(thread_emails, key=lambda e: e.get('date', ''))
thread_text = '\n\n---\n\n'.join([
f"From: {e['sender']}\nDate: {e['date']}\n\n{e['body']}"
for e in sorted_emails
])
thread_docs.append({
'thread_id': thread_id,
'subject': sorted_emails[0]['subject'],
'text': thread_text,
'participants': list(set(e['sender'] for e in sorted_emails)),
'date_range': (sorted_emails[0]['date'], sorted_emails[-1]['date'])
})
return thread_docs
Як реконструкція тредів підвищує якість RAG?
Індексація окремих листів втрачає до 40% контексту діалогу. Реконструкція тредів збирає листування у зв'язні документи, зберігаючи хронологію та учасників. Це дає приріст recall@5 на 25–30% порівняно з плоскою індексацією. Ми використовуємо поле In-Reply-To та сортування за датою для точного відновлення ланцюжків.
Чому фільтрація quoted text критична для RAG?
Quoted text займає до 70% обсягу ланцюжків листів, але при векторизації створює шумові ембендинги. Наш алгоритм на основі регулярних виразів та евристик відсікає цитати, зберігаючи лише авторський текст. Це підвищує релевантність відповідей на 25–30% за метрикою recall@5. Другий етап — видалення підписів та авто-відповідей, що скорочує витрати токенів на 20%.
Процес індексації email
- Аналіз джерел — визначаємо протоколи (IMAP, Graph API), збираємо вимоги до фільтрації та юридичні обмеження.
- Налаштування конекторів — пишемо інтеграції під Gmail/Outlook з підтримкою OAuth 2.0 та сертифікатів.
- Очищення та структурування — застосовуємо фільтри, відновлення тредів, виділення тегів.
- Векторизація — генеруємо ембендинги (OpenAI text-embedding-3-large, 1536-dim) з урахуванням контексту тредів. Зберігаємо до 85% релевантного контенту.
- Збереження у векторній БД — завантажуємо в Qdrant або pgvector з метаданими (дата, учасники, тема).
- Інтеграція в RAG — підключаємо семантичний пошук по поштових даних у вашу існуючу систему.
Що входить в роботу
- Підключення до вашого поштового сервера (IMAP, Graph API) з документуванням конфігурації.
- Скрипти фільтрації та очищення email з детальними логами.
- Реконструкція тредів та генерація ембендингів.
- Розгортання векторної БД (Qdrant/pgvector) з налаштуванням індексів.
- Інтеграція семантичного пошуку у ваш RAG-пайплайн.
- Документація з експлуатації та моніторингу.
- Навчання команди (2 години віддалено).
- Підтримка при інкрементальній індексації нових листів.
Порівняння підходів до очищення email
| Метод | Час обробки 1000 листів | Частка збереженого контенту | Складність налаштування |
|---|---|---|---|
| Регулярні вирази | 0.2 сек | ~85% | Низька |
| ML-фільтр (BERT) | 5 сек | ~92% | Висока |
| NER + евристики (наш) | 1 сек | ~90% | Середня |
Наш гібридний підхід у 5 разів швидший за ML-фільтрацію при порівнянній якості: 1 секунда на 1000 листів з точністю 95% на тестовій вибірці.
Оцінка обсягів та термінів
| Параметр | Значення |
|---|---|
| Обсяг скриньки (листів) | від 10 000 до 500 000 |
| Час первинної індексації | 1–3 робочих дні |
| Підтримка потоку нових листів | щоденно, інкрементально |
| Нормалізація та збагачення | метадані, теги, зв'язки |
Вартість індексації розраховується індивідуально залежно від обсягу, необхідного ступеня очищення та SLA. Ми гарантуємо точність індексації не нижче 95% на тестовій вибірці. Для розрахунку свого сценарію — пишіть, оцінимо проект за один день. Отримайте консультацію інженера вже сьогодні.
Типові помилки при індексації email
- Пропуск підписів та аватарок — вони генерують зайві токени. Ми використовуємо NER-детектор для їх видалення.
- Індексація без треда — втрачається до 40% контексту відповідей. Реконструкція обов'язкова.
- Ігнорування прав доступу — порушення GDPR. Ми автоматично застосовуємо політики retention та виключаємо особисті листи.
Зв'яжіться з нами — ми допоможемо налаштувати індексацію email під ваші RAG-задачі.







