コンテンツにスキップ
OmniRoute source

Memory System (日本語)

埋め込みプロバイダーの選択(v3.8.16+)

Section titled “埋め込みプロバイダーの選択(v3.8.16+)”

OmniRoute のメモリエンジンは、4 つの埋め込みソース(src/lib/memory/embedding/)をサポートしています。それぞれ、レイテンシ、コスト、モデル品質、セットアップの複雑さにおけるトレードオフが異なります。

プロバイダー ソース レイテンシ コスト 品質 セットアップ
transformers ローカル ONNX モデル(Xenova/all-MiniLM-L6-v2) 約50~150ms(CPU) 無料 良好 npm install のみ
static 事前計算済みベクトル(キャッシュ済み) 1ms未満 無料 該当なし(キャッシュヒットに依存) なし
remote OpenAI / Cohere / Voyage API 約100~300ms $0.02~0.10/100万トークン 非常に優れている API キー
auto 実行時に利用可能な最適なソースを選択 選択されたソースと同じ 無料 選択されたソースと同じ なし
(cache) 任意のソース上のインメモリ LRU レイヤー 1ms未満(ヒット)、全レイテンシ(ミス) 無料 基盤となるソースと同じ 常時有効(選択可能なソースではない)
デプロイ環境はどれですか?
│
┌───────────┼───────────┬──────────────┐
│ │ │ │
開発/テスト 小規模本番 大規模本番 エッジ/オフライン
│ │ │ │
▼ ▼ ▼ ▼
transformers transformers remote (Qdrant) transformers
(無料、API不要) (最高品質) (インターネット不要)
│ │ │ │
└────────┬──┴───────────┴──────────────┘
│
▼
必ず上に `cache` レイヤーを追加
(LruCache が任意のプロバイダーをラップ)

メモリ埋め込みオプションは、環境変数ではなく Settings API/UI を介して設定します。Settings における関連設定のデータベースキー(src/lib/memory/settings.ts の normalizeMemorySettings)は次のとおりです。

  • memoryEmbeddingSource: "transformers"(ローカル)、"remote"(API ベース、例:OpenAI)、"static"(外部ストア)、または "auto"
  • memoryEmbeddingProviderModel: リモート/静的ソースのモデル識別子(例:"text-embedding-3-small")
  • memoryTransformersEnabled: true | false
  • memoryStaticEnabled: true | false
  • memoryVectorStore: "sqlite-vec"、"qdrant"、または "auto"

内部で transformers.js を使用してローカルモデルを実行します。

ターミナルウィンドウ
# コード内で読み取られる環境変数(src/lib/memory/embedding/index.ts):
MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # HF モデルリポジトリ
MEMORY_STATIC_MODEL=minishlab/potion-base-8M # HF 静的 Potion モデル
MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # キャッシュディレクトリ

キャッシュはデフォルトで常に有効であり、環境変数を介して設定します。

ターミナルウィンドウ
MEMORY_EMBEDDING_CACHE_MAX=1000 # キャッシュされる項目の最大数
MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL(5分)

一般的な4コアx86サーバーでのベンチマーク(各テキスト約100トークン):

プロバイダー p50 p95 p99 100万埋め込みあたりのコスト
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 無料

extraction.ts モジュール (src/lib/memory/extraction.ts) は、正規表現によるパターンマッチングを使用して、会話メッセージから構造化されたファクトを抽出します。これらのパターンを理解することで、ユースケースに合わせて抽出品質を調整できます。

デフォルトのパターンカテゴリ

Section titled “デフォルトのパターンカテゴリ”
カテゴリ パターン例 抽出対象
PREFERENCE_PATTERNS "I prefer <X>"、"I like <X>"、"I hate <X>" ユーザーの好み
DECISION_PATTERNS "I'll use <X>"、"I decided to <X>"、"I went with <X>" ユーザーの決定(エピソード記憶)
PATTERN_PATTERNS "I usually <X>"、"I always <X>"、"I never <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 for this project”
pattern:commit_before_pushing pattern factual “commit before pushing”
preference:python preference factual “Python”

過剰な抽出を防ぐため、次の制限が適用されます:

| 最小コンテンツ長 | 3文字 | | 最大コンテンツ長 | 500文字 |

メモリが有効な場合、抽出は常に自動的に実行されます。抽出専用の切り替え設定はありません。無効にするには、PUT /api/settings/memory を介してメモリ全体を無効化してください (enabled: false)。次のような場合は、無効化を検討してください:

  • メッセージ量が多く、抽出コストを無視できない場合
  • 会話の大半が一時的なもの(チャット、デバッグ)で、長期的な価値がない場合
  • カスタムプラグインですでにコンテキストを取得している場合

ハイブリッドRRFのチューニング (v3.8.16+)

