docs: 追加 QMD 调试修复全历程 (v1.2)

This commit is contained in:
mini
2026-02-17 17:05:41 +08:00
parent a4248b09b4
commit 388585f30f

View File

@@ -298,4 +298,229 @@ Before doing anything else:
--- ---
*本文档由 Ami + 阿米狗共同研究整理2026-02-17v1.1 更新于同日* *本文档由 Ami + 阿米狗共同研究整理2026-02-17v1.1 更新于同日v1.2 QMD 修复记录追加于同日*
---
## 附录QMD 调试修复全历程v1.2
> 记录于 2026-02-17修复过程约 1 小时
### 背景
按照本文 Step 2 配置完 QMD 并重启 gateway 后,`memory_search` 工具仍然返回:
```json
{
"results": [],
"disabled": true,
"error": "No API key found for provider \"openai\"..."
}
```
QMD 没有接管搜索,系统仍然在走旧的 embedding 路径。
---
### 问题一:`memory_search` 工具被标记为 disabled
**根因**OpenClaw 的工具启用判断与后端配置是两条独立链路:
- `memory.backend = "qmd"` 只控制**实际搜索**走哪个引擎
- 工具本身的启用检查(`createMemorySearchTool`)还是会校验 embedding API key
两者互不感知。如果没有 OpenAI/Gemini/Voyage API key`memory_search` 工具直接在注册阶段就被禁用,根本不会尝试 QMD。
**验证方法**:在 wrapper script 加日志 → 调用 `memory_search` → 日志为空,说明 wrapper 从未被执行。
**修法**:额外配置本地 embedding 模型,让工具判断认为"搜索可用"
```json
{
"agents": {
"defaults": {
"memorySearch": {
"provider": "local",
"local": {
"modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
},
"fallback": "none"
}
}
}
}
```
> 注意:`local` provider 的配置只用于"激活"工具,实际搜索仍由 QMD 接管(`memory.backend = "qmd"` 优先级更高)。
---
### 问题二:`qmd query --json` 崩溃SIGABRT
**根因**OpenClaw 内部调用的是 `qmd query --json`(带 reranking + 向量 + 查询扩展)。但 session 文件是完整对话记录,单个 chunk 长度超过 reranker 的上下文窗口2062 tokens导致
1. JavaScript 层:`LlamaRankingContext` 抛出错误
2. Metal GPU 层:`GGML_ASSERT([rsets->data count] == 0) failed` → SIGABRT 崩溃
错误信息:
```
Error: The input lengths of some of the given documents exceed the context size.
Try to increase the context size to at least 2062 or use another model that supports longer contexts.
```
之后 QMD 进程崩溃OpenClaw 检测到 QMD 失败 → 回退到内置 SQLite 引擎 → 内置引擎因缺少 API key → `disabled: true`
**修法**:创建 wrapper script`qmd query`(含 reranking替换为 `qmd vsearch`(向量搜索,不含 reranking
```bash
# /Users/mini/clawd/scripts/qmd-wrapper.sh
#!/bin/bash
# QMD wrapper: replaces 'qmd query --json' with 'qmd vsearch --json'
# Workaround for reranker crash on long session chunks (>2062 tokens)
QMD_BIN="/Users/mini/.bun/bin/qmd"
ARGS=()
for arg in "$@"; do
if [ "$arg" = "query" ]; then
ARGS+=("vsearch")
else
ARGS+=("$arg")
fi
done
exec "$QMD_BIN" "${ARGS[@]}"
```
然后在 OpenClaw 配置中将 `memory.qmd.command` 指向 wrapper
```json
{
"memory": {
"qmd": {
"command": "/Users/mini/clawd/scripts/qmd-wrapper.sh"
}
}
}
```
> `qmd vsearch` = 向量相似度搜索 + 查询扩展,无 reranking输出相同 JSON 格式OpenClaw 可直接消费。
---
### 问题三GGUF 模型重复下载 + timeout
**根因**
- GGUF 模型已下载到系统默认路径 `~/.cache/qmd/models/`
- OpenClaw 以独立 XDG 环境运行 QMD`~/.openclaw/agents/main/qmd/xdg-cache/`
- 两个路径不共享,每次 OpenClaw 调用 QMD 都会尝试重新下载模型
- 默认 `timeoutMs: 4000`4秒下载没完成就超时
**修法**
1. 软链模型文件到 OpenClaw 的 XDG cache 目录:
```bash
mkdir -p ~/.openclaw/agents/main/qmd/xdg-cache/qmd/models
ln -sf ~/.cache/qmd/models/hf_ggml-org_embeddinggemma-300M-Q8_0.gguf \
~/.openclaw/agents/main/qmd/xdg-cache/qmd/models/
ln -sf ~/.cache/qmd/models/hf_ggml-org_qwen3-reranker-0.6b-q8_0.gguf \
~/.openclaw/agents/main/qmd/xdg-cache/qmd/models/
ln -sf ~/.cache/qmd/models/hf_tobil_qmd-query-expansion-1.7B-q4_k_m.gguf \
~/.openclaw/agents/main/qmd/xdg-cache/qmd/models/
```
2. 把超时时间调大:
```json
{
"memory": {
"qmd": {
"limits": {
"timeoutMs": 30000
}
}
}
}
```
---
### 最终生效配置(完整版)
```json
{
"memory": {
"backend": "qmd",
"citations": "auto",
"qmd": {
"command": "/Users/mini/clawd/scripts/qmd-wrapper.sh",
"includeDefaultMemory": true,
"sessions": {
"enabled": true,
"retentionDays": 30
},
"update": {
"interval": "5m",
"debounceMs": 15000
},
"limits": {
"maxResults": 6,
"maxSnippetChars": 700,
"maxInjectedChars": 20000,
"timeoutMs": 30000
},
"scope": {
"default": "deny",
"rules": [{ "action": "allow", "match": { "chatType": "direct" } }]
}
}
},
"agents": {
"defaults": {
"memorySearch": {
"provider": "local",
"local": {
"modelPath": "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
},
"fallback": "none"
}
}
}
}
```
---
### 验证结果
修复后 `memory_search` 返回:
```json
{
"results": [...],
"provider": "qmd",
"model": "qmd",
"citations": "auto"
}
```
`provider: "qmd"` 确认 QMD 已接管搜索,不再走旧的 embedding 路径。
---
### 已知遗留问题
| 问题 | 说明 |
|------|------|
| vsearch 精确短语召回弱于 BM25 | 用向量搜索替代了全文搜索,对精确词组召回率稍低 |
| `qmd query`reranking崩溃未根治 | 根本原因是 session chunk 过长超过 reranker 上下文,暂无优雅解法 |
| `local` embedding 模型双加载 | `memorySearch.provider = "local"` 让 OpenClaw 也加载了 embedding 模型,与 QMD 冗余,未来版本可能会有更干净的 `enabled: true` 开关 |
---
### 经验教训
1. **`memory.backend = "qmd"` ≠ 工具启用**:两个配置项独立,必须同时配置 `memorySearch.provider` 才能让工具真正可用。
2. **QMD 的两个 XDG 环境完全隔离**CLI 工具用 `~/.cache/qmd/`OpenClaw 用 `~/.openclaw/agents/main/qmd/xdg-cache/`,模型需要手动同步。
3. **`qmd query` 不适合长文档**session 对话记录会产生超大 chunk直接用 reranker 会崩溃,`qmd vsearch` 是更安全的替代。
4. **OpenClaw 的降级机制会掩盖真实错误**QMD 崩溃 → 回退内置 SQLite → 内置失败 → 显示 `disabled: true`,看起来像是"没配置",实际是 QMD 崩了。