Представьте: на жёстком диске 36 000 фотографий. Перемешаны: скриншоты, чеки, еда, котики, фото паспорта, мемы из 2010-го. В Gmail — 20 лет переписки: 15 тысяч рассылок, спам, уведомления от древних форумов, письма от бывших работодателей. Руками разгребать — год. Нанимать виртуального ассистента — дорого и небезопасно. Облачный AI — спасибо, но я свои данные в чужие GPU не отправляю.
Встречайте решение: локальный AI-агент, который сам сортирует фотки, чистит почту и не просит доступ к интернету. В этой статье я расскажу, как собрать такого агента из Ollama, LLaVA, Gmail API и пары скриптов на Python. Спойлер: это работает, но есть подводные камни, на которых я набил шишки. Поехали.
Проблема: 36 000 файлов и 20 лет почты — это не просто цифры
Когда я впервые посмотрел на содержимое своего фотоархива, мне стало физически плохо. 36 000 файлов — это не просто "много". Это полный хаос:
- Фотографии с телефона — последние 10 лет, но без альбомов.
- Скриншоты экранов — от билетов до ошибок кода.
- Фото документов — паспорт, права, договоры — все в одной куче.
- Десятки гигабайт дублей и похожих изображений.
С почтой та же история. Мой основной Gmail (зарегистрирован в 2006-м) дорос до 18 ГБ писем. Из них реально нужных — может, 100 штук. Остальное — рассылки, подтверждения регистраций, уведомления от сервисов, которые уже 10 лет как закрылись. Удалять руками — бессмысленно. Фильтры Gmail — примитивны.
Железобетонное правило: Не трогайте оригиналы, пока не сделаете бекап. У меня на внешнем диске лежит raw-копия всех файлов. Если AI что-то напутает — я не потеряю данные.
Решение: AI-агент на локальных моделях. Почему не облако?
Облачные сервисы (Google Photos, Dropbox AI) хороши, но есть нюанс: они видят все ваши фотки и письма. Я не параноик, но когда AI начинает описывать содержимое личных фото — мне это не нравится. К тому же заливать 36 000 файлов в облако — дохлый номер по времени и трафику.
Локальные модели решают обе проблемы: данные никуда не уходят, скорость ограничена только вашим железом. Мы используем Ollama (платформа для запуска LLM инференса), LLaVA (мультимодальная модель для анализа изображений) и Python для склеивания всего в единый пайплайн.
Кстати, если у вас слабая видеокарта или вообще только CPU — не беда. Я покажу, как адаптировать решение под $20-сервер. А если хотите полноценный AI-агент прямо на смартфоне — вот вам статья про A.I.R.I: превращаем Android в локальный AI-сервер — там как раз описан похожий подход.
Архитектура: как заставить AI-агента не ломаться на половине
Мой агент состоит из трёх модулей:
- Сборщик данных — проходит по файловой системе, собирает метаданные (EXIF, даты, размеры). Для почты — дергает Gmail API.
- Анализатор — отправляет каждый файл (или сэмпл) в локальную модель для описания и категоризации.
- Исполнитель — на основе анализа сортирует файлы в папки, удаляет дубли, помечает письма для архивации или удаления.
База — Ollama с моделями LLaVA 1.6 (7B) для фото и Qwen2.5 (7B) для текста писем. На моём ноутбуке с RTX 4060 (6 ГБ VRAM) одна обработка фото занимает ~1.5 секунды. Для 36 000 фото это ~15 часов непрерывной работы. Звучит долго, но это в 100 раз быстрее, чем делать руками.
Почему не ChatGPT API? Во-первых, цена: 36 000 запросов к GPT-4o — это $200-300. Во-вторых, конфиденциальность. В-третьих, скорость: локальная модель выдаёт ответ за 0.5-2 сек на моём GPU. API может тормозить из-за очереди.
Кстати, если вас интересует, как организовать бесконечную очередь задач и не уронить сервер — почитайте статью про RAG для 4 миллионов PDF. Там описана архитектура очередей, которая идеально ложится на наш сценарий.
Шаг 1: Сортировка фото — как не перепутать кота с кредиткой
1 Установка Ollama и запуск LLaVA
Устанавливаем Ollama любой версии (я на 0.5.0, но свежие работают так же):
curl -fsSL https://ollama.com/install.sh | sh
ollama pull llava:7b # последняя на 14.06.2026 версия 1.6
ollama run llava:7b
Проверяем, что модель отвечает. Теперь пишем скрипт-агент, который будет обходить папку с фото, отбирать кадры (не все 36 000 сразу) и отправлять в модель с промптом:
import os
import json
import subprocess
from PIL import Image
import concurrent.futures
def analyze_image(image_path):
prompt = "Опиши, что на фотографии. Ответь одним словом или короткой фразой: 'еда', 'документ', 'скриншот', 'пейзаж', 'портрет', 'животное', 'город', 'интерьер', 'прочее'. "
# Используем ollama через subprocess (или библиотеку)
result = subprocess.run(
['ollama', 'run', 'llava:7b', prompt],
input=image_path,
capture_output=True,
text=True
)
return result.stdout.strip()
# Пример с пулом потоков
paths = [...] # список файлов
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(analyze_image, paths))
Этот код — каркас. На практике я добавил извлечение EXIF, хэши для поиска дублей и буферизацию результатов. Без буфера вы рискуете, что при сбое (а они будут) придётся перезапускать с нуля. Я сохраняю промежуточные результаты в SQLite.
2 Категоризация и переименование
LLaVA часто ошибается. Например, фото паспорта может определить как "документ" (хорошо), а еду — как "натюрморт" (не очень). Чтобы повысить точность, я использую Chain-of-Thought промпт:
prompt = """Ты классификатор фотографий. Вот изображение. Определи категорию из списка: документ, еда, животное, портрет, пейзаж, скриншот, интерьер, город, прочее. Ответь только названием категории.
Если на фото есть лицо человека, это 'портрет' (даже если это селфи с едой). Если виден экран — 'скриншот'. Если расплывчато — 'прочее'."""
После категоризации я сортирую файлы по папкам вида /Фото/2024/2024-06-скриншоты. Даты беру из EXIF, если нет — из времени создания файла. Помогает избежать путаницы.
Грабли, на которые я наступил: LLaVA иногда выдаёт категорию на другом языке (например, "food" вместо "еда"). Нормализуйте ответы. Я добавил маппинг: food→еда, screenshot→скриншот. И всегда проверяйте, что ответ входит в допустимый список. Иначе — "прочее".
Если хотите готовое решение, посмотрите AI File Sorter 1.5 — как раз про локальную сортировку и переименование тысяч фото с помощью LLaVA.
Шаг 2: Очистка почты — AI-агент против 20 лет спама
Тут задача сложнее. Письма — не картинки, их надо читать и понимать контекст. Gmail API позволяет вытащить все письма, но лимиты: 10 запросов в секунду на пользователя. Для 20 тысяч писем это ~30 минут только на получение.
Мой подход: сначала агент собирает только метаданные (от кого, тема, дата) — они доступны без полного текста. Затем классифицирует письма локальной моделью. Если письмо подозрительное (спам, рассылка) — удаляет без открытия. Если важное — помечает звёздочкой.
3 Получение писем через Gmail API
import google.auth
from googleapiclient.discovery import build
creds, _ = google.auth.default()
service = build('gmail', 'v1', credentials=creds)
# Получить список всех писем
results = service.users().messages().list(userId='me', maxResults=500).execute()
messages = results.get('messages', [])
while 'nextPageToken' in results:
page_token = results['nextPageToken']
results = service.users().messages().list(
userId='me', maxResults=500, pageToken=page_token).execute()
messages.extend(results.get('messages', []))
# Для каждого письма получаем ID и тему (через snippet/metadata)
for msg in messages:
meta = service.users().messages().get(
userId='me', id=msg['id'], format='metadata',
metadataHeaders=['From', 'Subject']).execute()
headers = meta['payload']['headers']
subject = next(h['value'] for h in headers if h['name'] == 'Subject')
sender = next(h['value'] for h in headers if h['name'] == 'From')
# Отправляем в модель для анализа
Внимание: никогда не удаляйте письма сразу! Сначала переместите их в папку "на удаление" (пометьте меткой). Я создал метку "Удалить_через_30_дней". Через месяц проверяю, что ничего важного не потеряно, и только тогда удаляю окончательно.
4 Классификация писем локальной LLM
Для текста я использую Qwen2.5 7B (она лучше понимает русский и не требует мощного GPU). Промпт:
prompt = f"""Классифицируй письмо:
От: {sender}
Тема: {subject}
Варианты:
- 'спам/реклама' - если это незапрашиваемая реклама или спам
- 'рассылка' - если это подписка, уведомление, рассылка
- 'важное' - если письмо от человека или требует ответа
- 'транзакция' - если это чек, подтверждение заказа, квитанция
- 'прочее' - если не подходит ни под одну
Ответь только одним словом."""
Модель работает стабильно. Ошибки бывают: иногда важное письмо от банка помечает как "рассылка" — это решается добавлением белого списка отправителей. В целом точность ~92% на моём датасете.
Подводные камни: что пошло не так и как чинить
Не буду врать, что всё прошло гладко. Вот типичные проблемы:
- Лимиты Gmail API. Даже при 500 запросах в минуту, если запустить обход сразу всех писем, Google может временно заблокировать аккаунт. Решение: ввести задержку между запросами
time.sleep(0.1)и использовать экспоненциальный backoff. - Зависание модели. LLaVA иногда вешает ответ — бесконечно генерирует токены. Ставьте таймаут на запрос (я ставлю 30 секунд). Если превышен — считаем, что ошибка, и пропускаем файл.
- Нехватка VRAM. Если запускать две модели одновременно (фото и текст), 6 ГБ может не хватить. Решение: поставить очередь задач — сначала обработать все фото, потом письма. Или использовать CPU-версию для текстовой модели (медленно, но надёжно).
- Контекстная слепота. Агент видит только одно фото или одно письмо за раз, но не учитывает общий контекст всей коллекции. Как с этим бороться — описано в статье про контекстную слепоту агентов. Я добавил базу данных, где фиксирую историю решений, и модель иногда запрашивает похожие случаи.
Ещё один грабль — дубли в фото. Одно и то же фото может лежать в разных папках или с разным именем. LLaVA их не распознаёт. Для сравнения я использую средний хеш (pHash) — это отсекает 99% дублей. Если хотите готовую утилиту, Immich из коробки умеет находить дубли — гляньте Makimus-AI: ваш личный Google Photos, который не шпионит.
Результаты: что получилось после месяца работы
После прогона через мой AI-агент:
- Фото: 36 000 файлов отсортированы по годам и категориям. Удалены 4 000 дублей. Найдено 200 фото документов (раньше я искал их неделями).
- Почта: из 18 000 писем удалено 12 000 (спам и рассылки). Оставлено 6 000 важных и транзакционных. Ручная проверка показала, что случайно удалено всего ~50 писем (восстановил из корзины).
- Затраты: $0 — всё локально. Электроэнергия: ~500 рублей. Время: ~20 часов процессорного времени, из них 90% — работа модели.
Я остался доволен, но без боли не обошлось. Если вы решите повторить, начните с малого: возьмите 100 фото и 10 писем. Доведите пайплайн до ума, а только потом запускайте на полном датасете. И обязательно логируйте всё — иначе никогда не поймёте, почему агент решил удалить важный контракт.
Что дальше? Агент-оркестратор и база знаний
Сейчас я развиваю эту идею: хочу, чтобы агент не просто сортировал, а умел отвечать на вопросы по содержимому архива. Например, "Покажи все счета за электричество за 2023 год". Для этого нужна база знаний, в которую модель будет индексировать все документы и письма. Как построить такую базу — отлично расписано в руководстве по построению эффективной базы знаний для AI-моделей.
Если у вас много документов (чеки, договоры, PDF) — рекомендую изучить обработку 4700 инженерных PDF за 45 минут — системный дизайн там пригодится. А для распознавания текста на фото документов — гайд от OCR к ADE.
И не забывайте: AI-агент — это инструмент. Он тупой, как пробка, и делает только то, что вы ему скажете. Поэтому сначала опишите чёткие правила, потом внедряйте. Удачи, и пусть ваш архив будет в порядке.
Бонус-совет: не гонитесь за точностью 100%. Лучше пусть агент пересортирует 5% фото не туда, чем вы будете тратить недели на ручную классификацию. Исправляйте категории на лету, когда найдёте ошибку — добавляйте пример в few-shot промпт. Через 1000 итераций модель станет заметно точнее.