任务 ID: task-memsearch-api-94f118  |  文件: session.md  |  最后修改: 2026-02-27 22:15:07

Session Log — task-memsearch-api-94f118

OpenClaw memorySearch 外部接口调研

执行时间:2026-02-27

执行者:Ai.Res


信息来源

  1. 本地文档:/usr/lib/node_modules/openclaw/docs/concepts/memory.md(主要来源,内容完整)
  2. 本地文档:/usr/lib/node_modules/openclaw/docs/gateway/configuration-examples.md
  3. 源码反编译:/usr/lib/node_modules/openclaw/dist/manager-CsIZJ7mU.js(验证 HTTP 格式)

调研结论报告

Q1:agents.defaults.memorySearch 完整 Schema

核心字段

agents: {
  defaults: {
    memorySearch: {
      // 必填:选择 embedding provider
      provider: "openai" | "gemini" | "voyage" | "local" | "auto",

      // 指定 embedding 模型名
      // openai 默认: "text-embedding-3-small"
      // gemini 默认: "gemini-embedding-001"
      // voyage 默认: "voyage-4-large"
      model: "text-embedding-3-small",

      // 远程 provider 配置(openai/gemini/voyage/自定义兼容端点均用此字段)
      remote: {
        baseUrl: "http://127.0.0.1:8765/v1",  // 自定义端点
        apiKey: "YOUR_KEY_OR_EMPTY_STRING",     // 无认证时传空字符串或省略
        headers: {                               // 可选额外 header
          "X-Custom-Header": "value"
        },
        // Batch 索引配置(大语料库用)
        batch: {
          enabled: false,       // 默认关闭
          concurrency: 2,       // 并行 batch 数
          wait: true,           // 等待 batch 完成
          pollIntervalMs: 5000,
          timeoutMinutes: 60
        }
      },

      // 本地 GGUF 模型配置(provider="local" 时使用)
      local: {
        modelPath: "/path/to/model.gguf",   // 或 "hf:org/repo/file.gguf"
        modelCacheDir: "/path/to/cache"
      },

      // Fallback provider(主 provider 失败时切换)
      fallback: "openai" | "gemini" | "local" | "none",

      // 额外索引路径(workspace 外的 .md 文件)
      extraPaths: ["../team-docs", "/srv/shared-notes/overview.md"],

      // 索引存储位置
      store: {
        path: "~/.openclaw/memory/{agentId}.sqlite",
        vector: {
          enabled: true,
          extensionPath: "/path/to/sqlite-vec"   // 可选,覆盖内置路径
        }
      },

      // 混合搜索配置(BM25 + Vector)
      query: {
        hybrid: {
          enabled: true,
          vectorWeight: 0.7,
          textWeight: 0.3,
          candidateMultiplier: 4,
          mmr: {
            enabled: false,    // MMR 去重
            lambda: 0.7
          },
          temporalDecay: {
            enabled: false,    // 时间衰减
            halfLifeDays: 30
          }
        }
      },

      // Session transcript 索引(实验性)
      experimental: { sessionMemory: true },
      sources: ["memory", "sessions"],

      // 同步配置
      sync: {
        watch: true,   // 文件变更自动重索引
        sessions: {
          deltaBytes: 100000,
          deltaMessages: 50
        }
      },

      // 嵌入缓存
      cache: {
        enabled: true,
        maxEntries: 50000
      }
    }
  }
}

Q2:memory_search 工具工作机制

内置搜索类型

向量搜索(semantic),不是纯文本搜索。
- 默认使用 Hybrid Search(BM25 + Vector 加权合并),在 FTS5 可用时自动开启
- 纯 BM25 full-text 作为 vector 不可用时的兜底,但不是主模式

索引内容

配置外部 provider 后的行为

完全替换内置搜索的 embedding 生成部分(不并存):
- 文件分块 → 调用外部 embedding 服务 → 存入本地 SQLite → BM25+Vector 混合检索
- 外部服务只负责生成向量,检索逻辑仍在 OpenClaw 本地执行

Fallback 机制

