docs: 追加 QMD 调试修复全历程 (v1.2)
This commit is contained in:
@@ -298,4 +298,229 @@ Before doing anything else:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*本文档由 Ami + 阿米狗共同研究整理,2026-02-17,v1.1 更新于同日*
|
*本文档由 Ami + 阿米狗共同研究整理,2026-02-17,v1.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 崩了。
|
||||||
|
|||||||
Reference in New Issue
Block a user