Repository navigation
Память длинных чатов стала мастер-снимком строгой схемы: он собирается пакетами,
проверяется после каждого ответа модели и пересобирается вручную с прогрессом.
Заодно исправлены две тихие потери: снимок обрезался до 6000 символов, а
выбранный вручную бюджет хода в 1 млн сбрасывался при каждой загрузке.
Добавлено — иерархическая пакетная память
Хроника длинного чата была свободным конспектом: не больше шести кусков за ход и
только после ответа ассистента, без пауз, повторов и прогресса. Большой бэклог
(импорт на тысячу сообщений) догонялся за несколько ходов, пересобрать память
вручную было нельзя, а при повторных сжатиях конспект мог молча выронить пункт
списка или персонажа второго плана.
- Мастер-снимок строгой схемы из четырёх разделов: «Хроника и событийный
каркас», «Активные персонажи и их статусы», «Фактологический реестр и лор»,
«Списки и медиа-анкоры». Это Markdown: его читают во вкладке «Память» и в
экспорте, в контекст он идёт как есть. - Свёртка по пакетам:
снимок_N = слияние(снимок_{N−1}, пакет_N), пакеты по
20 сообщений (не больше 80 000 символов). Один движок и один промпт — и для
прохода после хода, и для полной пересборки. Новые модули:
backend/hierarchical_memory.py(чистое ядро, только stdlib) и
backend/memory_service.py(адаптер к БД). - Проверка каждого ответа: нет раздела, пустой ответ или снимок «сдулся»
больше чем вдвое — модель получает корректирующий ход (до двух). Брак после
них в память не пишется, указатель «учтено до» не двигается. - Обрыв лимитом вывода (обёртка
<master_state>не закрыта или шлюз сообщил
finish_reason=lengthу непустого ответа) — не брак, а сразу сжатие хроники с
арками и один повтор слияния: корректирующий ход с тем же лимитом оборвался бы
снова. Если реестр и списки сами больше лимита вывода, модель не зовётся —
понятная ошибка вместо платных попыток. - Страж записей: персонаж, запись реестра или элемент списка, которые модель
выронила, возвращаются из предыдущего снимка дословно. Обновлённую на месте или
слегка переименованную запись страж узнаёт (регистр, ё/е и пунктуация не
важны) и не дублирует; записи с одинаковым началом («Linkin Park — «Numb»» и
«Linkin Park — «In the End»») друг друга не прикрывают. - Бюджет снимка 12 000 токенов: сверх него старые записи хроники сворачиваются
в арки[#a–#b] Арка «…»; последние 12 записей хроники и разделы персонажей,
реестра и списков не трогаются — из ответа сжатия берётся только хроника, а
ответ без свежих записей хроники отвергается. - Пауза и повторы: 1,5 с между любыми запросами памяти (слияние, факты,
сжатие); при 429, таймауте, 5xx и обрыве связи — до 4 повторов через 2/4/8/16 с
(не меньшеRetry-After). Отказ по ключу, неверный запрос и фильтр провайдера —
ошибка сразу, без платных повторов. «Остановить» и «Сбросить память» обрывают
паузы сразу. Задания памяти идут по одному на процесс, остальные ждут в очереди. - Сбой обновления виден и не повторяется на каждом ходу: ошибка ежеходного
прохода записывается и показывается во вкладке «Память» («Память не
обновилась: … Следующая попытка — после ЧЧ:ММ или кнопкой «Догнать»»), а
следующий проход ждёт 10 мин, 20, 40… до 6 ч. - Лимит вывода модели памяти:
max_tokensпамяти и рабочий бюджет снимка
прижимаются к тому, что модель успевает написать (по карте LiteLLM). У модели
по умолчаниюgpt-4o(вывод до 16 384) запрос с 17 824 получал бы 400 на
каждом проходе. Модель неизвестна, а провайдер отвергmax_tokens— один
повтор сDEFAULT_MAX_TOKENSи предупреждение. - Вкладка «Память» → «Мастер-память этого чата»: размер снимка, до какого
сообщения он учтён и сколько сообщений ждут сжатия; предупреждения памяти и
ошибка последнего обновления; «Пересобрать с нуля» (подтверждение с оценкой
числа платных запросов), «Догнать» (с подтверждением, если пакетов больше
трёх), «Экспорт .md», «Сбросить память»; прогресс[Обработано 140/800 сообщений | Сжато до 4 200 токенов]с номером пакета и кнопкой «Остановить»;
поля «Размер пакета», «Пауза между запросами», «Бюджет снимка» (до 40 000 — с
предупреждением, если модель памяти столько не напишет). Итог задания честный:
«Сжимать нечего — весь чат в окне» вместо «0 сообщений → 0 токенов». - Пересборка идёт в буфер, а старый снимок работает до конца — подмена одним
коммитом. Прерванную перезапуском сервера пересборку продолжает кнопка
«Продолжить» или обычные проходы после ходов; остановленную кнопкой — только
«Продолжить», а проходы тем временем обновляют живой снимок. - Монитор токенов: из чего складывается ход — системный промпт и якоря,
мастер-снимок и факты, дословное окно; доля от бюджета хода и от лимита модели
(MODEL_CONTEXT_LIMIT, 1 000 000). Та же разбивка — в инспекторе хода. Для
групповых чатов не считается (tiers: null): ход группы собирается иначе. - Снимок входит в бюджет хода: снимок, база знаний, аватары и статичный хвост
резервируются до обрезки истории. Раньше они шли сверх бюджета — при бюджете
30 000 в модель уходило до 41 тыс. токенов. - Факты пакета, чей запрос фактов не удался (сбой, остановка), доизвлекает
следующий пакет. - В пакетах — настоящие имена авторов (персона, персонаж, говорящий в группе) и
время в часовом поясе чата; base64, data-URI, HTML-оформление и<think>
вырезаются, вложения подписываются типом и именем файла. - Сводки 2.4.x (свободный конспект) переводятся в новую схему ближайшим проходом
без отдельных затрат; чистая конвертация — «Пересобрать с нуля». - API:
GET/DELETE /api/sessions/{id}/memory,POST …/memory/rebuild,
POST …/memory/cancel,GET …/memory/export,report.tiers(у групп —
tiers_unavailable: "group") вGET /api/sessions/{id}/context; в статусе —
snapshot.last_error,snapshot.retry_after,settings.max_snapshot_tokens,
job.warnings,job.snapshot_tokens,staging.paused(см.
docs/API.md; подробно — docs/HORAE.md).
Исправлено — бюджет 1M сбрасывался на 200k при каждой загрузке
В 1.12.0 сохранённое окно контекста в 1 млн токенов обещали опускать до 200 тыс.
один раз, но проверка шла при каждой загрузке интерфейса: выбранный вручную
1 000 000 в «Окне контекста» тихо возвращался к 200 000 при следующем открытии
страницы. Теперь миграция выполняется один раз (флаг ctx_budget_v: 2 в
настройках ui), и выбранный после неё 1 млн остаётся.
Исправлено — снимок памяти обрезался до 6000 символов
_adopt_summary сохранял авто-сводку слайсом [:6000], так же резался и буфер
пересборки. Хвостовые разделы — персонажи, локации, незакрытые линии — пропадали
молча, без ошибки и предупреждения, и модель «забывала» договорённости из начала
чата. Теперь снимок не режется: от мусора защищает проверка ответа (снимок
длиннее max(200 000, 8 × бюджет снимка) символов не принимается), от
раздувания — бюджет снимка и сжатие хроники в арки.
Изменено — активное окно по умолчанию 50
Дословно модель видела последние 20–23 сообщения — меньше одной связной сцены, а
всё старше видит только через снимок.
DEFAULT_WINDOW = 50на сервере и в интерфейсе: дословно идут 50–53 сообщения.- Сохранённое прежнее значение по умолчанию 20 один раз поднимается до 50 (флаг
memory_defaults_v: 2); выставленное после этого вручную 20 не трогается. - Кнопки окна во вкладке «Память»: 20 / 50 / 80 / 150 / «вся история» (было
20 / 40 / 80 / 150).
Обновление
- Изменился серверный код, поэтому после
git pullсервис нужно перезапустить.
В Docker код бэкенда и бота запечён в образ — нужна пересборка:
docker compose up -d --build(простойrestartоставит старый бэкенд со
свежим интерфейсом). Без Docker — перезапускstart.shили systemd-сервиса. - Новые переменные
.env— все необязательные, умолчания заданы в коде:
MEMORY_BATCH_SIZE=20,MEMORY_BATCH_CHARS=80000,MEMORY_DELAY_MS=1500,
MEMORY_SNAPSHOT_TOKENS=12000,MEMORY_MAX_RETRIES=4,MEMORY_TEMPERATURE=0.2,
MEMORY_MAX_BATCHES_PER_TURN=6,MODEL_CONTEXT_LIMIT=1000000(образец — в
.env.example). Новых зависимостей и ручных миграций БД нет. summary_model(«Подключение») должна выдавать длинный ответ: снимок
переписывается целиком, и потолок вывода —max(DEFAULT_MAX_TOKENS, round(бюджет снимка × 1,4) + 1024), то есть 17 824 токена при бюджете 12 000.
Если LiteLLM знает модель, вывод и бюджет снимка прижимаются к её лимиту сами
(вкладка «Память» предупредит); иначе, если модель столько не выдаёт, уменьшите
«Бюджет снимка». Если же в лимит вывода не влезают реестр и списки снимка
(ошибка «реестр и списки больше лимита вывода»), уменьшение бюджета не поможет:
нужна модель с большим выводом или ручная чистка этих разделов.- Постоянная стоимость выросла. Снимок до «Бюджета снимка» (12 000 токенов)
идёт в каждый ход — в хвост, вне кэшируемого префикса; до 2.5.0 сводка была не
длиннее 6000 символов (1,5–2,5 тыс. токенов). И раз вsummary_every
сообщений модель памяти переписывает снимок целиком — до 17 824 токенов вывода
на обновление, в 5–10 раз больше, чем в 2.4.x. Снизить: меньше «Бюджет
снимка», реже «Как часто обновлять сводку» (каждые 20 сообщений вместо 10),
дешёваяsummary_model. - Пересборка платная: «Пересобрать с нуля» стоит ⌈(сообщений − окно) / размер
пакета⌉ запросов слияния (плюс изредка сжатие хроники). Факты заново не
извлекаются там, где они уже есть: у чата с фактами пересборка запросов фактов
не делает, а без фактов (после «Сбросить память», у сводки до 2.4.0) — до
одного на пакет. Чат на 1 050 сообщений при окне 50 и пакете 20 — около 50
запросов кsummary_model. Оценку показывает подтверждение перед запуском.
Сама по себе после обновления пересборка не запускается: сводки 2.4.x
переходят на новую схему обычными проходами после ходов, а сводки до 2.4.0
пересобираются фоном, как и в 2.4.0. - Окно выросло с 20 до 50 сообщений, поэтому вход длинного чата на каждом ходу
вырастет примерно на 30 реплик. Вернуть прежнее — «Память» → «Активное окно» →
20.