有,通过 memorySearch.fallback 配置:
- 主 provider 失败 → 自动切换到 fallback provider(如 "local""openai"
- 若 fallback = "none" 则失败后直接禁用向量搜索,降级为 BM25 only
- QMD backend 失败 → 自动回退到内置 SQLite manager


Q3:HTTP 请求/响应格式(最关键)

OpenClaw 的 provider: "openai" 使用标准 OpenAI Embeddings API 格式(源码确认):

请求(POST)

POST {baseUrl}/embeddings
Content-Type: application/json
Authorization: Bearer {apiKey}
{...custom headers}

{
  "model": "your-model-name",
  "input": ["text chunk 1", "text chunk 2", ...]  // 批量时是数组;单个查询是 ["query text"]
}

关键input 字段是字符串数组,不是单个字符串。

响应(必须满足此格式)

{
  "data": [
    { "embedding": [0.123, -0.456, ...] },  // float 数组,第一个元素对应 input[0]
    { "embedding": [0.789, 0.012, ...] }   // 第二个元素对应 input[1]
  ]
}

源码验证manager-CsIZJ7mU.js):

return ((await res.json()).data ?? []).map((entry) => entry.embedding ?? []);

URL 构造方式(源码):

const url = `${client.baseUrl.replace(/\/$/, "")}/embeddings`;

baseUrl = "http://127.0.0.1:8765/v1" → 请求 http://127.0.0.1:8765/v1/embeddings


Q4:集成方案设计

✅ 推荐方案:直接对接(完整支持)

OpenClaw 原生支持自定义 OpenAI-compat embedding 端点,无需任何 hack。

配置

agents: {
  defaults: {
    memorySearch: {
      provider: "openai",
      model: "Qwen3-Embedding-0.6B",   // 模型名会原样传给你的 server
      remote: {
        baseUrl: "http://127.0.0.1:8765/v1",
        apiKey: ""                       // 无认证,传空字符串
      },
      fallback: "none"                  // 本地服务挂了就禁用,不 fallback 到云端
    }
  }
}

你的 embed server 需要实现的接口

POST /v1/embeddings
Content-Type: application/json

{
  "model": "Qwen3-Embedding-0.6B",
  "input": ["text1", "text2", ...]
}

→ 响应:
{
  "data": [
    {"embedding": [0.1, 0.2, ...]},
    {"embedding": [0.3, 0.4, ...]}
  ]
}

注意:你草案的 POST /search {text, top_k} 格式不匹配 OpenClaw 期望的格式——需要改为 /v1/embeddings 端点,输出向量而非搜索结果。检索逻辑由 OpenClaw 自己做。

多 Agent 共享

所有 agent(main/coder/reviewer/researcher)的 agents.defaults.memorySearch 配置相同,共用同一个 http://127.0.0.1:8765/v1 服务即可。每个 agent 的向量索引仍然是独立的 SQLite 文件(~/.openclaw/memory/{agentId}.sqlite)。

无 apiKey 时的处理

remote.apiKey = "" 即可,源码中 Authorization: Bearer 会被发送但 FastAPI 端忽略即可。


补充:QMD backend 替代方案(可选)

如果你想要更高级的混合搜索(BM25 + reranking),可以考虑 memory.backend = "qmd"
- 安装独立 QMD CLI
- 但 QMD 不支持接入外部 embedding server,需要本地 GGUF
- 对于你的 usearch + Qwen3 方案,不推荐,直接用 provider: "openai" compat 更简洁


总结

问题 结论
Q1 schema 见上文完整字段表;核心:provider/model/remote.baseUrl/remote.apiKey
Q2 工作机制 向量搜索(hybrid BM25+vector),外部 provider 完全替换 embedding 生成,检索本地执行
Q3 HTTP 格式 POST {baseUrl}/embeddings,body {model, input: string[]},响应 {data: [{embedding: float[]}]}
Q4 替代方案 不需要替代——OpenClaw 原生支持 OpenAI-compat endpoint,修改 embed server 接口即可

关键改动:embed server 不需要做搜索,只需暴露 /v1/embeddings 端点返回向量,OpenClaw 自己做 usearch。但如果想保留自己的 usearch,则需要另一个路径(见下)。

⚠️ 如果主人希望让 OpenClaw 完全使用外部的 usearch 检索结果(而非只用外部 embedding),目前没有直接支持——OpenClaw 只能外包 embedding 生成,检索逻辑自己做。这种情况需要考虑 MCP tool override 方案,或通过 mcporter 暴露自定义 memory_search 工具。