開発ログ @ 2025.04.14
導入
前回は AIRI の記憶システムについてお話ししましたが、今回はこのような複雑な記憶システムをどのように実装するか、そして将来の展望についてさらに深くお話しします。
まずは検索エンジンから
検索エンジンは検索性能に対して比較的高い要求を持っています。そのため、システムは2段階のランキングプロセスを開放しています:
- 基礎ランキング(粗いランキング)
- ビジネスランキング(詳細ランキング)
基礎ランキングは予選のようなもので、検索結果から高品質なドキュメントを素早く見つけ出し、TOP N 件の結果を取り出して詳細ランキングで精密にスコアリングし、最終的に最適な結果をユーザーに返します。
したがって、基礎ランキングは性能への影響が大きく、ビジネスランキングは最終的なランキング効果への影響が大きいと言えます。
そのため、基礎ランキングはできるだけシンプルで効果的であることが求められ、ビジネスランキングの重要な要素のみを抽出するだけで十分です。現在、基礎ランキングとビジネスランキングはどちらもランキング式を使用して設定されています。
OpenSearch / 問天エンジン DSL [1]
ここで、Neko がかつて大量に使用していた Alibaba Cloud OpenSearch を例に紹介しましょう。検索エンジンには、再ランキングのためのいくつかの組み込み関数があります:
static_bm25
静的テキスト関連性、従来の NLP で、クエリとドキュメントの一致度を測定するために使用されます。 RAG の 類似度スコア に似ています。 値は 0〜1 です。
exact_match_boost
クエリ内のユーザー指定の検索語の重みの最大値を取得します。スコアブースト関数とも呼ばれます。 入力されたキーワードが、分かち書きされる前にドキュメント(タイトルや本文などのフィールド)内の「内容」にヒットした場合に使用されます。 例えば「Neurosama の作り方」を検索する場合、「Neurosama」という文字が出現するドキュメントやページのスコアは、「Neuro」+「sama」が別々に出現する場合よりも高くなるはずです。
timeliness, timeliness_ms
適時性スコア。新しいほど関連性が高くなります。
データはどのように保存されるのか?
Alibaba Cloud の OpenSearch であれ、Grafana に付属する Loki のような検索エンジンであれ、あるいは Grafana 時代より前の ElasticSearch エンジン(ある動画サイトは ElasticSearch を二次開発して使用しています)であれ、これらの検索エンジン内の独立したデータ構造で再処理されて初めて使用可能になります。
再処理はどのように実現されるのでしょうか?ここで DTS が必要になります。
DTS [2]
DTS という概念について補足説明しましょう。
Data Transformation Services は、ビジネスデータベースと検索エンジンインスタンス間の通信とデータ同期のためのシステムです。
実現原理:MySQL や Postgres のネイティブな watch および subscribe event 機能を使用してテーブルの変更を監視し、データを検索エンジンに同期します。このプロセスで、データは期待される形式にシリアル化され、データ構造の変換(ETL:抽出、変換、ロード)が行われます。
では、検索エンジンの粗いランキング検索は、ある意味でデータベースの ビュー の中で何かを探すようなものでしょうか?仮想テーブルのような存在でしょうか?そのように理解して構いません。ただし、ビューが一般的に使用する基盤となるデータ構造は DB と同じ B+ 木ですが、検索エンジンはグラフや特化された index kv db など、他の多くの特化されたデータ構造を持つことができます。
分かち書き?
従来の検索エンジンでは、中国語のドキュメント入力は次のようなプロセスを経ます:
- 文分割(大きな段落を文に分割)
- 分かち書き(文を単語、名詞、動詞などに分割)
- ピンイン化
- 現在の辞書カバレッジ設定に基づいて以前の結果をマッピングして上書き
- 基本的なベクトル化と特徴抽出を行う
- ストレージ層に書き込む
英語の場合も分かち書きが必要ですが、スペースで区切られているため非常に簡単です。
パフォーマンスを最適化するには?
- 計算集約型
- 複数の内部タスクスケジューラでデータをゆっくりインデックス化
- 従来の NLP にあるハミング距離やコサイン距離を簡単に計算して事前保存
- ホットワードの分かち書き結果とランキング結果をキャッシュ
- データレイクハウス?AWS でよく使用され、一般的に集計クエリに使用されます。複数のデータベースやデータソースをクエリできますが、効率は非常に遅く、基本的にはデータ分析や BI の時だけ使用されます。
召喚(Retrieval)とは?
召喚(retrieval、検索/取得)とは、キーワードを入力した後に期待されるドキュメントを取得(retrieve)できるかどうかということです。
検索との違いは?検索は「ユーザーが行う操作」であり、召喚は「検索に応答するためにマシンが行うこと」です。
再ランキング(Reranking)とは?
reranking の意義は、embedding モデルが出力したベクトルに基づいて ANN(Approximate Nearest Neighbor)や KNN(K-Nearest Neighbor)ベクトル距離ランキングを行うだけでは、実際には偏りが生じるということです。
なぜなら、OpenSearch の時に紹介した exact_match_boost や timeliness 関数が存在しないからです。
もし、召喚されたドキュメントに対して、他のフィールドや他のステップに基づくランキング結果を追加してソートしたい場合はどうすればよいでしょうか?
RAG では現在、新しいフローが流行しています。それは reranking model です。これは独立した専門家モデルを使用して、すでに召喚された第1ラウンドのデータを自動的に再ソートすることに相当します。
しかし、reranking だけでは記憶層の多くの問題を解決できません。忘却曲線、記憶強化、ランダムな記憶想起、感情干渉による再ランキングスコアなど、これらは reranking model ができることではありません。
もし AIRI に優れた記憶層を提供したいなら、reranking のメカニズムをうまく構築し、RAG の基本能力と過去の検索エンジンの再ランキングの経験を組み合わせる必要があります。
記憶層実験プラットフォーム
Project AIRI Memory Driver @duckdb/duckdb-wasm Playground

