语义检索运维(retrieval-ops)
> 本 skill 只 Own 老 knowledge-base-starter 没覆盖的运维真相层:引擎装好之后怎么让它真·可用、怎么判断覆盖了、卡死怎么救。搭建步骤指向 knowledge-base-starter,此处不重抄。
§1 引擎事实(本机实测)
- 版本:@tobilu/qmd v2.5.3,纯 CPU 无 GPU,极慢但零积分。
- CLI 启动器:
C:/Users/40280/.workbuddy/binaries/node/workspace/qmd.sh(封装node+ 环境变量)。 - 数据库:
C:/Users/40280/.cache/qmd/index.sqlite(含documents/content/content_vectors/vectors_vec_rowids等表)。 - 模型(3 个 GGUF,hf-mirror 拉):
- embed:
C:/Users/40280/.cache/qmd/models/embeddinggemma-300M-Q8_0.gguf - generate(query expansion):
C:/Users/40280/.cache/qmd/models/qmd-query-expansion-1.7B-q4_k_m.gguf - rerank:
C:/Users/40280/.cache/qmd/models/qwen3-reranker-0.6b-q8_0.gguf - 配置:
C:/Users/40280/.config/qmd/index.yml,collection 指向C:\Users\40280\Documents\Obsidian Vault,pattern**/*.md。
§2 启动器关键环境变量(qmd.sh 已固化)
export QMD_FORCE_CPU="${QMD_FORCE_CPU:-1}" # 强制 CPU
export QMD_EMBED_PARALLELISM="${QMD_EMBED_PARALLELISM:-1}" # ⚠️ 必须=1,>1 死锁
export QMD_RERANK_PARALLELISM="${QMD_RERANK_PARALLELISM:-1}"
补嵌卡死时,把 QMD_EMBED_PARALLELISM 压到 1、必要时降 embed 批大小减内存占用。
§3 唯一可信覆盖判据(★最高频踩坑)
覆盖率唯一可信来源 = qmd 自身输出的 X documents (Y%) need embeddings 或 qmd status 的 Pending 数。
⚠️ 两套 hash 不可比(已证伪误报):
- documents 表的 hash 是 000d… 命名空间
- content_vectors 表的 hash 是 000a… 命名空间
- 二者是两套不同编码,跨表行数硬比数值巧合接近 → 曾长期误报"100% 覆盖",真实仅 26.56%(1482/5580 块)。
- 旧 embed_loop.sh 用"向量表 hash ≥ 文档表 hash"、watchdog 用"向量行数 ≥ 文档行数"判完成 → 全错 → 假收摊。
✅ 正确判据:
cd C:/Users/40280/.workbuddy/binaries/node/workspace && bash qmd.sh status
# 看 Total files / Vectors stored / Pending (need embeddings)
Pending > 0 表示该补的还没补完。绝不数两表 hash 算覆盖。
§4 补嵌卡死排查(★第二大坑)
现象:进程活着、CPU 烧满 4 核,但覆盖不涨、向量不落盘。
根因 = 内存压力:
- 本机 16G 物理内存,已用 13.9G、仅剩 ~12.8%(2G)空闲时,嵌入模型会话反复 LLM session expired before embedding chunk,块全失败。
- 验证"假活"三信号(任一即卡死):
1. qmd status 的 Pending 长时间不降
2. content_vectors 的 embedded_at 最新时间戳停在启动那一刻
3. 最近 15 分钟新写入向量 ≈ 0(而 CPU 在烧)
救法:
1. 杀掉卡死进程树(见 automation-health skill 的自愈流程)
2. 腾内存:关浏览器/Obsidian/吃内存程序,让空闲回到 30%+
3. 降线程重跑:QMD_EMBED_PARALLELISM=1 bash qmd.sh embed
4. 内存实在不够 → 单线程硬试,成功率低,优先腾内存
§5 正常空转 vs 真卡死
- 正常:embed 启动后前几分钟只加载模型、不写库,像空转 → 勿误杀,等它热身。
- 真卡死:上述 §4 三信号同时成立 → 才杀。
§6 其他坑
- better-sqlite3 原生二进制:从 npmmirror 补
build/Release/,npm install会冲掉它 → 补完别再npm install。 - npm 缓存坏:用
--ignore-scripts --prefer-online重装。 - 向量不全时勿跑混合检索:
bash qmd.sh vsearch在覆盖 <100% 时结果不全,取料前先确认覆盖达标。
§7 诚实计量红线
- 任何"覆盖率"结论只能来自 qmd 自身输出(status / need embeddings),不得数库表行数自算。
- 报告覆盖时必须同时报:内容块总数、已对齐向量块、Pending、孤儿向量(000a 历史垃圾不计入覆盖)。
- 不虚高、不误报"已全量"。
§8 命令语义真相(★高频误用坑,2026-09-15 补)
qmd search ≠ 语义检索——它是 BM25 关键词全文匹配。用长句当语义查会返回空,极易误判"引擎坏了/没索引"。
| 子命令 | 语义 | 用途 |
|---|---|---|
qmd search "短语" |
纯关键词匹配 | 查"原文有没有某词",非语义 |
qmd query "短语" -n N |
混合检索(BM25+向量+rerank+query expansion) | 日常语义检索 |
qmd vsearch "短语" -n N |
纯向量语义检索 | 验证"某文件进没进语义索引"(不靠关键词) |
判可用性的唯一口径:bash qmd.sh status 的 Pending(need embeddings) 数。
§9 自动补齐机制(防"建得多反而搜不到",2026-09-15 补)
根因:大规模同步/建卡后,新建文件全落 Pending,纯语义层捞不到 → "建得越多 AI 越瞎"。
已上线组件:
- 触发器 C:/Users/40280/WorkBuddy/Claw/_qmd_trigger_embed.py:取 Pending,>50 才后台 qmd embed,原子锁 _qmd_embed.lock(O_CREAT|O_EXCL)防重入(30min 内视为在跑)。
- ⚠️ 勿用 pgrep 判 embed 在跑:子进程环境里 pgrep 匹配不到,曾误起第二个 embed 致 sqlite 争用。只用锁文件。
- 看门狗 C:/Users/40280/WorkBuddy/Claw/_qmd_embed_watchdog.py:触发补齐 + 打印 Pending/覆盖率 + 纯向量 vsearch 验证「头雁散稿」「15张作者思维卡」可召回 + PASS/FAIL 自证。
- 挂载点:Get笔记同步 _sync_getnote_to_vault.py 尾部、浮墨同步 floto_auto_sync.py 尾部(同步完自动补)。
- 每日自动化 automation-1789420455349(23:30)跑看门狗兜底。
假成功识别:embed 进程 exit 0 但中途 LLM session expired 只啃一半(Pending 不降)→ 判据必须是"Pending 真降",不是"进程结束"(详见 §4 卡死排查)。
踩坑沉淀:① search≠语义;② pgrep 不可靠改用锁;③ 新建资产不补就搜不到;④ 覆盖<100% 时勿用混合检索取关键料(结果不全)。