Fragments of verbose memory

冗長な記憶の断片 - Web技術のメモをほぼ毎日更新

Dec 25, 2025 - 日記

LEANN: 97%ストレージ削減のプライベートRAGシステム徹底解説

LEANN: 97%ストレージ削減のプライベートRAGシステム徹底解説

オープンソースのプライベートRAG システム LEANN が注目を集めています。本記事では、驚異的な97%のストレージ削減を実現しながら高速・高精度を両立するこのシステムについて、その革新的な仕組みと実装方法を詳しく解説します。

LEANNとは?

LEANN(Low-Storage Vector Index)は、ローカル環境で動作する完全プライベートなRAGシステムです。Berkeley Sky Computing Lab で開発され、従来のベクトルデータベースとは根本的に異なるアプローチを採用しています。

主な特徴:

  • 97%のストレージ削減: 従来のベクトルDB(FAISS等)と比較して大幅な軽量化
  • 精度の損失なし: 同等の検索品質を維持
  • 完全プライベート: すべての処理がローカルで完結
  • スケーラブル: 6,000万文書をノートPC上でインデックス化可能

個人のノートPCやMacで、メール(78万件 → 79MB)、ブラウザ履歴(38,000件 → 6.4MB)、WeChat(40万件 → 64MB)など、あらゆるデータをプライベートに検索できます。

革新的な仕組み:グラフベースの選択的再計算

LEANNの最大の特徴は、埋め込みベクトルを保存しないことです。

従来のベクトルDB(FAISS等)

# すべての埋め込みベクトルを事前計算して保存
documents = ["文書1", "文書2", "文書3", ...]
embeddings = model.encode(documents)  # 例: 100万文書 × 768次元 × 4バイト = 3GB

# 保存
index.add(embeddings)  # 3GB のストレージ使用

LEANNのアプローチ

# グラフ構造のみを保存(埋め込みは保存しない)
builder = LeannBuilder(backend_name="hnsw")
builder.add_text("文書1")
builder.add_text("文書2")
builder.build_index("index.leann")  # グラフのみ保存(90MB程度)

# 検索時に必要な埋め込みだけを動的に計算
searcher = LeannSearcher("index.leann")
results = searcher.search("クエリ", top_k=5)  # 検索パス上のノードのみ埋め込みを再計算

核心技術

  1. Graph-based Selective Recomputation

    • グラフ構造(HNSW/DiskANN)のみを保存
    • 検索時に探索パス上のノードだけ埋め込みを計算
    • 全体の数%のノードしか計算しないため高速
  2. High-degree Preserving Pruning

    • グラフの「ハブ」ノード(重要な接続点)を保持
    • 冗長な接続を削除してグラフサイズを最小化
    • CSR(Compressed Sparse Row)形式で効率的に保存
  3. Dynamic Batching

    • 複数の埋め込み計算をバッチ処理
    • GPUを効率的に活用

日本語サポートについて

LEANNはデフォルトで multilingual embedding モデルsentence-transformers/all-MiniLM-L6-v2 )を使用しており、日本語に対応しています。

重要な区別:

  • インデックス作成時: 埋め込みモデルのみ使用(LLM は不要)
  • 検索時: 埋め込みモデルで類似度計算
  • 回答生成時: LLM(Ollama等)を使用(オプション)

つまり、セマンティック検索だけならLLM不要で、軽量な埋め込みモデルだけで動作します。これにより:

  • ✅ インデックス構築が高速(3.21秒で98記事)
  • ✅ 検索も高速(ミリ秒単位)
  • ✅ メモリ使用量が少ない(埋め込みモデル約90MB)
  • ✅ 日本語の技術用語も正しく理解

セットアップ

前提条件

  • Python : 3.9以上(3.13まで対応)
  • プラットフォーム: macOS(ARM64/Intel)、Linux(Ubuntu/Arch/WSL)
  • 依存: uv (Pythonパッケージマネージャ)

インストール

# uvのインストール
curl -LsSf https://astral.sh/uv/install.sh | sh

# LEANNのインストール(PyPIから)
uv venv
source .venv/bin/activate  # Windowsの場合: .venv\Scripts\activate
uv pip install leann

基本的な使い方

シンプルな検索

from leann import LeannBuilder, LeannSearcher
from pathlib import Path

# インデックスを構築
builder = LeannBuilder(backend_name="hnsw")
builder.add_text("LEANNは97%のストレージ削減を実現します。")
builder.add_text("グラフベースの選択的再計算が核心技術です。")
builder.add_text("HNSWとDiskANNの2つのバックエンドをサポートします。")
builder.build_index("demo.leann")

