光景不待人,须叟发成丝。——李白

QMD:你的本地“记忆搜索引擎”——它不问云端要答案,只在你电脑里把一切翻出来

QMD 全名 Query Markup Documents
它更愿意被叫作:你设备上的搜索引擎

它的使命很朴素,但执行得很“狠”:

  • 把你的 Markdown 笔记会议纪要/转录文档知识库统统收进来
  • 让你用 关键词自然语言就能找回“我明明写过但死活想不起来在哪”的那段内容
  • 并且——全程本地运行:BM25、向量语义检索、LLM 重排,统统在你电脑里完成

它对自己的定位也很直白(repo description):

mini cli search engine for your docs, knowledge bases, meeting notes, whatever.
Tracking current sota approaches while being all local

它像一只非常克制的猎犬:不依赖外网、不把你的资料丢到云上,只在你本地的文件夹里闻味道、追踪线索,然后把结果叼回来。


QMD 的脾气:它不是“只会关键词”的老搜索,也不是“只会语义”的新搜索

QMD 的骨架是一个混合检索流水线,它把三种力量编成一支队伍:

  1. BM25 全文检索(SQLite FTS5):快速、精准、擅长命中“你写过的词”
  2. 向量语义检索(semantic search):擅长理解“你想表达的意思”
  3. LLM Re-ranking(重排):像个严苛的编辑,把“看起来相关”重新排序成“真正有用”

而且它强调:这些都在本地跑,通过 node-llama-cpp + GGUF 模型

它的气质更像一个“守在你资料库门口的本地总管”:

  • 你问得很直:它用 BM25 秒回
  • 你问得很虚:它用向量去理解
  • 你问得很难:它会扩展问题、融合候选、再重排,把最值得看的放前面

Quick Start:先让 QMD 认识你的世界

QMD 是 CLI 工具,开局很干脆:装上、把资料夹登记成 collection、加点 context、做 embedding、开始搜索。

1
2
3
4
5
6
7
8
# 全局安装(Node 或 Bun)
npm install -g @tobilu/qmd
# or
bun install -g @tobilu/qmd

# 或者直接运行(不装也能用)
npx @tobilu/qmd ...
bunx @tobilu/qmd ...

1)给你的资料建“集合”(collections)

你可以把不同类型的内容分开管理:notes / meetings / docs……

1
2
3
qmd collection add ~/notes --name notes
qmd collection add ~/Documents/meetings --name meetings
qmd collection add ~/work/docs --name docs

QMD 像个图书馆管理员,先给每一排书架贴上标签:
“这边是笔记”“那边是会议”“那边是文档”。

2)给它一点“语境”(context):这是 QMD 的关键气质

QMD 有一个非常关键的能力:context 是树状的,并且会跟着结果一起返回
它不只想给你“这段文字在哪”,它还想告诉你“这段文字属于哪个语境”。

1
2
3
qmd context add qmd://notes "Personal notes and ideas"
qmd context add qmd://meetings "Meeting transcripts and notes"
qmd context add qmd://docs "Work documentation"

它像在每个文件夹门口挂了一块牌子:
“这片区域是什么、你当初为什么把东西放这里”。
当你把搜索结果喂给 LLM/Agent 时,这块牌子会一起被带走——这就是它对 agent workflow 很友好的原因之一。

3)生成向量 embedding(语义检索的燃料)

1
qmd embed

从这一刻开始,QMD 不只是“会翻关键词”,它开始“会理解意思”。

4)开始搜:三种模式,各司其职

1
2
3
qmd search "project timeline"           # 关键词(快)
qmd vsearch "how to deploy" # 语义(懂意思)
qmd query "quarterly planning process" # 混合 + 重排(质量最好)

它像一个三段式的搜索专家:

  • search:老派但可靠,速度快
  • vsearch:语义派,擅长“换句话也能找”
  • query:精英派,扩展问题 + 融合候选 + LLM 重排,追求“最终答案的质量”

5)把某个文档直接“叫过来”

1
2
qmd get "meetings/2024-01-15.md"
qmd get "#abc123"

它不仅能通过路径叫人,还能用 docid 召唤——docid 会在搜索结果里出现。

6)批量带走(multi-get)

1
qmd multi-get "journals/2025-05*.md"

给 AI Agents 用:QMD 会“主动把自己变成结构化上下文”

QMD 很清楚它要服务的场景:agentic workflows。
所以它的输出格式从一开始就帮你准备好两种“给模型吃的口粮”:

  • --json:结构化结果 + snippets
  • --files:适合让 agent 批量拉文件/引用路径
1
2
3
4
5
6
7
8
# 给 LLM 结构化搜索结果
qmd search "authentication" --json -n 10

# 输出所有超过阈值的相关文件,便于 agent 全量收集上下文
qmd query "error handling" --all --files --min-score 0.4

# 拉完整文档内容
qmd get "docs/api-reference.md" --full

它像一个懂模型饮食习惯的厨师:
“你要吃 JSON?我给你切好;你要吃文件列表?我给你配好阈值;你要整篇?我给你端上来。”


MCP Server:它不只会在命令行里跑,它还能变成你的“模型工具”

