Files
NotesAgentic/docs/retrospectives/阶段F-Embedding与知识库问题与解决方案.md
T

8.3 KiB
Raw Blame History

阶段 F:Embedding 与知识库问题与解决方案

记录日期:2026-09-04。涉及分支:feat/multimodal-pipeline,前序功能提交:6eb97bf。 本文按既有复盘格式记录原因、后果、解决思路、实际方案和验证结果。修复随当前分支提交;合并状态以 Git 与 PR 记录为准。

1. 背景

阶段 F 将占位向量替换为真实本地 Embedding,并加入 API 路由、CPU/CUDA 模型子进程、转写笔记和聊天知识库上下文。联调问题跨越运行环境、索引、持久化与本地处理约束,不能仅根据“模型已下载”判断整条链路正常。

前端 / 后续 Tauri WebView → 后端 API → 模型路由
→ 带模型空间标识的向量索引 → 知识检索 / 聊天来源

2. 问题总览

编号 问题 后果 实际方案
F-01 配置、推理与索引错误共用提示 已有模型却被提示未配置 区分推理错误与索引错误
F-02 Windows 热重载事件循环不支持异步子进程 命令行成功,HTTP 失败 线程管道子进程兼容路径
F-03 重建忽略向量失败,异常游标未关闭 虚报成功或阻塞重建 严格校验、回滚和显式关闭
F-04 搜索记录仅保存在内存或浏览器 刷新丢失,桌面无法统一管理 SQLite 历史与 API
F-05 聊天忽略 use_rag 没有知识库内容 真实 Block 上下文与来源事件
F-06 本地转写导出未传递限制 正文可能发送给远程 Embedding 持久化本地索引标记
F-07 推理结束才读取当前模型标识 向量与空间错配 冻结模型、revision 和设备配置

3. F-01 / F-03:模型可用不等于索引可用

原因与后果

embed_remote 捕获异常后返回 None,上层将调用失败与索引缺失统一显示为“请配置 Embedding”。默认库曾有 29 个 Block 和模型元信息,但没有向量侧表。普通保存允许降级保留正文,这一策略又被用于严格重建,导致没有向量也可能报告完成。异常回溯保留查询游标时,还会影响后续写事务。

解决思路与实际方案

纯向量查询使用严格错误处理:模型失败保留安全错误码;模型可用但索引缺失时返回 SEMANTIC_INDEX_UNAVAILABLE,明确提示重建。混合检索仍可退到全文检索。重建先准备向量,再进入事务,检查空间一致和完整覆盖,失败保留旧索引。查询游标在 finally 中关闭。

前序真实本地验证:重建后 29 个 Block 对应 29 条向量,查询返回 20 条结果。这是当时样本库的历史记录,不代表全部环境与规模。

4. F-02Windows 热重载下本地模型无法启动

原因与后果

Windows 下 uvicorn --reload 使用的事件循环可能不支持 asyncio.create_subprocess_exec,抛出 NotImplementedError。模型与依赖均已安装,普通 asyncio.run 冒烟成功,但实际 HTTP 请求失败。第一轮只修提示和索引,未覆盖此启动方式。

实际方案与验证

优先保留异步子进程,仅在不支持时使用 ThreadedProcess。同步创建进程以避免取消时失去进程归属;管道读写与等待在线程中执行,保留输出上限、隐藏窗口、超时、取消和回收逻辑。

修复后通过前端实际连接的 /api/search 验证成功;测试覆盖真实小子进程的结果读取与取消回收。重新安装权重不能解决此类事件循环兼容问题。

5. F-04 / F-05:搜索记录与聊天知识库

原因与后果

历史最初包含硬编码示例并只在内存更新;第一轮改成 localStorage 虽解决刷新丢失,却不符合后续 Tauri 统一管理应用数据的要求。聊天只有 use_rag 字段,没有执行检索。

实际方案

SQLite v5 增加 search_history,保留最近 10 条去重查询,重复项置顶。记录属于配置的 APP_DB_PATH,Tauri 可复用后端;这不等于已实现 Rust 原生存储。此前浏览器记录没有自动迁入 SQLite。

