Skip to content

[Bug] Local/Ollama 模式下 scheduleEmbed 是静默 no-op:向量更新后不会重建(embedder.schedule 未实现) #6

Closed

Description

@heptaspirit

#3 的跟进问题

环境

  • dsh-mneme 0.3.3(0.2.x ~ 0.3.x 均存在)
  • 配置:embedProvider: local(ONNX / transformers.js,离线本地语义)
  • 平台:Windows 11,dsh web profile

现象

Local 模式下,记忆内容被更新(工具写入 / 人工编辑合并 / 冲突三路合并)后,
该记忆的 embedding 向量被清空,但不会重新生成。后果:

  • 语义检索对该条记忆静默失效,只能靠 keyword 兜底;
  • 旧向量未保留,也无法恢复,只能手动 reindex(/api/dsh-mneme/vector-reindex)。

根因(代码级)

lib/service.js 的 scheduleEmbed() 依赖 embedder.schedule(memory):

function scheduleEmbed(memory) {
  if (txDepth > 0) return; // deferred to the transaction's commit
  if (embedder && memory?.id) {
    try { embedder.schedule(memory); } catch { /* ignore */ }
  }
}

但 LocalEmbedder 与 OllamaEmbedder 都没有实现 schedule 方法:

Embedder 文件 有 embed/embedSingle 有 schedule
LocalEmbedder lib/local-embedder.js ✅ ❌
OllamaEmbedder lib/local-embedder.js ✅ ❌
OpenAIEmbedder lib/embedding.js ✅ ✅(第 68 行)

因此 Local/Ollama 模式下,每次 update 后的实际执行链是:

  1. store.update 将 embedding 置空(无新向量传入时,existing.embedding 因 toRow 剥离恒为 undefined → null);
  2. scheduleEmbed 调用 embedder.schedule(...) → undefined is not a function → TypeError;
  3. 被 catch {} 静默吞掉 → 重建从未发生。

即:"清空 + 重建"的设计闭环,在 Local/Ollama 模式下只有"清空"生效。

补充:v0.3.3 为 mergeHumanEdits 等路径补了 scheduleEmbed 调用
(issue #3),但受限于上述根因,对 Local/Ollama 用户仍无效。

复现步骤

  1. 配置 embedProvider: local,确认记忆库有向量(/api/dsh-mneme/semantic 的 index.embeddedCount > 0);

  2. 通过工具更新某条记忆的 content(触发 service.update → store.update + scheduleEmbed);

  3. 等待数秒后查库:

    SELECT COUNT(*) FROM memories WHERE embedding IS NOT NULL AND embedding != '';

    该条记忆的向量已为空;

  4. 对该条记忆做语义搜索(mode=vector),无向量命中,仅 keyword 命中。

影响

  • Local/Ollama 模式下,任何记忆更新都会静默丢失该条向量,语义检索逐步退化;
  • 与 OpenAI 模式行为不一致(OpenAI 有 schedule,能异步重建);
  • 用户感知为"语义搜索越来越不准 / 结果变少",且无任何日志提示。

修复建议(二选一)

方案 A:给 LocalEmbedder / OllamaEmbedder 实现 schedule(memory)

与 OpenAI 版语义一致:收到 memory 后异步调用 embedSingle 写入新向量。

方案 B:让 scheduleEmbed 对无 schedule 方法的 embedder 走通用重嵌

function scheduleEmbed(memory) {
  if (txDepth > 0) return;
  if (embedder && memory?.id) {
    try {
      if (typeof embedder.schedule === "function") {
        embedder.schedule(memory);
      } else if (typeof embedder.embedSingle === "function") {
        // fire-and-forget 通用重嵌(Local/Ollama 兜底)
        const text = [memory.title, memory.content].filter(Boolean).join("\n");
        embedder.embedSingle(text)
          .then((v) => { if (v?.length) store.setEmbedding(memory.id, v); })
          .catch(() => { /* 重嵌失败不阻断主流程 */ });
      }
    } catch { /* ignore */ }
  }
}

Activity

  1. added
    bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)
    on Aug 16, 2026
  2. Anans-Amarre commented on Aug 16, 2026

    @Anans-Amarre
    Collaborator

    感谢深挖!@heptaspirit 你定位得非常准——scheduleEmbed 假设所有 embedder 都有 schedule,但 Local/Ollama/OpenAI 新路由都没有,导致向量清空后不重建(静默 no-op)。这个比 issue #3 更隐蔽,赞。

    已在 v0.3.5 修复:

    • scheduleEmbed 加能力检测:有 schedule 走原路径;否则用 embedSingle 拼 title+content 生成向量 → store.setEmbedding 重建
    • 覆盖 Local / Ollama / OpenAI 三种 provider,一处改动
    • 告别静默吞错:外层 try/catch + logger.warn

    发布:npm @modusensus/[email protected],429 测试全绿。欢迎复验!

  3. heptaspirit commented on Aug 16, 2026

    @heptaspirit
    CollaboratorAuthor

    @Anans-Ivresse

    跟进:

    启动时回灌重建仍会失败(Local 模式启动竞态)

    已在 v0.3.5 验证,运行中的 update+重建已正常 ✅,但启动时的 mergeHumanEdits 回灌路径仍有问题:

    现象:

    人工编辑 md 镜像 → 重启 dsh → content 正常合并回库,但该条向量被清空后重建失败(embeddedCount 减 1,该条无向量)。

    根因:

    时序问题
    mergeHumanEdits 在插件 apply 的同步段执行(index.js 读取 md → 合并),而 embedder.init() 是异步的(init().catch(...),模型加载约 0.3~0.9s)。回灌触发 scheduleEmbed → embedSingle 时,extractor 尚未就绪 → 抛错被 logger.warn 记录 → 重建未发生。

    建议:

    让启动回灌的重建等待 embedder 就绪——

    1. mergeHumanEdits 的 scheduleEmbed 改为在 embedder.init() 完成后执行(例如 init 的 .then() 里统一补一次回灌/重建);或
    2. embedSingle 在 extractor 未就绪时返回一个"pending"(等待 init 完成后重试),而非直接抛错。

    复现:

    改一条 md → 重启 → 查 SELECT COUNT(*) FROM memories WHERE embedding IS NOT NULL 比重启前少 1。

  4. Anans-Amarre commented on Aug 16, 2026

    @Anans-Amarre
    Collaborator

    感谢你非常精确的复现和时序分析,root cause 定位得很准——启动阶段 mergeHumanEdits 在同步段触发 embedSingle,而 embedder.init() 尚未完成,错误被吞导致静默失败,这个判断完全正确。

    对应你提出的两个方向,我们已在 v0.3.7 中一并修复:

    1. index.js:humanEdits 回灌移入 embedder.init().then() 内执行,extractor 就绪后再重建向量;若 init 失败则降级为 keyword 模式(setEmbedder(null)),纯内容回灌不中断。
    2. service.js:scheduleEmbed 增加就绪门控——embedder 带 ready 标志时,未就绪的请求写入 pending 队列(上限 100),init 完成后 flush(~3s 轮询封顶,fail-safe)。这同时也覆盖了运行中热写入的同类小窗口竞态。
    3. local-embedder.js:init 加上幂等保护及 ready 标志,dispose 时正确复位。

    请升级到 npm @modusensus/[email protected] 后复验一下启动场景。如果仍有异常,欢迎继续反馈。感谢你对社区的贡献!

  5. heptaspirit commented on Aug 16, 2026

    @heptaspirit
    CollaboratorAuthor

    @Anans-Ivresse 0.3.7 已确认修复所有相关问题

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions