← 返回公开榜单
qm

tobi/qmd

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

本周新增
1,223 星
近 3 周倍数
1.1x
距上次推送
238 天
距上次发布
242 天
许可证
MIT

星标增长

每根柱子是这个周期记录下来的 star 增长:优先取逐个 stargazer 到达时间分桶的到达数,还没被 stargazer 扫描覆盖的仓库改用该周期的 star 净增;两个写者都没记录的周期画成虚线空档,而不是当成 0。柱高按各自窗口的峰值归一,两张图不共用坐标轴。指向任意一根柱子, tooltip 里是该周期的九项指标:当前值与周期内的净增。

指向或点击一根柱子,看当天的九项指标。

2/232/263/13/43/73/103/133/16

2026 年 2 月 25 日 新增 382 个星标; 2026 年 2 月 27 日 新增 313 个星标; 2026 年 2 月 28 日 新增 232 个星标; 2026 年 3 月 1 日 新增 167 个星标; 2026 年 3 月 2 日 新增 203 个星标; 2026 年 3 月 3 日 新增 507 个星标; 2026 年 3 月 4 日 新增 458 个星标; 2026 年 3 月 5 日 新增 296 个星标; 2026 年 3 月 8 日 新增 657 个星标; 2026 年 3 月 10 日 新增 609 个星标; 2026 年 3 月 11 日 新增 321 个星标; 2026 年 3 月 16 日 新增 1,223 个星标

窗口内最高的一天是 2026 年 3 月 16 日,新增 1223 个星标。

证据时间轴

  1. 第 2026 年第 11 周发布 3 次

    累计 9 个 release

  2. 第 2026 年第 10 周发布 3 次

    累计 6 个 release

README
该仓库存储的文档,若已生成中文翻译则一并显示

QMD - 查询标记文档

一个可在设备上运行的搜索引擎,适用于您需要记住的所有内容。索引您的 Markdown 笔记、会议记录、文档和知识库。使用关键词或自然语言进行搜索。非常适合您的智能代理流程。

QMD 结合了 BM25 全文搜索、向量语义搜索和 LLM 重排序—all 通过 node-llama-cpp 在本地运行,使用 GGUF 模型。

QMD 架构

您可以阅读更多关于 QMD 进展的信息 CHANGELOG。

快速入门

# 全局安装(Node 或 Bun)
npm install -g @tobilu/qmd
# 或
bun install -g @tobilu/qmd

# 直接运行
npx @tobilu/qmd ...
bunx @tobilu/qmd ...

# 为您的笔记、文档和会议记录创建集合
qmd collection add ~/notes --name notes
qmd collection add ~/Documents/meetings --name meetings
qmd collection add ~/work/docs --name docs

# 添加上下文以帮助搜索结果,每段上下文将在匹配的子文档返回时一起返回。这就像一个树状结构。这是 QMD 的关键特性,因为它允许 LLM 在选择文档时做出更好的上下文选择。不要忽视它!
qmd context add qmd://notes "个人笔记和想法"
qmd context add qmd://meetings "会议记录和笔记"
qmd context add qmd://docs "工作文档"

# 生成嵌入以用于语义搜索
qmd embed

# 搜索所有内容
qmd search "项目时间线"           # 快速关键词搜索
qmd vsearch "如何部署"             # 语义搜索
qmd query "季度规划流程"  # 混合 + 重排序(最佳质量)

# 获取特定文档
qmd get "meetings/2024-01-15.md"

# 通过 docid 获取文档(显示在搜索结果中)
qmd get "#abc123"

# 通过 glob 模式获取多个文档
qmd multi-get "journals/2025-05*.md"

# 在特定集合内搜索
qmd search "API" -c notes

# 导出所有匹配项以供代理使用
qmd search "API" --all --files --min-score 0.3

与 AI 代理一起使用

QMD 的 --json 和 --files 输出格式专为智能代理工作流程设计:

# 获取 LLM 的结构化结果
qmd search "authentication" --json -n 10

# 列出高于阈值的所有相关文件
qmd query "error handling" --all --files --min-score 0.4

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

MCP 服务器

虽然您只需在命令行中告诉您的代理使用它,它也暴露了一个 MCP(模型上下文协议)服务器,以便更紧密的集成。

暴露的工具:

  • query — 使用带类型的子查询(lex/vec/hyde)进行搜索,通过 RRF + 重排序组合
  • get — 通过路径或 docid 检索文档(带模糊匹配建议)
  • multi_get — 通过 glob 模式、逗号分隔列表或 docids 批量检索
  • status — 索引健康和集合信息

