diff --git a/OpenClaw对话和记忆优化初步方案.md b/OpenClaw对话和记忆优化初步方案.md index 399b1c5..4374368 100644 --- a/OpenClaw对话和记忆优化初步方案.md +++ b/OpenClaw对话和记忆优化初步方案.md @@ -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 崩了。