Files
OmniRoute/docs/i18n/ru/docs/frameworks/MEMORY.md
Diego Rodrigues de Sa e Souza 8feea123bb feat(docs): mirror every docs/ page in all 65 locales (#14106)
* feat(docs): mirror every docs/ page in all 65 locales

Extends the documentation mirrors from the 22-page core set (#13940) to
every Markdown page under docs/: 152 sources x 65 locales = 9,880 mirrors
(6,208 new), language bars rewritten for the full locale list, state
adopted so the blocking drift gate now covers all 152 pages.

run-translation.mjs: an oversized block made only of table rows or list
items (PROVIDER_REFERENCE.md 244-row table, FREE_TIERS.md 71-item list) is
cut at item boundaries and rejoined without a blank line — the single
16-40 KB request outlived the backend socket for verbose scripts. 48
older mirrors whose tables had lost rows were retranslated with --force.

* docs(i18n): refresh mirrors for the sources the base changed since the branch cut

Section-level retranslation of the 29 docs (and README.md) whose source
or mirrors moved on release/v3.8.51 during the run, then state adoption;
the drift gate is green again on the merged tree.
2026-09-18 13:16:46 -03:00

98 KiB
Raw Blame History

Memory System (Русский)

🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW


Источник истины: src/lib/memory/ и src/app/api/memory/ Последнее обновление: 2026-06-28 — v3.8.40 (отключение по умолчанию + доработка квантования int8)

OmniRoute предоставляет постоянную память диалогов, привязанную к API-ключу (и необязательно к идентификатору сессии). Воспоминания автоматически извлекаются из ответов LLM с помощью облегчённого сопоставления с регулярными выражениями и добавляются в последующие запросы в виде начального системного сообщения (или первого пользовательского сообщения для провайдеров, которые отклоняют системную роль).

По умолчанию память ОТКЛЮЧЕНА (v3.8.30+). Значение DEFAULT_MEMORY_SETTINGS.enabled теперь равно false (src/lib/memory/settings.ts). Включение памяти добавляет до maxTokens (~2k) извлечённого контекста в каждый запрос чата, за что взимается плата — это может стать неожиданными расходами для новых установок и клиентов, которые управляют контекстом самостоятельно. Явно включите её в разделе Настройки → Память (вкладка MemorySkillsTab отображает предупреждение о расходе токенов, когда память включена). Клиент может отключить память для отдельного запроса с помощью заголовка x-omniroute-no-memory (true/1/yes) — см. таблицу заголовков запросов в API_REFERENCE.md. Запрос без памяти устанавливает memoryOwnerId = null, что отключает как добавление памяти, так и добавление навыков для этого запроса (open-sse/handlers/chatCore/headers.ts::isNoMemoryRequested).

Память изолирована на уровне API-ключа, а не пользователя — все запросы, аутентифицированные одним и тем же API-ключом, используют общий пул памяти с возможностью дополнительной изоляции по sessionId.

Архитектура

Клиент → /v1/chat/completions (apiKeyInfo определён ранее)
  → handleChatCore() [open-sse/handlers/chatCore.ts]
    → resolveMemoryOwnerId(apiKeyInfo)        # извлекает id
    → getMemorySettings()                     # кэшированные настройки
    → shouldInjectMemory(body, {enabled})     # проверка условия
    → retrieveMemories(apiKeyId, config)      # SQL + FTS5 + необязательный векторный поиск
    → injectMemory(body, memories, provider)  # системное или пользовательское сообщение
  → вызов вышестоящего провайдера
  → при получении ответа: extractFacts(text, apiKeyId, sessionId)  # неблокирующий вызов
    → setImmediate → createMemory(fact) для каждого совпадения
                   → embed(content) + upsertVector(id, vec)

Точки вызова добавления и извлечения подключены в open-sse/handlers/chatCore.ts (ищите retrieveMemories, injectMemory и extractFacts).

Архитектура движка (трёхуровневое разрешение)

Memory Engine определяет путь извлечения во время выполнения с учётом доступной инфраструктуры и настроек. Существуют три уровня, применяемые в порядке приоритета:

  ┌─────────────────────────────────────────────────────────────┐
  │  УРОВЕНЬ 0 — Ключевые слова (FTS5)                           │
  │  Доступность определяется проверкой: FTS5 используется,      │
  │  когда сборка SQLite поддерживает его                        │
  │  (better-sqlite3 / node:sqlite / bun:sqlite); недоступен     │
  │  в сборках без FTS5 (например, sql.js/WASM —                 │
  │  "no such module: fts5"). Используется при strategy =        │
  │  "exact" или как резервный вариант; поле keyword в статусе   │
  │  движка отражает результат проверки.                         │
  └──────────────────────────────────┬──────────────────────────┘
                                     │ strategy = semantic|hybrid?
                                     ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  УРОВЕНЬ 1 — Встроенный векторный поиск (sqlite-vec)         │
  │  sqlite-vec v0.1.9 загружается через db.loadExtension().     │
  │  Полный перебор KNN по векторам Float32. Активен, когда:     │
  │   • sqlite-vec loadExtension выполняется успешно             │
  │   • Доступен источник эмбеддингов (remote | static |         │
  │     transformers), способный создать Float32Array            │
  │   • Существует таблица vec_memories (создаётся при первом    │
  │     вызове ready())                                          │
  └──────────────────────────────────┬──────────────────────────┘
                                     │ qdrant.enabled?
                                     ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  УРОВЕНЬ 2 — Qdrant (подключаемая внешняя векторная БД)      │
  │  Когда включён, заменяет sqlite-vec для semantic/hybrid.     │
  │  Требует запущенный экземпляр Qdrant и настроенные host/port.│
  └─────────────────────────────────────────────────────────────┘

Понижение уровня выполняется автоматически и прозрачно:

  • Если sqlite-vec не удаётся загрузить, уровень 1 недоступен → выполняется переход на уровень 0.
  • Если источник эмбеддингов возвращает ошибку, уровень 1 переходит на уровень 0.
  • Если Qdrant неработоспособен, уровень 2 переходит на уровень 1 (или на уровень 0, если уровень 1 также недоступен).

Источники эмбеддингов

Слой эмбеддингов (src/lib/memory/embedding/) определяет, какой источник использовать, на основе MemorySettingsExtended.embeddingSource:

Источник Описание Требуется ключ Холодный старт
remote Использует API эмбеддингов настроенного провайдера (OpenAI, Cohere и т. д.) Да Отсутствует
static Локальные табличные эмбеддинги через potion-base-8M (WordPiece + усреднение) Нет ~200 мс
transformers Локальный ONNX-инференс через @huggingface/transformers v4, all-MiniLM-L6-v2 Нет ~3 с + ~400 МБ ОЗУ
auto Выбор во время выполнения: remote (при наличии ключа) → static → transformers → null Зависит Зависит

Порядок выбора для auto:

  1. Найти первого провайдера в listEmbeddingProviders(), у которого hasKey === trueremote.
  2. Если settings.staticEnabled === truestatic.
  3. Если settings.transformersEnabled === truetransformers.
  4. В противном случае → null (переход к полнотекстовому поиску FTS5 по ключевым словам).

Кэш эмбеддингов (src/lib/memory/embedding/cache.ts) использует хранящуюся в памяти LRU-карту с ключом ${source}:${model}:${dim}:${sha256(text)}, ограниченную MEMORY_EMBEDDING_CACHE_MAX записями (по умолчанию 1000) и TTL MEMORY_EMBEDDING_CACHE_TTL_MS (по умолчанию 5 мин). Кэш является общим для всех вызывающих компонентов на протяжении жизненного цикла процесса.

Гибридный RRF (k=60)

Когда strategy = "hybrid" и векторное хранилище доступно, при извлечении используется Reciprocal Rank Fusion для объединения результатов FTS5 и векторного поиска:

RRF(d) = Σ  1 / (k + rank_i(d))      где k = 60 (настраивается через MEMORY_RRF_K)
          i

В частности:

  1. Выполнить поиск FTS5 → ранжированный список R_fts (позиции 1..N).
  2. Выполнить векторный поиск KNN → ранжированный список R_vec (позиции 1..M).
  3. Для каждого уникального memoryId:
    rrf_score = 1/(60 + fts_rank) + 1/(60 + vec_rank) (0, если отсутствует в списке).
  4. Отсортировать по rrf_score DESC и применить последовательный отбор с учётом бюджета токенов.

Известно, что RRF эффективен без необходимости нормализовать оценки между разнородными системами поиска. Значение k=60 по умолчанию взято из оригинальной статьи Cormack et al. и хорошо работает для небольших корпусов (<10 тыс. воспоминаний).

Обратное заполнение (ленивое + переиндексация)

Когда модель эмбеддингов изменяется (что определяется по embedding_signature), векторное хранилище перестраивается, а все существующие воспоминания помечаются как needs_reindex = 1 в таблице memories.

Ленивое обратное заполнение: При следующем извлечении для каждого воспоминания, у которого отсутствует векторная запись, создаётся эмбеддинг и добавляется в vec_memories до запуска поиска. Это распределяет затраты на обратное заполнение между реальными запросами, не блокируя запуск.

Явная переиндексация: На вкладке Engine в /dashboard/memory имеется кнопка "Переиндексировать сейчас", которая вызывает POST /api/memory/reindex. Обработчик вызывает runReindexBatch() из src/lib/memory/reindex.ts, который обрабатывает до limit ожидающих записей за один запрос. Прогресс можно опрашивать через GET /api/memory/engine-status (vectorStore.needsReindex).

Таблица memory_vec_meta (миграция 083_memory_vec.sql) хранит:

  • active_dim — текущая размерность вектора (null = ещё не откалибрована).
  • embedding_signature${source}:${model}:${dim}, используемая для обнаружения изменений.
  • last_reset_at — временная метка последнего полного сброса.
  • vec_loaded — флаг 0/1, указывающий, успешно ли загружен sqlite-vec.

Расширение настроек

Девять полей эмбеддингов и векторного хранилища доступны в MemorySettingsExtended в src/shared/schemas/memory.ts и сохраняются через src/lib/db/settings.ts:

Поле Тип Значение по умолчанию Описание
embeddingSource "remote" | "static" | "transformers" | "auto" "auto" Какой источник эмбеддингов использовать
embeddingProviderModel string | null null Провайдер/модель в формате provider/model
customBaseUrl string | null null Базовый URL OpenAI-совместимой конечной точки только для Memory
customModelId string | null null Идентификатор модели, отправляемый пользовательской конечной точке
transformersEnabled boolean false Явное включение Transformers.js (MiniLM, ~400 МБ)
staticEnabled boolean false Явное включение локальной статической модели potion-base-8M
rerankEnabled boolean false Включить этап переранжирования (добавляет +200500 мс/запрос)
rerankProviderModel string | null null Провайдер/модель переранжирования в формате provider/model
vectorStore "sqlite-vec" | "qdrant" | "auto" "auto" Какую векторную серверную часть использовать

Они доступны через GET /PUT /api/settings/memory (схема MemorySettingsExtendedSchema).

Для источника remote Memory также принимает необязательные настройки customBaseUrl и customModelId. Вместе они выбирают OpenAI-совместимую конечную точку /embeddings и модель без изменения глобального реестра эмбеддингов. Перед использованием конечная точка нормализуется и проверяется политикой исходящих URL провайдера: требуется HTTP(S), встроенные учётные данные и строки запроса отклоняются, а адреса облачных служб метаданных остаются заблокированными. Пустые значения сохраняют выбранного провайдера реестра. Ошибки, возвращаемые на панель управления, очищаются, а учётные данные конечной точки никогда не записываются в журналы.

TODO (D20): Область действия global (совместное использование воспоминаний всеми ключами API) в этом выпуске не реализована. Для неё требуются изменения схемы и глобальный путь извлечения. Отслеживать отдельно.

Уровни хранения

Основной: SQLite (таблица memories)

Создаётся миграцией 015_create_memories.sql:

Столбец Тип Примечания
id TEXT PRIMARY KEY UUID, созданный через crypto.randomUUID()
api_key_id TEXT NOT NULL Идентификатор владеющего ключа API
session_id TEXT Необязательная область действия для отдельного диалога
type TEXT NOT NULL Одно из значений: factual, episodic, procedural, semantic
key TEXT Стабильный ключ обновления или вставки, например preference:i_prefer_python
content TEXT NOT NULL Фактический текст факта
metadata TEXT Блок JSON (категория, время извлечения, источник, ...)
created_at / updated_at TEXT Строки ISO 8601
expires_at TEXT Необязательный срок действия; NULL означает постоянное хранение
memory_id INTEGER UNIQUE Добавлен 023_fix_memory_fts_uuid.sql для связи UUID ↔ rowid FTS5

Индексы: api_key_id, session_id, type, expires_at, а также уникальный индекс memory_id.

Семантика обновления или вставки: createMemory() ищет существующую строку с той же парой (api_key_id, key) и при обнаружении обновляет её на месте (объединяя metadata посредством поверхностного развёртывания). Это предотвращает неограниченный рост таблицы при повторяющихся утверждениях о предпочтениях.

Полнотекстовый поиск (виртуальная таблица memory_fts)

022_add_memory_fts5.sql создаёт виртуальную таблицу FTS5 для content и key. 023_fix_memory_fts_uuid.sql исправляет встречавшуюся на практике ошибку, при которой первичный ключ UUID не связывался с целочисленным rowid FTS5: миграция добавляет столбец memory_id, пересоздаёт таблицу FTS и подключает триггеры (memory_fts_ai, memory_fts_ad, memory_fts_au), которые синхронизируют FTS при INSERT, DELETE и UPDATE.

Используется retrieval.ts для стратегий semantic и hybrid (см. ниже). Код извлечения выполняет защитную проверку с помощью hasTable("memory_fts") и возвращается к хронологическому порядку, если таблица FTS отсутствует или запрос FTS завершается с ошибкой.

Необязательно: Qdrant (векторное хранилище уровня 2)

src/lib/memory/qdrant.ts реализует необязательную интеграцию с Qdrant в качестве векторного хранилища уровня 2. Извлечение направляется в Qdrant только тогда, когда селектор движка memoryVectorStore === "qdrant" — значения по умолчанию "auto""sqlite-vec") никогда не выбирают Qdrant. Переключатель на вкладке Engine одновременно задаёт оба параметра — qdrantEnabled и memoryVectorStore: при включении Qdrant становится основным хранилищем, а при отключении значение сбрасывается на "auto" (#5597 — до этого исправления включение не давало эффекта, поскольку селектор движка нигде не записывался). Если Qdrant недоступен или ничего не возвращает, извлечение последовательно переключается на sqlite-vec → FTS5.

  • upsertSemanticMemoryPoint() — создаёт эмбеддинг для key + content с помощью настроенной модели эмбеддингов, проверяет существование коллекции (при первом использовании создаёт векторы с косинусной метрикой) и выполняет upsert точки с полезной нагрузкой {memoryId, apiKeyId, sessionId, key, content, metadata, createdAtUnix, expiresAtUnix}.
  • searchSemanticMemory(query, topK, scope) — создаёт эмбеддинг запроса и выполняет поиск по коллекции с фильтром kind = "omniroute_memory" и, при необходимости, по apiKeyId / sessionId. Ограничивает topK диапазоном [1, 20].
  • deleteSemanticMemoryPoint(id) — удаляет одну точку. Вызывается deleteMemory() после удаления строки SQLite (D15).
  • cleanupSemanticMemoryPoints({retentionDays}) — массово удаляет точки, у которых expiresAtUnix находится в прошлом или createdAtUnix предшествует пороговой дате хранения. Сначала подсчитывает их, чтобы панель мониторинга могла отображать фактические значения.
  • checkQdrantHealth() — проверка работоспособности через GET /readyz с измерением задержки.

Интерфейс настроек предоставляет конфигурацию Qdrant, проверку работоспособности, тест семантического поиска и очистку на вкладке Движок страницы /dashboard/memory. Соответствующие маршруты в src/app/api/settings/qdrant/ полностью подключены начиная с v3.8.6:

Маршрут Метод Описание
/api/settings/qdrant GET / PUT Чтение / обновление настроек Qdrant
/api/settings/qdrant/health GET Проверка доступности + задержка
/api/settings/qdrant/search POST Тест семантического поиска
/api/settings/qdrant/cleanup POST Удаление просроченных / старых точек
/api/settings/qdrant/embedding-models GET Список доступных моделей эмбеддингов

Примечания о поведении (чего ожидать):

  • Выбор движка — включение Qdrant на вкладке «Движок» делает его основным хранилищем (задаёт memoryVectorStore="qdrant"); отключение сбрасывает значение на "auto" (#5597).
  • Без обратного заполнения — в Qdrant записываются только воспоминания, созданные/обновлённые после его включения (двойная запись без ожидания результата). Существующие воспоминания SQLite не мигрируются; «Переиндексировать сейчас» перестраивает только индекс sqlite-vec, но не Qdrant.
  • Размерность вектора определяется автоматически по фактическому эмбеддингу при первом использовании — поле для ввода размерности отсутствует. Изменение модели эмбеддингов после создания коллекции не обрабатывается автоматически: существующая коллекция остаётся без изменений, а операции записи/поиска с несовпадающей размерностью завершаются ошибкой и переключаются на sqlite-vec. Чтобы сменить модель эмбеддингов, пересоздайте коллекцию (задайте новое имя или удалите её в Qdrant).
  • Метрика расстояния — всегда косинусная (жёстко задана при создании коллекции; настройка недоступна).
  • Аутентификация — только API-ключ (передаётся в заголовке api-key; необязателен для локального Docker без аутентификации). JWT/RBAC не используются.
  • Поля конфигурации — интерфейс предоставляет host, port, collection, embeddingModel, apiKey. vectorSize / hnswEfConstruct доступны только через переменные окружения/БД, а vectorSize не используется при создании коллекции (размерность определяется по эмбеддингу).

Квантование векторов (int8 — опционально, для обоих бэкендов)

Оба векторных бэкенда поддерживают опциональное квантование int8, уменьшающее объём памяти, занимаемый сохранёнными векторами (примерно в 4 раза меньше по сравнению с Float32), ценой небольшого снижения полноты поиска. По умолчанию оно отключено в обоих бэкендах — векторы сохраняют полную точность, если квантование не включено явно.

Бэкенд Настройка Тип По умолчанию Где считывается
Qdrant qdrantQuantization (ключ БД) "none" | "int8" | "binary" "none" src/lib/memory/qdrant.ts::normalizeQdrantConfig()
sqlite-vec MEMORY_VEC_QUANTIZATION (окр.) "none" | "int8" "none" src/lib/memory/vectorStore.ts::requestedVecQuantization()
  • Qdrant настраивается отдельно для каждого экземпляра с помощью ключа настройки qdrantQuantization (представлен как поле quantization в PUT /api/settings/qdrant). При значении "int8" функция buildQuantizationConfig() запрашивает скалярное квантование (always_ram, квантиль 0.99), а при поиске включается rescore: true, чтобы векторы полной точности уточняли набор кандидатов int8.
  • Квантование sqlite-vec настраивается только через окружение (это не настройка БД): задайте MEMORY_VEC_QUANTIZATION=int8, чтобы хранить локальные векторы в столбце int8[dim] с помощью vec_quantize_int8(?, 'unit'). Выбранный режим включается в embedding_signature (суффикс :int8), поэтому переключение режимов запускает полную переиндексацию таблицы vec_memories — через тот же путь ленивого обратного заполнения, который используется при изменении модели эмбеддингов.

Типы памяти

MemoryType (src/lib/memory/types.ts):

Тип Назначение
factual Предпочтения, устойчивые факты о пользователе, поведенческие паттерны
episodic Решения, привязанные к конкретному моменту («Я выбрал Postgres»)
procedural Память о рабочих процессах и инструкциях (зарезервировано; автоматического извлекателя пока нет)
semantic Зарезервировано для записей в векторном хранилище

Стратегия извлечения MemoryConfig может быть exact, semantic или hybrid, а область видимости — session, apiKey или global. Область видимости по умолчанию, возвращаемая getMemorySettings(), — apiKey.

Извлечение фактов (extraction.ts)

Извлечение выполняется на основе регулярных выражений, а не LLM — оно запускается внутри процесса с помощью setImmediate(), поэтому никогда не блокирует поток ответа:

  • Шаблоны предпочтенийMemoryType.FACTUAL (например, I prefer …, I really like …, my favorite is …, I hate …)
  • Шаблоны решенийMemoryType.EPISODIC (например, I'll use …, I chose …, I went with …, I'm going to adopt …)
  • Шаблоны поведенияMemoryType.FACTUAL (например, I usually …, I always …, I tend to …)

Каждое совпадение очищается (trim, свёртывание пробельных символов, ограничение до 500 символов), дедуплицируется внутри пакета с помощью стабильного factKey(category, content) и сохраняется через createMemory() с метаданными {category, extractedAt, source: "llm_response"}. Входной текст ограничен 64 КиБ (MAX_EXTRACTION_TEXT_LENGTH) — если он длиннее, используется конец текста, чтобы самый свежий контент ассистента всегда участвовал в обработке.

extractFactsFromText(text) экспортируется для тестов и возвращает структурированные факты без их сохранения.

Извлечение из памяти (retrieval.ts)

retrieveMemories(apiKeyId, config) — основная точка входа. Эта функция:

  1. Нормализует и проверяет конфигурацию через MemoryConfigSchema.
  2. Немедленно возвращает [], если enabled имеет значение false или maxTokens <= 0.
  3. Ограничивает maxTokens диапазоном [1, 8000].
  4. Определяет наличие современной таблицы memories (вместо устаревшей таблицы memory), чтобы сохранять совместимость со старыми базами данных.
  5. Формирует базовый запрос с проверкой срока действия (expires_at IS NULL OR datetime(expires_at) > datetime('now')), необязательным ограничением по сессии и необязательным порогом retentionDays.
  6. Выбирает ветвь в зависимости от стратегии:
    • exact (по умолчанию): хронологический порядок ORDER BY created_at DESC LIMIT 100.
    • semantic: если задан config.query и существует memory_fts, выполняется memory_fts MATCH ? через JOIN с сортировкой по рангу FTS; если FTS возвращает 0 строк, используется хронологический порядок.
    • hybrid: объединение результатов FTS (с более высокой релевантностью) и хронологического набора с дедупликацией по id.
  7. При наличии запроса вычисляет оценку релевантности по ключевым словам (getRelevanceScore) на основе content, key и JSON в metadata. Строки с нулевой оценкой отфильтровываются.
  8. Сортирует сначала по убыванию оценки, затем по убыванию createdAt.
  9. Последовательно обходит ранжированный список и принимает записи, пока накопленное значение estimateTokens(content) (≈ length / 4) не превышает бюджет. Если есть хотя бы одно совпадение, всегда возвращает как минимум одну запись.

estimateTokens экспортируется и используется при извлечении из памяти, суммаризации и в инструменте MCP omniroute_memory_search.

Инъекция (injection.ts)

injectMemory(request, memories, provider):

  1. Объединяет содержимое всех воспоминаний в единую строку Memory context: ….
  2. Выбирает стратегию по имени провайдера:
    • Системное сообщение (по умолчанию для OpenAI, Anthropic, Gemini, …) — добавляет {role: "system", content: memoryText} перед всеми существующими системными сообщениями, чтобы пользовательские системные промпты по-прежнему имели приоритет.
    • Пользовательское сообщение (резервный вариант) — для провайдеров из PROVIDERS_WITHOUT_SYSTEM_MESSAGE: o1, o1-mini, o1-preview, glm, glmt, glm-cn, zai, qianfan. Они не поддерживают системную роль, поэтому в противном случае возвращали бы ошибку 400 (см. issue #1701 для GLM/Zhipu).
  3. Записывает в журнал количество, стратегию и модель под ключом memory.injection.injected.

providerSupportsSystemMessage(provider) экспортируется для вызывающего кода, которому необходимо самостоятельно принимать решения о маршрутизации. Для неизвестных провайдеров по умолчанию возвращается true (системная роль разрешена) в целях безопасности.

Настройки (settings.ts)

Конфигурация памяти хранится в таблице настроек БД, а не в переменных окружения. getMemorySettings() считывает данные из getSettings() и кэширует результат в процессе; invalidateMemorySettingsCache() вызывается маршрутом PUT настроек после записи.

Устаревшие поля (все версии)

Ключ БД Тип Значение по умолчанию Элемент управления в UI
memoryEnabled boolean false (отключено по умолчанию начиная с v3.8.30) Включение/выключение памяти
memoryMaxTokens integer 2000 (диапазон 016000) Бюджет токенов для инъекции
memoryRetentionDays integer 30 (диапазон 1365) Период хранения
memoryStrategy enum "hybrid" (одно из recent, semantic, hybrid) Стратегия извлечения
skillsEnabled boolean false Переключает инъекцию навыков для отдельных ключей (см. SKILLS.md)

Примечание: стратегия UI "recent" сопоставляется с внутренней стратегией извлечения "exact" через toMemoryRetrievalConfig() (хронологический порядок).

Новые поля (v3.8.6, план 21 D9)

Описания полей также приведены выше в разделе «Расширение настроек».

Ключ БД Поле API Значение по умолчанию
memoryEmbeddingSource embeddingSource "auto"
memoryEmbeddingModel embeddingProviderModel null
memoryTransformersEnabled transformersEnabled false
memoryStaticEnabled staticEnabled false
memoryRerankEnabled rerankEnabled false
memoryRerankModel rerankProviderModel null
memoryVectorStore vectorStore "auto"

Связанные с Qdrant ключи БД (qdrantEnabled, qdrantHost, qdrantPort, qdrantApiKey, qdrantCollection со значением по умолчанию "omniroute_memory", qdrantEmbeddingModel со значением по умолчанию "openai/text-embedding-3-small") считываются функцией normalizeQdrantConfig() в qdrant.ts.

Переменные окружения (v3.8.6)

Шесть необязательных переменных окружения настраивают поведение движка во время выполнения (описаны в .env.example):

Переменная Значение по умолчанию Описание
MEMORY_EMBEDDING_CACHE_TTL_MS 300000 TTL кэша эмбеддингов (5 мин)
MEMORY_EMBEDDING_CACHE_MAX 1000 Максимальное количество записей в LRU-кэше эмбеддингов
MEMORY_TRANSFORMERS_MODEL Xenova/all-MiniLM-L6-v2 Репозиторий HF для модели Transformers.js
MEMORY_STATIC_MODEL minishlab/potion-base-8M Репозиторий HF для статической модели potion
MEMORY_STATIC_CACHE_DIR <DATA_DIR>/embeddings Место хранения загруженных моделей
MEMORY_VEC_TOP_K 20 Значение top-K по умолчанию для векторного поиска
MEMORY_RRF_K 60 Константа k алгоритма RRF для гибридного поиска
MEMORY_VEC_QUANTIZATION none Установите int8, чтобы хранить локальные векторы sqlite-vec в квантованном виде (примерно в 4 раза меньше; включается явно). Изменение режима приводит к принудительной переиндексации.

Суммаризация (summarization.ts)

summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) сжимает старое содержимое, когда текущая сумма токенов в записях памяти для ключа превышает установленный лимит. Функция перебирает строки по created_at в порядке DESC, сохраняет строки, укладывающиеся в лимит, а у остальных заменяет content на первые три предложения исходного текста. tokensSaved — это разница значений estimateTokens для старого и нового содержимого.

Эта процедура доступна, но не вызывается автоматически в текущем конвейере чата — для постоянного сжатия вызывайте её из cron, действия администратора или связующего кода MemoryConfig.autoSummarize. Потеря данных необратима: исходный текст перезаписывается.

REST API

Все конечные точки требуют управленческой аутентификации (requireManagementAuth).

Основные конечные точки памяти (существующие + обновлённые)

Метод Путь Описание
GET /api/memory Постраничный список с фильтрами: apiKeyId, type, sessionId, q, limit, page, offset. Ответ содержит stats.total, stats.tokensUsed, stats.hitRate, cacheStats
POST /api/memory Создаёт запись (проверяется Zod: content, key, необязательные type, sessionId, apiKeyId, metadata, expiresAt). Вызывает createMemory(), выполняющую upsert по (apiKeyId, key)
GET /api/memory/[id] Получает одну запись по UUID
PUT /api/memory/[id] Обновляет поля записи (type, key, content, metadata). Тело: MemoryUpdatePutSchema. Также синхронизирует вектор, если доступен источник эмбеддинга.
DELETE /api/memory/[id] Удаляет запись, а также удаляет её из vec_memories (D15) и, по возможности, из Qdrant. Возвращает 404, если запись отсутствует.
GET /api/memory/health Запускает verifyExtractionPipeline("health-check") — полный цикл создания→получения списка→удаления. Возвращает {working, latencyMs, error?}

Новые конечные точки движка памяти (план 21)

Метод Путь Описание
POST /api/memory/retrieve-preview Пробный запуск retrieveMemories — возвращает ранжированные результаты с оценкой, уровнем и количеством токенов. Тело: RetrievePreviewSchema. НЕ внедряет и не изменяет записи памяти.
GET /api/memory/embedding-providers Выводит список провайдеров с моделями эмбеддингов и указывает, для каких из них настроен ключ API.
GET /api/memory/engine-status Возвращает полный статус движка: уровень ключевых слов, разрешение эмбеддингов, статистику векторного хранилища, состояние Qdrant и конфигурацию повторного ранжирования. Структура: MemoryEngineStatusSchema.
POST /api/memory/summarize Вручную запускает сжатие памяти. Тело: MemorySummarizeSchema (olderThanDays, apiKeyId?, dryRun). Возвращает {candidates, tokensSaved}.
POST /api/memory/reindex Запускает переиндексацию векторов для записей памяти с needs_reindex=1. Тело: MemoryReindexSchema (force). Возвращает {started, pending}.

Конечные точки настроек

Метод Путь Описание
GET /api/settings/memory Текущая нормализованная структура MemorySettingsExtended (7 новых полей + устаревшие поля)
PUT /api/settings/memory Обновляет любое поле из MemorySettingsExtendedSchema (всего 12 полей)
GET /api/settings/qdrant Текущие настройки Qdrant (QdrantSettingsSchema)
PUT /api/settings/qdrant Обновляет настройки Qdrant. Тело: QdrantSettingsUpdateSchema. apiKey = пустая строка удаляет ключ.
GET /api/settings/qdrant/health Проверяет работоспособность настроенного экземпляра Qdrant. Возвращает QdrantHealthResultSchema.
POST /api/settings/qdrant/search Тест семантического поиска в Qdrant. Тело: QdrantSearchSchema (query, topK).
POST /api/settings/qdrant/cleanup Удаляет из Qdrant точки, относящиеся к просроченным или старым записям памяти.
GET /api/settings/qdrant/embedding-models Выводит список моделей эмбеддингов, доступных для Qdrant.

Запрос списка /api/memory поддерживает либо пагинацию на основе page (parsePaginationParams), либо необработанный параметр offset — если указан offset, он имеет приоритет, а производное значение page вычисляется для структуры ответа.

Инструменты MCP (open-sse/mcp-server/tools/memoryTools.ts)

Когда сервер MCP включён, регистрируются три инструмента памяти:

  • omniroute_memory_search{apiKeyId, query?, type?, maxTokens?, limit?} → оборачивает retrieveMemories(). Начиная с v3.8.6 (D16), strategy считывается из getMemorySettings(), а не задаётся жёстко как "exact". Если указан query, а strategy имеет значение semantic или hybrid, при наличии используется векторное хранилище.
  • omniroute_memory_add{apiKeyId, sessionId?, type, key, content, metadata?} → оборачивает createMemory(). Принимает только 4 канонических типа: factual, episodic, procedural, semantic (D17).
  • omniroute_memory_clear{apiKeyId, type?, olderThan?} → выводит список соответствующих записей, при необходимости фильтрует их по временной метке создания и затем удаляет каждую через deleteMemory() (что также удаляет векторы из sqlite-vec и Qdrant).

Подробности о транспорте и областях действия см. в MCP-SERVER.md.

Панель управления (Студия памяти)

src/app/(dashboard)/dashboard/memory/page.tsx теперь представляет собой студию с 3 вкладками:

Вкладка: Воспоминания

  • Карточка концепции (сворачиваемое пояснение «Как это работает»).
  • Список, поиск и пагинация в реальном времени (задержка 300 мс).
  • Фильтр по типу (factual / episodic / procedural / semantic / все).
  • Модальное окно добавления воспоминания (ключ, содержимое, тип).
  • Встроенное редактирование (кнопка с карандашом → PUT /api/memory/[id]).
  • Удаление отдельной строки (с диалоговым окном подтверждения).
  • Экспорт текущей страницы в JSON; импорт JSON через средство выбора файла.
  • Карточки статистики: totalEntries, tokensUsed, hitRate.
  • Кнопка «Сжать старые» → POST /api/memory/summarize (сначала пробный запуск показывает количество кандидатов, затем запрашивается подтверждение).
  • Зелёный/красный индикатор состояния, управляемый через GET /api/memory/health.

Вкладка: Песочница

  • Поле запроса + выбор стратегии (точная / семантическая / гибридная) + бюджет токенов.
  • «Симулировать» → POST /api/memory/retrieve-preview — показывает ранжированные результаты с score, tier, tokens, vecScore, ftsScore.
  • Панель разрешения, показывающая, какой источник эмбеддингов / векторное хранилище использовались и произошло ли переключение на резервный вариант.

Вкладка: Движок

  • Панель состояния движка (индикатор ключевых слов FTS5, индикатор эмбеддингов, индикатор векторного хранилища, индикатор состояния Qdrant, индикатор переранжирования).
  • Кнопка «Переиндексировать сейчас» → POST /api/memory/reindex.
  • Выбор источника эмбеддингов (автоматический / удалённый / статический / transformers + переключатели).
  • Карточка конфигурации Qdrant (переключатель включения, хост/порт/коллекция/ключ, проверка подключения, проверка семантического поиска, очистка).
  • Карточка конфигурации переранжирования (переключатель включения, выбор поставщика/модели).

Настройки памяти и Qdrant также находятся в /dashboard/settings → Memory & Skills (MemorySkillsTab.tsx) в устаревшем/глобальном интерфейсе настроек.

Кэширование

src/lib/memory/store.ts содержит внутрипроцессный кэш, подобный LRU (MEMORY_CACHE_TTL = 1 мин, MEMORY_MAX_CACHE_SIZE = 500, с удалением 20 % самых старых записей), для операций чтения getMemory(id), а также универсальный слой кэша «ключ/значение» memoryCache (src/lib/memory/cache.ts) с методами get/set/invalidate, используемый вызывающими компонентами, которым нужен собственный изолированный кэш (LRU на 1 000 записей, TTL по умолчанию — 5 мин).

Конфиденциальность и жизненный цикл

  • Владельцем памяти является идентификатор ключа API (resolveMemoryOwnerId в chatCore.ts). Без apiKeyInfo.id не выполняются ни извлечение данных, ни их внедрение, ни выделение воспоминаний.
  • Записи со значением expires_at в будущем отфильтровываются при извлечении; старые записи за пределами retentionDays исключаются условием created_at >= cutoff в retrieveMemories.
  • Для безвозвратного удаления используйте DELETE /api/memory/[id] или omniroute_memory_clear.
  • Выделение воспоминаний выполняется в фоновом режиме через setImmediate; ошибки регистрируются под именем memory.extraction.background.failed и никогда не передаются вызывающей стороне.
  • Циклы проверки (verifyExtractionPipeline) удаляют собственные тестовые записи в блоке finally.

См. также

  • SKILLS.md — настройка skillsEnabled внедряет определения инструментов вместе с памятью.
  • MCP-SERVER.md — транспорт и области доступа MCP.
  • API_REFERENCE.md — более широкий интерфейс API.
  • Исходные модули:
    • src/lib/memory/types.ts, schemas.ts
    • src/lib/memory/store.ts, retrieval.ts, injection.ts, reindex.ts
    • src/lib/memory/extraction.ts, summarization.ts, verify.ts
    • src/lib/memory/settings.ts, qdrant.ts, cache.ts
    • src/lib/memory/vectorStore.ts — sqlite-vec + гибридный RRF
    • src/lib/memory/embedding/index.ts — слой эмбеддингов с несколькими источниками
    • src/lib/memory/embedding/types.ts, remote.ts, staticPotion.ts, transformersLocal.ts, cache.ts
    • src/shared/schemas/memory.ts — схемы Zod для тел всех запросов API памяти
    • src/shared/schemas/qdrant.ts — схемы Zod для настроек и операций Qdrant
    • src/lib/db/memoryVec.ts — CRUD для memory_vec_meta
    • src/lib/db/migrations/015_create_memories.sql, 022_add_memory_fts5.sql, 023_fix_memory_fts_uuid.sql, 083_memory_vec.sql
    • src/app/api/memory/route.ts, [id]/route.ts, health/route.ts
    • src/app/api/memory/retrieve-preview/route.ts
    • src/app/api/memory/engine-status/route.ts
    • src/app/api/memory/embedding-providers/route.ts
    • src/app/api/memory/summarize/route.ts
    • src/app/api/memory/reindex/route.ts
    • src/app/api/settings/memory/route.ts
    • src/app/api/settings/qdrant/route.ts + вложенные маршруты
    • src/app/(dashboard)/dashboard/memory/ — интерфейс Studio (страница + компоненты + вкладки + хуки)
    • open-sse/handlers/chatCore.ts (подключение внедрения / выделения)
    • open-sse/mcp-server/tools/memoryTools.ts

Выбор поставщика эмбеддингов (v3.8.16+)

Механизм памяти OmniRoute поддерживает четыре источника эмбеддингов (src/lib/memory/embedding/). Каждый из них предлагает разные компромиссы в отношении задержки, стоимости, качества модели и сложности настройки.

Источники эмбеддингов

Поставщик Источник Задержка Стоимость Качество Настройка
transformers Локальная модель ONNX (Xenova/all-MiniLM-L6-v2) ~50-150ms (CPU) Бесплатно Хорошее Только npm install
static Предварительно вычисленные векторы (кэшированные) <1ms Бесплатно Н/Д (зависит от попадания в кэш) Не требуется
remote API OpenAI / Cohere / Voyage ~100-300ms $0.02-0.10/1M токенов Отличное Ключ API
auto Выбирает лучший доступный источник во время выполнения Как у выбранного источника Бесплатно Как у выбранного источника Не требуется
(кэш) Слой LRU в памяти поверх любого источника <1ms (попадание), полная задержка (промах) Бесплатно Как у базового источника Всегда включён (не является выбираемым источником)

Дерево решений

                  Каков контекст вашего развёртывания?
                  │
      ┌───────────┼───────────┬──────────────┐
      │           │           │              │
 РАЗРАБОТКА/  НЕБОЛЬШОЙ    КРУПНЫЙ ПРОД.  ПЕРИФЕРИЯ /
 ТЕСТИРОВАНИЕ   ПРОД.                       ОФЛАЙН
      │           │           │              │
      ▼           ▼           ▼              ▼
  transformers transformers remote (Qdrant) transformers
 (бесплатно, без API)       (лучшее качество) (без интернета)
      │           │           │              │
      └────────┬──┴───────────┴──────────────┘
               │
               ▼
            ВСЕГДА добавляйте сверху слой `cache`
            (LruCache оборачивает любого поставщика)

Настройка базы данных и API

Параметры эмбеддингов памяти настраиваются через API/интерфейс настроек, а не через переменные окружения. Соответствующие ключи базы данных настроек в разделе Settings (normalizeMemorySettings в src/lib/memory/settings.ts):

  • memoryEmbeddingSource: "transformers" (локальный), "remote" (на основе API, например OpenAI), "static" (внешнее хранилище) или "auto"
  • memoryEmbeddingProviderModel: идентификатор модели для удалённых/статических источников (например, "text-embedding-3-small")
  • memoryTransformersEnabled: true | false
  • memoryStaticEnabled: true | false
  • memoryVectorStore: "sqlite-vec", "qdrant" или "auto"

Локальная модель (transformers)

Для запуска локальных моделей внутри используется transformers.js:

# Переменные окружения, считываемые в коде (src/lib/memory/embedding/index.ts):
MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2  # Репозиторий модели HF
MEMORY_STATIC_MODEL=minishlab/potion-base-8M       # Статическая модель potion из HF
MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings      # Каталог кэша

Кэш эмбеддингов LRU

Кэш всегда включён по умолчанию и настраивается с помощью переменных окружения:

MEMORY_EMBEDDING_CACHE_MAX=1000                    # Максимальное количество кэшированных элементов
MEMORY_EMBEDDING_CACHE_TTL_MS=300000               # TTL (5 мин)

Показатели производительности

Бенчмарк на типичном 4-ядерном сервере x86 (тексты объёмом ~100 токенов каждый):

Провайдер p50 p95 p99 Стоимость / 1 млн эмбеддингов
transformers (CPU) 80ms 180ms 350ms Бесплатно
remote (OpenAI) 120ms 220ms 400ms ~$0.02 (ada-002) / $0.13 (3-large)
static (Qdrant) 15ms 30ms 60ms Зависит от хостинга Qdrant
cache (попадание) <1ms <1ms 2ms Бесплатно

Шаблоны извлечения фактов (v3.8.16+)

Модуль extraction.ts (src/lib/memory/extraction.ts) использует сопоставление по регулярным выражениям для извлечения структурированных фактов из сообщений беседы. Понимание этих шаблонов поможет вам настроить качество извлечения для своего сценария использования.

Категории шаблонов по умолчанию

Категория Пример шаблона Что извлекается
PREFERENCE_PATTERNS "Я предпочитаю <X>", "Мне нравится <X>", "Я ненавижу <X>" Предпочтения пользователя
DECISION_PATTERNS "Я буду использовать <X>", "Я решил <X>", "Я выбрал <X>" Решения пользователя (эпизодические)
PATTERN_PATTERNS "Я обычно <X>", "Я всегда <X>", "Я никогда не <X>" Устойчивые поведенческие шаблоны

Примеры шаблонов (упрощённые)

// Из src/lib/memory/extraction.ts
const PREFERENCE_PATTERNS = [
  /\bI\s+(?:really\s+)?prefer\s+([^.,\n]+)/gi,
  /\bI\s+(?:really\s+)?like\s+([^.,\n]+)/gi,
  /\bI\s+(?:hate|dislike|avoid)\s+([^.,\n]+)/gi,
];
const DECISION_PATTERNS = [
  /\bI'?(?:ll|will)\s+use\s+([^.,\n]+)/gi,
  /\bI\s+(?:have\s+)?decided\s+(?:to\s+)?([^.,\n]+)/gi,
];
const PATTERN_PATTERNS = [/\bI\s+usually\s+([^.,\n]+)/gi, /\bI\s+always\s+([^.,\n]+)/gi];

Что извлекается

Когда пользователь говорит:

"Я предпочитаю TypeScript. Я буду использовать Postgres для этого проекта. Я всегда делаю коммит перед отправкой изменений. Мне не нравится Python." В результате извлечения создаются 4 записи памяти:

Ключ Категория Тип Содержимое
preference:typescript preference factual "TypeScript"
decision:postgres_for_this_project decision episodic "Postgres для этого проекта"
pattern:commit_before_pushing pattern factual "делаю коммит перед отправкой изменений"
preference:python preference factual "Python"

Ограничения извлечения

Чтобы предотвратить неконтролируемое извлечение, применяются следующие ограничения:

| Минимальная длина содержимого | 3 символа | | Максимальная длина содержимого | 500 символов |

Когда следует отключить извлечение

Извлечение запускается автоматически всякий раз, когда включена память; отдельного переключателя только для извлечения нет. Чтобы отключить его, полностью отключите память (enabled: false через PUT /api/settings/memory). Это стоит сделать в следующих случаях:

  • У вас большой объём сообщений, и затраты на извлечение существенны
  • Ваши беседы в основном кратковременны (общение, отладка) и не имеют долгосрочной ценности
  • Вы уже сохраняете контекст с помощью пользовательских плагинов

Настройка гибридного RRF (v3.8.16+)

Алгоритм Reciprocal Rank Fusion (RRF) объединяет результаты FTS5 (по ключевым словам) и векторного (семантического) поиска. Параметр k определяет, какой вес получают результаты с более низкими позициями в рейтинге.

Формула

Для каждой записи-кандидата в памяти оценка RRF вычисляется следующим образом:

RRF(d) = Σ  1 / (k + rank_i(d))

Где:

  • k — константа (по умолчанию 60)
  • rank_i(d) — позиция документа d в i-й поисковой системе (FTS, векторный поиск)
  • Суммирование выполняется по всем поисковым системам

Как k влияет на результаты

Значение k Эффект Лучше всего подходит для
k=0 Чистое объединение рейтингов (без сглаживания) Теоретического базового уровня
k=10-30 Результаты на верхних позициях получают большой вес, низкие позиции почти не влияют Случаев, когда первые 3 результата обычно верны
k=60 (по умолчанию) Сбалансированный вариант — все первые 10 результатов вносят значимый вклад Универсального поиска
k=100+ Более равномерное распределение — даже результаты с низкими позициями могут доминировать, если присутствуют в нескольких системах Случаев, когда полнота важнее точности

Настройка k на практике

# Значение по умолчанию
MEMORY_RRF_K=60

# Повышенная точность (небольшая память, мало документов)
MEMORY_RRF_K=20

# Максимальная полнота (большая память, разнообразные запросы)
MEMORY_RRF_K=120

Пример с k=20:

  • Позиция 1 в FTS → вклад 1/21 = 0.048
  • Позиция 10 в FTS → вклад 1/30 = 0.033
  • Позиция 1 в векторном поиске → вклад 0.048
  • Максимальная суммарная оценка: 0.096

Пример с k=60:

  • Позиция 1 в FTS → вклад 1/61 = 0.016
  • Позиция 10 в FTS → вклад 1/70 = 0.014
  • Позиция 1 в векторном поиске → вклад 0.016
  • Максимальная суммарная оценка: 0.033

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

Когда следует изменить k

Симптом Что попробовать
Первый результат всегда побеждает, но он неверен Уменьшить k (например, до 20) — уверенность в верхних позициях важнее
Правильный ответ входит в топ-5, но не занимает первое место Увеличить k (например, до 100) — более равномерная оценка поощряет согласованность
Полнота высокая, но точность низкая Уменьшить k — сделать ранжирование более строгим
Полнота низкая (релевантные документы пропускаются) Увеличить k — дать шанс документам с более низкими позициями

Взвешивание RRF

При объединении на основе обратного ранга используются равные веса для семантического векторного ранга и ранга полнотекстового поиска:

RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)

Переменных окружения для настройки отдельных весов не существует (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT отсутствуют).


Стратегия суммаризации (v3.8.16+)

Модуль summarization.ts (src/lib/memory/summarization.ts) сжимает более старые воспоминания, чтобы сохранять активный набор небольшим, не ухудшая возможность поиска.

Когда запускается суммаризация

Триггер Пороговое значение (по умолчанию)
Ручной запуск через API н/д

Что суммаризируется

Из summarization.ts экспортируются две точки входа:

  • summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) — сжимает воспоминания сеанса в единый текст сводки, ограниченный бюджетом токенов.
  • summarizeMemoriesOlderThan(apiKeyId, days, dryRun) — используемое API возрастное сжатие: выбирает все воспоминания старше days, создаёт из них одно сжатое воспоминание-сводку и (когда dryRun имеет значение false) удаляет оригиналы. Передайте dryRun: true, чтобы просмотреть набор кандидатов и общее количество токенов, ничего не изменяя.

Прохода кластеризации по тегам/ключам или оценки отдельных воспоминаний по принципу «основное или пригодное для суммаризации» нет — выбор выполняется исключительно по возрастному порогу, а текст сводки представляет собой сжатую строку с префиксом типа для каждого кандидата.

Запуск суммаризации

Суммаризация выполняется вручную / по желанию — настройка autoSummarize по умолчанию имеет значение false, поэтому автоматически ничего не сжимается. Запустите её через API:

curl -X POST http://localhost:20128/api/memory/summarize \
  -H "Authorization: Bearer $OMNIROUTE_KEY"

Чтобы оставить её отключённой, просто сохраните для autoSummarize значение по умолчанию (false).

Советы по качеству суммаризации

  • Сначала выполните предварительный просмотр с dryRunsummarizeMemoriesOlderThan(..., true) возвращает список кандидатов и общее количество токенов, чтобы вы могли проверить, что именно будет объединено, прежде чем удалять оригиналы.
  • Запускайте суммаризацию в часы низкой нагрузки, если у вас большой корпус воспоминаний — вызов LLM является самой медленной частью
# В стиле cron: суммаризация ежедневно в 3 часа ночи
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
  -H "Authorization: Bearer $OMNIROUTE_KEY"

Шаблон провайдера MemoryBackend

Источник истины: src/lib/memory/backend.ts, src/lib/memory/genericBackend.ts, src/lib/memory/manager.ts Тесты: src/lib/memory/__tests__/generic-backend.test.ts

Шаблон провайдера MemoryBackend добавляет подключаемый уровень абстракции бэкенда поверх существующего движка памяти. Вместо привязки к одной реализации хранилища система памяти теперь поддерживает несколько бэкендов (SQLite, Obsidian, Notion, пользовательские HTTP-бэкенды) с настраиваемой маршрутизацией между основным и резервными бэкендами.

Архитектура

┌──────────────────────────────────────────────────────────┐
│                    Маршруты API                           │
│            (src/app/api/memory/route.ts)                  │
└──────────────────────┬───────────────────────────────────┘
                       │
┌──────────────────────▼───────────────────────────────────┐
│                   MemoryManager                           │
│        Singleton-оркестратор (manager.ts)                 │
│                                                          │
│  Основной ──► Бэкенд A  (например, SQLite)               │
│  Резервный ─► Бэкенд B  (например, Obsidian)             │
│               Бэкенд C  (например, Notion через          │
│                           GenericBackend)                 │
└──────────────────────┬───────────────────────────────────┘
                       │
        ┌──────────────┼──────────────┐
        ▼              ▼              ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ Бэкенд     │ │ Бэкенд     │ │ GenericMemory    │
│ SQLite     │ │ Obsidian   │ │ Backend (HTTP)   │
└────────────┘ └────────────┘ └──────────────────┘

Основной интерфейс (backend.ts)

Каждый бэкенд должен реализовывать интерфейс MemoryBackend:

interface MemoryBackend {
  readonly id: string;
  readonly displayName: string;

  // CRUD
  create(input: CreateMemoryInput): Promise<Memory>;
  get(id: string): Promise<Memory | null>;
  update(id: string, updates: Partial<...>): Promise<boolean>;
  delete(id: string): Promise<boolean>;
  list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;

  // Поиск
  search(config: SearchConfig): Promise<Memory[]>;

  // Состояние
  health(): Promise<HealthCheckResult>;

  // Жизненный цикл (необязательно)
  initialize?(): Promise<void>;
  shutdown?(): Promise<void>;
}

MemoryManager (manager.ts)

Singleton-оркестратор, который:

  • Регистрирует бэкенды через register(backend) — вызывается при запуске из index.ts
  • Настраивает основной и резервные бэкенды через configure(primary, fallbacks)
  • Маршрутизирует операции CRUD и поиск в основной бэкенд, используя цепочку резервных бэкендов при сбое
  • Периодически проверяет состояние всех бэкендов

Поведение резервных бэкендов:

Операция Основной бэкенд Резервные бэкенды
create Только основной
get Сначала основной Резервный, если результат — null
update Только основной Асинхронная синхронизация без ожидания
delete Только основной Асинхронная синхронизация без ожидания
list Только основной
search Сначала основной Резервный при ошибке

GenericMemoryBackend (genericBackend.ts)

Универсальный HTTP-коннектор, адаптирующий любой REST API к MemoryBackend. Полезен для:

  • Notion — подключение через Notion API
  • Obsidian — подключение через Obsidian Local REST API
  • Пользовательских бэкендов — любой сервис, предоставляющий RESTful API памяти

Конфигурация:

interface GenericBackendConfig {
  baseUrl: string;           // Базовый URL API бэкенда
  apiKey?: string;           // Bearer-токен для аутентификации
  headers?: Record<string, string>;  // Пользовательские HTTP-заголовки
  timeout?: number;          // Тайм-аут запроса (по умолчанию: 30000ms)
  backendType?: string;      // Для ведения журнала

  // Переопределения конечных точек (по умолчанию используются соглашения REST)
  endpoints?: {
    search?: string;   // по умолчанию: "/memories/search"
    create?: string;   // по умолчанию: "/memories"
    list?: string;     // по умолчанию: "/memories"
    get?: string;      // по умолчанию: "/memories/{id}"
    update?: string;   // по умолчанию: "/memories/{id}"
    delete?: string;   // по умолчанию: "/memories/{id}"
    health?: string;   // по умолчанию: "/health"
  };

  // Сопоставления имён параметров запроса
  queryParams?: {
    query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
  };

  // Сопоставления имён параметров пути
  pathParams?: {
    id?/memoryId?
  };
}

Известные бэкенды предварительно настроены в KNOWN_BACKENDS:

createKnownBackend("obsidian"); // → GenericMemoryBackend, указывающий на localhost:27123
createKnownBackend("notion"); // → GenericMemoryBackend, указывающий на api.notion.com/v1

Встроенные бэкенды

SQLiteBackend (sqliteBackend.ts)

Основной бэкенд по умолчанию. Представляет собой обёртку существующего хранилища памяти на базе SQLite, использующего src/lib/memory/store.ts. Автоматически регистрируется при запуске.

import { sqliteBackend } from "./sqliteBackend";
memoryManager.register(sqliteBackend);
ObsidianBackend (obsidianBackend.ts)

Представляет собой обёртку существующей интеграции с Obsidian (src/lib/memory/obsidianBackend.ts). Подключается к хранилищу Obsidian через Obsidian Local REST API.

Настройки

Настройки бэкенда памяти хранятся в таблице настроек приложения и управляются через src/lib/memory/settings.ts:

Настройка Ключ окружения/конфигурации Значение по умолчанию Описание
Основной бэкенд memoryPrimaryBackend "sqlite" ID основного бэкенда
Резервные бэкенды memoryFallbackBackends [] Упорядоченный список ID резервных бэкендов
Конфигурации бэкендов memoryBackendConfigs {} Переопределения конфигурации для каждого бэкенда

Настройки нормализуются с помощью normalizeMemorySettings() и кэшируются в getMemorySettings().

Процесс инициализации

Запуск приложения
  → импорты index.ts (побочный эффект): регистрируют SQLiteBackend
  → initMemoryBackends() вызывается из жизненного цикла приложения:
      1. Загрузка настроек (getMemorySettings)
      2. Настройка основного и резервных бэкендов
      3. Инициализация всех бэкендов (проверка работоспособности)
      4. Готовность к обработке запросов

Добавление нового бэкенда

  1. Реализуйте интерфейс MemoryBackend в src/lib/memory/<name>Backend.ts
  2. Экспортируйте его из src/lib/memory/index.ts
  3. Зарегистрируйте с помощью memoryManager.register(yourBackend) при запуске
  4. Настройте через параметры: задайте для memoryPrimaryBackend ID вашего бэкенда
  5. Протестируйте, используя src/lib/memory/__tests__/generic-backend.test.ts в качестве примера

Пример: бэкенд Brain

import { createGenericMemoryBackend } from "./genericBackend";

const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", {
  baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099",
  apiKey: process.env.BRAIN_API_KEY,
  endpoints: {
    search: "/api/memory/search",
    create: "/api/memory",
    health: "/api/health",
  },
});

memoryManager.register(brainBackend);

Проверка

Модульные тесты

npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose

Ожидаемый результат: 35 успешно пройденных тестов, охватывающих:

  • Конструктор (2)
  • Проверка работоспособности (4) — успех, ошибка 500, сетевая ошибка, задержка
  • Инициализация (2) — успех, ошибка
  • Создание (2) — конечная точка по умолчанию, пользовательская конечная точка
  • Получение (4) — успех, 404 → null, исключение при коде, отличном от 404, пользовательские параметры пути
  • Обновление (2) — успех, 404 → false
  • Удаление (2) — успех, 404 → false
  • Получение списка (2) — параметры запроса, пользовательские имена параметров
  • Поиск (3) — параметры запроса, пользовательская конечная точка, сериализация параметров
  • Заголовки аутентификации (2) — Bearer-токен, пользовательские заголовки
  • Фабрика (1)

Проверка типов

npm run typecheck:core

Ожидаемый результат: 0 ошибок.