Claude 桌面配置 (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}

Claude 代码 — 安装插件(推荐):

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

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

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}

HTTP 传输

默认情况下,QMD 的 MCP 服务器使用 stdio(作为每个客户端的子进程启动)。为了共享、长时间运行的服务器并避免重复加载模型,请使用 HTTP 传输:

# 前台(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 服务器暴露两个端点:

  • POST /mcp — MCP 流式化 HTTP(JSON 响应,无状态)
  • GET /health — 带运行时间的存活检查

LLM 模型在 VRAM 中跨请求保持加载。嵌入/重排序上下文在 5 分钟空闲后销毁,并在下一个请求中透明地重新创建(~1 秒惩罚,模型保持加载)。

将任何 MCP 客户端指向 http://localhost:8181/mcp 以连接。

SDK / 库使用

在您自己的 Node.js 或 Bun 应用程序中将 QMD 作为库使用。

安装

npm install @tobilu/qmd

快速入门

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()

存储创建

createStore() 接受三种模式:

import { createStore } from '@tobilu/qmd'

// 1. 内联配置 — 除了数据库不需要其他文件
const store = await createStore({
  dbPath: './index.sqlite',
  config: {
    collections: {
      docs: { path: '/path/to/docs', pattern: '**/*.md' },
      notes: { path: '/path/to/notes' },
    },
  },
})

// 2. YAML 配置文件 — 在文件中定义集合
const store2 = await createStore({
  dbPath: './index.sqlite',
  configPath: './qmd.yml',
})

// 3. 仅数据库 — 重新打开之前配置的存储
const store3 = await createStore({ dbPath: './index.sqlite' })

搜索

统一的 search() 方法处理简单查询和预扩展的结构化查询:

// 简单查询 — 自动通过 LLM 扩展,然后 BM25 + 向量 + 重排序
const results = await store.search({ query: "authentication flow" })

// 带选项
const results2 = await store.search({
  query: "rate limiting",
  intent: "API 节流和滥用预防",
  collection: "docs",
  limit: 5,
  minScore: 0.3,
  explain: true,
})

// 预扩展查询 — 跳过自动扩展,控制每个子查询
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 })

对于直接后端访问:

// BM25 关键词搜索(快速,无 LLM)
const lexResults = await store.searchLex("auth middleware", { limit: 10 })

// 向量相似度搜索(嵌入模型,无重排序)
const vecResults = await store.searchVector("how users log in", { limit: 10 })

// 手动查询扩展以完全控制
const expanded = await store.expandQuery("auth flow", { intent: "user login" })
const results4 = await store.search({ queries: expanded })

检索

// 通过路径或 docid 获取文档
const doc = await store.get("docs/readme.md")
const byId = await store.get("#abc123")

if (!("error" in doc)) {
  console.log(doc.title, doc.displayPath, doc.context)
}

// 获取文档正文和行范围
const body = await store.getDocumentBody("docs/readme.md", {
  fromLine: 50,
  maxLines: 100,
})

// 批量通过 glob 或逗号分隔列表检索
const { docs, errors } = await store.multiGet("docs/**/*.md", {
  maxBytes: 20480,
})

集合

// 添加集合
await store.addCollection("myapp", {
  path: "/src/myapp",
  pattern: "**/*.ts",
  ignore: ["node_modules/**", "*.test.ts"],
})

// 列出集合和文档统计
const collections = await store.listCollections()
// => [{ name, pwd, glob_pattern, doc_count, active_count, last_modified, includeByDefault }]

// 获取默认情况下包含在查询中的集合名称
const defaults = await store.getDefaultCollectionNames()

// 删除 / 重命名
await store.removeCollection("myapp")
await store.renameCollection("old-name", "new-name")

上下文

上下文添加描述性元数据,提高搜索相关性,并随结果一起返回:

// 为集合中的路径添加上下文
await store.addContext("docs", "/api", "REST API 参考文档")

// 设置全局上下文(适用于所有集合)
await store.setGlobalContext("内部工程文档")

// 列出所有上下文
const contexts = await store.listContexts()
// => [{ collection, path, context }]

// 删除上下文
await store.removeContext("docs", "/api")
await store.setGlobalContext(undefined)  // 清除全局

索引

