
オープンソースのプライベート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) # 検索パス上のノードのみ埋め込みを再計算
核心技術
Graph-based Selective Recomputation
- グラフ構造(HNSW/DiskANN)のみを保存
- 検索時に探索パス上のノードだけ埋め込みを計算
- 全体の数%のノードしか計算しないため高速
High-degree Preserving Pruning
- グラフの「ハブ」ノード(重要な接続点)を保持
- 冗長な接続を削除してグラフサイズを最小化
- CSR(Compressed Sparse Row)形式で効率的に保存
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 で自然言語の質問をするだけで、ブログ記事から関連情報を検索できます。

質問: “blog.tumf.dev 記事からSSHのおしゃれな使い方を5つ選んで”
Claude が leann_search ツールを自動的に呼び出し、ブログ記事から SSH 関連の記事を検索。その結果から以下の2つを選出しました:
- 📁 ~/.ssh/config を分割して管理する - プロジェクトごと・環境ごとに分割
- 🖥️ ssh-argv0 で
sshコマンドを省略 -example.comだけで接続
どのように動作しているか
上記のデモでは、裏側で以下の処理が行われています:
- 自然言語の質問 → Claude が意図を理解
leann_searchツールを自動呼び出し → MCPプロトコル経由- セマンティック検索実行 → LEANN がブログ記事を検索
- 結果の選出と要約 → 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):
- Score: 0.373 -
~/.ssh/configを分割して管理する(改良版) - Score: 0.317 - shell-jsonrpc(JSON-RPC経由でシェルコマンド実行)
- Score: 0.309 - JSON-RPC経由シェル実行スクリプト
- Score: 0.301 - GoogleSpreadSheet JSON取り込み
- Score: 0.280 - SSH設定ファイル分割管理(2025年版ベストプラクティス)
日本語セマンティック検索の精度:
- ✅ 日本語記事の検索が正常動作(98記事すべて日本語)
- ✅ キーワード完全一致でなく、意味的に関連する記事を発見
- ✅ “config”、“Include”、“プロキシ” などの技術用語を正しく理解
- ✅ 日英混在の技術文書でも適切に検索
- ⚠️ スコア 0.280未満には無関係な記事も混入(Authgear、Ethereumなど)
使用モデル:
- Embedding: sentence-transformers/all-MiniLM-L6-v2 (384次元、multilingual対応)
- LLM不要でセマンティック検索が可能
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%ローカル)
💡 活用シーン:
- ブログ記事の再発見: 過去に書いた記事を忘れていても検索可能
- 技術的な質問への回答: 自分の知識ベースから情報抽出
- 記事間の関連性発見: 類似トピックの記事を発見
課題と今後の改善方向
実験を通じて、いくつかの課題が見えてきました。
🔍 検索精度の課題:
共通語彙による誤検出
- 症状: スコア 0.280未満で無関係な記事が混入(Authgear、Ethereumなど)
- 原因: “config”, “JSON”, “管理”, “設定” などの技術文書で頻出する共通語彙
- 例: SSH設定を検索 → Authgear設定、Ethereum設定もヒット
- 影響:
top_k=10の場合、下位5件にノイズが含まれる
スコア分布の問題
- 最高スコア: 0.373(関連度高)
- 平均スコア: 0.278(中程度)
- ノイズ混入: 0.280未満
- 完全一致スコア(0.9以上)が出ない
💡 次回以降の改善テーマ:
パラメータチューニング
top_kを5-7に減らす(ノイズ削減)complexityを64に上げる(検索精度向上)- メタデータフィルタの活用
埋め込みモデルの変更
- 日本語特化モデル(intfloat/multilingual-e5-large )
- より高次元のモデル(768次元以上)
- ドメイン特化モデルの検証
ハイブリッド検索の導入
- セマンティック検索 + キーワード検索
- BM25とのハイブリッド
- リランキングの活用
大規模データでの検証
- 1,000記事以上で97%ストレージ削減を実証
- 検索精度の変化を測定
- パフォーマンスのスケーラビリティ確認
これらの改善については、別途記事で取り上げる予定です。
ストレージ比較
| データソース | 件数 | FAISS | LEANN | 削減率 |
|---|---|---|---|---|
| DPR (論文) | 210万 | 3.8GB | 324MB | 91% |
| Wikipedia | 6,000万 | 201GB | 6GB | 97% |
| 40万 | 1.8GB | 64MB | 97% | |
| 78万 | 2.4GB | 79MB | 97% | |
| Browser | 3.8万 | 130MB | 6.4MB | 95% |
バックエンドの選択
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_textとupdate_indexメソッドのみが存在し、削除用のメソッドは提供されていません。
回避策:
- インデックスの再構築(推奨)
削除したいドキュメントを除外して、インデックスを再構築します:
# 削除したいドキュメントを除外して再構築
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")
- メタデータフィルタリングによるソフト削除
インデックス自体は変更せず、検索時にフィルタリングで除外する方法です:
# 削除フラグをメタデータに設定しておく
# 検索時にフィルタリングで除外
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の代替として非常に魅力的な選択肢です。興味のある方はぜひ試してみてください。