# 検索
searcher = LeannSearcher("demo.leann")
results = searcher.search("ストレージ削減の方法", top_k=2)

for result in results:
    print(f"スコア: {result.score:.3f}")
    print(f"テキスト: {result.text}\n")

LLMとの統合(RAGパイプライン)

from leann import LeannChat

# Ollamaと統合(完全プライベート)
chat = LeannChat(
    "demo.leann",
    llm_config={
        "type": "ollama",
        "model": "llama3.2:1b"
    }
)

# 質問
response = chat.ask("LEANNの主な特徴は?", top_k=3)
print(response)

コマンドラインインターフェース

LEANNはCLIツールも提供しています。

# グローバルインストール(Claude Code統合に必要)
uv tool install leann-core --with leann

# ドキュメントからインデックスを構築
leann build my-docs --docs ./documents

# 検索
leann search my-docs "機械学習の概念"

# インタラクティブチャット
leann ask my-docs --interactive

# インデックス一覧
leann list

# インデックス削除
leann remove my-docs

CLIは自動的にファイル形式を検出し、PDF、TXT、MD、DOCX、PPTXなどに対応します。Python、Java、C#、TypeScriptのコードファイルには AST-aware chunking(構文解析を考慮したチャンク分割)を適用します。

実用的なユースケース

1. メール検索(Apple Mail)

# macOSでApple Mailをインデックス化
python -m apps.email_rag --query "DoorDashで注文した食べ物は?"

結果: 78万件のメール → 78MBのストレージ

2. ブラウザ履歴検索

# Chrome履歴を検索
python -m apps.browser_rag --query "機械学習に関する閲覧履歴"

結果: 38,000件の履歴 → 6.4MBのストレージ

3. チャット履歴検索(WeChat/iMessage/ChatGPT/Claude)

# WeChat履歴
python -m apps.wechat_rag --query "週末の予定についての会話"

# iMessage履歴
python -m apps.imessage_rag --query "家族の集まりの話"

# ChatGPT会話履歴
python -m apps.chatgpt_rag --export-path conversations.html --query "Pythonの質問"

# Claude会話履歴
python -m apps.claude_rag --export-path claude_data.json --query "デバッグのアドバイス"

4. ドキュメント検索

# PDFやマークダウンファイルを検索
python -m apps.document_rag --data-dir ~/Documents/Papers --query "LEANNの主要技術"

5. MCP 統合(Slack/Twitter)

# Slackメッセージ検索
python -m apps.slack_rag \
  --mcp-server "slack-mcp-server" \
  --workspace-name "my-team" \
  --channels general dev-team \
  --query "製品ローンチの決定事項は?"

# Twitterブックマーク検索
python -m apps.twitter_rag \
  --mcp-server "twitter-mcp-server" \
  --query "[AI](/tags/ai/)に関する記事"

Claude Desktop統合:実際に動かしてみた

LEANN の真価を確かめるべく、自分のブログ記事(98記事、600KB)でClaude Desktop統合を試してみました。

実際の動作デモ

まず、実際に動いている様子をご覧ください。Claude Desktop で自然言語の質問をするだけで、ブログ記事から関連情報を検索できます。

Claude Desktop LEANN Demo

質問: “blog.tumf.dev 記事からSSHのおしゃれな使い方を5つ選んで”

Claude が leann_search ツールを自動的に呼び出し、ブログ記事から SSH 関連の記事を検索。その結果から以下の2つを選出しました:

  1. 📁 ~/.ssh/config を分割して管理する - プロジェクトごと・環境ごとに分割
  2. 🖥️ ssh-argv0 で ssh コマンドを省略 - example.com だけで接続

どのように動作しているか

上記のデモでは、裏側で以下の処理が行われています:

  1. 自然言語の質問 → Claude が意図を理解
  2. leann_search ツールを自動呼び出し → MCPプロトコル経由
  3. セマンティック検索実行 → LEANN がブログ記事を検索
  4. 結果の選出と要約 → Claude が適切な記事を選んで説明

つまり、LEANNのMCPサーバーがClaude Desktopと連携し、自然言語での質問をセマンティック検索に変換してくれます。ユーザーは検索クエリを考える必要がなく、普通に話すように質問するだけで、適切な記事を見つけてくれます。

セットアップ方法

このデモ環境を構築するには、わずか3ステップです。