// 通过扫描文件系统重新索引集合
const result = await store.update({
  collections: ["docs"],  // 可选 — 默认为所有
  onProgress: ({ collection, file, current, total }) => {
    console.log(`[${collection}] ${current}/${total} ${file}`)
  },
})
// => { collections, indexed, updated, unchanged, removed, needsEmbedding }

// 生成向量嵌入
const embedResult = await store.embed({
  force: false,           // true 以重新嵌入所有内容
  onProgress: ({ current, total, collection }) => {
    console.log(`Embedding ${current}/${total}`)
  },
})

类型

为 SDK 消费者导出的关键类型:

import type {
  QMDStore,            // 存储接口
  SearchOptions,       // search() 的选项
  LexSearchOptions,    // searchLex() 的选项
  VectorSearchOptions, // searchVector() 的选项
  HybridQueryResult,   // 带分数、片段、上下文的搜索结果
  SearchResult,        // searchLex/searchVector 的结果
  ExpandedQuery,       // 带类型的子查询 { type: 'lex'|'vec'|'hyde', query }
  DocumentResult,      // 文档元数据 + 正文
  DocumentNotFound,    // 带相似文件建议的错误
  MultiGetResult,      // 批量检索结果
  UpdateProgress,      // update() 的进度回调信息
  UpdateResult,        // 聚合更新结果
  EmbedProgress,       // embed() 的进度回调信息
  EmbedResult,         // 嵌入结果
  StoreOptions,        // createStore() 的选项
  CollectionConfig,    // 内联配置形状
  IndexStatus,         // 从 getStatus() 获取
  IndexHealthInfo,     // 从 getIndexHealth() 获取
} from '@tobilu/qmd'

工具导出:

import {
  extractSnippet,              // 从文本中提取相关片段
  addLineNumbers,              // 为文本添加行号
  DEFAULT_MULTI_GET_MAX_BYTES, // multiGet 的默认最大文件大小(10KB)
  Maintenance,                 // 数据库维护操作
} from '@tobilu/qmd'

生命周期

// 关闭存储 — 销毁 LLM 模型和数据库连接
await store.close()

SDK 要求显式 dbPath — 不假设默认值。这使得它可以在任何应用程序中嵌入而不会产生副作用。

架构

┌─────────────────────────────────────────────────────────────────────────────┐
│                         QMD 混合搜索管道                          │
└─────────────────────────────────────────────────────────────────────────────┘

                              ┌─────────────────┐
                              │   用户查询    │
                              └────────┬────────┘
                                       │
                        ┌──────────────┴──────────────┐
                        ▼                             ▼
               ┌────────────────┐            ┌────────────────┐
               │ 查询扩展 (fine-tuned)  │            │  原始查询    │
               │  (×2 weight)  │            │   (×2 weight)  │
               └───────┬────────┘            └───────┬────────┘
                       │                             │
                       │ 2 alternative queries       │
                       └──────────────┬──────────────┘
                                      │
              ┌───────────────────────┼───────────────────────┐
              ▼                       ▼                       ▼
     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
     │ 原始查询  │     │ 扩展查询 1│     │ 扩展查询 2│
     └────────┬────────┘     └────────┬────────┘     └────────┬────────┘
              │                       │                       │
      ┌───────┴───────┐       ┌───────┴───────┐       ┌───────┴───────┐
      ▼               ▼       ▼               ▼       ▼               ▼
  ┌───────┐       ┌───────┐ ┌───────┐     ┌───────┐ ┌───────┐     ┌───────┐
  │ BM25  │       │Vector │ │ BM25  │     │Vector │ │ BM25  │     │Vector │
  │(FTS5) │       │Search │ │(FTS5) │     │Search │ │(FTS5) │     │Search │
  └───┬───┘       └───┬───┘ └───┬───┘     └───┬───┘ └───┬───┘     └───┬───┘
      │               │         │             │         │             │
      └───────┬───────┘         └──────┬──────┘         └──────┬──────┘
              │                        │                       │
              └────────────────────────┼───────────────────────┘
                                       │
                                       ▼
                          ┌───────────────────────┐
                          │   RRF 融合 + 奖励  │
                          │  原始查询: ×2   │
                          │  Top-rank 奖励: +0.05│
                          │     Top 30 Kept       │
                          └───────────┬───────────┘
                                      │
                                      ▼
                          ┌───────────────────────┐
                          │    LLM 重排序     │
                          │  (qwen3-reranker)     │
                          │  Yes/No + logprobs    │
                          └───────────┬───────────┘
                                      │
                                      ▼
                          ┌───────────────────────┐
                          │  位置感知混合     │
                          │  Top 1-3:  75% RRF    │
                          │  Top 4-10: 60% RRF    │
                          │  Top 11+:  40% RRF    │
                          └───────────────────────┘

