QMD 查询语法深度指南:结构化多路检索、Intent 消歧与本地混合搜索实战
【免费下载链接】qmdmini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local项目地址: https://gitcode.com/GitHub_Trending/qmd1/qmd
QMD(Quick Markdown Database)是一个完全本地运行的 mini CLI 搜索引擎,面向文档、知识库、会议纪要等纯文本语料。本文以 docs/SYNTAX.md 为骨架,系统讲解 QMD 的结构化查询语言:从 EBNF 文法、lex/vec/hyde三种子查询类型、expand:隐式展开、intent:消歧,到多行查询文档、集合作用域(scoping)以及 CLI 与 MCP/HTTP 两种调用方式的完整参数细节。读完本文,你将掌握如何写出高召回、高精度的 QMD 查询,理解底层 BM25、向量检索、HyDE 与 RRF 融合的配合方式,并能在命令行与 MCP/HTTP 接口中灵活运用intent、collections等关键参数。
QMD 查询的核心理念:结构化文档 + 类型化子查询
QMD 查询不是普通的关键词字符串,而是"结构化文档"(structured document):每一行都声明一个搜索类型(lex、vec、hyde)和对应的查询文本。查询解析器会按行拆分、trim、丢弃空行,然后根据行首前缀将各子查询分派到不同的检索后端。
顶层文法如下(源自 docs/SYNTAX.md 的 EBNF 定义):
query = expand_query | query_document ; expand_query = text | explicit_expand ; explicit_expand= "expand:" text ; query_document = [ intent_line ] { typed_line } ; intent_line = "intent:" text newline ; typed_line = type ":" text newline ; type = "lex" | "vec" | "hyde" ; text = quoted_phrase | plain_text ; quoted_phrase = '"' { character } '"' ; plain_text = { character } ; newline = "\n" ;从文法可以读出一个关键约束:顶层查询只有两种合法形态——要么是一个独立的 expand 查询(单行),要么是一个多行查询文档(由可选的intent:行和若干lex:/vec:/hyde:类型行组成)。不存在"多个裸文本行"这种中间形态。
这个约束在源码中得到严格验证:src/cli/qmd.ts中的parseStructuredQuery(见 src/cli/qmd.ts)逐行扫描,遇到无前缀的多行文本会直接抛出错误:
Line N is missing a lex:/vec:/hyde:/intent: prefix. Each line in a query document must start with one.
expand:行如果出现在多行文档中也会报错:query documents cannot mix expand with typed lines. Submit a single expand query instead.同时,intent:单独出现(没有任何搜索行)也会被拒绝:intent: cannot appear alone. Add at least one lex:, vec:, or hyde: line.
三种查询类型:lex、vec 与 hyde
| 类型 | 检索方法 | 描述 |
|---|---|---|
lex | BM25 | 精确关键词匹配的关键词搜索 |
vec | Vector | 语义相似度搜索 |
hyde | Vector | 假设文档嵌入(Hypothetical Document Embedding) |
这三种类型对应源码中src/llm.ts定义的QueryType = 'lex' | 'vec' | 'hyde',它们分别驱动不同的检索后端:
lex:走 BM25 关键词检索(对应searchLex,基于 SQLite FTS),速度最快,适合精确匹配已知术语、专有名词和代码标识符。vec:走向量相似度检索(对应searchVector),适合用自然语言表达意图、词汇未知的场景。hyde:也是向量检索,但查询本身是一段"假设性答案"(hypothetical answer passage)。先把期望答案的样子写出来,再做向量匹配,从而拉近查询与文档在语义空间中的距离。
QMD 的完整检索管线(search)会串联"查询展开 → 多信号检索 → RRF 融合 → LLM 重排"。src/bench/bench.ts的注释清楚地列出了四种后端对比:bm25(纯关键词)、vector(纯向量)、hybrid(BM25 + 向量 RRF 融合、无重排)、full(完整混合管线 + LLM 重排)。
默认行为:单行裸查询自动走 expand
任何单行、无前缀的查询都会被当作 expand 查询处理,交给本地查询展开模型,自动生成lex、vec、hyde三种变体:
# 下面两种写法等价,且都不能与类型化行混用: how does authentication work expand: how does authentication work展开模型生成了哪些子查询,CLI 会以树形结构打印到 stderr 用于进度反馈——logExpansionTree(见 src/cli/qmd.ts)输出类似:
├─ how does authentication work ├─ lex: authentication ├─ vec: how does authentication work └─ hyde: The authentication flow typically involves...注意展开模型的调用在src/llm.ts中以expandQuery(query, options)接口暴露,SDK 层面对应qmd.expandQuery();展开所需的上下文大小可通过expandContextSize或环境变量QMD_EXPAND_CONTEXT_SIZE配置(默认 2048,必须是正整数,见 src/llm.ts)。
Lex 查询语法:前缀匹配、短语与否定
Lex 查询支持专门语法用于精确关键词匹配:
lex_query = { lex_term } ; lex_term = negation | phrase | word ; negation = "-" ( phrase | word ) ; phrase = '"' { character } '"' ; word = { letter | digit | "'" } ;| 语法 | 含义 | 示例 |
|---|---|---|
word | 前缀匹配 | perf匹配 "performance" |
"phrase" | 精确短语 | "rate limiter" |
-word | 排除该词 | -sports |
-"phrase" | 排除该短语 | -"test data" |
注意几个实现细节:
word是前缀匹配:perf能命中performance、perfmon等词,这是设计行为而非模糊匹配;- 否定只对单个词或短语生效,形如
-foo bar会整体按前缀词处理; -term与"phrase"语法只在lex行内有效,vec与hyde行按纯文本处理。
实际示例:
lex: CAP theorem consistency lex: "machine learning" -"deep learning" lex: auth -oauth -samlVec 与 HyDE 查询:自然语言与假设答案
Vec 查询没有特殊语法——直接写下你想找的内容即可:
vec: how does the rate limiter handle burst traffic vec: what is the tradeoff between consistency and availabilityHyde 查询是 50–100 词的假设答案段落——写出你期望答案长什么样子:
hyde: The rate limiter uses a sliding window algorithm with a 60-second window. When a client exceeds 100 requests per minute, subsequent requests return 429 Too Many Requests.HyDE 的价值在于把"提问"变成"作答",让向量匹配更贴近文档正文的表达方式。写 hyde 段落时,尽量模仿目标文档的语气与术语(例如提到具体的算法名、状态码、参数名),效果会更好。
多行查询文档:混合检索 + 首行 2 倍权重
把多种查询类型组合在一个文档里是 QMD 获得最佳效果的标准姿势。第一条查询在融合(fusion)中享有 2 倍权重,因此应把最强的信号放在第一行:
lex: rate limiter algorithm vec: how does rate limiting work in the API hyde: The API implements rate limiting using a token bucket algorithm...融合机制对应源码中的 RRF(Reciprocal Rank Fusion)实现:各子查询分别检索后,按排名取倒数分融合(hybrid 模式即"BM25 + vector RRF fusion",见 src/bench/bench.ts)。MCP 服务端的策略文档也明确写着 "First sub-query gets 2× weight — put your strongest signal first"(见 src/mcp/server.ts)。
针对不同目标,MCP 服务端给出的选型建议:
| 目标 | 做法 |
|---|---|
| 通用搜索(推荐) | 传query,自动展开为类型化变体并融合、重排 |
| 已知精确术语/名称 | 只用lex |
| 概念搜索 | 只用vec |
| 最佳召回 | lex+vec |
| 复杂/微妙问题 | lex+vec+hyde |
| 词汇未知 | 用自然语言传query,让服务端自动展开 |
Expand 查询:显式与隐式两种写法
Expand 查询必须独立存在,不能与类型化行混用。既可以依赖默认的无前缀形式,也可以显式加expand:前缀:
expand: error handling best practices # 等价于 error handling best practices两种形式都会调用本地查询展开模型,自动生成lex、vec、hyde变体。源码验证:parseStructuredQuery识别到expand:前缀且文档只有一行时,直接返回null表示"这是一条独立展开查询"(见 src/cli/qmd.ts),后续交给 LLM 展开流程处理。
Intent 行:给歧义查询提供背景语境
可选的intent:行用于为歧义查询提供背景语境,指导查询展开、重排(reranking)和摘要(snippet)抽取,但它本身不参与检索。规则如下:
- 每个查询文档最多一条
intent:行; intent:不能单独出现——至少需要一条lex:、vec:或hyde:行;- intent 还可以通过 CLI 的
--intent参数或 MCP 的intent参数传入。
典型示例:
intent: web page load times and Core Web Vitals lex: performance vec: how to improve performance没有 intent 时,"performance" 是歧义的(网页性能?团队健康度?体能?);带上 intent 后,检索管线会优先选取并排序与网页性能相关的内容。
源码层面,intent 的消歧作用体现在多处:src/index.ts的SearchOptions中intent字段注释为 "Domain intent hint — steers reranking and snippet/chunk selection"(见 src/index.ts);extractSnippet(row.body, query, ...)在生成摘要时接收opts.intent参与上下文选择(见 src/cli/qmd.ts)。专门的 test/intent.test.ts 覆盖了extractSnippet结合 intent 的跨文档片段消歧、chunk 选择打分、以及 intent 存在时绕过强信号(strong-signal bypass)等行为。
一个值得注意的演进细节:ExpandQueryOptions.intent字段已被标记为@deprecated——意图信息不再喂给展开模型(因为模型会把它当作元语言原样复制成子查询),而是改为通过SearchOptions.intent传给重排与摘要阶段(见 src/index.ts)。也就是说:intent 的正确用法是作为检索后处理(重排/摘要)的上下文,而不是查询展开的输入。
约束汇总
- 顶层查询必须是独立的 expand 查询或多行查询文档二选一;
- 查询文档只允许
lex、vec、hyde、intent类型行(内部不允许出现expand:); lex语法(-term、"phrase")只在 lex 查询中生效;- 每个查询文档最多一条
intent:行,且不能单独出现; - 空行会被忽略;
- 行首/行尾空白会被裁剪。
这些规则全部可以在parseStructuredQuery(src/cli/qmd.ts)中找到对应的报错分支与容错逻辑,例如空lex:/vec:/hyde:行会报must include text.,行内出现换行会报Keep each query on a single line.。
Scoping:用集合限定检索范围
默认情况下 QMD 会检索所有默认包含的集合(collection)。通过-c(CLI)或collections(MCP/SDK)可以把查询限定到特定集合:
# CLI —— 按集合名过滤(集合列表见 `qmd collection list`) qmd query -c docs "how does auth work" qmd query -c docs -c notes $'lex: auth\nvec: authentication flow'MCP / HTTP 侧传入复数collections数组(OR 匹配):
{ "searches": [ { "type": "lex", "query": "auth" } ], "collections": ["docs", "notes"] }关键语义:
-c/collections按集合名匹配,且从任意目录下都生效;- 多个值之间是OR 合并;
- 未指定时搜索所有默认包含的集合;被标记为排除的集合(
qmd collection exclude <name>)默认跳过,除非显式指名; - MCP 侧参数必须是复数
collections数组——单数collection会被静默忽略(见 docs/SYNTAX.md 与 src/mcp/server.ts 的工具描述)。
MCP / HTTP API:结构化 searches 数组
MCP 的query工具(以及 REST/query端点)接受结构化查询,核心是searches数组。没有q字符串参数——searches是必需的:
{ "searches": [ { "type": "lex", "query": "CAP theorem" }, { "type": "vec", "query": "consistency vs availability" } ], "collections": ["docs"], "limit": 10 }带 intent 的请求:
{ "searches": [ { "type": "lex", "query": "performance" } ], "intent": "web page load times and Core Web Vitals" }在 src/mcp/server.ts 的 schema 定义中,query与searches是互斥的:query是纯文本查询,由 SDK 自动展开为 lex/vec/hyde 变体、RRF 融合并重排(推荐默认);searches是类型化子查询数组(最多 10 个),第一条享有 2 倍权重,用于精确控制检索策略。intent在每次搜索调用中都被建议提供,以消歧并改善摘要(服务端系统提示明确写道 "Always provideintenton every search call to disambiguate and improve snippets.")。
CLI 用法速查
# 单行(隐式 expand) qmd query "how does auth work" # 多行带类型 qmd query $'lex: auth token\nvec: how does authentication work' # 结构化 qmd query $'lex: keywords\nvec: question\nhyde: hypothetical answer...' # 带 intent(内联) qmd query $'intent: web performance and latency\nlex: performance\nvec: how to improve performance' # 带 intent(参数) qmd query --intent "web performance and latency" "performance"CLI 侧search()的实现会先解析结构化查询,再解析集合过滤(resolveCollectionFilter支持多个-c),然后走 FTS 检索并用extractSnippet生成带 intent 上下文的摘要(见 src/cli/qmd.ts)。
小结与最佳实践
- 能用一句自然语言表达就写一句:单行裸查询自动 expand,是最省心也最通用的入口;
- 需要精确控制时用查询文档:把最强的信号放第一行(2 倍融合权重),
lex抓精确词、vec抓语义、hyde抓答案形态,三者互补达到最佳召回; - 歧义查询务必给 intent:一行
intent:就能让重排与摘要阶段聚焦到正确领域; - 善用集合作用域:通过
-c/collections缩小检索面,既提速又降噪,注意 MCP 参数是复数形式; - 了解底层管线:
lex走 BM25、vec/hyde走向量、多路结果经 RRF 融合、LLM 重排收尾——理解了这条链路,才能写出真正贴合检索器特性的查询。
更多实现细节可继续阅读 src/cli/qmd.ts、src/index.ts、src/llm.ts、src/mcp/server.ts 以及 test/intent.test.ts。
【免费下载链接】qmdmini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local项目地址: https://gitcode.com/GitHub_Trending/qmd1/qmd
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考