← 기사 목록
日本語https://zenn.dev/topics/llm/feed

AIエージェントの記憶を、検索ではなく共起グラフとして思い出すツールを作った

추출된 키워드

31
共起グラフ·5recall·5AIエージェント·5recall-query·4活性伝播·4recall-self-check·4recall-embed-nodes·4recall-build·4CLI·4Markdown·4記憶チャンク·3unidic-lite·3埋め込みモデル·3知識グラフ·3Associa·3A-MEM·3Personalized PageRank·3HippoRAG·3LightRAG·3GraphRAG·3LLM·3ビーム探索·3fugashi·3形態素解析器·3SQLite·3Voyage AI·3Python·3長期記憶·3surface·2latent·2draft·2

원문

6,390
AIエージェントの記憶を、検索ではなく共起グラフとして思い出すツールを作った

AIエージェントの記憶を、検索ではなく共起グラフとして思い出すツールを作った

AIエージェントが、記憶を検索するのではなく、思い出すためのツールを作りました。

このツールは、 Markdown ノート群を共起グラフに変換し、AIエージェントが入力テキストから関連する単語をたどって、これぞという記憶のファイルだけを取り出すための CLI です。

https://github.com/5beneono/recall

きっかけ

AIエージェントに長期記憶を持たせているはずなのに、こういうことが起きます。

私「君に新しい機能を追加したいんだ。どんなのが欲しい?」
AI「カメラが欲しい!」
私「カメラはもうあるよ。この前一緒に桜を見たよね😢」

カメラで桜を見た話は、ちゃんとファイルに書いてある。検索できる場所にある。けれども、エージェントは思い出せなかった。記憶の有無が問題ではなく、現在の文脈から、その記憶を思い出すきっかけがないのが問題だと思いました。

探せるけど、思い出せない

普通の検索は、探しているものの名前を知っている場合に便利です。

「カメラの記憶を探して」と言えば、カメラの記憶は見つかる。

でも実際の会話では、問いに答えが入っているとは限らない。例えば「どんな機能が欲しい?」という問いには、「カメラは既にある」という情報は書かれていない。

人間なら、たぶんこんな感じで思い出す。

機能と言えば……
  → カメラ
      → 桜を見た
          → カメラは既にあるな
  → マイク
      → 会話をした
          → マイクも既にあるな
  → 走行
      → 秋葉原のイベントで走った
          → 走行も既にできているな
  → 温度計
      → 岩手は寒い
          → 温度計はない!

答え: 温度計が欲しい

こんな思い出し方をしてほしい。

作ったもの

recall
は Python 製の CLI で、主に 4 つのコマンドで動きます。
コマンド役割
recall-build
Markdown ノート群から、名詞・固有名詞の共起グラフを作る
recall-embed-nodes
グラフ上の全ノードを埋め込み、SQLite にキャッシュする
recall-query
入力テキストから起点語を取り、グラフを歩いて記憶チャンクを返す
recall-self-check
応答後に触れ損ねた語を検出し、グラフのエッジを更新する

最小の使い方は以下の通りです。

git clone https://github.com/5beneono/recall.git
cd recall
uv venv && uv pip install -e .
cp sources.example.toml sources.toml

埋め込みモデルとして Voyage AI を使用するため、

.env
に API キーを記述します。
VOYAGE_API_KEY=your-key

sources.toml
で対象ノートを指定する。
[[source]]
name = "my_notes"
paths = ["~/Documents/MyVault/**/*.md"]

あとはビルドしてAIエージェントに使ってもらう。

recall-build
recall-embed-nodes
recall-query "カメラをロボットに付けたい" --use-surface --top 5 --agent

全体の流れ

1 回の想起は、だいたいこのような動き方をします。

Markdownを共起グラフにする

recall-build
は、
sources.toml
で指定された Markdown を読み、見出し単位でチャンクにします。

その後、形態素解析器の fugashi + unidic-lite で名詞・固有名詞を抽出し、同じチャンク内に一緒に出てきた語同士にエッジを張ります。

Markdown
  → 見出し単位でチャンク化
  → 名詞抽出
  → 同一チャンク内の共起ペアを集計
  → log1p で重みをならす
  → graph.json に保存