QMD 除了 CLI,还提供 MCP(Model Context Protocol)服务器,用于更紧密的工具集成。

它暴露的工具包括:

  • query — typed sub-queries(lex/vec/hyde)+ RRF + reranking
  • get — 按 path 或 docid 拿文档(带 fuzzy suggestions)
  • multi_get — 批量拿(glob / 列表 / docids)
  • status — 索引健康与 collection 信息

Claude Desktop 配置(macOS 例子)

~/Library/Application Support/Claude/claude_desktop_config.json

1
2
3
4
5
6
7
8
{
"mcpServers": {
"qmd": {
"command": "qmd",
"args": ["mcp"]
}
}
}

Claude Code:装插件(推荐)

1
2
claude plugin marketplace add tobi/qmd
claude plugin install qmd@qmd

或者手动在 ~/.claude/settings.json 配置 MCP:

1
2
3
4
5
6
7
8
{
"mcpServers": {
"qmd": {
"command": "qmd",
"args": ["mcp"]
}
}
}

HTTP Transport:让 MCP 服务器常驻,避免反复加载模型

默认是 stdio(每个 client 启一个子进程),如果你想要共享、长生命周期、避免重复加载模型,就用 HTTP:

1
2
3
4
5
6
7
8
# 前台运行(Ctrl-C 结束)
qmd mcp --http # localhost:8181
qmd mcp --http --port 8080 # 自定义端口

# 后台守护进程
qmd mcp --http --daemon # 启动,PID 写到 ~/.cache/qmd/mcp.pid
qmd mcp stop # 通过 PID 文件停止
qmd status # 看到 "MCP: running (PID ...)" 说明活着

HTTP server 暴露:

  • POST /mcp — MCP Streamable HTTP(JSON responses,无状态)
  • GET /health — 存活检查 + uptime

它还很“本地派”地强调了一点:
LLM 模型会常驻在 VRAM,跨请求不卸载;embedding/reranking 的上下文空闲 5 分钟会释放,下次再透明重建(约 1 秒开销),但模型仍保持加载。


SDK / Library:QMD 还愿意当你的库,不只是一把 CLI 小刀

如果你想把它塞进自己的 Node/Bun 程序里,它也给了你干净的入口:createStore()

安装

1
npm install @tobilu/qmd

Quick Start(TypeScript)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createStore } from '@tobilu/qmd'

const store = await createStore({
dbPath: './my-index.sqlite',
config: {
collections: {
docs: { path: '/path/to/docs', pattern: '**/*.md' },
},
},
})

const results = await store.search({ query: "authentication flow" })
console.log(results.map(r => `${r.title} (${Math.round(r.score * 100)}%)`))

await store.close()

它的“人格”很克制:
SDK 强制要求显式 dbPath,不做任何默认假设——你把它嵌进任何应用都不会莫名其妙往你系统里写东西。

createStore() 的三种姿势:它很会配���你的工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { createStore } from '@tobilu/qmd'

// 1) Inline config:只有 DB
const store = await createStore({
dbPath: './index.sqlite',
config: {
collections: {
docs: { path: '/path/to/docs', pattern: '**/*.md' },
notes: { path: '/path/to/notes' },
},
},
})

// 2) YAML config file:集合写在文件里
const store2 = await createStore({
dbPath: './index.sqlite',
configPath: './qmd.yml',
})

// 3) DB-only:复用既有 store
const store3 = await createStore({ dbPath: './index.sqlite' })

搜索的“心法”:统一 search() + 可控的子通道

QMD 给了一个统一入口 search(),既能处理简单 query,也能处理你手动展开的 structured queries。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 简单 query:自动用 LLM 展开,然后 BM25 + 向量 + 重排
const results = await store.search({ query: "authentication flow" })

// 带 intent / collection / explain 等选项
const results2 = await store.search({
query: "rate limiting",
intent: "API throttling and abuse prevention",
collection: "docs",
limit: 5,
minScore: 0.3,
explain: true,
})

// 预展开:你自己控制每个 sub-query
const results3 = await store.search({
queries: [
{ type: 'lex', query: '"connection pool" timeout -redis' },
{ type: 'vec', query: 'why do database connections time out under load' },
],
collections: ["docs", "notes"],
})

// 追求速度:跳过重排
const fast = await store.search({ query: "auth", rerank: false })

如果你想完全掌控底层,它也给你直通车:

1
2
3
4
5
6
7
8
9
// 纯 BM25(快,不用 LLM)
const lexResults = await store.searchLex("auth middleware", { limit: 10 })

// 纯向量(embedding,不用重排)
const vecResults = await store.searchVector("how users log in", { limit: 10 })

// 手动扩展 query
const expanded = await store.expandQuery("auth flow", { intent: "user login" })
const results4 = await store.search({ queries: expanded })

Indexing、Embedding、Chunking:QMD 很在意“把内容切得像人类写的一样”

QMD 的 embedding 不是“随便切 900 tokens”。
它会尽量在自然边界切割,让语义单元完整:章节、段落、代码块……

1
2
3
4
5
# 默认:regex chunking
qmd embed

