关于 library-rag
Library RAG 使用 NVIDIA NIM 的嵌入向量和 sqlite-vec 数据库,为文本集合建立语义搜索索引。它面向跨语言检索书籍与文档的用户,并提供 EPUB/PDF 转换和增量索引功能。
主要功能
- 01多语言语义搜索
- 02基于 NVIDIA NIM 的向量索引
- 03基于哈希追踪的增量索引
- 04EPUB 和 PDF 转换
- 05搜索、统计及添加书籍的 MCP 工具
适用场景
- 01在书籍集合中查找相关段落
- 02转换并索引 EPUB 或 PDF 文档
- 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 外,安装时一起下载的参考资料和脚本。
.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
使用 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/launchdplist 是比 cron 更可靠的对应方案。需要提及的 Cron 注意事项:
- Cron 的运行环境极简 — 设置
NVIDIA_API_KEY、LIBRARY_ROOT,并使用 绝对路径指定python3(或在包装脚本中激活 venv)。 - 切勿在同一个 DB 上同时运行两个
--rebuild任务(参见“常见问题”)。每晚运行增量索引是安全的;完整的--rebuild应手动运行。 - NVIDIA 免费试用套餐提供免费的 NIM 嵌入;日志仍会显示 token 用量。
- Cron 的运行环境极简 — 设置
完成引导后,向用户总结:选择的三个路径、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):
- 多个目录残留:大型 EPUB 会产生多个目录块。查找正文中的章节标题时,请跳过所有目录块。
- CHAPTER N 与 N. Title:EPUB 的目录和正文可能使用不同的标题格式。应匹配正文格式。
- 标题 + 副标题泄漏到 YAML:
title:字段只使用主标题。 - slug 截断:将 slug 截断至 60 个字符(保留
chNN-前缀)。 - 页码块:某些 EPUB 会提取出大量独立页码。请检测并排除这些页码。
- 后处理步骤:始终确认每个
.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 → 索引。
手动路径
-
转换为结构化 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 免费试用层级中可免费使用。个人使用无需支付按 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,放到另一台机器上即可使用。
如何构建独立实例
- 复制
rag_index.py和rag_query.py到技能的scripts/目录 - 参数化数据库路径——将硬编码的
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()应将每个块的尾部内容带到下一个块中,以提高召回率。 - 清理嵌入输入——除 Markdown 标记外,
clean_for_embedding()还必须剥离<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 搜索工具 | 希望技能可移植/自包含 |
| 内容应可全局搜索 | 内容只应在加载该技能时显示 |
注意事项
- 必须先加载 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,并在批次间稍作延迟,仍是稳妥的默认设置。