library-rag について
Library RAGはNVIDIA NIMの埋め込みとsqlite-vecデータベースを使い、テキスト群をセマンティック検索可能にします。書籍や文書を多言語で検索する用途に向け、EPUB/PDF変換と差分インデックスにも対応します。
主な機能
- 01多言語セマンティック検索
- 02NVIDIA NIMによるベクトル索引
- 03ハッシュ追跡による差分インデックス
- 04EPUB・PDF変換
- 05検索・統計・書籍追加のMCPツール
活用シーン
- 01書籍群から関連する記述を探す
- 02EPUBやPDFを変換して索引化
- 03追加・変更ファイルを差分で索引化
MCP 対応
MCPサーバーとして設定すると、検索、索引統計、EPUB/PDF書籍の追加をMCPクライアントにツールとして公開します。書籍追加ツールはファイル変換と索引化を行います。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ moonlight-lupin/ agent-skills --skill library-ragインストール後、Claude Code で/library-ragを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.gitignoreLICENSEREADME.mdrequirements-dev.txtrequirements.txtreferences/chunking-strategies.mdepub-conversion.mdopenrouter-embeddings.mdportable-rag-per-skill.mdrag-pipeline-review.mdscripts/convert_epub_library.pyconvert_pdf_library.pymcp_server.pyrag_common.pyrag_index.pyrag_query.pytests/conftest.pytest_chunking.pytest_convert_pdf.pytest_convert.pytest_index_search.pytest_indexing_atomicity.pytest_prune_vacuum.pytest_rag_common.py詳細
Library RAG
NVIDIA NIM 経由の Nemotron-3-Embed-1B 埋め込みを sqlite-vec に保存し、~/.hermes/library/ を対象にセマンティック検索を行います。あらゆる言語のテキストコーパス(書籍、ドキュメント、参考資料など)から、意味に基づく検索ができます。
アーキテクチャ
NVIDIA NIM API (nemotron-3-embed-1b, 2048-dim)
│
▼
~/.hermes/library/rag_index.db (sqlite-vec)
├── chunks table — text + metadata (source, book, chapter, section)
├── vec_chunks table — L2-normalized vector embeddings
└── indexed_files — SHA-256 hash tracking for incremental updates
MCP Server (mcp_server.py)
├── search(query, top_k, source_type) — semantic search, auto-available
├── stats() — index statistics
└── add_book(file_path, ...) — EPUB/PDF → md → index in one call
オンボーディングのワークフロー(ユーザーが初めてこのスキルをセットアップするときに実行)
ユーザーが Library RAG の利用を開始したい場合は、次の手順を案内します。
ASK と記された選択項目には AskUserQuestion を使用してください。パスを決めつけたり、
ホームフォルダーの外にディレクトリを黙って作成したりしないでください。
ステップ 1 — 前提条件を確認する
python3 --version # need 3.9+
python3 -c "import sqlite_vec; print('sqlite-vec ok')" 2>&1
test -n "$NVIDIA_API_KEY" && echo "key in env" || grep -qs NVIDIA_API_KEY ~/.hermes/.env && echo "key in .env" || echo "NO API KEY"
sqlite-vecのインポートに失敗した場合 →pip install -r requirements.txtを実行します(外部管理の Python では--break-system-packagesを追加します)。README の「インストール」を参照してください。NO API KEYの場合 → https://build.nvidia.com/nvidia/nemotron-3-embed-1b で 無料の NVIDIA NIM キーを作成するようユーザーを案内し、echo 'NVIDIA_API_KEY=nvapi-...' >> ~/.hermes/.envで保存します。 (従来の OpenRouter キーも引き続き使えます。OPENROUTER_API_KEYだけが設定されている場合、load_api_key()が bge-m3 の OpenRouter エンドポイントに自動で切り替えます。)
ステップ 2 — ディレクトリを設定する(ASK)
すべてを左右する場所は 3 つあります。ユーザーに確認して環境変数をエクスポートし、 シェルのプロファイルに永続化します。括弧内はデフォルト値です。
| 用途 | 環境変数 | デフォルト | 備考 |
|---|---|---|---|
| インデックス対象のコンテンツ + ベクターデータベース | LIBRARY_ROOT | ~/.hermes/library | 埋め込み対象となる構造化された .md/.txt と rag_index.db を格納します。 |
| ベクターデータベースファイル | (派生値) | $LIBRARY_ROOT/rag_index.db | 別の場所に保存したい場合は、実行ごとに rag_index.py --db <path> で上書きできます。 |
| 元の書籍 + 変換用ステージング | BOOKS_DIR | ~/.hermes/book | 元の EPUB/PDF の配置先であり、テキストの抽出先です。 |
推奨されるディレクトリ構成(元のファイル/テキストは LIBRARY_ROOT の外に置きます。下記の注意点を参照してください):
$BOOKS_DIR/ staging — NOT indexed
raw/ original EPUBs/PDFs
text/ extracted plain text
$LIBRARY_ROOT/ indexed — scanned recursively
rag_index.db
<source_type>/ top-level dir name becomes the "source type"
markdown/<slug>/chapters/*.md the structured files that get embedded
注意 — 二重インデックスを避ける:
discover_files()はLIBRARY_ROOT以下の.mdおよび.txtをすべてインデックスします。同じ書籍の抽出済み.txtと変換後の.mdの両方がLIBRARY_ROOT以下にあると、コンテンツが二重に埋め込まれます。LIBRARY_ROOT以下には どちらか一方(構造化された.mdを推奨)だけを置き、元のファイルとテキストのステージング先はBOOKS_DIRに置いて、その外に保管してください。
選択したディレクトリを作成します。
mkdir -p "$LIBRARY_ROOT" "$BOOKS_DIR/raw" "$BOOKS_DIR/text"
ステップ 3 — 最初のコンテンツを追加してインデックスを構築する
- MCP 経由の EPUB/PDF(サーバーが設定済みの場合):
add_book(...)を呼び出します。変換と インデックス作成を一度に行います。 - 手動: ソースファイルを
$BOOKS_DIR/rawに配置し、構造化された Markdown に変換して$LIBRARY_ROOT/<source_type>/以下に保存します(convert_epub_library.pyとreferences/epub-conversion.mdを参照)。その後、次を実行します。
python3 scripts/rag_index.py --dry-run # preview chunk counts, no API cost
python3 scripts/rag_index.py # incremental build
python3 scripts/rag_query.py --stats # confirm chunks landed
python3 scripts/rag_query.py "a test query"
ステップ 4 — 自動スキャン/変換/インデックス作成(ASK)
新しいファイルのインデックスを自動または手動で作成するか、ユーザーに尋ねます。
-
手動(デフォルト。まずはこちらがおすすめです): ファイルを追加した後、ユーザーが
python3 scripts/rag_index.pyを実行します。
差分処理と SHA-256 による追跡により、再実行の負荷は小さく、新規または変更されたファイルのみが処理されます。セットアップは不要です。 -
自動(cron): 定期的に差分インデックスを作成するようスケジュールします。この方法は、ステップ 3 がエンドツーエンドで動作することを確認してから提案してください。例 — 毎晩 02:00 にインデックスを作成し、出力をログに記録します。
# crontab -e (adjust the repo path and env vars) 0 2 * * * NVIDIA_API_KEY=nvapi-... LIBRARY_ROOT=$HOME/.hermes/library \ /usr/bin/python3 $HOME/library-rag/scripts/rag_index.py >> $HOME/.hermes/rag_index.log 2>&1インデックス作成前に EPUB も自動変換したい場合は、同じ cron の行に変換ステップを追加して
rag_index.pyの前に実行するか、小さなラッパースクリプトを使います。macOS では、launchd/launchdの plist が cron に相当する、より信頼性の高い方法です。cron に関する注意事項:
- cron の環境は最小限です。
NVIDIA_API_KEYとLIBRARY_ROOTを設定し、python3の絶対パスを指定してください(またはラッパースクリプト内で venv を有効化してください)。 - 同じ DB に対して
--rebuildを 2 つ同時に実行しないでください(「落とし穴」を参照)。毎晩の差分実行は安全ですが、全件の--rebuildは手動で実行してください。 - NIM の埋め込みは NVIDIA の無料トライアル枠で無料です。ログには引き続きトークン使用量が表示されます。
- cron の環境は最小限です。
オンボーディングの後、選択した 3 つのパス、DB の保存場所、インデックス作成が手動かスケジュール実行かをユーザーにまとめて伝えます。
MCP ツール(すべての会話で自動的に利用可能)
config.yaml で設定すると、これらのツールを mcp_library_rag_search、mcp_library_rag_stats、mcp_library_rag_add_book として利用できます。
設定
# ~/.hermes/config.yaml
mcp_servers:
library_rag:
command: "python3"
args: ["~/.hermes/skills/research/library-rag/scripts/mcp_server.py"]
timeout: 60
追加後、ゲートウェイを再起動してください。
検索
mcp_library_rag_search(query="your search query", top_k=10)
mcp_library_rag_search(query="search terms", source_type="my-source-type")
similarity、source_type、book、chapter、section_title、text を含む結果の JSON 配列を返します。
統計
mcp_library_rag_stats()
→ {"total_chunks": 78000, "files_indexed": 1383, "by_source": {...}}
add_book
mcp_library_rag_add_book(
file_path="/path/to/book.epub", # or /path/to/doc.pdf
book_slug="my-new-book",
author="Author Name",
title="Book Title",
year="2024"
)
EPUB を章に、または PDF をページに変換してから、インデックスを作成します。ライブラリに新しい本を追加するためのワークフローを 1 回の呼び出しで実行できます。
スクリプト(バッチ処理用)
rag_index.py — インデックス作成ツール
# Index all new/changed files (incremental — SHA-256 hash tracking)
python3 scripts/rag_index.py
# Full rebuild (drop everything, re-index from scratch)
python3 scripts/rag_index.py --rebuild
# Dry run (show what would be indexed, no API calls)
python3 scripts/rag_index.py --dry-run
# Index only one source type
python3 scripts/rag_index.py --source my-source-type
# Prune DB rows for files deleted/moved out of the library, then shrink the file
python3 scripts/rag_index.py --prune-missing --vacuum
使用するタイミング:
- ライブラリに新しいファイルを追加した後 → 差分処理を実行します(デフォルト)
- チャンク分割の方法を変更した後 →
--rebuildを実行します - コストや所要時間を見積もる場合 → まず
--dry-runを実行します - ライブラリからファイルを削除または移動した後 →
--prune-missingを実行します(孤立したチャンクとベクトルを削除するため、DB に不要な行が残りません。差分インデックス作成と併用しても安全です。ディスク上に存在しないファイルのみが対象になります) --rebuildまたは大規模な--prune-missingの後 →--vacuumを実行して DB ファイルを縮小します(SQLite は解放されたページを再利用しますが、これを実行しないとファイル自体は縮小されません)
rag_query.py — CLI クエリ
python3 scripts/rag_query.py "your search query"
python3 scripts/rag_query.py "search terms" --top-k 5
python3 scripts/rag_query.py "query" --source my-source-type --verbose
python3 scripts/rag_query.py --stats
インポートして使うこともできます。
from rag_query import search
results = search("query text", top_k=10)
変換ツール
EPUB → Markdown (scripts/convert_epub_library.py)
EPUB ファイルからテキストを抽出し、章ごとの Markdown に分割して YAML フロントマターを付与します。
CLI として実行することも、convert_epub() としてインポートすることもできます。同じ実装が MCP の
add_book ツールにも使われるため、変換処理は一元化されています。
python3 scripts/convert_epub_library.py \
--epub book.epub --slug my-book --title "Book Title" --author "Author" --year 2024 \
--md-root ~/.hermes/library/books/markdown # under LIBRARY_ROOT so it gets indexed
元の EPUB と抽出したテキストは $BOOKS_DIR(LIBRARY_ROOT の外)に保存されます。構造化された
Markdown のみが --md-root に書き込まれるため、元のテキストが二重にインデックス化されることはありません。
章の検出では複数の形式(CHAPTER 12/CHAPTER One/PART IV/
12. Title/12\nTitle のみ)を試し、確実に適用できる最初の形式を採用して、日付やページ番号を誤検出しないようにします。変換結果を信頼する前に、
convert_epub_library.audit_chapter_markers(text) を目視で確認してください。各項目が日付やページ番号ではなく、実際の章になっている必要があります。
主な注意点(詳しくは references/epub-conversion.md を参照):
- 目次の残骸が複数ある:大きな EPUB では、目次ブロックが複数生成されます。本文中の章見出しを探す際は、すべての目次をスキップしてください。
- CHAPTER N と N. Title:EPUB によって、目次と本文で異なる見出し形式が使われます。本文の形式に合わせてください。
- タイトルとサブタイトルが YAML に混入する:
title:フィールドにはメインタイトルのみを使用してください。 - スラッグの切り詰め:スラッグは 60 文字に切り詰めてください(
chNN-接頭辞は保持します)。 - ページ番号のブロック:EPUB によっては、単独のページ番号が大きなブロックとして抽出されることがあります。検出して除外してください。
- 後処理:すべての
.mdファイルに有効な YAML フロントマターがあることを必ず確認してください。
PDF → Markdown (scripts/convert_pdf_library.py)
pdfplumber を使用してテキストと表を抽出します。各ページは ## Page N セクションになり(Markdown チャンカーがページごとの引用を生成します)、表は **[Table N]** ブロックになります。CLI または
convert_pdf() を使用できます。また、.pdf 入力では add_book の処理にも使われます。
python3 scripts/convert_pdf_library.py \
--pdf doc.pdf --slug my-doc --title "Doc" --author "Author" --year 2024 \
--md-root ~/.hermes/library/books/markdown
表ブロックは表示専用です。clean_for_embedding は、埋め込み用入力から **[Table N]** と <br> で終わる行を取り除きます(ページの説明文にはすでに表の内容が文章として含まれているためです)。そのため、表を二重に埋め込まずに検索可能な状態に保てます。
組み込みチャンカー(rag_index.py)
| チャンカー | 入力 | 戦略 |
|---|---|---|
chunk_markdown() | 見出しを含む .md | ##/### で分割(### のチャンクには、親の ## を セクション — サブセクション のパンくずとして含めます)、段落結合によるフォールバック、単一改行の正規化 |
chunk_plain_text() | .txt ファイル | 15% の重複を設けて段落を結合 |
ドメイン固有の形式(PDF、XML、JSON)には、カスタムチャンカーを作成して
discover_files() に登録してください。パターンについては references/chunking-strategies.md を参照してください。
ライブラリへの新しい書籍の追加
簡単な方法(MCP 経由の EPUB/PDF)
mcp_library_rag_add_book を使用します。拡張子を判別し、1 回の呼び出しで
EPUB/PDF → md → インデックス作成を処理します。
手動で追加する方法
-
構造化 Markdown に変換するには、形式に対応するコンバーター CLI を使用します。どちらも 元ファイルと抽出したテキストを
$BOOKS_DIR(LIBRARY_ROOTの外に保存するため、インデックス化されません)に保存し、Markdown を--md-rootに書き込みます。# EPUB → per-chapter markdown python3 scripts/convert_epub_library.py \ --epub book.epub --slug <slug> --title "..." --author "..." --year 2024 \ --md-root $LIBRARY_ROOT/books/markdown # PDF → per-page markdown (tables preserved) python3 scripts/convert_pdf_library.py \ --pdf doc.pdf --slug <slug> --title "..." --author "..." --year 2024 \ --md-root $LIBRARY_ROOT/books/markdownその他のソース(XML/JSON など)については、
references/chunking-strategies.mdの抽出パターンに従い、 Markdown を$LIBRARY_ROOT/<source_type>/の下に自分で書き込んでください。 -
チャンカーを登録する(新しいファイル形式の場合):
rag_index.pyにチャンカー関数を追加しますdiscover_files()に登録します パターンと注意点についてはreferences/chunking-strategies.mdを参照してください。 スキルごとに独立した RAG を構成するパターンについてはreferences/portable-rag-per-skill.mdを参照してください。 体系的な監査チェックリストについてはreferences/rag-pipeline-review.mdを参照してください。
-
インデックスを作成します:
python3 scripts/rag_index.py # incremental — only new files
コスト
デフォルトのプロバイダーは NVIDIA NIM(nvidia/nemotron-3-embed-1b)です。NVIDIA API の無料トライアル枠で利用できます。個人利用では、トークンごとの埋め込みコストはかかりません。
従来の OpenRouter 経由の方法(baai/bge-m3、約 100 万トークンあたり 0.01 ドル)も、フォールバックとして引き続き利用できます。詳しくは references/openrouter-embeddings.md を参照してください。
個人利用なら、実質無料です。
ポータブルなスタンドアロン RAG インスタンス
同じ Nemotron-3-Embed-1B → sqlite-vec のパターンは、MCP への登録や ~/.hermes/library/ への依存なしで、任意のスキルディレクトリ内にスタンドアロンで自己完結した RAGとしてデプロイできます。これによりスキルのポータビリティが高まります。フォルダーを zip にして別のマシンに配置すれば、そのまま動作します。
スタンドアロンインスタンスの構築方法
- コピーします。
rag_index.pyとrag_query.pyをスキルのscripts/ディレクトリにコピーします - DB パスをパラメーター化します。ハードコードされた
LIBRARY_ROOT/DB_PATHを環境変数または CLI フラグに置き換え、デフォルトではスキル自身のreferences/ディレクトリを指定します - ドメイン固有のチャンク分割処理を作成します。ドキュメント本来の
構造(見出し、セクション)に沿ってチャンクに分割します。PDF から抽出したコンテンツでは、
split_by_pages()を使って `` マーカーを解析し、チャンクごとにページの引用情報を付与します。 - MCP サーバーを登録しないでください。スクリプトから
rag_query.pyを直接インポートします。from rag_query import search results = search("query text", top_k=5) - 保存時とクエリ時にベクトルを L2 正規化します。
float_to_blob()(インデクサー)とget_embedding()(クエリ)にnormalize_vec()を追加します。これにより、暗黙の前提ではなく、コサイン類似度を正確に計算できます。 - チャンクの約 15%を重複させます。検索再現率を高めるため、
split_long_text()で各チャンクの末尾を次のチャンクに含めます。 - 埋め込み入力をクリーンアップします。
clean_for_embedding()で、Markdown のマーカーに加えて<br>、**[Table N]**、および `` を除去する必要があります。 MIN_CHUNK_CHARSを組み込みます。チャンク分割後にmerge_tiny_chunks()を呼び出し、空に近いチャンクによって検索結果の質が低下するのを防ぎます。- 共有依存関係: NVIDIA API キー(NIM 経由の Nemotron-3-Embed-1B)。 埋め込みモデルと sqlite-vec 拡張機能は共有され、複製されません。
コードパターンの詳細は references/portable-rag-per-skill.md を、
体系的な監査チェックリストは references/rag-pipeline-review.md を参照してください。
スタンドアロンとメインライブラリの使い分け
| メインライブラリを使う場合… | スタンドアロンを使う場合… |
|---|---|
| コンテンツが、全体から検索したい参考資料や書籍の場合 | コンテンツがドメイン固有(ルールブック、コードドキュメントなど)の場合 |
| MCP 検索ツールを自動で利用できるようにしたい場合 | スキルをポータブルで自己完結したものにしたい場合 |
| コンテンツを全体から検索できるようにする場合 | コンテンツを、そのスキルの読み込み時にのみ表示させる場合 |
注意点
- vec0 テーブルを DROP/CREATE する前に sqlite-vec をロードする必要があります:
init_db()で最初に拡張機能をロードしてください。 - 保存する前にチャンクへ
file_hashを追加する必要があります:インデクサーはチャンク化の後、埋め込みの前に追加します。新しいチャンク分割器を追加する際は忘れないでください。 - 単一改行による折り返し:一部のテキストソースでは、段落区切りではなく、行の折り返しに単一の
\nを使用しています。\n\nで分割する前に、スペースに正規化してください。 - MCP サーバーのタイムアウト:デフォルトの 60s のタイムアウトは、
searchとstatsには問題ありません。大きな EPUB ではadd_bookにもっと時間が必要な場合があります。必要に応じて、設定のtimeoutを増やしてください。 - sqlite-vec のソースフィルタリング:sqlite-vec の
MATCH/kベクトル検索では、任意のWHERE句を組み合わせられません。source_typeとsource_bookのフィルターは、結果を過剰取得した後の後処理フィルターとして適用する必要があります(top_k * 5件を取得してから、チャンクのメタデータでフィルタリングします)。正しいパターンについては、references/portable-rag-per-skill.mdを参照してください。 - ポータブルな RAG インスタンス:別のスキル用にスタンドアロンの RAG を作成するには、
rag_index.pyとrag_query.pyをコピーし、次のようにパラメーター化してください:(1)ハードコードされたLIBRARY_ROOTではなく、__file__からパスを解決する、(2)DB をスキルのreferences/ディレクトリ内に保存する、(3)MCP サーバーを省略し、代わりにsearch()を直接インポートする、(4)ドメイン固有のチャンク分割器を作成する、(5)チャンクごとのページ引用を得るために `` マーカーを解析する、(6)保存時とクエリ時にベクトルを L2 正規化する、(7)再現率を高めるために約 15% のチャンク重複を追加する。全体のパターンについては、references/portable-rag-per-skill.mdを参照してください。 - ページ引用にはマーカーの解析が必要です:PDF 抽出ツールは `` コメントを挿入しますが、チャンク分割器がそれらを積極的に解析する必要があります。そうしないと、すべてのチャンクの引用が章単位に劣化します。チャンク分割器で
split_by_pages()を使用してマーカーごとに分割し、各チャンクにページを記録してください。 - 正確な類似度の算出には L2 正規化が必要です:
1 - dist²/2が正確なコサイン類似度になるのは、保存されたベクトルとクエリベクトルの両方が単位長に正規化されている場合のみです。Nemotron-3-Embed-1B(および従来の bge-m3 経路)は単位長に近いベクトルを返す場合がありますが、これを明示的に保証するため、保存時(float_to_blob)とクエリ時(get_embedding)にnormalize_vec()が呼び出されます。 clean_for_embeddingではテーブル/PDF のノイズを除去する必要があります:Markdown のマーカーに加え、埋め込み入力から<br>、**[Table N]**、および `` を除去してください。これらは PDF の表抽出によるノイズです。DB 内のチャンクテキストには、表示の読みやすさを保つため、これらを残します。- 同じ DB で
--rebuildプロセスを 2 つ同時に実行しないでください:SQLite は同時接続を許可しますが、--rebuildはテーブルを削除して再作成します。フォアグラウンドのテストとバックグラウンドジョブが重なると、バックグラウンドプロセスで誤って「Failed after 3 retries」エラーが発生します。バックグラウンドジョブを開始する前に、フォアグラウンドの--rebuildテストを必ず完全に終了させるか、DB に触れない簡易テストには--dry-runを使用してください。 - ライブラリ全体の再インデックスには約 50~90 分かかります:小さなファイルはすぐに完了しますが、大規模なテキストコレクションが大部分の時間を占めます。NIM の埋め込みは無料です。従来の OpenRouter 経路では、コーパスのサイズに応じて約 $0.01~$0.12 かかりました。バッチサイズ 32 と、バッチ間の短い待機時間を安全なデフォルトとして引き続き使用できます。