分数归一化 & 融合

搜索后端

后端原始分数转换范围
FTS (BM25)SQLite FTS5 BM25Math.abs(score)0 to ~25+
Vector余弦距离1 / (1 + distance)0.0 to 1.0
RerankerLLM 0-10 评分score / 100.0 to 1.0

融合策略

query 命令使用 Reciprocal Rank Fusion (RRF) 和位置感知混合:

  1. 查询扩展: 原始查询 (×2 for weighting) + 1 LLM 变体
  2. 并行检索: 每个查询都在 FTS 和向量索引上搜索
  3. RRF 融合: 使用 score = Σ(1/(k+rank+1)) 结合所有结果列表,其中 k=60
  4. Top-Rank 奖励: 在任何列表中排名第一的文档获得 +0.05,#2-3 获得 +0.02
  5. Top-K 选择: 取前 30 个候选文档进行重排序
  6. 重排序: LLM 为每个文档评分(带 yes/no 和 logprobs 置信度)
  7. 位置感知混合:
    • RRF 排名 1-3: 75% 检索, 25% reranker (保留精确匹配)
    • RRF 排名 4-10: 60% 检索, 40% reranker
    • RRF 排名 11+: 40% 检索, 60% reranker (更信任 reranker)

为什么这种方法: 纯 RRF 在扩展查询不匹配时可能会稀释精确匹配。Top-rank 奖励保留了原始查询中排名第一的文档。位置感知混合防止 reranker 破坏高置信度的检索结果。

分数解释

分数含义
0.8 - 1.0高度相关
0.5 - 0.8中度相关
0.2 - 0.5有一定相关性
0.0 - 0.2低度相关

要求

系统要求

  • Node.js >= 22
  • Bun >= 1.0.0
  • macOS: Homebrew SQLite (用于扩展支持)
    brew install sqlite
    

GGUF 模型 (通过 node-llama-cpp)

QMD 使用三个本地 GGUF 模型(首次使用时自动下载):

模型用途大小
embeddinggemma-300M-Q8_0向量嵌入 (默认)~300MB
qwen3-reranker-0.6b-q8_0重排序~640MB
qmd-query-expansion-1.7B-q4_k_m查询扩展 (fine-tuned)~1.1GB

模型从 HuggingFace 下载并缓存在 ~/.cache/qmd/models/。

自定义嵌入模型

通过 QMD_EMBED_MODEL 环境变量覆盖默认嵌入模型。 这对于多语言语料库(例如中文、日语、韩语)很有用,其中 embeddinggemma-300M 覆盖范围有限。

# 使用 Qwen3-Embedding-0.6B 更好地支持多语言 (CJK)
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"

# 更改模型后,重新嵌入所有集合:
qmd embed -f

支持的模型系列:

  • embeddinggemma (默认) — 优化英语,小占用空间
  • Qwen3-Embedding — 多语言 (119 种语言包括 CJK), MTEB 顶级排名

注意: 切换嵌入模型时,必须使用 qmd embed -f 重新索引,因为模型之间的向量不兼容。提示格式会自动调整每个模型系列。

安装

npm install -g @tobilu/qmd
# or
bun install -g @tobilu/qmd

开发

git clone https://github.com/tobi/qmd
cd qmd
npm install
npm link

使用

集合管理

# 从当前目录创建集合
qmd collection add . --name myproject

# 创建具有显式路径和自定义 glob 掩码的集合
qmd collection add ~/Documents/notes --name notes --mask "**/*.md"

# 列出所有集合
qmd collection list

# 删除集合
qmd collection remove myproject

# 重命名集合
qmd collection rename myproject my-project

# 列出集合中的文件
qmd ls notes
qmd ls notes/subfolder

生成向量嵌入

# 嵌入所有索引文档 (900 tokens/chunk, 15% 重叠)
qmd embed

# 强制重新嵌入所有内容
qmd embed -f

上下文管理

上下文为集合和路径添加描述性元数据,帮助搜索理解您的内容。

# 为集合添加上下文(使用 qmd:// 虚拟路径)
qmd context add qmd://notes "个人笔记和想法"
qmd context add qmd://docs/api "API 文档"

# 在集合目录内添加上下文
cd ~/notes && qmd context add "个人笔记和想法"
cd ~/notes/work && qmd context add "与工作相关的笔记"

