返回更新记录
v0.9.0

检索证据血缘与可回放检索 (v0.9.0)

每次检索都有稳定身份、可校验引用与完整血缘;管理员可在保留期内按原查询回放,排查 RAG 召回问题不再靠猜。

检索可观测性证据血缘

检索是 RAG 里最难调试的一环:同一个问题,今天召回三条、明天召回两条,你却很难说清差异来自模型、分块、还是 Generation 快照。v0.9.0 给每次检索建立一个稳定身份,把结果能回到的具体版本、分块和引用都记下来,并提供一个不暴露内部 ID 给公开 API 的管理员回放入口。

每次检索都有一个 search_id

每次成功检索会创建一个 SearchRun,对外暴露一个不透明的 search_id(UUID),并关联到一个或多个 Generation 快照。这个 ID 只指向「这次检索用的是哪一版索引」,不会泄露 Generation ID 给 API 调用方——Generation 是内部概念,公开协议里你看不到它。

单库 REST 仍然返回 []SearchResult 数组,响应体不变;运行元数据通过 X-Search-ID、X-Retrieval-Status、X-Generation-IDs 响应头携带。多库检索和 MCP 在 wrapper 结构里扩展 search_id、retrieval_status 等字段。

结果能回到具体版本和分块

SearchResult 现在带完整血缘:

  • DocumentRevisionID —— 命中的是文档的哪个版本。
  • IndexGenerationID —— 来自哪一次索引构建。
  • Citation —— 含 ContentSHA256,是返回正文的 UTF-8 SHA-256,可以独立复算校验,确保「你看到的证据」和「检索器返回的证据」一致。

Source Anchor 仍然只表达位置(页码 / 行列 / 偏移),不含内容;位置和内容分开,让你既能定位原文,又能验证内容没被篡改。

检索状态可区分

retrieval_status 把结果状态明确分成四档:

  • available —— 正常返回结果。
  • empty —— 0 结果(注意:不是失败)。
  • degraded —— 触发了降级,例如 rerank 不可用后回退到纯混合召回。
  • failed —— 失败,必须带非空 failure_class,告诉你失败发生在哪一类。

这对排查 RAG 问题很关键:0 结果和失败是两回事,rerank fallback 和正常召回也不一样,协议和日志里都能区分开。

管理员可以回放

Workspace owner / admin 可以在 SearchRun 保留期内(默认 168h)用原查询回放一次检索:

  • 回放固定使用原 SearchRun 记录的 Generation、topK 和 rerank 快照,不会 fallback 到当前 active Generation——这样才能复现当时的结果。
  • 查询 hash 不一致会返回 409 search_query_mismatch;涉及的 Generation 已被清理则返回 409 generation_not_available。
  • 回放会重新校验调用方对每个知识库的访问权限,并创建一个新的 SearchRun,通过 replay_of_id 指向原 run,形成可追溯的回放链。
  • 回放只接受浏览器 Session 身份(owner/admin),Bearer API Key 返回 403——API Key 不该有回放能力。

隐私边界:不存查询原文

SearchRun 只保存运行元数据,不保存原始查询、正文、向量或凭证。查询只以 query_hash(SHA-256 of trimmed query,带前缀 sha256:v1:)的形式留存,够用来校验回放时的查询一致性,但无法反推原文。这与琅嬛一贯的日志脱敏策略一致:query 原文不进 span、不进日志、不进持久化。

清理上,SearchRun 的保留期不会超过 retired Generation 的保留期——search_run_generations 通过 ON DELETE RESTRICT 引用 Generation,确保不会出现「Generation 还在引用、SearchRun 却先被删了」的悬空状态。