Перейти к содержанию
Публикация AiManual

ИИ-память из переписки в Telegram: как устроен конвейер memory.tg без векторной базы

Разбор кейса memory.tg: как переписка в Telegram превращается в карточки людей, проектов и задач для ассистента без RAG с векторной базой. Архитектура ночного к

Коротко

Что будет в материале

  1. 01

    Почему классический RAG с векторной базой не подходит для переписки в Telegram

  2. 02

    Архитектура конвейера memory.tg: от сообщения до карточки

  3. 03

    Цифры проекта memory.tg: масштаб и стоимость

  4. 04

    Типичные поломки и как их обходят

memory.tg собирает персональную память для ассистента из переписки в Telegram и не использует векторную базу. Сообщения группируются по чатам, уходят в DeepSeek со строгой JSON-схемой, а на выходе появляются карточки людей, проектов, тем, фактов и задач. Каждая карточка хранит ссылку на исходное сообщение, поэтому любой факт можно поднять из первоисточника.

Конвейер запускается ночью: Telethon забирает только новые сообщения, таблица состояния отсекает уже обработанные, а готовые карточки выгружаются в markdown-файлы для Obsidian.

Отказ от классического RAG с векторной базой здесь не идеологический. Переписка плохо делится на чанки, один факт часто собирается из реплик в разных чатах, а один человек в разных диалогах записан под разными именами. Векторный поиск на таких данных выдаёт ложные нулевые результаты: факт в базе есть, а поиск его не находит.

Почему классический RAG с векторной базой не подходит для переписки в Telegram

Векторный поиск ищет по смыслу и хорошо работает на однородных текстах: статьи, документация, регламенты, тикеты. Диалог в мессенджере устроен иначе: короткие реплики, десятки параллельных веток, ответы через сутки, вложения без текста. Эмбеддинг упирается в способ упаковки данных, а не в качество модели.

Три свойства переписки, которые ломают векторный поиск

  1. Чанки режут диалог на куски без смысла. Классический конвейер нарезает текст окнами по 300-800 токенов с перекрытием. В статье или документации окно почти всегда содержит законченную мысль. В переписке реплики короткие, а смысл держится на соседних сообщениях: вопрос, ответ, ссылка, уточнение через сутки. Окно ловит половину обсуждения, и вектор описывает огрызок, а не факт.
  2. Один факт собирается из реплик в разных чатах. В личке приходит «он сказал, что переезжает», в рабочем чате позже появляется «Илья подтвердил адрес», в третьем чате всплывает «скинь данные для договора». Ни один чанк не содержит факта целиком, а близость векторов не связывает три разных чата между собой. Поиск вернёт один из трёх фрагментов и промолчит про остальные.
  3. Разные имена одного человека не унифицируются. В переписке человек выступает как @username, как имя в профиле, как фамилия в подписи, как прозвище и просто как «он». Запрос «Илья Егоров» не находит тред, где тот же человек записан как ilya_dev, потому что строки разные, а эмбеддинг имени не несёт информации о том, что это один и тот же контакт.

Во всех трёх случаях вопрос не в embedding-модели, а в представлении данных. У вектора нет полей «человек», «проект», «дата», «задача», поэтому связать реплики из разных чатов он может только через похожесть формулировок, то есть случайно.

Что происходит с памятью, если всё-таки использовать RAG

Ассистент получает top-k чанков и отвечает по ним. Когда нужный фрагмент не попал в выдачу, возможны два сценария: модель говорит «в переписке этого нет», хотя факт сохранён, либо собирает ответ из соседних, менее релевантных кусков. Первый сценарий даёт ложный нулевой результат, второй - неполный факт, который выглядит достоверно и потому опаснее.

Ложные нулевые результаты поиска стали одной из типичных поломок в memory.tg, и обошли их не настройкой модели, а структурой хранения: факт записывается в карточку с обязательной ссылкой на исходное сообщение, поэтому проверка занимает один переход. RAG с векторной базой остаётся рабочим инструментом для документов, статей и справочников, где чанк осмыслен. Для диалогов цена ошибки выше, чем выигрыш от семантического поиска.

Архитектура конвейера memory.tg: от сообщения до карточки

Порядок шагов в конвейере выглядит так:

  1. Telethon ночью забирает новые сообщения из чатов, к которым есть доступ у аккаунта.
  2. Таблица состояния отсекает всё, что уже обрабатывалось ранее.
  3. Сообщения группируются по чату, а не по одному.
  4. DeepSeek обрабатывает группу и возвращает JSON по строгой схеме.
  5. Результат раскладывается в карточки людей, проектов, тем, фактов и задач.
  6. Каждая карточка получает обязательную ссылку на исходное сообщение.
  7. Готовые карточки выгружаются в markdown-файлы для Obsidian.