Section titled “ハイブリッドRRFのチューニング (v3.8.16+)”

Reciprocal Rank Fusion (RRF) アルゴリズムは、FTS5(キーワード)とベクトル(セマンティック)の結果を組み合わせます。k パラメータは、順位の低い結果にどの程度の重みを与えるかを制御します。

各候補メモリのRRFスコアは次のとおりです:

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

ここで:

  • k は定数(デフォルトは60)
  • rank_i(d) は、i番目の検索システム(FTS、ベクトル)におけるドキュメント d の順位
  • 合計はすべての検索システムについて計算される
k の値 効果 最適な用途
k=0 純粋な順位融合(平滑化なし) 理論上のベースライン
k=10-30 上位の結果を大幅に重視し、下位の結果はほとんど寄与しない 上位3件が通常正しい場合
k=60(デフォルト) バランス型 — 上位10件の結果がすべて有意に寄与する 汎用的な検索
k=100+ より平坦 — 複数のシステムに現れる場合、下位の結果でも優勢になり得る 適合率より再現率が重要な場合
ターミナルウィンドウ
# デフォルト
MEMORY_RRF_K=60
# 積極的に適合率を重視(小規模なメモリ、少数のドキュメント)
MEMORY_RRF_K=20
# 最大限に再現率を重視(大規模なメモリ、多様なクエリ)
MEMORY_RRF_K=120

k=20 の例:

  • FTS順位1位 → 寄与度 1/21 = 0.048
  • FTS順位10位 → 寄与度 1/30 = 0.033
  • ベクトル順位1位 → 寄与度 0.048
  • 合計の最大値: 0.096

k=60 の例:

  • FTS順位1位 → 寄与度 1/61 = 0.016
  • FTS順位10位 → 寄与度 1/70 = 0.014
  • ベクトル順位1位 → 寄与度 0.016
  • 合計の最大値: 0.033

k が大きいほど、1位と10位の相対的な差は小さくなるため、アルゴリズムは上位順位の確信度よりも、検索システム間の一致を重視するようになります。

症状 試すこと
最上位の結果が常に選ばれるが、誤っている kを下げる(例: 20)— 上位順位の確信度をより重視する
正解が上位5件にはあるが、1位ではない kを上げる(例: 100)— より平坦なスコアリングで一致を評価する
再現率は高いが適合率が低い kを下げる — 順位付けをより明確にする
再現率が低い(関連ドキュメントを逃す) kを上げる — 下位のドキュメントにも機会を与える

Reciprocal Rank Fusionでは、セマンティックベクトル順位と全文検索順位に同じ重みを使用します:

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

個別の重みを調整するための環境変数はありません(MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT は存在しません)。


summarization.ts モジュール (src/lib/memory/summarization.ts) は、想起能力を維持しながらアクティブなセットを小さく保つため、古いメモリを圧縮します。

要約がトリガーされるタイミング

Section titled “要約がトリガーされるタイミング”
トリガー しきい値 (デフォルト)
API 経由の手動トリガー 該当なし

summarization.ts からは、次の 2 つのエントリポイントがエクスポートされています。

  • summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) — セッションの メモリを、トークン予算内に収まる単一の要約テキストへ圧縮します。
  • summarizeMemoriesOlderThan(apiKeyId, days, dryRun) — API で使用される 経過日数ベースの圧縮です。days より古いすべてのメモリを選択し、それらから 1 つの圧縮された要約メモリを作成し、dryRun が false の場合は 元のメモリを削除します。何も変更せずに候補セットと合計トークン数を プレビューするには、dryRun: true を渡します。

タグ/キーによるクラスタリング処理や、メモリごとの「中核か要約可能か」のスコアリングはありません。 選択は経過日数のカットオフのみに基づき、要約テキストは候補ごとに タイプを接頭辞として付けた圧縮済みの行で構成されます。

要約は手動 / オプトインです。autoSummarize 設定はデフォルトで false のため、 自動的に圧縮されることはありません。API 経由でトリガーしてください。

ターミナルウィンドウ
curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"

無効のままにするには、autoSummarize をデフォルト値 (false) のままにしてください。

要約品質を高めるためのヒント

Section titled “要約品質を高めるためのヒント”
  • 最初に dryRun でプレビューする — summarizeMemoriesOlderThan(..., true) は、 候補リストと合計トークン数を返すため、元のメモリを削除する前に 何が統合されるかを確認できます。
  • メモリのコーパスが大きい場合は、トラフィックの少ない時間帯に要約を実行する — LLM 呼び出しが最も時間のかかる部分です
ターミナルウィンドウ
# Cron 形式: 毎日午前 3 時に要約
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"

MemoryBackend プロバイダーパターン

