* 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.
98 KiB
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:
- Найти первого провайдера в
listEmbeddingProviders(), у которогоhasKey === true→remote. - Если
settings.staticEnabled === true→static. - Если
settings.transformersEnabled === true→transformers. - В противном случае →
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
В частности:
- Выполнить поиск FTS5 → ранжированный список
R_fts(позиции 1..N). - Выполнить векторный поиск KNN → ранжированный список
R_vec(позиции 1..M). - Для каждого уникального
memoryId:
rrf_score = 1/(60 + fts_rank)+1/(60 + vec_rank)(0, если отсутствует в списке). - Отсортировать по
rrf_scoreDESC и применить последовательный отбор с учётом бюджета токенов.
Известно, что 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 |
Включить этап переранжирования (добавляет +200–500 мс/запрос) |
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) — основная точка входа. Эта функция:
- Нормализует и проверяет конфигурацию через
MemoryConfigSchema. - Немедленно возвращает
[], еслиenabledимеет значение false илиmaxTokens <= 0. - Ограничивает
maxTokensдиапазоном[1, 8000]. - Определяет наличие современной таблицы
memories(вместо устаревшей таблицыmemory), чтобы сохранять совместимость со старыми базами данных. - Формирует базовый запрос с проверкой срока действия
(
expires_at IS NULL OR datetime(expires_at) > datetime('now')), необязательным ограничением по сессии и необязательным порогомretentionDays. - Выбирает ветвь в зависимости от стратегии:
exact(по умолчанию): хронологический порядокORDER BY created_at DESC LIMIT 100.semantic: если заданconfig.queryи существуетmemory_fts, выполняетсяmemory_fts MATCH ?через JOIN с сортировкой по рангу FTS; если FTS возвращает 0 строк, используется хронологический порядок.hybrid: объединение результатов FTS (с более высокой релевантностью) и хронологического набора с дедупликацией по id.
- При наличии запроса вычисляет оценку релевантности по ключевым словам
(
getRelevanceScore) на основеcontent,keyи JSON вmetadata. Строки с нулевой оценкой отфильтровываются. - Сортирует сначала по убыванию оценки, затем по убыванию
createdAt. - Последовательно обходит ранжированный список и принимает записи, пока
накопленное значение
estimateTokens(content)(≈length / 4) не превышает бюджет. Если есть хотя бы одно совпадение, всегда возвращает как минимум одну запись.
estimateTokens экспортируется и используется при извлечении из памяти,
суммаризации и в инструменте MCP omniroute_memory_search.
Инъекция (injection.ts)
injectMemory(request, memories, provider):
- Объединяет содержимое всех воспоминаний в единую строку
Memory context: …. - Выбирает стратегию по имени провайдера:
- Системное сообщение (по умолчанию для 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).
- Системное сообщение (по умолчанию для OpenAI, Anthropic, Gemini, …) — добавляет
- Записывает в журнал количество, стратегию и модель под ключом
memory.injection.injected.
providerSupportsSystemMessage(provider) экспортируется для вызывающего кода, которому
необходимо самостоятельно принимать решения о маршрутизации. Для неизвестных провайдеров
по умолчанию возвращается true (системная роль разрешена) в целях безопасности.
Настройки (settings.ts)
Конфигурация памяти хранится в таблице настроек БД, а не в переменных окружения.
getMemorySettings() считывает данные из getSettings() и кэширует результат
в процессе; invalidateMemorySettingsCache() вызывается маршрутом PUT настроек
после записи.
Устаревшие поля (все версии)
| Ключ БД | Тип | Значение по умолчанию | Элемент управления в UI |
|---|---|---|---|
memoryEnabled |
boolean | false (отключено по умолчанию начиная с v3.8.30) |
Включение/выключение памяти |
memoryMaxTokens |
integer | 2000 (диапазон 0–16000) |
Бюджет токенов для инъекции |
memoryRetentionDays |
integer | 30 (диапазон 1–365) |
Период хранения |
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.tssrc/lib/memory/store.ts,retrieval.ts,injection.ts,reindex.tssrc/lib/memory/extraction.ts,summarization.ts,verify.tssrc/lib/memory/settings.ts,qdrant.ts,cache.tssrc/lib/memory/vectorStore.ts— sqlite-vec + гибридный RRFsrc/lib/memory/embedding/index.ts— слой эмбеддингов с несколькими источникамиsrc/lib/memory/embedding/types.ts,remote.ts,staticPotion.ts,transformersLocal.ts,cache.tssrc/shared/schemas/memory.ts— схемы Zod для тел всех запросов API памятиsrc/shared/schemas/qdrant.ts— схемы Zod для настроек и операций Qdrantsrc/lib/db/memoryVec.ts— CRUD дляmemory_vec_metasrc/lib/db/migrations/015_create_memories.sql,022_add_memory_fts5.sql,023_fix_memory_fts_uuid.sql,083_memory_vec.sqlsrc/app/api/memory/route.ts,[id]/route.ts,health/route.tssrc/app/api/memory/retrieve-preview/route.tssrc/app/api/memory/engine-status/route.tssrc/app/api/memory/embedding-providers/route.tssrc/app/api/memory/summarize/route.tssrc/app/api/memory/reindex/route.tssrc/app/api/settings/memory/route.tssrc/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|falsememoryStaticEnabled:true|falsememoryVectorStore:"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:typescriptpreference factual "TypeScript" decision:postgres_for_this_projectdecision episodic "Postgres для этого проекта" pattern:commit_before_pushingpattern factual "делаю коммит перед отправкой изменений" preference:pythonpreference 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).
Советы по качеству суммаризации
- Сначала выполните предварительный просмотр с
dryRun—summarizeMemoriesOlderThan(..., 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. Готовность к обработке запросов
Добавление нового бэкенда
- Реализуйте интерфейс
MemoryBackendвsrc/lib/memory/<name>Backend.ts - Экспортируйте его из
src/lib/memory/index.ts - Зарегистрируйте с помощью
memoryManager.register(yourBackend)при запуске - Настройте через параметры: задайте для
memoryPrimaryBackendID вашего бэкенда - Протестируйте, используя
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 ошибок.