Группировка по чату сохраняет контекст диалога и заодно снижает число обращений к модели: один вызов покрывает десятки сообщений одной ветки вместо одного вызова на реплику.

Ночной запуск: Telethon и таблица состояния

Telethon работает с Telegram через MTProto от имени пользовательского аккаунта, а не бота. Для личной памяти это принципиально: бот видит только те чаты, куда его добавили, а пользовательский клиент читает собственную историю переписки.

Защита от повторной обработки держится на таблице состояния: пара «chat_id + message_id» уникальна, и повторный запуск просто пропускает уже отмеченные строки. Это даёт идемпотентность. Если прогон упал на середине, следующий старт продолжит с того места, где остановился, и не создаст дублей карточек. Ночное окно выбрано потому, что конвейер не конкурирует с рабочими задачами, а к утру карточки уже готовы.

DeepSeek и строгая JSON-схема: как модель раскладывает сообщения по карточкам

На вход модели уходит группа сообщений одного чата плюс системная инструкция и описание схемы. На выходе ожидается JSON фиксированной структуры: массивы people, projects, topics, facts и tasks, где у каждой записи есть текст и ссылка на сообщение-источник.

{
  "people": [
    {
      "name": "Илья",
      "aliases": ["@ilya_dev", "Егоров И."],
      "facts": [
        {
          "text": "переезжает в другой город",
          "source_message_id": 18422,
          "chat_id": 771
        }
      ],
      "tasks": [
        {
          "text": "прислать адрес для документов",
          "source_message_id": 18430,
          "chat_id": 771
        }
      ]
    }
  ],
  "projects": [],
  "topics": []
}

Строгая схема решает две задачи. Первая: модель заполняет поля, а не пересказывает переписку свободным текстом, поэтому результат сравним между прогонами. Вторая: валидация на стороне кода становится тривиальной. Если JSON не парсится, не хватает source_message_id или имя поля отличается от ожидаемого, карточка не сохраняется, а сообщения возвращаются в очередь на следующий прогон.

Про сам DeepSeek стоит помнить, что у семейства есть открытые веса и облачный API, а бенчмарки отдельных версий расходятся с реальным качеством на узких задачах вроде извлечения сущностей из диалога. Подробнее о том, как читать сравнения DeepSeek с Qwen и GLM, мы разбирали отдельно.

Карточки и выгрузка в Obsidian

Пять типов карточек покрывают почти всё, что вообще извлекается из переписки: люди, проекты, темы, факты, задачи. Факт отвечает на вопрос «что известно», задача фиксирует «что нужно сделать» и обычно привязана к человеку или проекту, тема группирует обсуждения, которые не сводятся к одному человеку.

Выгрузка идёт в markdown-файлы, которые открываются в Obsidian как обычное хранилище заметок. Такая выгрузка оставляет данные читаемыми без кода: файлы можно листать, связывать внутренними ссылками Obsidian и править руками. Ссылка на исходное сообщение в каждой карточке превращает память в проверяемую: любое утверждение ассистента можно сверить с реальной репликой.

Цифры проекта memory.tg: масштаб и стоимость

В публичном описании кейса точных значений нет: ни объёма переписки, ни числа карточек, ни стоимости вызовов модели, ни длительности ночного прогона. Подставлять сюда правдоподобные числа смысла нет, поэтому разберём, какие метрики вообще определяют масштаб такого конвейера и как их считать на своих данных.

Сколько сообщений и карточек обрабатывается за ночь

Ключевая метрика здесь не число сообщений, а число чатов с новой активностью. При группировке по чату количество вызовов модели равно количеству активных чатов за сутки, а не количеству реплик: сотня сообщений в одном рабочем чате даёт один вызов, а не сто. Именно это соотношение определяет и стоимость, и длительность прогона.

Второй показатель, который стоит снимать с самого начала, это доля сообщений, не породивших ни одной карточки. Высокая доля означает одно из двух: либо в выборке много служебного шума (стикеры, «ок», реакции), либо модель не вытягивает извлечение на этом типе переписки.

Стоимость вызовов DeepSeek и распределение усилий

Расход на модель считается просто: входные токены (системная инструкция, описание схемы и текст группы сообщений) плюс выходные (JSON с карточками). Описание схемы повторяется в каждом вызове и даёт фиксированную добавку к входу, поэтому при ночном прогоне на сотни чатов выгоднее держать схему компактной.

По описанию кейса видно другое соотношение: время автора уходит не на вызовы DeepSeek, а на инфраструктуру вокруг них, то есть на дедупликацию, идемпотентность, перезапуски после сбоев и починку сессий Telegram. Вызовы модели предсказуемы, а инфраструктура ломается каждую неделю по-новому.