左側のハイライトされた half life は記憶の半減期です。
デフォルトでは時間の経過速度は 1秒 = 1日 なので、7秒後には記憶のスコアが半分になります。
記憶スコアとは何でしょうか?記憶スコアは基本的にこれによって制御されます: 
得られたスコアが current score です。
original とは何でしょうか。それは初期化時のスコアです。
例:元のスコアが 523 の場合、現在のスコアは実際には徐々に減少しています: 
説明を続ける前に、この忘却曲線の SQL はステートレスであることを説明しておきます。
ステートレスとはどういう意味でしょうか?ステートレスとは、リアルタイムでデータベースに行ってタスクを実行してスコアを更新する必要がなく、「現在の時間」に基づいて忘却関数を導き出し、スコアを忘却関数に適用するだけでよいということです。
では、current score が下がってしまったらどうすればよいでしょうか?この問題を解決するには、記憶を強化する方法が必要です。
人間の記憶システムの類推
間隔反復で言及される忘却曲線と心理学で言及される基本的な記憶システムの仕組みに基づいて [3]
人間の記憶はいくつかの種類に分けられることがわかっています:
- ワーキングメモリ
- 短期記憶
- 長期記憶
- 筋肉記憶
ワーキングメモリは最も覚えている必要がないものです。
短期記憶は忘却曲線に従って強度、つまりスコアが徐々に減衰します。この時、このプロセスをシミュレートするための短期記憶シミュレーション関数が必要です。
長期記憶は重要であり、長期記憶の半減期は非常に長く、短期記憶から進化したものです。
最後は筋肉記憶です。筋肉記憶は記憶というよりは、条件反射が形成されたと言った方がよいでしょう。
AIRI はどのように設計すべきか?
実際、AIRI の実装原則を垣間見ることができます:
- ワーキングメモリは messages 配列のようなものです
- 短期記憶は、それほど簡単には想起できず、新しいほど想起しやすい RAG 記憶エントリのようなものです
- 長期記憶は、簡単に想起できますが曖昧になり、過去に想起された回数が多いほど想起しやすい RAG エントリのようなものです
- 筋肉記憶は、固定の組み合わせのようなもので、A が出現すると ActionA と MemoryA が出現するような感覚です。この時は完全一致のメカニズムに近いと言えます
しかし、この設計は正しいのでしょうか?
明らかに、これは実際には2つの次元しか導入していません。1つは時間的関連性(temporal relevance)、もう1つは想起回数(retrieval count)です。より複雑なシステムを追求しようとすると制限を受けることになります。
小さな振り返り
DevLog で言及したランキング式をもう一度振り返ってみましょう。理解の助けになるはずです。