方法 路径 行为
POST /api/search 记录提交的非空查询,再执行检索
GET /api/search/history 返回 {"queries": [...]},最近项在前
DELETE /api/search/history 清空历史,返回空数组

前端通过 API 加载与清空,失败显示错误。聊天开启知识库时最多取 6 个来源,每段正文最多 3000 字符、合计最多 12000 字符;资料明确标为非指令,SSE 返回 Citation 供定位。关闭时不附加笔记,无命中时不生成来源。请求与事件测试不等于外部模型回答质量验收。

6. F-06:仅本地转写的笔记索引

原因与后果

create_transcript_note 调用通用 create_note,后者默认执行 API Embedding。转写本身遵守 local_only,导出索引却可能上传正文。只增加临时调用标记也无法覆盖后续重建。

实际方案

仅本地任务新生成的 Markdown 写入 frontmatter

---
embedding_local_only: true
---

解析器读取标记,索引向路由传入 local_only=True,跳过远程绑定与凭据解析。普通笔记保持 API 优先和本地回退。本地向量失败时,普通保存仍可保留正文与全文索引;严格重建报错并保留旧索引。

标记随 Vault 持久化,编辑保留标记和重建时继续生效。此标记约束 Embedding,不是笔记的通用联网权限;显式开启远程聊天知识库仍可能提供相关片段。旧导出笔记不会自动补标记,必要时应人工补入;删除标记恢复普通索引路由。

7. F-07:异步推理中的模型空间一致性

原因与后果

请求开始使用 Bekko,推理期间切到 Granite,结束时重新读取 model_id 就可能把旧向量标为新模型。两者都是 384 维,维度校验无法发现错误。

实际方案

进入本地路径时调用 LocalEmbedding.snapshot() 复制模型和设备配置。通过请求级 ContextVar 将相同配置传给 Runtime,结束或异常时恢复上下文;返回模型标识取自同一快照,下一请求使用新设置。

快照仅在实际进入本地路径时创建,避免正常 API 请求额外依赖本地配置。API 的 URL、模型、维度和请求扩展冻结规则保持不变。

8. 回退行为与验证

场景 预期
普通请求,API 有效 使用 API,不执行本地推理
普通请求,无 API 使用本地模型
普通请求,API 失败或响应无效 回退本地,保留 fallback_reason
本地限定索引,存在 API 不请求 API、不解析远程凭据
本地限定后再发普通请求 API 仍可调用,不泄漏临时限制
推理期间修改设置 当前向量与身份一致,下一请求采用新设置
空间混用或索引覆盖不完整 严格重建拒绝提交,不混合不同向量空间

本地限定笔记与远程索引并存时,现有完整覆盖检查可能使纯向量查询提示不完整、重建拒绝混合空间;混合检索仍可退到全文检索。不能为获得完整远程索引而绕过本地标记。

提交审阅时已用隔离数据库复现:一篇普通 API 笔记与一篇本地限定笔记共存,配置未变化,全量重建仍返回 EMBEDDING_SPACE_CHANGED。此项列为合并阻碍,尚未修复;后续需区分预期的处理策略差异和实际配置漂移,并明确各向量空间的索引覆盖范围。

本轮在 backend/ 执行:

.venv/Scripts/python.exe -m pytest tests/test_model_routing.py tests/test_media_jobs.py tests/test_routed_retrieval.py tests/test_local_models.py -q -p no:cacheprovider

结果:132 项通过,覆盖 API 回退、本地限定导出和重建、模型切换及 Windows 子进程路径,不调用真实外部 API;有既有 Starlette/httpx 弃用提示。

前序持久化修复记录:后端搜索历史、聊天与媒体相关 9 项,前端搜索与聊天 5 项及类型检查通过。各轮结果是针对性验证,不相加当作全仓测试数。

9. 工程经验

区分配置、权重安装、推理运行、索引覆盖四种状态;按用户实际启动方式验证;跨异步边界冻结身份;持久化处理限制;增加限制时也验证普通 API 回退没有被破坏。