Типичные поломки и как их обходят

Отмена заданий при смене модели

Если переключить модель на середине обработки, незавершённые задания отменяются, и часть сообщений остаётся без карточек. Пока состояние не сохраняется по каждому чату, понять, что именно потерялось, невозможно.

Решение: коммит результатов по одному чату. Каждая успешно обработанная группа сообщений фиксируется отдельно вместе с отметкой в таблице состояния. Перезапуск после смены модели просто продолжает работу, а не начинает её заново, и не создаёт дублей там, где карточки уже есть.

Ложные нулевые результаты поиска

Симптом: ассистент утверждает, что в переписке ничего нет, хотя факт обсуждался. Причины две. Первая: карточка создана под именем, которого нет в запросе, потому что алиасы не унифицированы. Вторая: сообщение попало в обработанные, но карточку по нему модель не собрала, например из-за обрыва ответа.

Решение складывается из трёх частей: обязательная ссылка на исходное сообщение (без неё карточка не сохраняется), строгая валидация JSON и повторный проход по сообщениям, у которых нет ни одной связанной карточки. Алиасы собираются в одну запись человека, а не живут отдельными сущностями.

Отвал сессий Telegram и доступ из России через MTProto-прокси

Сессия Telethon живёт не вечно: она может отвалиться после смены устройства, при подозрительной активности или из-за политики самого Telegram. Симптом узнаваемый: ночной прогон стартует и падает на авторизации, а карточки за сутки просто не появляются.

Решение: хранить строку сессии вне кода, ловить ошибку авторизации отдельно от прочих сбоев и уведомлять о необходимости повторного входа. Полезно держать отдельный аккаунт под конвейер, чтобы сбой не затрагивал основную переписку.

Доступ из России добавляет ещё один слой: подключение идёт через MTProto-прокси, а прокси это дополнительная точка отказа. Он может замедлиться, умереть или потребовать ротации. Практический вывод простой: параметры подключения выносятся в конфиг, ретраи описываются явно, а прокси-серверов держат больше одного. Стабильность доступа к Telegram зависит от региона, версии клиента и политики платформы, поэтому рассчитывать на разовую настройку не стоит.

Почему основная работа уходит в инфраструктуру, а не в LLM

Вывод автора кейса звучит неожиданно для проекта про ИИ: основное время съедают дедупликация, идемпотентность и обработка сбоев. Вызов DeepSeek это самая предсказуемая часть конвейера, а ломается всё вокруг него.

Дедупликация и идемпотентность как основа стабильности

Идемпотентность здесь означает простое правило: повторный прогон не меняет результат. Достигается это таблицей состояния, где каждая пара «чат + сообщение» обрабатывается один раз, и коммитом по каждому чату. Если ночью конвейер упал, утром он догонит пропущенное без дублей карточек и без повторной оплаты токенов за уже обработанные сообщения.

Отдельная задача, дедупликация внутри самой памяти: один и тот же факт всплывает в разных чатах и разными словами. Без проверки на повторы карточка человека быстро зарастает пятью версиями одного и того же утверждения.

Слой оптимизации данных и вычислений перед вызовом LLM

Общий принцип, применимый к таким конвейерам, но не описанный в кейсе как часть архитектуры memory.tg: перед дорогим обращением к большой модели данные проходят предварительную обработку. Мусор и служебные сообщения отбрасываются, повторы схлопываются, часть данных достаётся из структурированных таблиц без модели, часть сжимается без потери смысла, а что-то считается заранее. О подходе к сжатию знаний, который снижает объём базы в разы перед отправкой в модель, есть отдельный разбор TAKC.

Метрика здесь не сэкономленные токены, а полезный результат задачи на потреблённые ресурсы. Сэкономить половину входных токенов и получить карточку без половины фактов это плохая сделка, и наоборот: дорогой вызов, который закрывает целый чат и не требует переделки, окупается.

Сравнение с классическим RAG: когда что выбирать

Векторный поиск и структурированные карточки решают разные задачи, и выбор определяется типом данных, а не модой.

КритерийRAG с векторной базойКарточки memory.tg
Тип данныхДокументы, статьи, справочникиДиалоги, переписка, заметки о людях
Единица поискаЧанк и его эмбеддингКарточка и ссылка на исходное сообщение
ПоискПо смыслу, приблизительныйДетерминированный: по имени, проекту, дате
ПрослеживаемостьЧанк без привязки к автору и времениКаждый факт привязан к конкретному сообщению
Источник ошибокПромах выдачи, ложные нулиНеполные карточки, алиасы, сбои прогона
Основная стоимостьЭмбеддинги, индекс, качество чанкингаИнфраструктура, дедупликация, идемпотентность

