Фільтрація, очищення та векторизація 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-задачі.







