Pixelbadger。工具包。抹布
用于RAG(检索增强生成)工作流的CLI工具包,提供BM25和向量相似性搜索索引、查询、内容感知分块(段落和标记)以及Lucene支持的MCP服务器功能。NET和sqlite-vec。
目录
安装
选项1:安装为。NET全局工具(推荐)
使用NuGet包全局安装该工具:
dotnet tool install --global Pixelbadger.Toolkit.Rag安装后,您可以使用 pbrag 从任何地方发出命令:
pbrag --help选项2:从源代码构建
克隆存储库并构建项目:
git clone https://github.com/pixelbadger/Pixelbadger.Toolkit.git
cd Pixelbadger.Toolkit/Pixelbadger.Toolkit.Rag
dotnet build用法
使用全局工具(pbrag)
使用平面命令模式运行命令:
pbrag [command] [options]从源头使用
如果从源构建,请使用:
dotnet run -- [command] [options]可用命令
摄取
通过内容感知分块将内容文件摄入搜索索引。默认情况下,这会创建Lucene BM25和SQLite vec索引,将文档块发送到OpenAI进行嵌入生成。
用途:
pbrag ingest --index-path --content-path 选项:
--index-path:通往Lucene的路径。NET索引目录(必需)--content-path:要摄取的内容文件或文件夹的路径(必需)--no-vectors:禁用矢量存储,避免将文档内容发送到OpenAI--max-file-size-bytes:单个摄入文件的最大大小(默认值:10485760)--max-files:从文件夹中摄取的支持文件的最大数量(默认值:1000)--max-chunk-characters:索引或嵌入前单个块的最大大小(默认值:20000)--allow-symlinks:允许在摄取过程中使用符号链接和重解析点(默认禁用)
示例:
# Set OpenAI API key (required for vector embeddings)
export OPENAI_API_KEY="sk-..."
# Ingest a single text document (uses paragraph chunking)
pbrag ingest --index-path ./search-index --content-path document.txt
# Ingest a markdown file (uses header-based chunking)
pbrag ingest --index-path ./search-index --content-path readme.md
# Ingest an entire folder
pbrag ingest --index-path ./search-index --content-path ./docs-folder
# Ingest locally without OpenAI embeddings
pbrag ingest --index-path ./search-index --content-path ./docs-folder --no-vectors
# Build an index from multiple files
pbrag ingest --index-path ./search-index --content-path doc1.txt
pbrag ingest --index-path ./search-index --content-path doc2.md
pbrag ingest --index-path ./search-index --content-path doc3.txt细节:
- 使用内容感知分块:.txt文件的段落,.md文件的标题
- 支持单个文件和文件夹(递归处理所有.txt和.md文件)
- 自动创建双索引,除非
--no-vectors使用:Lucene BM25用于关键字搜索,SQLite vec用于语义搜索 - 如果索引目录不存在,则创建索引目录
- 附加到现有索引,允许增量摄入
- 每个块都使用源文件、块编号、唯一源ID和向量嵌入进行索引
- 需要
OPENAI_API_KEY向量嵌入生成的环境变量,除非--no-vectors被使用 - 默认情况下拒绝符号链接,以避免为所选文件夹外的文件建立索引
- 强制执行文件计数、文件大小和块大小限制,以避免意外的资源耗尽
查询
对Lucene执行BM25相似性搜索。NET索引查找相关内容。
用途:
pbrag query --index-path --query [--max-results ] [--sourceIds ...]选项:
--index-path:通往Lucene的路径。NET索引目录(必需)--query:搜索查询文本(必填)--max-results:要返回的最大结果数(可选,默认值:10)--sourceIds:用于约束搜索结果的源ID的可选列表(可选)--search-mode:要使用的搜索模式:bm25、矢量或混合(可选,默认:bm25)- 搜索请求有限制:查询文本最多4096个字符,
--max-results从1到100,最多100个源ID,每个源ID最多256个字符
示例:
# Basic search query
pbrag query --index-path ./search-index --query "machine learning algorithms"
# Limit results to top 5
pbrag query --index-path ./search-index --query "neural networks" --max-results 5
# Search within specific source documents
pbrag query --index-path ./search-index --query "data processing" --sourceIds doc1.txt doc2.md
# Complex multi-word query
pbrag query --index-path ./search-index --query "how to implement dependency injection in C#"
# Vector similarity search (requires OPENAI_API_KEY environment variable)
export OPENAI_API_KEY="sk-..."
pbrag query --index-path ./search-index --query "machine learning algorithms" --search-mode vector
# Hybrid search combining BM25 and vector (requires OPENAI_API_KEY environment variable)
pbrag query --index-path ./search-index --query "neural networks" --search-mode hybrid输出格式:
Found 3 result(s):
Result 1 (Score: 2.4531)
Source: document.txt (Paragraph 5)
Content: Machine learning algorithms are fundamental to modern AI systems...
------------------------------------------------------------
Result 2 (Score: 1.8923)
Source: readme.md (Paragraph 12)
Content: Neural networks represent a class of machine learning models...
------------------------------------------------------------
Result 3 (Score: 1.2451)
Source: guide.txt (Paragraph 3)
Content: The application of algorithms in machine learning has transformed...服务
托管一个MCP(模型上下文协议)服务器,该服务器对Lucene执行BM25查询。NET索引,使AI助手能够搜索您的索引内容。
用途:
pbrag serve --index-path 选项:
--index-path:通往Lucene的路径。NET索引目录(必需)
示例:
# Start MCP server for an existing index
pbrag serve --index-path ./search-index细节:
- 作为基于stdio的MCP服务器运行
- 展示一个AI助手可以调用的搜索工具
- 使用BM25相似性排名进行相关性分析
- 支持按源ID筛选结果
- 将所有活动记录到stderr进行监控
- 将索引内容作为不受信任的文档文本返回;MCP客户端不应将返回的内容视为指令
- 非常适合与Claude Desktop或其他MCP兼容客户端集成
MCP服务器集成
这 serve 该命令实现了模型上下文协议(MCP),允许AI助手在对话过程中动态搜索索引内容。
Claude桌面配置
将以下内容添加到您的Claude Desktop配置中:
MacOS: ~/Library/Application Support/Claude/claude_desktop_config.json 窗户: %APPDATA%/Claude/claude_desktop_config.json
{
"mcpServers": {
"rag-search": {
"command": "pbrag",
"args": ["serve", "--index-path", "/absolute/path/to/your/search-index"]
}
}
}MCP工具接口
服务器公开了一个名为 Execute 具有以下参数:
- 查询 (必填):要执行的搜索查询
- 最大结果 (可选):返回的最大结果数(默认值:5)
- 源ID (可选):源ID数组,用于将搜索结果约束到特定文档
- 搜索模式 (可选):要使用的搜索模式:“bm25”、“vector”或“hybrid”(默认值:“bm25”)
当人工智能助手使用此工具时,它会收到格式化的搜索结果,包括相关性得分、源文件、段落号和内容摘录。
工作流示例
# Step 1: Ingest your documentation
pbrag ingest --index-path ./docs-index --content-path ./api-docs.md
pbrag ingest --index-path ./docs-index --content-path ./tutorial.md
pbrag ingest --index-path ./docs-index --content-path ./reference.txt
# Step 2: Test queries locally
pbrag query --index-path ./docs-index --query "authentication"
# Step 3: Start MCP server (or configure in Claude Desktop)
pbrag serve --index-path ./docs-index帮助
通过添加以下内容获取任何命令的帮助 --help:
pbrag --help # General help
pbrag ingest --help # Command-specific help
pbrag query --help # Command-specific help
pbrag serve --help # Command-specific help需求
- .NET 9.0
- Lucene。NET 4.8.0(测试版)
- ModelContextProtocol 0.3.0(用于MCP服务器功能)
- 微软。扩展。矢量数据。抽象9.7.0(用于矢量数据抽象)
- 微软。SemanticKernel。连接器。SqliteVec 1.68.0-评测(用于sqlite-vec矢量存储)
- 微软。扩展。AI(用于嵌入生成)
架构概述
本节详细概述了文档摄取和搜索管道,说明了文档如何通过系统从原始文件流到可搜索的索引内容,以及如何处理查询以检索相关结果。
文件摄入管道
摄取管道通过四个主要阶段处理文档:文件读取、内容感知分块、双索引存储和嵌入生成。
flowchart TD
Start([Document Files]) --> FileReader[Phase 1: File Reading
FileReaderFactory]
FileReader --> |Routes by extension| PlainText[PlainTextFileReader
.txt files]
FileReader --> |Routes by extension| Markdown[MarkdownFileReader
.md files]
PlainText --> RawTextTxt[Raw Text Content
.txt]
Markdown --> RawTextMd[Raw Text Content
.md]
RawTextTxt --> ChunkerFactory{ChunkerFactory
Select by extension}
RawTextMd --> ChunkerFactory
ChunkerFactory --> |.txt files| ParaChunker[ParagraphTextChunker
Split by paragraphs]
ChunkerFactory --> |.md files| MdChunker[MarkdownTextChunker
Split by headers H1-H6]
ParaChunker --> |Paragraph boundaries| Chunks[Phase 2: Text Chunks
IChunk objects]
MdChunker --> |Header sections| Chunks
Chunks --> Storage[Phase 3: Dual-Index Storage]
Storage --> Lucene[Lucene BM25 Index
LuceneRepository
Stores chunks]
Storage --> Vector[SQLite-vec Database
VectorRepository
Generates + stores embeddings]
Lucene --> |Stores| LuceneFields[Fields:
- content
- source_file
- source_path
- source_id
- paragraph_number
- document_id]
Vector --> |Phase 4: Per-chunk| EmbedGen[Embedding Generation
OpenAI text-embedding-3-large
3072 dimensions]
EmbedGen --> VectorFields[ChunkVectorRecord:
- Key
- Content
- Metadata fields
- Embedding vector]
LuceneFields --> Complete([Indexed Content
Ready for Search])
VectorFields --> Complete
style Start fill:#e1f5ff
style Complete fill:#d4edda
style Lucene fill:#fff3cd
style Vector fill:#fff3cd
style ChunkerFactory fill:#f8d7da
style EmbedGen fill:#d1ecf1摄入管道阶段
第一阶段:文件读取
- 组件:
FileReaderFactory和IFileReader实现 - 位置:
Pixelbadger.Toolkit.Rag/Components/FileReaders/ - 过程:
- FileReaderFactory通过扩展名标识文件类型 - 将文件路由到适当的读取器(PlainTextFileReader用于 .txt,MarkdownFileReader .md) - 从文件中提取原始文本内容 - 支持基于单个文件和文件夹的批接收
第二阶段:内容感知分块
- 组件:
ChunkerFactory和ParagraphTextChunker和MarkdownTextChunker - 位置:
Pixelbadger.Toolkit.Rag/Components/ - 过程:
- ChunkerFactory根据文件扩展名选择chunker - 对于.txt文件: ParagraphTextChunker - 双换行符上的拆分(段落边界) - 如果未检测到段落,则回退到单个新行 - 保留自然文档结构 - 对于.md文件: MarkdownTextChunker - 用途 MarkdownChunker 按标题拆分(H1-H6) - 保留标头层次结构和上下文 - 每个块包括标题及其内容 - 适当处理第一个标头之前的内容 - 生产 IChunk 具有内容和块编号的对象 - 此阶段没有嵌入生成(推迟到存储阶段)
第三阶段:双索引存储
- 组件:
- LuceneRepository 用于BM25关键字搜索 - VectorRepository 用于语义向量搜索
- 存储位置:
- Lucene: {index-path}/ (FSDirectory结构) - 矢量: {index-path}/vectors.db (SQLite数据库) - 索引内容和源路径元数据以明文形式存储。使用文件系统权限保护索引目录,并避免索引机密,除非这是可接受的。
- 过程:
- 两个索引操作都按顺序运行 - 在存储之前过滤掉空块 - Lucene直接存储块(不需要嵌入) - VectorRepository在存储过程中生成嵌入(第4阶段) - 每个块都以一致的元数据存储在两个索引中
- 朗讯油田:
- content:全文内容(可搜索、存储) - source_file:原始文件名 - source_path:完整文件路径 - source_id:唯一源标识符 - paragraph_number:块序列号 - document_id:唯一文档标识符
阶段4:嵌入生成(在矢量存储期间)
- 组件:
OpenAIEmbeddingService和text-embedding-3-large模型 - 位置:
VectorRepository.StoreVectorsAsync() - 维度:3072维向量
- 过程:
- 对于存储的每个块,按需生成嵌入 - 用途 IEmbeddingService.GenerateEmbeddingAsync() 每块 - 嵌入是在存储期间计算的,而不是在分块期间计算的 - 需要 OPENAI_API_KEY 环境变量
- 矢量记录结构:
- 密钥:唯一标识符 - 内容:块状文本 - 源元数据(文件、路径、id) - 组号:序列号 - DocumentId:唯一文档标识符 - 嵌入:3072维向量(存储时生成)
编排器: SearchIndexer.IngestContentAsync() 和 SearchIndexer.IngestFolderAsync()
搜索和检索管道
检索管道支持三种搜索模式:BM25关键字搜索、向量语义搜索和将这两种策略与交互秩融合(RRF)相结合的混合搜索。
flowchart TD
Query([Query Text]) --> ModeSwitch{Search Mode?}
ModeSwitch --> |BM25| BM25Path[BM25 Search Path]
ModeSwitch --> |Vector| VectorPath[Vector Search Path]
ModeSwitch --> |Hybrid| HybridPath[Hybrid Search Path]
BM25Path --> QueryParser[Phase 1a: Query Parsing
StandardAnalyzer
QueryParser]
QueryParser --> LuceneSearch[Phase 2a: Lucene Search
BM25Similarity scoring]
LuceneSearch --> |Optional filter| SourceFilter1[Filter by source_id
BooleanQuery]
SourceFilter1 --> BM25Results[BM25 Scored Results]
VectorPath --> EmbedQuery[Phase 1b: Query Embedding
IEmbeddingService
text-embedding-3-large]
EmbedQuery --> VectorSearch[Phase 2b: Vector Search
Euclidean distance
SQLite-vec]
VectorSearch --> |Convert| CosineSim[Convert to Cosine Similarity
1.0 - distance² / 2.0]
CosineSim --> |Optional filter| SourceFilter2[Filter by source_id
VectorSearchOptions]
SourceFilter2 --> VectorResults[Vector Scored Results]
HybridPath --> ParallelSplit[Execute Both Searches
in Parallel]
ParallelSplit --> BM25Branch[BM25 Search
Fetch 2x max results
minimum 20]
ParallelSplit --> VectorBranch[Vector Search
Fetch 2x max results
minimum 20]
BM25Branch --> RRF[Phase 3: Reciprocal Rank Fusion
RrfReranker]
VectorBranch --> RRF
RRF --> |k = 60| RRFCalc[RRF Score Calculation
Σ 1 / k + rank
for each result list]
RRFCalc --> MergeScores[Merge by DocumentId
Sum RRF scores]
MergeScores --> SortFused[Sort by fused score
Return top N]
SortFused --> HybridResults[Hybrid Fused Results]
BM25Results --> Output([Search Results
with scores and metadata])
VectorResults --> Output
HybridResults --> Output
style Query fill:#e1f5ff
style Output fill:#d4edda
style BM25Path fill:#fff3cd
style VectorPath fill:#d1ecf1
style HybridPath fill:#f8d7da
style RRF fill:#e7e7ff搜索管道阶段
BM25搜索模式
*阶段1a:查询解析*
- 组件:
QueryParser和StandardAnalyzer - 位置:
LuceneRepository.QueryLuceneAsync() - 过程:
- 使用Lucene的QueryParser解析查询文本 - 应用StandardAnalyzer进行标记化和规范化 - 对“内容”字段生成查询 - 可选:为source_id约束添加BooleanQuery过滤器
*第2a阶段:Lucene搜索*
- 组件:
LuceneRepository和BM25Similarity - 过程:
- 打开Lucene FSDirectory索引 - 使用BM25相关性评分执行搜索 - BM25考虑了术语频率、文档频率和文档长度 - 回报得分 SearchResult 带有元数据的对象 - 结果包括:分数、内容、源文件、源路径、源id、段落号、文档id
矢量搜索模式
*阶段1b:查询嵌入*
- 组件:
IEmbeddingService使用OpenAItext-embedding-3-large - 位置:
VectorRepository.QueryVectorsAsync() - 过程:
- 为查询文本生成3072维嵌入向量 - 使用与文档摄取相同的模型以实现一致性 - 需要 OPENAI_API_KEY 环境变量
*阶段2b:向量相似性搜索*
- 组件:
VectorRepository使用SQLite vec - 过程:
- 在以下位置打开SQLite vec数据库 {index-path}/vectors.db - 使用欧几里德距离执行向量相似性搜索 - 将欧几里德距离转换为余弦相似度: 1.0 - (distance² / 2.0) - 可选:通过VectorSearchOptions应用source_id过滤器 - 回报得分 SearchResult 物体 - 按与查询的语义相似性排序的结果
混合搜索模式
*第1-2阶段:并行执行*
- 过程:
- 同时执行BM25和矢量搜索 - 获取 2 × max_results 每个指数(每个指数至少20个) - 过取确保了更好的融合质量 - 如果指定,两个搜索都应用相同的source_id筛选器
*第三阶段:交互秩融合(RRF)*
- 组件:
RrfReranker - 算法:k=60的互易秩融合(标准参数)
- 公式:
RRF_score(doc) = Σ(1 / (k + rank))在两个结果列表中 - 过程:
1. 接收BM25和矢量搜索的结果 1. 计算每个列表中每个文档的RRF分数 1. 按DocumentId对结果进行分组 1. 将两个列表中出现的文档的RRF分数相加 1. 按RRF总分(降序)对合并结果进行排序 1. 返回max_results指定的前N个结果
- 好处:
- 结合关键字匹配(BM25)和语义理解(向量)的优势 - 出现在两个结果集中的文档得分更高 - 对BM25和向量搜索之间的评分量表差异具有鲁棒性 - 无需手动调整重量
编排器: SearchIndexer.SearchAsync()
搜索结果输出
所有搜索模式返回 SearchResult 对象包含:
- 得分:相关性评分(BM25评分、余弦相似性或RRF评分)
- 内容:块文本内容
- 源文件:原始文件名
- 源路径:完整文件路径
- 源标识:唯一源标识符
- 段落编号:块序列号
- 文档ID:唯一文档标识符
技术细节
BM25相似性搜索
向量相似性搜索
向量相似性搜索通过对内容的语义理解补充了BM25。本次实施:
- 使用OpenAI
text-embedding-3-large用于生成嵌入的模型(3072维) - 将嵌入与Lucene索引一起存储在sqlite-vec数据库中
- 计算查询嵌入和存储文档嵌入之间的余弦相似度
- 支持三种搜索模式:纯向量搜索、BM25关键字搜索,或将两者与交互排名融合(RRF)相结合的混合搜索
- 需要
OPENAI_API_KEY用于在摄取和查询过程中嵌入生成的环境变量 - 嵌入是在内容摄取过程中生成的,并持久存储以实现高效查询
- 启用语义搜索,了解关键字匹配之外的含义和上下文
内容分块
该系统使用针对每种文件类型量身定制的内容感知分块策略:
段落分块(.txt文件)
- 由...实施
ParagraphChunker和ParagraphTextChunker - 在双换行符上拆分文本(
\n\n,\r\n\r\n)确定段落边界 - 如果没有找到双换行符,则回退到单换行符
- 仅过滤空白或空白段落
- 在不破坏中间思想的情况下保留自然的文档结构
- 每个块接收一个连续的块编号
Markdown分块(.md文件)
- 由...实施
MarkdownChunker和MarkdownTextChunker - 按标题拆分标记文件(H1-H6:
#到######) - 每个块包括标题行和直到下一个标题的所有内容
- 保留标头层次结构和上下文
- 将第一个标头之前的内容作为单独的块处理
- 捕获标题文本、标题级别和行号元数据
- 非常适合标题表示主题边界的文档
Chunker工厂
- 根据文件扩展名自动选择合适的分块器
.md文件→MarkdownTextChunker- 所有其他文件(包括
.txt) →ParagraphTextChunker - 未来的文件类型(PDF、DOCX等)将转换为markdown并使用markdown分块器
内容感知分块的好处
- 尊重自然文档结构,而不是任意的标记限制
- 保持块内的语义连贯性
- Markdown分块通过标题保持主题边界
- 简单、确定、快速(分块不需要ML模型)
- 嵌入在存储过程中生成一次,而不是在分块过程中生成
索引结构
每个索引块包含以下字段:
- 源文件:来源的原始文件路径
- 段落编号:源文件中的顺序块编号
- 内容:正在索引和搜索的实际文本内容
- 源ID:从文件路径导出的唯一标识符(用于筛选)
该指数使用:
- 标准解析器:用于强大的标记化和规范化
- BM25相似性:用于基于相关性的排名
- SimpleDirectory:用于持久磁盘存储
- 原子写入:索引更新是事务性的,并且具有崩溃安全性