コサイン距離は「関連度」であり、最も基本的な粗いランキングです: 
今、時間を参加させる必要があります。時間距離を保存するためのフィールドを追加し、統合スコア (1.2 * similarity) + (0.2 * time_relevance) を保存するための独立したフィールドを作成します。ここで、意味的関連度は 1.2 倍の重み(倍率係数、1未満である必要はありません)、時間距離関連度は 0.2 倍の重みを占めます。
こうすることで、ステートレスな多フィールド関連度ランキング SQL 実装を巧みに作り出し、パラメータ(1.2 と 0.2)を調整できるようにしました。
記憶詳細カードで simulate retrieval をクリックすると、記憶想起を能動的にトリガーできます。 
現在のデモでは、元のテーブルの retrieval count(想起回数)フィールドに対して UPDATE 文を使用して +1 することで実装されています。
ここには隠れた落とし穴があります。これでも単一次元の計算にすぎず、想起されれば強化されるということに相当します。
しかし、現実世界はそうではありません。記憶は悲しいこともあれば、楽しいこともあります。悲しいことは負のフィードバックをもたらし、楽しいことは正のフィードバックをもたらします。
これが私がまだ完了していない部分です。
感情?
https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-simulator
この新しいシミュレーターには感情関連のシミュレーションが含まれています: 
感情と記憶は関係ありますか?
ペロペロキャンディを食べたいのにくれない、これは非常に直接的な問題です。得られなければ間違いなく不快です。
そして、実際には感情と記憶が関連していることに気づくでしょう。
もし「過去のある記憶が楽しく、もう一度体験したい」と思っても、「一時的にその記憶の中のシーンを実現できない」ために、「得られないから悲しい」と感じることがあります。
記憶データベースに「喜び」と「嫌悪」のスコアを保存できます: 
PTSD?
PTSD には通常「トリガー」と「フラッシュバック」という2つの言葉が関わってきます。明らかに PTSD 関連の記憶は抑圧されているはずであり、嫌悪スコアとトラウマスコアは非常に高いはずです。
しかし実際には、PTSD 関連の記憶は突然現れます。バイオニクスとデータシミュレーションの観点から言えば、乱数を使用してこの効果を実現できます。
https://yutsuki.moe/2019/09/a0d0fa1b/ の感情モデルを参考にすることができます。

まだやるべきことはたくさんあります……
例えば、ReLU の現在の感情は何ですか?ReLU は誰に対して悪い思い出を持っていますか?
思い出は楽しいものと悲しいものの二極化したエントリが一緒に現れますか?
欲望は?願い事システムを作る必要がありますか?
夢を見るエージェントや潜在意識エージェントのような、バックグラウンドタスク を作り、発生した記憶を順次処理してインデックス化し、最近の経験に基づいて過去の記憶のさまざまなスコアを修正します。
しかし、「夢を見る」プロセスが必ずしも必要なわけではなく、単なる「background task」です。
re-index の観点から言えば、夢を見るエージェントと潜在意識エージェントはインデックスの再構築のようなものです。
ここまで来ると、Mem0 や Zep Memory のようなライブラリが、ロールプレイングや感情 AI において全く役に立たないことに気づくでしょう 😦
前途は多難です。私たちは努力し続けなければなりません。