# 1. LEANNをグローバルインストール
uv tool install leann-core --with leann

# 2. ブログ記事をインデックス化
cd ~/work/blog.tumf.dev
leann build blog.tumf.dev --docs content/posts/**/*.md

# 3. Claude Desktop設定
# ~/Library/Application Support/Claude/claude_desktop_config.json を編集

設定ファイル(zshサブシェル方式・推奨):

{
  "mcpServers": {
    "leann": {
      "command": "/bin/zsh",
      "args": [
        "-c",
        "cd ~/work/blog.tumf.dev && exec leann_mcp"
      ]
    }
  }
}

ポイント:

  • .zshrc の PATH が自動的に使われる(環境変数の手動設定不要)
  • 作業ディレクトリを明示的に指定(.leann/ を正しく見つけるため)
  • シェルスクリプトラッパー不要(設定ファイルのみで完結)

Claude Desktop を再起動すれば、すぐに使い始められます。

検索の裏側を覗いてみる

先ほどのデモで、Claudeがどのような検索を実行したか見てみましょう。

検索パラメータ(Claudeが自動生成):

{
  "query": "SSH config Include sshconf プロキシ ポートフォワード",
  "top_k": 10,
  "index_name": "blog.tumf.dev",
  "show_metadata": true
}

Claudeは質問「SSHのおしゃれな使い方を5つ選んで」から、自動的にキーワード(“SSH config Include sshconf プロキシ ポートフォワード”)を抽出しています。

検索結果(トップ5):

  1. Score: 0.373 - ~/.ssh/config を分割して管理する(改良版)
  2. Score: 0.317 - shell-jsonrpc(JSON-RPC経由でシェルコマンド実行)
  3. Score: 0.309 - JSON-RPC経由シェル実行スクリプト
  4. Score: 0.301 - GoogleSpreadSheet JSON取り込み
  5. Score: 0.280 - SSH設定ファイル分割管理(2025年版ベストプラクティス)

日本語セマンティック検索の精度:

  • 日本語記事の検索が正常動作(98記事すべて日本語)
  • ✅ キーワード完全一致でなく、意味的に関連する記事を発見
  • ✅ “config”、“Include”、“プロキシ” などの技術用語を正しく理解
  • ✅ 日英混在の技術文書でも適切に検索
  • ⚠️ スコア 0.280未満には無関係な記事も混入(Authgear、Ethereumなど)

使用モデル:

Claude の後処理能力:

  • ✅ 検索結果10件から適切に2つを選出
  • ✅ 無関係な記事(キーボード、Ethereumなど)は除外
  • ✅ 自然言語での質問(“おしゃれな使い方”)を適切に解釈

パフォーマンス

インデックス構築:

  • 処理時間: 3.21秒
  • 処理速度: 32,140 chunks/秒
  • ストレージ: 597.55 KB(98記事)

ストレージ削減率:

  • 小規模データ(98記事): -1.6%(削減効果なし)
  • 理由: グラフ構造のオーバーヘッド > 埋め込み削減効果
  • 重要: 97%削減は1,000文書以上の大規模データで実現

LEANN 内部動作:

[read_HNSW NL v4] Read levels vector, size: 98
[read_HNSW NL v4] Read entry_point: 73, max_level: 1
INFO: Skipping external storage loading, since is_recompute is true.
ZmqDistanceComputer initialized: d=384, metric=0
  • グラフノード数: 98(記事数と一致)
  • 埋め込み次元: 384(sentence-transformers/all-MiniLM-L6-v2)
  • is_recompute: true(埋め込みを保存せず、検索時に再計算)

実用性の評価

✅ 実現できたこと:

  • 自然言語での質問(“SSHのおしゃれな使い方を5つ選んで”)
  • ブログ記事からのセマンティック検索
  • Claude による結果の要約と選出
  • 完全プライベート環境(100%ローカル)

💡 活用シーン:

  • ブログ記事の再発見: 過去に書いた記事を忘れていても検索可能
  • 技術的な質問への回答: 自分の知識ベースから情報抽出
  • 記事間の関連性発見: 類似トピックの記事を発見

課題と今後の改善方向

実験を通じて、いくつかの課題が見えてきました。