# 添加全局上下文(适用于所有集合)
qmd context add / "我项目的知识库"

# 列出所有上下文
qmd context list

# 删除上下文
qmd context rm qmd://notes/old

搜索命令

┌──────────────────────────────────────────────────────────────────┐
│                        搜索模式                              │
├──────────┬───────────────────────────────────────────────────────┤
│ search   │ 仅 BM25 全文搜索                           │
│ vsearch  │ 仅向量语义搜索                          │
│ query    │ 混合: FTS + 向量 + 查询扩展 + 重排序  │
└──────────┴───────────────────────────────────────────────────────┘
# 全文搜索 (快速,基于关键词)
qmd search "API"

# 向量搜索 (语义相似度)
qmd vsearch "如何登录"

# 混合搜索带重排序(最佳质量)
qmd query "用户认证"

选项

# 搜索选项
-n <num>           # 结果数量 (默认: 5, 或 20 for --files/--json)
-c, --collection   # 限制搜索到特定集合
--all              # 返回所有匹配项 (使用 --min-score 过滤)
--min-score <num>  # 最小分数阈值 (默认: 0)
--full             # 显示完整文档内容
--line-numbers     # 为输出添加行号
--explain          # 包含检索分数跟踪 (查询, JSON/CLI 输出)
--index <name>     # 使用命名索引

# 输出格式 (用于搜索和 multi-get)
--files            # 输出: docid,score,filepath,context
--json             # JSON 输出带片段
--csv              # CSV 输出
--md               # Markdown 输出
--xml              # XML 输出

# 获取选项
qmd get <file>[:line]  # 获取文档,可选从行开始
-l <num>               # 返回的最大行数
--from <num>           # 从行号开始

# Multi-get 选项
-l <num>           # 每个文件的最大行数
--max-bytes <num>  # 跳过大于 N 字节的文件 (默认: 10KB)

输出格式

默认输出是彩色 CLI 格式(尊重 NO_COLOR 环境):

docs/guide.md:42 #a1b2c3
Title: 软件匠人精神
Context: 工作文档
Score: 93%

本节涵盖了 **匠人精神** 的内容,即如何用注重细节的态度构建高质量软件。
另见: 工程原则


notes/meeting.md:15 #d4e5f6
Title: Q4 规划
Context: 个人笔记和想法
Score: 67%