Плюсы и минусы подхода без векторной базы

Плюсы: каждый факт проверяем по первоисточнику, структура полей задана заранее, нет проблем с нарезкой диалога на чанки, а выгрузка остаётся читаемой без кода. Минусы честнее перечислить сразу: больше инфраструктурной работы, жёсткая зависимость от качества JSON-схемы, ручная поддержка при смене модели и растущая цена дедупликации по мере накопления памяти.

Когда векторный RAG всё ещё уместен

Для больших текстовых корпусов, документации, базы знаний поддержки и поиска по смыслу RAG остаётся рабочим инструментом. Если запрос формулируется словами, а ответ лежит в длинном связном тексте, вектор найдёт его быстрее и дешевле, чем структурированные карточки. Полезно смотреть и на многошаговые схемы поиска, которые итеративно уточняют запрос: пример такого подхода разобран в материале про многошаговый семантический поиск в Amazon Bedrock.

Правило простое: если у вас переписка с людьми и задачами, собирайте структурированные карточки. Если база знаний из документов, стройте векторный поиск. Смешивать оба подхода в одном хранилище стоит только тогда, когда есть понимание, какой из них отвечает за какие запросы.

Как использовать память из Telegram в Obsidian и для ассистента

Структура карточек и ссылки на исходные сообщения

Карточка человека обычно содержит имя, список алиасов, факты с датами и ссылками на сообщения, связанные задачи и упоминания проектов. Карточка проекта собирает участников, решения и открытые вопросы. Ссылка на исходное сообщение обязательна: именно она отличает память от пересказа.

## Илья

Алиасы: @ilya_dev, Егоров И.

### Факты
- Переезжает в другой город (chat 771, msg 18422)
- Отвечает за интеграцию с подрядчиком (chat 812, msg 9031)

### Задачи
- [ ] Прислать адрес для документов (chat 771, msg 18430)

Такой файл одинаково годится и для чтения глазами, и для машинного разбора: структура стабильна, ссылки на источник сохранены, а Obsidian добавляет поиск, граф связей и быструю навигацию между людьми и проектами.

Интеграция с ассистентом: поиск без векторной базы

Ассистент обращается к карточкам по ключевым сущностям: имени человека, названию проекта, теме. Такой поиск детерминирован, и его результат предсказуем: если карточка есть, факт найдётся; если карточки нет, ассистент об этом скажет, а не выдаст похожий по смыслу, но чужой фрагмент.

Дополнительный слой над карточками может проверять, что ассистент не теряет ранее заданные условия задачи. Похожая логика, где проверки требований работают без эмбеддингов и вызовов модели, разобрана в кейсе про intent continuity и верификацию требований. Общая схема памяти и инструментов агента, включая обработку ошибок, описана в разборе архитектуры самописного AI-агента.

Итоги: кому подходит конвейер memory.tg и что учесть

Подход имеет смысл, если вы ведёте активную переписку в Telegram и хотите, чтобы ассистент помнил контекст: кто есть кто, что обсуждалось, что осталось сделать. Если переписка минимальна или речь о базе из документов, выигрыш от такой инфраструктуры не окупится.

Чек-лист для запуска своего конвейера

  • Telethon и отдельный аккаунт под сбор сообщений.
  • Таблица состояния с парой «chat_id + message_id» и коммитом по каждому чату.
  • Группировка сообщений по чату перед отправкой в модель.
  • DeepSeek или другая модель с поддержкой строгого JSON на выходе.
  • JSON-схема с обязательным source_message_id и валидацией перед сохранением.
  • Пять типов карточек: люди, проекты, темы, факты, задачи.
  • Выгрузка в markdown и хранилище Obsidian.
  • MTProto-прокси с параметрами в конфиге и ретраями, если доступ к Telegram нестабилен.
  • Обработка ошибок авторизации отдельно от прочих сбоев.

Ограничения и риски

Сессии Telegram отваливаются, задания отменяются при смене модели, а поиск по памяти иногда даёт ложные нули, пока алиасы и карточки не приведены в порядок. Прокси добавляет точку отказа, ночной прогон нужно мониторить, а схему придётся править по мере появления новых типов сущностей.

Главное ожидание, которое стоит скорректировать заранее: работа уйдёт не в промпты, а в инфраструктуру. Дедупликация, идемпотентность, перезапуски и слежение за доступом к Telegram займут больше времени, чем все вызовы DeepSeek вместе взятые. Зато на выходе получается память, каждый факт в которой можно проверить исходным сообщением.

Подписаться на канал