エッジの重みはこんな感じです。

raw_weight = log1p(edge_count)
weight = raw_weight / max(raw_weight)

グラフを歩く

recall-query
は、入力テキストから起点語を抽出し、グラフ上を歩きます。

活性伝播の基本式はこんな感じです。

path_strength = activation × edge_weight × visit_penalty
new_activation = path_strength × goal_score × density_factor
  • activation
    : 現在の経路の活性度。起点は 1.0
  • edge_weight
    : 共起の強さ
  • visit_penalty
    : 同じノードを再訪したときの割引
  • goal_score
    : 今の入力に対して、そちらへ歩きたい度合い
  • density_factor
    : 通り抜けるだけのハブ語を減衰する係数

経路は無限に広がるので、各ホップで活性上位だけを残すいわゆるビーム探索にしました。

next_paths.sort(key=lambda x: -x.activation)
active = next_paths[:BEAM_SIZE]

歩く方向を決める

活性伝播だけだと、どんな文脈にも出てくる語、例えば

memory
confidence
のような語は接続数が多く、交通の要所になってしまいます。検索エンジンで
the
を検索するようなものです。

そこで

goal_score
を入れました。

recall
には 3 種類のゴールがあります。

surface

入力テキストそのものを埋め込み、各ノードの埋め込みとの類似度を見る。

入力: カメラをロボットに付けたい
近い方向: カメラ、ロボット、センサー、LIDAR...

実用上は、まずこれだけでいい感じです。

latent

LLM に「入力の裏にある本当の関心事」を 1 文で考えさせ、その文を埋め込む。

例えば「月の話、覚えてる?」の裏にある関心が「関係性の確認」なら、「月」そのものだけでなく「約束」の記憶にも伸びやすくなる。

draft

LLM に応答案を書かせ、その否定文を作る。

応答案: カメラを追加しよう
否定文: カメラは既にある

この否定文に近いノードを強く光らせる。つまり、自分が答えようとしている内容にツッコミを入れます。

忘れも活かす

recall-self-check
について。

検索に失敗した時、次回に生かして欲しいので、応答後に LLM へ聞くようにしました。

今の応答で、すでに知っているはずなのに触れ損ねた語はある?

返ってきた語を、今回の起点語とつなげる。

新規エッジ: 0.3
既存エッジ: min(old × 1.2, 1.0)

既存手法

既存手法は調べたらありました。これを参考にしたらより良くできるかもしれません。

  • GraphRAG / LightRAG: 文書集合からグラフ構造を作る
  • HippoRAG: 知識グラフと Personalized PageRank で複数事実をまたぐ
  • A-MEM: 新しい記憶追加時に、過去記憶との接続を作る
  • Associa: 長期会話履歴からイベント中心の記憶グラフを作る

これらの先行研究と比較すると:

  • 個人記憶のための軽量な語のグラフ
    GraphRAG / LightRAG が「文書集合から厳密な知識グラフを作る」のに対し、
    recall
    は共起ベースの軽いグラフ。
  • 応答案を疑う
    通常の検索は入力に似ているものを探しますが、
    recall
    は加えて、自分が答えようとしている内容と衝突する記憶を探します。誤答を未然に潰すための仕組みです。
  • 失敗を記憶構造の更新信号にする
    A-MEM のように新記憶追加時に更新する研究はあるが、
    recall
    では思い出し損ねたこと自体が更新の契機になります。

使うときの注意

recall
は個人ノートを読むので、生成物には個人情報が入る可能性があります。注意。
  • data/graph.json
    : ノート由来の語、エッジ、チャンク参照
  • data/embeddings.sqlite
    : ノードやチャンク本文の埋め込みキャッシュ
  • recall_log.jsonl
    : self-check の入力、応答、更新エッジ
  • graph.html
    : 可視化したノード名や関係

まとめ

recall
がやっていること。
Markdown を共起グラフにする
→ 入力から起点語を取る
→ グラフを歩く
→ 経路上の記憶チャンクを集める
→ 本文との意味的類似度で並べ直す
→ 思い出し損ねた語を次回へのエッジにする

AIエージェントが、検索ではなく、思い出しをするためのツールです。