关于代码质量和匠人精神在开发过程中的讨论。
  • 路径: 集合相对路径 (例如,docs/guide.md)
  • Docid: 短哈希标识符 (例如,#a1b2c3) - 使用 qmd get #a1b2c3
  • 标题: 从文档中提取 (第一个标题或文件名)
  • 上下文: 如果配置了 qmd context add
  • 分数: 带颜色编码 (绿色 >70%, 黄色 >40%, 其他为暗色)
  • 片段: 带查询词高亮的匹配上下文

示例

# 获取 10 个结果,最小分数 0.3
qmd query -n 10 --min-score 0.3 "API 设计模式"

# 输出为 Markdown 以供 LLM 上下文使用
qmd search --md --full "错误处理"

# JSON 输出用于脚本
qmd query --json "季度报告"

# 查看每个结果的分数如何计算 (RRF + 重排序混合)
qmd query --json --explain "季度报告"

# 使用单独的索引为不同的知识库
qmd --index work search "季度报告"

索引维护

# 显示索引状态和带上下文的集合
qmd status

# 重新索引所有集合
qmd update

# 先 git pull 再重新索引 (适用于远程仓库)
qmd update --pull

# 通过文件路径获取文档 (带模糊匹配建议)
qmd get notes/meeting.md

# 通过 docid 获取文档 (来自搜索结果)
qmd get "#abc123"

# 从第 50 行开始获取文档,最多 100 行
qmd get notes/meeting.md:50 -l 100

# 通过 glob 模式获取多个文档
qmd multi-get "journals/2025-05*.md"

# 通过逗号分隔列表获取多个文档 (支持 docids)
qmd multi-get "doc1.md, doc2.md, #abc123"

# 限制 multi-get 到 20KB 以下的文件
qmd multi-get "docs/*.md" --max-bytes 20480

# 将 multi-get 输出为 JSON 以供代理处理
qmd multi-get "docs/*.md" --json

# 清理缓存和孤立数据
qmd cleanup

数据存储

索引存储在: ~/.cache/qmd/index.sqlite

模式

collections     -- 索引的目录,带名称和 glob 模式
path_contexts   -- 集合的上下文描述 (qmd://...)
documents       -- Markdown 内容带元数据和 docid (6 字符哈希)
documents_fts   -- FTS5 全文索引
content_vectors -- 嵌入块 (hash, seq, pos, 900 tokens each)
vectors_vec     -- sqlite-vec 向量索引 (hash_seq key)
llm_cache       -- 缓存的 LLM 响应 (查询扩展, rerank 分数)

环境变量

变量默认值描述
XDG_CACHE_HOME~/.cache缓存目录位置

工作原理

索引流程

Collection ──► Glob Pattern ──► Markdown Files ──► 解析标题 ──► 哈希内容
    │                                                   │              │
    │                                                   │              ▼
    │                                                   │         生成 docid
    │         (6 字符哈希)                            │              │
    │                                                   │              │
    └───────────────────────────────────────────────────────┘              │
                                                                       ▼
                                                                  FTS5 索引

嵌入流程

文档被分成 ~900 个 token 的块,带有 15% 的重叠,使用智能边界检测:

Document ──► 智能块 (~900 tokens) ──► 格式化每个块 ──► node-llama-cpp ──► 存储向量
                │                           "title | text"        embedBatch()
                │
                └─► 块存储为:
                    - hash: 文档哈希
                    - seq: 块序列 (0, 1, 2...)
                    - pos: 原始中的字符位置

智能分块

而不是在硬 token 边界处切割,QMD 使用一个评分算法来找到自然的 Markdown 分割点。这会保留语义单元(章节、段落、代码块)在一起。

分割点分数:

模式分数描述
# Heading100H1 - 主要章节
## Heading90H2 - 子章节
### Heading80H3
#### Heading70H4
##### Heading60H5
###### Heading50H6
``` `80代码块边界
--- / ***60水平规则
空行20段落边界
- item / 1. item5列表项
行分隔符1最小分割

算法:

  1. 扫描文档中所有带分数的分割点
  2. 当接近 900 个 token 目标时,在截断点之前扫描 200 个 token 窗口
  3. 评分每个分割点: finalScore = baseScore × (1 - (distance/window)² × 0.7)
  4. 在评分最高的分割点处切割

平方距离衰减意味着 200 个 token 后的标题(分数 ~30)仍然比目标处的简单行分割(分数 1)要好,但更近的标题会胜过较远的标题。

代码围栏保护: 分割点在代码块内被忽略—代码会保留在一起。如果一个代码块超过块大小,在可能的情况下会保持完整。

查询流程 (混合)

Query ──► LLM 扩展 ──► [原始查询, 变体 1, 变体 2]
                │
      ┌─────────┴─────────┐
      ▼                   ▼
   For each query:     FTS (BM25)
      │                   │
      ▼                   ▼
   Vector Search      Ranked List
      │
      ▼
   Ranked List
      │
      └─────────┬─────────┘
                ▼
         RRF 融合 (k=60)
         原始查询 ×2 weight
         Top-rank 奖励: +0.05/#1, +0.02/#2-3
                │
                ▼
         Top 30 候选文档
                │
                ▼
         LLM 重排序
         (yes/no + logprob 置信度)
                │
                ▼
         位置感知混合
         排名 1-3:  75% RRF / 25% reranker (保留精确匹配)
         排名 4-10: 60% RRF / 40% reranker
         排名 11+:  40% RRF / 60% reranker (更信任 reranker)

模型配置

模型在 src/llm.ts 中配置为 HuggingFace URI:

const DEFAULT_EMBED_MODEL = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
const DEFAULT_RERANK_MODEL = "hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf";
const DEFAULT_GENERATE_MODEL = "hf:tobil/qmd/query-expansion-1.7B-gguf/qmd-query-expansion-1.7B-q4_k_m.gguf";

EmbeddingGemma 提示格式

// 对于查询
"task: search result | query: {query}"

// 对于文档
"title: {title} | text: {content}"

Qwen3-Reranker

使用 node-llama-cpp 的 createRankingContext() 和 rankAndSort() API 进行跨编码重排序。返回按相关性分数排序的文档 (0.0 - 1.0)。

Qwen3 (查询扩展)

用于通过 LlamaChatSession 生成查询变体。

许可证

MIT

页面上的每个数字都来自记录下来的 stargazer 到达时间,没有评分公式,也没有 AI 判定。 任何结论都可以用 /api/v1/repos/{id}/stats 里的原始增量自己重算。