Section titled “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 │
│ シングルトンオーケストレーター (manager.ts) │
│ │
│ プライマリ ──► バックエンド A (例: SQLite) │
│ フォールバック ─► バックエンド B (例: Obsidian) │
│ バックエンド C (例: GenericBackend │
│ 経由の Notion) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ SQLite │ │ Obsidian │ │ GenericMemory │
│ バックエンド│ │ バックエンド│ │ バックエンド │
│ │ │ │ │ (HTTP) │
└────────────┘ └────────────┘ └──────────────────┘

コアインターフェース (backend.ts)

Section titled “コアインターフェース (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&lt;boolean&gt;;
delete(id: string): Promise&lt;boolean&gt;;
list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// 検索
search(config: SearchConfig): Promise<Memory[]>;
// ヘルスチェック
health(): Promise<HealthCheckResult>;
// ライフサイクル (任意)
initialize?(): Promise&lt;void&gt;;
shutdown?(): Promise&lt;void&gt;;
}

次の処理を行うシングルトンオーケストレーターです。

  • register(backend) を介してバックエンドを登録 — 起動時に index.ts から呼び出されます
  • configure(primary, fallbacks) を介してプライマリ + フォールバックを設定
  • CRUD/検索をプライマリへルーティングし、失敗時にはフォールバックチェーンを使用
  • すべてのバックエンドを定期的にヘルスチェック

フォールバックの動作:

操作 プライマリ フォールバック
create ✅ プライマリのみ ❌
get ✅ 最初にプライマリを試行 ✅ null の場合にフォールバック
update ✅ プライマリのみ ✅ 非同期で同期処理
delete ✅ プライマリのみ ✅ 非同期で同期処理
list ✅ プライマリのみ ❌
search ✅ 最初にプライマリを試行 ✅ エラー時にフォールバック

任意の REST API を MemoryBackend に適合させる汎用 HTTP コネクターです。次の用途に役立ちます。

  • Notion — Notion API 経由で接続
  • Obsidian — Obsidian Local REST API 経由で接続
  • カスタムバックエンド — RESTful なメモリ API を公開する任意のサービス

設定:

interface GenericBackendConfig {
baseUrl: string; // バックエンド API のベース URL
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"); // → localhost:27123 を参照する GenericMemoryBackend
createKnownBackend("notion"); // → api.notion.com/v1 を参照する GenericMemoryBackend

デフォルトのプライマリバックエンドです。src/lib/memory/store.ts を使用して、既存の SQLite ベースのメモリストアをラップします。起動時に自動的に登録されます。

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

既存の Obsidian 統合(src/lib/memory/obsidianBackend.ts)をラップします。Obsidian Local REST API を介して Obsidian Vault に接続します。

メモリバックエンドの設定はアプリの設定テーブルに保存され、src/lib/memory/settings.ts を介して管理されます:

設定 環境変数/設定キー デフォルト 説明
プライマリバックエンド memoryPrimaryBackend "sqlite" プライマリバックエンドの ID
フォールバックバックエンド memoryFallbackBackends [] 順序付けされたフォールバックバックエンドの ID
バックエンド設定 memoryBackendConfigs {} バックエンドごとの設定オーバーライド

設定は normalizeMemorySettings() によって正規化され、getMemorySettings() でキャッシュされます。

アプリのブートストラップ
→ index.ts のインポート(副作用): SQLiteBackend を登録
→ アプリのライフサイクルから initMemoryBackends() を呼び出し:
1. 設定を読み込む(getMemorySettings)
2. プライマリとフォールバックを設定
3. すべてのバックエンドを初期化(ヘルスチェック)
4. リクエストを処理する準備が完了
  1. src/lib/memory/&lt;name&gt;Backend.ts で MemoryBackend を実装
  2. src/lib/memory/index.ts から エクスポート
  3. 起動時に memoryManager.register(yourBackend) で 登録
  4. 設定を介して 構成: memoryPrimaryBackend をバックエンド ID に設定
  5. src/lib/memory/__tests__/generic-backend.test.ts を参考に テスト
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 件。


OmniRoute ソースコード (a58000c7685f)

HagiCode

HagiCode は構造化ワークフロー、マルチエージェント実行、Hero Dungeon ビューを備えたエージェント型コーディングワークスペースです。

よりスマートで速く、楽しいエージェント型ワークフローで、使いやすいソフトウェアを形にします。

HagiCode ライトテーマのメイン画面
  • Smart構造化ワークフローは意図をアイデアから変更のリリースまで実行可能な道筋にします。
  • Efficientマルチエージェントのワークフローで調査、実装、レビューを並行して進めます。
  • FunHero Dungeon により長時間のコーディングを視覚的で協力的な体験にします。
HagiCode を見る