Memory System (Русский)
Выбор поставщика эмбеддингов (v3.8.16+)
Заголовок раздела «Выбор поставщика эмбеддингов (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»Параметры эмбеддингов памяти настраиваются через 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)»Для запуска локальных моделей внутри используется transformers.js:
# Переменные окружения, считываемые в коде (src/lib/memory/embedding/index.ts):MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # Репозиторий модели HFMEMORY_STATIC_MODEL=minishlab/potion-base-8M # Статическая модель potion из HFMEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # Каталог кэшаКэш эмбеддингов LRU
Заголовок раздела «Кэш эмбеддингов 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+)
Заголовок раздела «Шаблоны извлечения фактов (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.tsconst 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+)
Заголовок раздела «Настройка гибридного 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 |
Эффект | Лучше всего подходит для |
|---|---|---|
k=0 |
Чистое объединение рейтингов (без сглаживания) | Теоретического базового уровня |
k=10-30 |
Результаты на верхних позициях получают большой вес, низкие позиции почти не влияют | Случаев, когда первые 3 результата обычно верны |
k=60 (по умолчанию) |
Сбалансированный вариант — все первые 10 результатов вносят значимый вклад | Универсального поиска |
k=100+ |
Более равномерное распределение — даже результаты с низкими позициями могут доминировать, если присутствуют в нескольких системах | Случаев, когда полнота важнее точности |
Настройка k на практике
Заголовок раздела «Настройка 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»| Симптом | Что попробовать |
|---|---|
| Первый результат всегда побеждает, но он неверен | Уменьшить k (например, до 20) — уверенность в верхних позициях важнее |
| Правильный ответ входит в топ-5, но не занимает первое место | Увеличить k (например, до 100) — более равномерная оценка поощряет согласованность |
| Полнота высокая, но точность низкая | Уменьшить k — сделать ранжирование более строгим |
| Полнота низкая (релевантные документы пропускаются) | Увеличить k — дать шанс документам с более низкими позициями |
Взвешивание RRF
Заголовок раздела «Взвешивание RRF»При объединении на основе обратного ранга используются равные веса для семантического векторного ранга и ранга полнотекстового поиска:
RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)Переменных окружения для настройки отдельных весов не существует (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT отсутствуют).
Стратегия суммаризации (v3.8.16+)
Заголовок раздела «Стратегия суммаризации (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
Заголовок раздела «Шаблон провайдера 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)
Заголовок раздела «Основной интерфейс (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)
Заголовок раздела «MemoryManager (manager.ts)»Singleton-оркестратор, который:
- Регистрирует бэкенды через
register(backend)— вызывается при запуске изindex.ts - Настраивает основной и резервные бэкенды через
configure(primary, fallbacks) - Маршрутизирует операции CRUD и поиск в основной бэкенд, используя цепочку резервных бэкендов при сбое
- Периодически проверяет состояние всех бэкендов
Поведение резервных бэкендов:
| Операция | Основной бэкенд | Резервные бэкенды |
|---|---|---|
create |
✅ Только основной | ❌ |
get |
✅ Сначала основной | ✅ Резервный, если результат — null |
update |
✅ Только основной | ✅ Асинхронная синхронизация без ожидания |
delete |
✅ Только основной | ✅ Асинхронная синхронизация без ожидания |
list |
✅ Только основной | ❌ |
search |
✅ Сначала основной | ✅ Резервный при ошибке |
GenericMemoryBackend (genericBackend.ts)
Заголовок раздела «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:27123createKnownBackend("notion"); // → GenericMemoryBackend, указывающий на api.notion.com/v1Встроенные бэкенды
Заголовок раздела «Встроенные бэкенды»SQLiteBackend (sqliteBackend.ts)
Заголовок раздела «SQLiteBackend (sqliteBackend.ts)»Основной бэкенд по умолчанию. Представляет собой обёртку существующего хранилища памяти на базе SQLite, использующего src/lib/memory/store.ts. Автоматически регистрируется при запуске.
import { sqliteBackend } from "./sqliteBackend";memoryManager.register(sqliteBackend);ObsidianBackend (obsidianBackend.ts)
Заголовок раздела «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
Заголовок раздела «Пример: бэкенд 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 ошибок.
HagiCode
HagiCode — агентная среда разработки со структурированными процессами, параллельным выполнением несколькими агентами и интерфейсами Hero Dungeon.
Превращайте идеи в полезное ПО с более умным, быстрым и увлекательным агентным рабочим процессом.

- SmartСтруктурированные процессы превращают намерение в исполнимый путь от идеи до готового изменения.
- EfficientМультиагентные процессы параллельно продвигают исследование, реализацию и проверку.
- FunHero Dungeon делает длительную совместную разработку наглядной и увлекательной.