🔍 検索精度の課題:

  1. 共通語彙による誤検出

    • 症状: スコア 0.280未満で無関係な記事が混入(Authgear、Ethereumなど)
    • 原因: “config”, “JSON”, “管理”, “設定” などの技術文書で頻出する共通語彙
    • 例: SSH設定を検索 → Authgear設定、Ethereum設定もヒット
    • 影響: top_k=10 の場合、下位5件にノイズが含まれる
  2. スコア分布の問題

    • 最高スコア: 0.373(関連度高)
    • 平均スコア: 0.278(中程度)
    • ノイズ混入: 0.280未満
    • 完全一致スコア(0.9以上)が出ない

💡 次回以降の改善テーマ:

  1. パラメータチューニング

    • top_k を5-7に減らす(ノイズ削減)
    • complexity を64に上げる(検索精度向上)
    • メタデータフィルタの活用
  2. 埋め込みモデルの変更

  3. ハイブリッド検索の導入

    • セマンティック検索 + キーワード検索
    • BM25とのハイブリッド
    • リランキングの活用
  4. 大規模データでの検証

    • 1,000記事以上で97%ストレージ削減を実証
    • 検索精度の変化を測定
    • パフォーマンスのスケーラビリティ確認

これらの改善については、別途記事で取り上げる予定です。

ストレージ比較

データソース件数FAISSLEANN削減率
DPR (論文)210万3.8GB324MB91%
Wikipedia6,000万201GB6GB97%
WeChat40万1.8GB64MB97%
Email78万2.4GB79MB97%
Browser3.8万130MB6.4MB95%

バックエンドの選択

HNSW(デフォルト)

  • 特徴: 最大のストレージ削減(完全な再計算)
  • 適用: ほとんどのユースケース
  • メモリ: 比較的少ない
builder = LeannBuilder(backend_name="hnsw")

DiskANN

  • 特徴: 最高の検索速度
  • 技術: PQベースのグラフ探索 + リアルタイム再ランキング
  • 適用: 大規模データセット(100万件以上)
builder = LeannBuilder(backend_name="diskann")

メタデータフィルタリング

# メタデータ付きでインデックス化
builder.add_text(
    "def authenticate_user(token): ...",
    metadata={"file_extension": ".py", "lines_of_code": 25}
)

# フィルタ付き検索
results = searcher.search(
    query="認証関数",
    metadata_filters={
        "file_extension": {"==": ".py"},
        "lines_of_code": {"<": 100}
    }
)

対応演算子: ==, !=, <, <=, >, >=, in, not_in, contains, starts_with, ends_with

詳細はメタデータフィルタリングガイド を参照。

動的インデックス更新

LEANNでは、一度作成したインデックスにドキュメントを追加・削除・変更することが可能です。この「Dynamic Index Update(動的インデックス更新)」機能により、データの増減に柔軟に対応できます。

2つの更新モード

1. Recompute Mode(再計算モード) - デフォルト

# 埋め込みを動的に再計算(ストレージ効率最大)
builder = LeannBuilder(backend_name="hnsw", is_recompute=True)
builder.add_text("既存のドキュメント...")
builder.build_index("index.leann")

# 後から追加
builder.add_text("新しいドキュメント")
builder.build_index("index.leann")  # 既存インデックスを更新
  • 97%のストレージ削減を維持
  • ✅ メモリ使用量が少ない
  • ⚠️ 更新時に再計算のオーバーヘッド

2. No-Recompute Mode(非再計算モード) - 頻繁な更新に推奨

# 埋め込みを保存して再利用(更新が高速)
builder = LeannBuilder(
    backend_name="hnsw",
    is_recompute=False,
    is_compact=False  # no-recomputeの場合は必須
)
  • 更新が高速(埋め込み再計算不要)
  • ✅ 頻繁なデータ追加に最適
  • ⚠️ ストレージ使用量が増加

No-Recompute後のまとめて再計算

頻繁な更新時はNo-Recompute Modeで高速に追加し、後でRecompute Modeに切り替えてストレージを最適化したいケースがあります。

現状: leann reindex コマンドはIssue #141 で提案されていますが、現時点では未実装です。

現時点での回避策:

# 1. No-Recomputeで高速に追加
builder = LeannBuilder(backend_name="hnsw", is_recompute=False, is_compact=False)
for doc in new_documents:
    builder.add_text(doc)
builder.build_index("temp_index.leann")

# 2. 全データを取得して新しいインデックスを再構築
searcher = LeannSearcher("temp_index.leann")
all_texts = extract_all_texts(searcher)  # 全文書を取得

# 3. Recomputeモードで最適化されたインデックスを作成
new_builder = LeannBuilder(backend_name="hnsw", is_recompute=True)
for text in all_texts:
    new_builder.add_text(text)
