Проблема: 60 ГБ писем и слабое железо
Представьте: у вас накопилось 60 ГБ электронных писем за 10+ лет работы. Это десятки тысяч писем — контракты, переговоры, технические обсуждения, личная переписка. Вам нужно найти конкретное письмо, но стандартный поиск в почтовом клиенте не справляется. Вы хотите задать вопрос на естественном языке: "Какие условия были в договоре с компанией X в 2020 году?" или "Кто предлагал решение проблемы Y в прошлом месяце?".
Идеальное решение — RAG (Retrieval-Augmented Generation). Но есть проблема: у вас обычный компьютер с 8 ГБ оперативной памяти. Все облачные решения отпадают — ваши письма содержат конфиденциальную информацию. Можно ли развернуть полноценную локальную систему поиска на таком железе? Да, можно, но с правильным подходом.
Главный миф: многие думают, что для работы с 60 ГБ данных нужно минимум 32 ГБ ОЗУ. Это неверно. Ключ — в потоковой обработке и правильном выборе инструментов.
Архитектура решения: как обойти ограничения памяти
Вместо того чтобы загружать все 60 ГБ в память одновременно, мы будем использовать стратегию "обрабатывай по частям, храни эффективно". Вот ключевые компоненты нашей системы:
- Потоковый парсинг писем — обрабатываем письма батчами по 100-200 МБ
- Локальная векторная база на диске — ChromaDB в persistent-режиме
- Крошечная, но эффективная модель эмбеддингов — all-MiniLM-L6-v2 (80 МБ)
- Оптимизированная локальная LLM — Mistral 7B или Phi-3 через Ollama
- Умное чанкирование — разбиваем письма на смысловые блоки
| Компонент | Потребление ОЗУ | Альтернатива |
|---|---|---|
| Модель эмбеддингов | ~300 МБ | SentenceTransformers |
| Векторная база (Chroma) | ~500 МБ | Qdrant, FAISS |
| Локальная LLM (7B параметров) | ~4.5 ГБ (4-бит квант.) | Phi-3 mini (3.8B) |
| Обработка данных | ~1 ГБ | Потоковая загрузка |
| Итого (пиковое) | ~6.3 ГБ | Вписывается в 8 ГБ! |
Пошаговый план реализации
1 Подготовка данных: экспорт и парсинг писем
Первым делом нужно получить письма в машиночитаемом формате. Большинство почтовых клиентов позволяют экспортировать письма в формате MBOX или EML.
# Пример структуры проекта
email-rag-project/
├── data/
│ ├── raw_emails.mbox # 60 ГБ исходных данных
│ └── processed/ # Обработанные письма
├── scripts/
│ ├── parse_emails.py # Парсинг MBOX
│ ├── create_embeddings.py # Создание эмбеддингов
│ └── query_system.py # Система запросов
└── chroma_db/ # Векторная база
Для парсинга используем Python с потоковой обработкой:
import mailbox
import json
from tqdm import tqdm
def process_mbox_in_chunks(mbox_path, chunk_size=1000):
"""Обрабатывает MBOX файл частями для экономии памяти"""
mbox = mailbox.mbox(mbox_path)
emails_processed = 0
for i in tqdm(range(0, len(mbox), chunk_size)):
chunk = []
for msg in mbox[i:i+chunk_size]:
email_data = {
'id': emails_processed,
'from': msg['from'],
'to': msg['to'],
'subject': msg['subject'],
'date': msg['date'],
'body': extract_email_body(msg),
'size': len(msg.as_string())
}
chunk.append(email_data)
emails_processed += 1
# Сохраняем чанк на диск
save_chunk(chunk, i)
del chunk # Освобождаем память
2 Умное чанкирование: как разбивать письма
Простого разбиения по символам недостаточно. Длинные письма с перепиской нужно разбивать по смыслу:
from langchain.text_splitter import RecursiveCharacterTextSplitter
def chunk_email(email_text, max_chunk_size=500):
"""Разбивает письмо на смысловые блоки"""
# Определяем естественные разделители в письмах
separators = ["\n\n--- Forwarded message ---",
"\n\nOn ", # Начало цитаты
"\n\n> ", # Уровень цитаты
"\n\n", # Двойной перенос
"\n", # Одиночный перенос
" ", # Пробел
""] # Любой символ
text_splitter = RecursiveCharacterTextSplitter(
separators=separators,
chunk_size=max_chunk_size,
chunk_overlap=50,
length_function=len
)
return text_splitter.split_text(email_text)
3 Создание векторной базы с ChromaDB
ChromaDB — идеальный выбор для локального развертывания. Она работает в persistent-режиме, хранит данные на диске и загружает в память только индексы.
import chromadb
from sentence_transformers import SentenceTransformer
import torch
# Используем легковесную модель эмбеддингов
model = SentenceTransformer('all-MiniLM-L6-v2')
# Инициализируем Chroma с сохранением на диск
chroma_client = chromadb.PersistentClient(path="./chroma_db")
collection = chroma_client.create_collection(
name="emails",
metadata={"hnsw:space": "cosine"} # Используем HNSW для скорости
)
# Пакетная обработка для экономии памяти
def add_emails_to_chroma_in_batches(email_chunks, batch_size=100):
"""Добавляет письма в векторную базу батчами"""
for i in range(0, len(email_chunks), batch_size):
batch = email_chunks[i:i+batch_size]
# Генерируем эмбеддинги для батча
embeddings = model.encode(batch)
# Создаем ID для каждого чанка
ids = [f"email_{i+j}" for j in range(len(batch))]
# Добавляем в коллекцию
collection.add(
embeddings=embeddings.tolist(),
documents=batch,
ids=ids
)
# Очищаем память
del embeddings
torch.cuda.empty_cache() if torch.cuda.is_available() else None
Важно: Используйте `all-MiniLM-L6-v2` вместо больших моделей типа `all-mpnet-base-v2`. Она в 5 раз меньше (80 МБ vs 420 МБ), но сохраняет 90% качества для поиска по письмам.
4 Настройка локальной LLM через Ollama
Для генерации ответов используем Ollama — самый простой способ запускать локальные LLM. В нашем полном гиде по Ollama мы подробно разбирали все тонкости установки и настройки.
# Устанавливаем Ollama (Linux/Mac)
curl -fsSL https://ollama.com/install.sh | sh
# Загружаем оптимизированную модель
ollama pull mistral:7b-instruct-q4_K_M # 4.5 ГБ, 4-битное квантование
# Или еще более легкую
ollama pull phi3:mini-128k-instruct-q4_K_M # 2.3 ГБ
Для 8 ГБ ОЗУ лучше всего подходят:
- Phi-3 mini (3.8B) — 2.3 ГБ в 4-битном формате, отличное качество
- Mistral 7B (4-бит) — 4.5 ГБ, проверенная производительность
- Llama 3.2 3B — новейшая маленькая модель от Meta
В статье "Лучшие локальные LLM 2025 года" сообщество Reddit рекомендует именно эти модели для слабого железа.
5 Сборка системы запросов
Теперь объединяем все компоненты в единую систему:
import requests
import json
class EmailRAGSystem:
def __init__(self, chroma_path="./chroma_db"):
self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
self.chroma_client = chromadb.PersistentClient(path=chroma_path)
self.collection = self.chroma_client.get_collection("emails")
def search_emails(self, query, n_results=5):
"""Ищет релевантные письма"""
query_embedding = self.embedding_model.encode([query]).tolist()[0]
results = self.collection.query(
query_embeddings=[query_embedding],
n_results=n_results
)
return results['documents'][0]
def generate_answer(self, query, context):
"""Генерирует ответ на основе найденных писем"""
prompt = f"""Ты — помощник для поиска в электронных письмах.
Пользователь спрашивает: {query}
Вот релевантные письма из архива:
{context}
Ответь на вопрос, используя информацию из писем.
Если информации недостаточно, скажи об этом.
Не выдумывай информацию."""
# Отправляем запрос в Ollama
response = requests.post(
'http://localhost:11434/api/generate',
json={
'model': 'phi3:mini',
'prompt': prompt,
'stream': False
}
)
return response.json()['response']
Оптимизация производительности
Даже с правильно выбранными инструментами нужно дополнительно оптимизировать систему:
1. Используйте HNSW индекс в ChromaDB
collection = chroma_client.create_collection(
name="emails_optimized",
metadata={
"hnsw:space": "cosine",
"hnsw:construction_ef": 200, # Для лучшего качества
"hnsw:M": 16 # Баланс скорость/память
}
)
2. Настройка подкачки (swap) в Linux
# Создаем файл подкачки 8 ГБ
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Делаем постоянным
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
3. Приоритизация процессов
# Запускаем Ollama с низким приоритетом ввода-вывода
ionice -c 3 -p $(pgrep ollama)
# Ограничиваем использование CPU для фоновых процессов
cpulimit -l 50 -p $(pgrep chroma)
Возможные ошибки и их решение
| Проблема | Причина | Решение |
|---|---|---|
| Out of Memory при создании эмбеддингов | Слишком большой батч | Уменьшите batch_size до 50 или 25 |
| Медленный поиск в ChromaDB | Слишком много векторов в памяти | Используйте persistent режим и HNSW |
| LLM генерирует "галлюцинации" | Слишком мало контекста или слабая модель | Увеличьте n_results до 7-10, используйте Phi-3 |
| Обработка занимает дни | Последовательная обработка 60 ГБ | Обрабатывайте только последние N лет или по отправителям |
Альтернативные подходы
Если описанный подход все еще требует слишком много ресурсов, рассмотрите эти альтернативы:
1. Гибридный подход: облачные эмбеддинги + локальная LLM
Используйте бесплатные API для создания эмбеддингов (например, Google AI Studio предлагает бесплатные квоты), а LLM запускайте локально. Это снизит нагрузку на CPU при индексации.
2. Предварительная фильтрация по метаданным
Перед семантическим поиском фильтруйте письма по дате, отправителю или теме. Это уменьшит размер векторного поиска в 10-100 раз.
# Сначала фильтруем по дате
filtered_emails = collection.query(
query_embeddings=[query_embedding],
n_results=1000,
where={"date": {"$gte": "2023-01-01"}} # Только письма за 2 года
)
3. Поэтапное развертывание
Как в кейсе с мультиагентными системами, начинайте с малого: обработайте сначала 1 ГБ писем, проверьте качество, затем масштабируйтесь.
Заключение: это реально!
Создание локального RAG для 60 ГБ писем на компьютере с 8 ГБ ОЗУ — абсолютно реальная задача. Ключевые моменты:
- Используйте потоковую обработку данных — не загружайте все сразу
- Выбирайте легковесные, но эффективные модели (all-MiniLM-L6-v2, Phi-3 mini)
- Храните векторную базу на диске с persistent-режимом ChromaDB
- Оптимизируйте чанкирование под структуру писем
- Настройте систему подкачки и приоритеты процессов
Первые результаты вы получите уже после обработки 5-10% данных. Полная индексация 60 ГБ займет от нескольких дней до недели на слабом железе, но система будет работать в фоне. Главное — вы получите полностью приватную, локальную систему поиска по вашему архиву писем, которая не отправляет ваши данные в облако.