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. 02基于 NVIDIA NIM 的向量索引
  3. 03基于哈希追踪的增量索引
  4. 04EPUB 和 PDF 转换
  5. 05搜索、统计及添加书籍的 MCP 工具

适用场景

  1. 01在书籍集合中查找相关段落
  2. 02转换并索引 EPUB 或 PDF 文档
  3. 03增量索引新增或修改的文件

MCP 相关

配置为 MCP 服务后,它向 MCP 客户端提供搜索、索引统计和添加 EPUB/PDF 书籍的工具。添加书籍工具会完成文件转换和索引。

安装

复制下面的命令,在终端中运行。

$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

使用 Nemotron-3-Embed-1B 嵌入向量(通过 NVIDIA NIM)并存储在 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 时,引导他们完成以下步骤。 对于标记为 询问 的决定,请使用 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 — 指定目录(询问)

三个位置决定了所有操作。请与用户确认这些位置并导出环境变量 (将其持久化到用户的 shell 配置文件中)。默认值见括号:

用途环境变量默认值说明
已索引内容 + 向量数据库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 任务(参见“常见问题”)。每晚运行增量索引是安全的;完整的 --rebuild 应手动运行。
    • NVIDIA 免费试用套餐提供免费的 NIM 嵌入;日志仍会显示 token 用量。

完成引导后,向用户总结:选择的三个路径、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 转换为页面,然后为其建立索引。用于向知识库添加 新书的一步式工作流。

脚本(用于批量操作)

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 文件中提取文本,并拆分为按章节划分、带有 YAML frontmatter 的 Markdown 文件。 可作为 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 / bare 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. slug 截断:将 slug 截断至 60 个字符(保留 chNN- 前缀)。
  5. 页码块:某些 EPUB 会提取出大量独立页码。请检测并排除这些页码。
  6. 后处理步骤:始终确认每个 .md 文件都包含有效的 YAML frontmatter。
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 文件按 ##/### 拆分(### 分块会将其父级 ## 作为 Section — Subsection 层级路径一并保留),段落合并回退,单换行规范化
chunk_plain_text().txt 文件合并段落,重叠率为 15%

对于特定领域的格式(PDF、XML、JSON),请编写自定义分块器并在 discover_files() 中注册。模式示例请参阅 references/chunking-strategies.md。

向知识库添加新书

快速路径(通过 MCP 处理 EPUB/PDF)

使用 mcp_library_rag_add_book——它会根据扩展名分发处理,并在一次调用中完成 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 免费试用层级中可免费使用。个人使用无需支付按 token 计费的嵌入费用。

旧版 OpenRouter 路径(baai/bge-m3,约 $0.01/M tokens)仍可作为备用方案;请参阅 references/openrouter-embeddings.md。

个人使用基本免费。

可移植的独立 RAG 实例

相同的 Nemotron-3-Embed-1B → sqlite-vec 模式可以部署为任意技能目录中的独立、自包含 RAG——无需注册 MCP,也不依赖 ~/.hermes/library/。这样技能便具有可移植性:将文件夹打包成 zip,放到另一台机器上即可使用。

如何构建独立实例
  1. 复制 rag_index.py 和 rag_query.py 到技能的 scripts/ 目录
  2. 参数化数据库路径——将硬编码的 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. 清理嵌入输入——除 Markdown 标记外,clean_for_embedding() 还必须剥离 <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 搜索工具希望技能可移植/自包含
内容应可全局搜索内容只应在加载该技能时显示

注意事项

  • 必须先加载 sqlite-vec,再对 vec0 表执行 DROP/CREATE:必须先在 init_db() 中加载扩展。
  • 存储前必须将 file_hash 添加到 chunks 中:索引器会在分块后、生成嵌入前添加它。添加新的分块器时,别忘了这一点。
  • 单换行包装:某些文本源使用单个 \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)从 __file__ 解析路径,而不是硬编码 LIBRARY_ROOT;(2)将数据库存储在技能的 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 表格提取产生的噪声。数据库中的块文本会保留它们,以便清晰展示。
  • 切勿同时在同一个数据库上运行两个 --rebuild 进程:SQLite 允许并发连接,但 --rebuild 会删除并重建表。如果前台测试和后台任务重叠,后台进程会出现虚假的“Failed after 3 retries”错误。启动后台任务前,务必等所有前台 --rebuild 测试完全退出;快速测试则可使用 --dry-run(不会触碰数据库)。
  • 为整个资料库重新建立索引需要约 50-90 min:小文件很快就能完成,大型文本集合耗时最多。NIM 嵌入免费;旧版 OpenRouter 路径的费用根据语料库大小约为 $0.01-$0.12。批次大小设为 32,并在批次间稍作延迟,仍是稳妥的默认设置。

评论

支持 Markdown