# 强制全部重做
qmd embed -f

它还有一个让代码库更舒服的选项:AST-aware chunking(tree-sitter)。

1
2
3
4
5
# 代码文件启用 AST-aware chunking(TS/JS/Python/Go/Rust 等)
qmd embed --chunk-strategy auto

# query 也能用同样策略保持 chunk 一致
qmd query "auth flow" --chunk-strategy auto

它的态度是:
“你把代码库交给我,我就按函数、类、import 的边界来切;你别让我拿刀乱剁。”


运行环境要求:它要你升级一点点,但换来的是全本地的强能力

系统要求(README):

  • Node.js >= 22
  • Bun >= 1.0.0
  • macOS:需要 Homebrew SQLite(为了扩展支持)
1
brew install sqlite

GGUF 本地模型:QMD 的三位“本地特工”

QMD 默认会自动下载并缓存三种 GGUF 模型(第一次用的时候):

  • embeddinggemma-300M-Q8_0:向量 embedding(默认)
  • qwen3-reranker-0.6b-q8_0:重排
  • qmd-query-expansion-1.7B-q4_k_m:query expansion(微调模型)

缓存目录:

  • ~/.cache/qmd/models/

如果你需要更好的中文/日文/韩文(CJK)覆盖:换 embedding 模型

QMD 允许用环境变量覆盖 embedding model:

1
2
3
4
5
# 换成 Qwen3-Embedding-0.6B(多语言更强)
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"

# 换模型后需要重做 embedding(向量不兼容)
qmd embed -f

QMD 的个性像个严谨的实验员:
“你可以换试剂,但换了就得重新做实验,别拿旧向量糊弄我。”


CLI 使用:collections、context、search、输出格式、维护一条龙

Collection 管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 当前目录建一个 collection
qmd collection add . --name myproject

# 自定义 glob mask
qmd collection add ~/Documents/notes --name notes --mask "**/*.md"

# 列出 collections
qmd collection list

# 删除 / 重命名
qmd collection remove myproject
qmd collection rename myproject my-project

# 列出 collection 内文件
qmd ls notes
qmd ls notes/subfolder

Context 管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 给 collection / 路径加 context
qmd context add qmd://notes "Personal notes and ideas"
qmd context add qmd://docs/api "API documentation"

# 在 collection 内部直接加
cd ~/notes && qmd context add "Personal notes and ideas"
cd ~/notes/work && qmd context add "Work-related notes"

# 全局 context(作用于所有 collections)
qmd context add / "Knowledge base for my projects"

# 查看 / 删除
qmd context list
qmd context rm qmd://notes/old

搜索模式

1
2
3
qmd search "authentication flow"
qmd vsearch "how to login"
qmd query "user authentication"

常用选项与输出格式

1
2
3
4
5
6
7
8
9
10
11
# 返回 10 条 + 分数阈值
qmd query -n 10 --min-score 0.3 "API design patterns"

# 以 Markdown 输出,适合喂给 LLM(可配 --full)
qmd search --md --full "error handling"

# JSON + explain 看清楚每条结果怎么来的
qmd query --json --explain "quarterly reports"

# 选择 index(不同知识库)
qmd --index work search "quarterly reports"

输出格式选项(search / multi-get):

  • --files(docid, score, filepath, context)
  • --json
  • --csv
  • --md
  • --xml

它甚至会在 TTY 场景输出可点击的终端超链接(OSC 8),并且允许你配置点击打开哪个编辑器:

1
2
3
4
5
6
7
8
9
10
11
# VS Code
export QMD_EDITOR_URI="vscode://file/{path}:{line}:{col}"

# Cursor
export QMD_EDITOR_URI="cursor://file/{path}:{line}:{col}"

# Zed
export QMD_EDITOR_URI="zed://file/{path}:{line}:{col}"

# Sublime
export QMD_EDITOR_URI="subl://open?url=file://{path}&line={line}"

数据存储:它把“记忆索引”关在你的本地缓存里

索引位置:

  • ~/.cache/qmd/index.sqlite

数据库里大致包含:

  • collections
  • path_contexts
  • documents + docid
  • documents_fts(FTS5)
  • content_vectors + 向量索引
  • llm_cache(query expansion / rerank 缓存)

它像个很懂边界的管家:
“我只在你本地的缓存里建索引,不干扰你原始文件,也不把东西搬去别处。”


���发模式:如果你想参与它的进化

1
2
3
4
git clone https://github.com/tobi/qmd
cd qmd
npm install
npm link

结尾:QMD 的一句话总结

QMD 像一个不睡觉的“本地记忆检索官”:

  • 你把资料放哪,它就在哪建立索引
  • 你用关键词问,它用 BM25 快速命中
  • 你用自然语言问,它用向量理解你真正想找的东西
  • 你要高质量结果,它会扩展 query、融合候选、再用本地 LLM 重排
  • 你要接入 Agent,它给你 JSON、files、MCP server、HTTP 常驻、SDK 全套接口

它不是一个“搜索工具”,它更像你电脑里的一套可控、可嵌入、可扩展的“记忆召回系统”。


License

MIT