An on-device hybrid search engine for Markdown documents
Project description
QMD-Py — Query Markup Documents
本地运行的混合文档搜索引擎。索引你的 Markdown 笔记、会议记录、文档和知识库,用关键词或自然语言搜索。Python 移植版,忠实复现 qmd 的核心算法。
QMD-Py 结合 BM25 全文检索、向量语义检索和 LLM 重排序,全部本地运行。支持 llama-cpp-python(GGUF 模型)、sentence-transformers、FlagEmbedding 三种后端。
快速开始
# 安装
pip install -e .
# 带 LLM 后端
pip install -e ".[mvp]"
# 带 MCP 支持
pip install -e ".[mcp]"
# 创建 collection
qmd-py add notes ~/notes
qmd-py add docs ~/Documents/docs --pattern "**/*.md"
# 添加上下文(关键特性——帮助 LLM 理解文档归属)
qmd-py context add notes "" "个人笔记和想法"
qmd-py context add docs "api" "API 文档"
# 生成 embedding
qmd-py embed
# 搜索
qmd-py search "项目进度" # BM25 关键词检索
qmd-py query "如何部署服务" # 混合检索 + 重排序(最佳质量)
# 获取文档
qmd-py get qmd://notes/meeting.md
qmd-py get "#abc123" # 用 docid
# 列出文件
qmd-py ls
qmd-py ls notes
架构
┌─────────────────────────────────────────────────────────────────┐
│ QMD-Py Hybrid Search Pipeline │
└─────────────────────────────────────────────────────────────────┘
┌──────────────┐
│ User Query │
└──────┬───────┘
│
┌──────────────┴──────────────┐
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Query Expansion│ │ Original Query│
│ (fine-tuned) │ │ (×2 weight) │
└───────┬────────┘ └───────┬────────┘
│ │
│ lex / vec / hyde 变体 │
└──────────────┬──────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ BM25+Vec │ │ BM25+Vec │ │ BM25+Vec │
│(原始 query)│ │(扩展 query1)│ │(扩展 query2)│
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└─────────────────────┼─────────────────────┘
▼
┌─────────────────────────┐
│ RRF Fusion (k=60) │
│ 原始 query ×2 权重 │
│ Top-rank bonus: +0.05 │
│ 取 Top 40 候选 │
└────────────┬────────────┘
▼
┌─────────────────────────┐
│ LLM Re-ranking │
│ (qwen3-reranker) │
└────────────┬────────────┘
▼
┌─────────────────────────┐
│ Position-Aware Blend │
│ Rank 1-3: 75% RRF │
│ Rank 4-10: 60% RRF │
│ Rank 11+: 40% RRF │
└─────────────────────────┘
检索算法
分数归一化
| 后端 | 原始分数 | 转换 | 范围 |
|---|---|---|---|
| BM25 (FTS5) | SQLite FTS5 BM25 | abs(score) |
0 ~ 25+ |
| Vector | 余弦距离 | 1 / (1 + distance) |
0.0 ~ 1.0 |
| Reranker | LLM 0-10 评分 | score / 10 |
0.0 ~ 1.0 |
融合策略
query 命令使用 Reciprocal Rank Fusion (RRF) + 位置感知混合:
- Query Expansion: 原始查询 (×2 权重) + LLM 生成的变体查询
- 并行检索: 每个查询同时搜索 FTS 和向量索引
- RRF 融合:
score = Σ(1/(k+rank+1)),k=60 - Top-Rank Bonus: 任意列表中排名 #1 的文档 +0.05,#2-3 +0.02
- 强信号检测: BM25 top-1 分数 ≥0.85 且与 top-2 差距 ≥0.15 时跳过 expansion
- Top-K 筛选: 取 top 40 候选进入重排序
- LLM 重排序: 对每个 chunk(非全文)打分
- Position-Aware Blending:
- RRF rank 1-3: 75% 检索 / 25% 重排序(保护精确匹配)
- RRF rank 4-10: 60% 检索 / 40% 重排序
- RRF rank 11+: 40% 检索 / 60% 重排序(信赖重排序)
分数解读
| 分数 | 含义 |
|---|---|
| 0.8 - 1.0 | 高度相关 |
| 0.5 - 0.8 | 中等相关 |
| 0.2 - 0.5 | 有一定相关 |
| 0.0 - 0.2 | 低相关 |
智能分块
文档按 ~900 token 分块,15% 重叠,使用断点检测算法寻找自然切割点:
| 模式 | 分数 | 说明 |
|---|---|---|
# Heading |
100 | H1 标题 |
## Heading |
90 | H2 标题 |
### Heading |
80 | H3 标题 |
#### ~ ###### |
70~50 | H4-H6 |
``` |
80 | 代码块边界 |
--- / *** |
60 | 分隔线 |
| 空行 | 20 | 段落边界 |
- item / 1. item |
5 | 列表项 |
| 换行 | 1 | 最小断点 |
算法: 接近 900 token 目标时,在前 200 token 窗口内搜索最佳断点。分数衰减公式:finalScore = baseScore × (1 - (distance/window)² × 0.7)。代码块内的断点被忽略——代码保持完整。
Context 系统
Context 是 QMD 的核心特性——为路径添加描述性元数据,帮助 LLM 理解文档归属。
# Collection 级别
qmd-py context add notes "" "个人笔记和想法"
# 子路径级别
qmd-py context add notes "work" "工作相关笔记"
qmd-py context add notes "work/meetings" "会议记录"
# 层级继承:搜索 notes/work/meetings/2024.md 会返回所有匹配的 context 拼接
# → "个人笔记和想法\n工作相关笔记\n会议记录"
# 列出所有 context
qmd-py context list
# 删除
qmd-py context remove notes "work/meetings"
CLI 命令
Collection 管理
qmd-py add <name> <path> [--pattern "**/*.md"] # 添加 collection
qmd-py remove <name> # 删除 collection
qmd-py collection rename <old> <new> # 重命名
qmd-py list # 列出所有 collection
qmd-py ls [collection] # 列出文件
qmd-py update [name] # 重新索引
qmd-py status # 索引状态
搜索
qmd-py search <query> [-c collection] [-n 10] # BM25 检索
qmd-py query <query> [-c collection] [-n 10] # 混合检索 + 重排序
输出格式
--format cli # 默认终端格式
--format json # JSON(适合 agent 消费)
--format csv # CSV
--format xml # XML
--format md # Markdown
--format files # 简单文件列表:docid,score,filepath,context
--full # 显示完整内容
--line-numbers # 显示行号
文档操作
qmd-py get <file> [-c collection] # 获取文档
qmd-py get qmd://notes/file.md # 虚拟路径
qmd-py get "#abc123" # docid
qmd-py get file.md:42 --max-lines 20 # 指定行范围
qmd-py embed [--force] # 生成 embedding
qmd-py cleanup # 清理孤立数据 + VACUUM
MCP Server
QMD-Py 提供 MCP (Model Context Protocol) 服务器,通过 stdio transport 与 Claude Desktop 等客户端通信。
工具列表:
qmd_search— BM25 关键词检索qmd_deep_search— 混合检索 + query expansion + 重排序qmd_vector_search— 向量语义检索qmd_get— 获取文档(路径或 docid,支持模糊匹配建议)qmd_index— 索引/更新 collectionqmd_status— 索引健康状态qmd_collections— 列出 collection
Claude Desktop 配置 (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"qmd": {
"command": "qmd-py",
"args": ["serve"]
}
}
}
LLM 后端
QMD-Py 支持三种后端,按优先级自动选择:
llama-cpp-python(推荐)
使用 GGUF 模型,与原版 qmd 相同的模型:
| 模型 | 用途 | 大小 |
|---|---|---|
embeddinggemma-300M-Q8_0 |
向量 embedding | ~300MB |
qwen3-reranker-0.6b-q8_0 |
重排序 | ~640MB |
qmd-query-expansion-1.7B-Q4_K_M |
查询扩展 | ~1.1GB |
模型从 HuggingFace 下载,缓存在 ~/.cache/qmd-py/models/。
sentence-transformers(fallback)
纯 Python embedding,不需要编译 llama-cpp。适合快速测试。
FlagEmbedding
专用 reranker 后端(FlagReranker),可与其他后端组合使用。
数据存储
数据库: ~/.config/qmd/qmd.db (SQLite)
collections -- 集合目录配置
path_contexts -- 路径 context 描述
documents -- 文档元数据(path, title, hash, active)
documents_fts -- FTS5 全文索引
content -- 文档内容(content-addressable,按 SHA256 去重)
content_vectors -- embedding 分块(hash, seq, pos)
vectors_vec -- sqlite-vec 向量索引
llm_cache -- LLM 响应缓存(query expansion, rerank)
配置文件: ~/.config/qmd/qmd.yaml
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
QMD_CONFIG_DIR |
~/.config/qmd |
配置目录 |
QMD_DATA_DIR |
~/.cache/qmd |
数据/缓存目录 |
XDG_CONFIG_HOME |
~/.config |
XDG 配置根目录 |
XDG_CACHE_HOME |
~/.cache |
XDG 缓存根目录 |
系统要求
- Python >= 3.11
- SQLite >= 3.35(FTS5 支持)
- GPU(可选): CUDA 或 Apple MPS 加速 embedding/reranking
安装
# 基础安装
pip install -e .
# 完整安装(所有 LLM 后端 + MCP)
pip install -e ".[mvp,mcp]"
# 开发环境
pip install -e ".[mvp,mcp,dev]"
pytest tests/ -v
项目结构
qmd/
├── core/
│ ├── db.py # SQLite 数据库层(schema、CRUD、FTS5、sqlite-vec)
│ ├── config.py # YAML 配置管理、collection/context 操作
│ ├── store.py # 文档索引层(content-addressable 存储、增量更新)
│ ├── retrieval.py # 混合检索引擎(BM25 + Vector + RRF + Rerank)
│ ├── chunking.py # 智能分块(断点检测、代码围栏保护)
│ ├── document.py # 文档查找辅助(docid、模糊匹配、glob、cleanup)
│ └── watcher.py # 文件监听(watchdog,自动索引)
├── cli/
│ ├── main.py # CLI 入口(argparse,所有命令)
│ └── formatter.py # 输出格式化(JSON/CSV/XML/MD/Files)
├── llm/
│ ├── base.py # LLM 抽象接口
│ ├── llama_cpp.py # llama-cpp-python 后端
│ ├── sentence_tf.py # sentence-transformers 后端
│ ├── flagembed.py # FlagEmbedding reranker 后端
│ └── models.py # 模型配置、GPU 检测
├── mcp/
│ └── server.py # MCP Server(stdio transport)
├── utils/
│ ├── paths.py # 路径工具、VirtualPath (qmd://)
│ ├── snippet.py # 摘要提取、标题提取
│ └── hashing.py # SHA256 content hash
└── __init__.py # create_store() / create_llm_backend() 入口
致谢
Python 移植自 qmd(Tobias Lütke),核心检索算法、分块策略和融合逻辑忠实复现原版设计。
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qmd-0.1.0.tar.gz.
File metadata
- Download URL: qmd-0.1.0.tar.gz
- Upload date:
- Size: 107.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5388bd4da98625186f39f043675b2f3e4ba668efbdfc6e84e196a0ed66480047
|
|
| MD5 |
7a55d075dfb7fe4654759f5bdf65442c
|
|
| BLAKE2b-256 |
f2ca7ae4405af2e12f6a16075ed2148e65aa7a636b0b161133af2b860f898047
|
File details
Details for the file qmd-0.1.0-py3-none-any.whl.
File metadata
- Download URL: qmd-0.1.0-py3-none-any.whl
- Upload date:
- Size: 74.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bcac46cfaf6b1aa81a32251a3d06e683489bc207ed335f56b983333a2b728b9
|
|
| MD5 |
f5b10716ae8e060bd949f28863f19df8
|
|
| BLAKE2b-256 |
b0f223c393ff8e52680280f03aaf3943f6532427aebede302127302015f01d74
|