mcpField
L
library-rag

library-rag

github.com/moonlight-lupin生産性113122 回インストール1 日前に更新
リポジトリ

library-rag について

Library RAGはNVIDIA NIMの埋め込みとsqlite-vecデータベースを使い、テキスト群をセマンティック検索可能にします。書籍や文書を多言語で検索する用途に向け、EPUB/PDF変換と差分インデックスにも対応します。

主な機能

  1. 01多言語セマンティック検索
  2. 02NVIDIA NIMによるベクトル索引
  3. 03ハッシュ追跡による差分インデックス
  4. 04EPUB・PDF変換
  5. 05検索・統計・書籍追加のMCPツール

活用シーン

  1. 01書籍群から関連する記述を探す
  2. 02EPUBやPDFを変換して索引化
  3. 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 と一緒にインストールされる参考資料やスクリプト。

.gitignore
LICENSE
README.md
requirements-dev.txt
requirements.txt
references/
chunking-strategies.md
epub-conversion.md
openrouter-embeddings.md
portable-rag-per-skill.md
rag-pipeline-review.md
scripts/
convert_epub_library.py
convert_pdf_library.py
mcp_server.py
rag_common.py
rag_index.py
rag_query.py
tests/
conftest.py
test_chunking.py
test_convert_pdf.py
test_convert.py
test_index_search.py
test_indexing_atomicity.py
test_prune_vacuum.py
test_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 の無料トライアル枠で無料です。ログには引き続きトークン使用量が表示されます。

オンボーディングの後、選択した 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 を参照):

  1. 目次の残骸が複数ある:大きな EPUB では、目次ブロックが複数生成されます。本文中の章見出しを探す際は、すべての目次をスキップしてください。
  2. CHAPTER N と N. Title:EPUB によって、目次と本文で異なる見出し形式が使われます。本文の形式に合わせてください。
  3. タイトルとサブタイトルが YAML に混入する:title: フィールドにはメインタイトルのみを使用してください。
  4. スラッグの切り詰め:スラッグは 60 文字に切り詰めてください(chNN- 接頭辞は保持します)。
  5. ページ番号のブロック:EPUB によっては、単独のページ番号が大きなブロックとして抽出されることがあります。検出して除外してください。
  6. 後処理:すべての .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 → インデックス作成を処理します。

手動で追加する方法
  1. 構造化 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>/ の下に自分で書き込んでください。

  2. チャンカーを登録する(新しいファイル形式の場合):

    • rag_index.py にチャンカー関数を追加します
    • discover_files() に登録します パターンと注意点については references/chunking-strategies.md を参照してください。 スキルごとに独立した RAG を構成するパターンについては references/portable-rag-per-skill.md を参照してください。 体系的な監査チェックリストについては references/rag-pipeline-review.md を参照してください。
  3. インデックスを作成します:

    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 にして別のマシンに配置すれば、そのまま動作します。

スタンドアロンインスタンスの構築方法
  1. コピーします。rag_index.py と rag_query.py をスキルの scripts/ ディレクトリにコピーします
  2. DB パスをパラメーター化します。ハードコードされた LIBRARY_ROOT / DB_PATH を環境変数または CLI フラグに置き換え、デフォルトではスキル自身の references/ ディレクトリを指定します
  3. ドメイン固有のチャンク分割処理を作成します。ドキュメント本来の 構造(見出し、セクション)に沿ってチャンクに分割します。PDF から抽出したコンテンツでは、 split_by_pages() を使って `` マーカーを解析し、チャンクごとにページの引用情報を付与します。
  4. MCP サーバーを登録しないでください。スクリプトから rag_query.py を直接インポートします。
    from rag_query import search
    results = search("query text", top_k=5)
    
  5. 保存時とクエリ時にベクトルを L2 正規化します。float_to_blob()(インデクサー)と get_embedding()(クエリ)に normalize_vec() を追加します。これにより、暗黙の前提ではなく、コサイン類似度を正確に計算できます。
  6. チャンクの約 15%を重複させます。検索再現率を高めるため、split_long_text() で各チャンクの末尾を次のチャンクに含めます。
  7. 埋め込み入力をクリーンアップします。clean_for_embedding() で、Markdown のマーカーに加えて <br>、 **[Table N]**、および `` を除去する必要があります。
  8. MIN_CHUNK_CHARS を組み込みます。チャンク分割後に merge_tiny_chunks() を呼び出し、空に近いチャンクによって検索結果の質が低下するのを防ぎます。
  9. 共有依存関係: 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 と、バッチ間の短い待機時間を安全なデフォルトとして引き続き使用できます。

コメント