new_builder.build_index("optimized_index.leann")  # 97%削減を実現

将来の展望(Issue #141で提案中):

# 頻繁な更新フェーズ(No-Recompute)
leann build my-docs --docs ./initial --no-recompute --no-compact
leann update my-docs --docs ./batch1
leann update my-docs --docs ./batch2

# 定期的な最適化フェーズ(計画中)
leann reindex my-docs  # Recomputeモードに変換

また、PR #184 では leann update コマンドの実装が進められており、既存インデックスへの増分追加がCLIから簡単に行えるようになる予定です。

ドキュメントの削除

現状: 個別ドキュメントの削除APIは現時点で未実装です。LeannBuilderクラスにはadd_textupdate_indexメソッドのみが存在し、削除用のメソッドは提供されていません。

回避策:

  1. インデックスの再構築(推奨)

削除したいドキュメントを除外して、インデックスを再構築します:

# 削除したいドキュメントを除外して再構築
builder = LeannBuilder(backend_name="hnsw")
for doc in all_documents:
    if doc.id not in documents_to_delete:
        builder.add_text(doc.text, metadata=doc.metadata)
builder.build_index("new_index.leann")
  1. メタデータフィルタリングによるソフト削除

インデックス自体は変更せず、検索時にフィルタリングで除外する方法です:

# 削除フラグをメタデータに設定しておく
# 検索時にフィルタリングで除外
results = searcher.search(
    query="検索クエリ",
    metadata_filters={"deleted": {"==": False}}
)

この方法はインデックス再構築のコストを避けられますが、ストレージは削減されません。定期的なメンテナンス時に再構築することを推奨します。

実装の詳細

この機能は以下のPRで実装されました:

  • PR #108 : “Introducing dynamic index update”(2025年9月)- 基本実装
  • PR #148 : “Faster Update”(2025年11月)- 更新処理の高速化

両方のバックエンド(HNSW と DiskANN)で動作します。詳細は benchmarks/update/README.md を参照してください。

Grep検索

セマンティック検索ではなく、完全一致検索も可能です。

# 完全一致検索
results = searcher.search("banana‑crocodile", use_grep=True, top_k=1)

エラーメッセージ、関数名、特定のコードパターンの検索に有用です。

注意点と制限事項

再計算のオーバーヘッド

検索時に埋め込みを動的に計算するため、初回検索時は若干の遅延があります。ただし、動的バッチングとGPU最適化により、実用上は問題ありません。

メモリ使用量

ストレージは削減されますが、検索時には埋め込みモデルをメモリにロードする必要があります。

ビルド時間

大規模データセット(100万件以上)では、インデックス構築に時間がかかります。

# プログレス表示付きで実行
python -m apps.document_rag --data-dir ./large_corpus --max-items 1000000

まとめ

LEANNは、プライバシーを重視しつつ効率的なRAGシステムを構築したい開発者にとって画期的な選択肢です。従来の「すべての埋め込みを保存する」アプローチから、「必要な時だけ計算する」アプローチへのパラダイムシフトを実現しています。

実証実験から分かったこと

本記事では、実際に自分のブログ記事(98記事、600KB)でLEANNとClaude Desktopの統合を試しました。その結果:

✅ 成功したこと:

  • 3ステップ(3分未満)で完全プライベートなRAG環境を構築
  • 日本語セマンティック検索が正常動作
  • 自然言語での質問(“SSHのおしゃれな使い方を5つ選んで”)に適切に回答
  • Claude Desktop/Code から MCP 経由でシームレスに統合

⚠️ 注意点:

  • 小規模データ(100文書未満)ではストレージ削減効果は限定的
  • グラフ構造のオーバーヘッドが埋め込み削減効果を上回る
  • 97%削減は1,000文書以上の大規模データで真価を発揮

推奨ユースケース

個人的には、以下のユースケースで特に有用だと感じました:

  • 企業の機密情報検索: データを外部に送信せずに社内ドキュメントを検索
  • 個人の知識ベース: メール、チャット、ブラウザ履歴など、すべての個人データを統合検索
  • 開発者ツール: コードベース全体をClaude Code経由でセマンティック検索
  • 大規模文書コレクション: 1,000件以上の文書で97%のストレージ削減を実現

ノートPC1台で6,000万文書を扱える時代が来ました。特に大規模データを扱う場合、LEANNは従来のベクトルDBの代替として非常に魅力的な選択肢です。興味のある方はぜひ試してみてください。

参考リンク