memory.tg собирает персональную память для ассистента из переписки в Telegram и не использует векторную базу. Сообщения группируются по чатам, уходят в DeepSeek со строгой JSON-схемой, а на выходе появляются карточки людей, проектов, тем, фактов и задач. Каждая карточка хранит ссылку на исходное сообщение, поэтому любой факт можно поднять из первоисточника.
Конвейер запускается ночью: Telethon забирает только новые сообщения, таблица состояния отсекает уже обработанные, а готовые карточки выгружаются в markdown-файлы для Obsidian.
Отказ от классического RAG с векторной базой здесь не идеологический. Переписка плохо делится на чанки, один факт часто собирается из реплик в разных чатах, а один человек в разных диалогах записан под разными именами. Векторный поиск на таких данных выдаёт ложные нулевые результаты: факт в базе есть, а поиск его не находит.
Почему классический RAG с векторной базой не подходит для переписки в Telegram
Векторный поиск ищет по смыслу и хорошо работает на однородных текстах: статьи, документация, регламенты, тикеты. Диалог в мессенджере устроен иначе: короткие реплики, десятки параллельных веток, ответы через сутки, вложения без текста. Эмбеддинг упирается в способ упаковки данных, а не в качество модели.
Три свойства переписки, которые ломают векторный поиск
- Чанки режут диалог на куски без смысла. Классический конвейер нарезает текст окнами по 300-800 токенов с перекрытием. В статье или документации окно почти всегда содержит законченную мысль. В переписке реплики короткие, а смысл держится на соседних сообщениях: вопрос, ответ, ссылка, уточнение через сутки. Окно ловит половину обсуждения, и вектор описывает огрызок, а не факт.
- Один факт собирается из реплик в разных чатах. В личке приходит «он сказал, что переезжает», в рабочем чате позже появляется «Илья подтвердил адрес», в третьем чате всплывает «скинь данные для договора». Ни один чанк не содержит факта целиком, а близость векторов не связывает три разных чата между собой. Поиск вернёт один из трёх фрагментов и промолчит про остальные.
- Разные имена одного человека не унифицируются. В переписке человек выступает как @username, как имя в профиле, как фамилия в подписи, как прозвище и просто как «он». Запрос «Илья Егоров» не находит тред, где тот же человек записан как ilya_dev, потому что строки разные, а эмбеддинг имени не несёт информации о том, что это один и тот же контакт.
Во всех трёх случаях вопрос не в embedding-модели, а в представлении данных. У вектора нет полей «человек», «проект», «дата», «задача», поэтому связать реплики из разных чатов он может только через похожесть формулировок, то есть случайно.
Что происходит с памятью, если всё-таки использовать RAG
Ассистент получает top-k чанков и отвечает по ним. Когда нужный фрагмент не попал в выдачу, возможны два сценария: модель говорит «в переписке этого нет», хотя факт сохранён, либо собирает ответ из соседних, менее релевантных кусков. Первый сценарий даёт ложный нулевой результат, второй - неполный факт, который выглядит достоверно и потому опаснее.
Ложные нулевые результаты поиска стали одной из типичных поломок в memory.tg, и обошли их не настройкой модели, а структурой хранения: факт записывается в карточку с обязательной ссылкой на исходное сообщение, поэтому проверка занимает один переход. RAG с векторной базой остаётся рабочим инструментом для документов, статей и справочников, где чанк осмыслен. Для диалогов цена ошибки выше, чем выигрыш от семантического поиска.
Архитектура конвейера memory.tg: от сообщения до карточки
Порядок шагов в конвейере выглядит так:
- Telethon ночью забирает новые сообщения из чатов, к которым есть доступ у аккаунта.
- Таблица состояния отсекает всё, что уже обрабатывалось ранее.
- Сообщения группируются по чату, а не по одному.
- DeepSeek обрабатывает группу и возвращает JSON по строгой схеме.
- Результат раскладывается в карточки людей, проектов, тем, фактов и задач.
- Каждая карточка получает обязательную ссылку на исходное сообщение.
- Готовые карточки выгружаются в 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 вместе взятые. Зато на выходе получается память, каждый факт в которой можно проверить исходным сообщением.