首页 项目 博客 简历 联系 English
返回列表
2026年7月5日 约 1,472 字 预计 4 分钟读完

08-动态知识库更新:新增、删除、版本回滚和权限过滤

前面讲完了评测体系。到这里,NexaRAG 已经有了一条比较完整的普通 RAG 证据链:能入库,能切 chunk,能检索,能 rerank,能构造 evidence,能引用和校验,也能用 Golden Set 做回归。

#NexaRAG#动态知识库#权限过滤#版本管理

08-动态知识库更新:新增、删除、版本、回滚和权限过滤

前面讲完了评测体系。到这里,NexaRAG 已经有了一条比较完整的普通 RAG 证据链:能入库,能切 chunk,能检索,能 rerank,能构造 evidence,能引用和校验,也能用 Golden Set 做回归。

真实知识库还会继续变化。

产品参数会更新,售后政策会变化,故障排查文档会修订,错误文档要删除,旧版本可能要回滚,内部文档只对授权用户可见。RAG 项目只支持“新增文档”时,很快就会变成一个难治理的知识堆。

所以这一篇讲动态知识库更新。

这篇的核心判断是:

知识库治理必须进入 RAG 链路。 新增、删除、版本、回滚、权限和索引同步,都会影响最终证据能否被正确召回。

API 覆盖完整生命周期

知识库 API 当前拆在 api/knowledge_router/。上传在 upload.py,查询和管理操作在 search.py,路由注册在 router.py

除了 /knowledge/upload,NexaRAG 还支持:

GET    /knowledge/documents
GET    /knowledge/documents/{doc_id}
GET    /knowledge/documents/{doc_id}/versions
POST   /knowledge/documents/{doc_id}/rollback
DELETE /knowledge/documents/{doc_id}
POST   /knowledge/search
POST   /knowledge/reindex
GET    /knowledge/index/status

这说明知识库已经从“上传文件”的入口,扩展成一个有生命周期的资源。

api/knowledge_router/search.py 里的 handler 都很薄,它们主要判断 knowledge service 是否支持对应能力,然后调用:

  • list_documents()
  • get_document()
  • list_document_versions()
  • rollback_document()
  • delete_document()
  • reindex()
  • index_status()

真正的状态变化在 KnowledgeBaseDocumentRegistryIndexManager 和 local store 里。

DocumentRegistry 记录文档和版本

文档注册表的记录定义在 rag/_registry/records.py

DocumentRecord
DocumentVersionRecord
RegistryEvent

DocumentRecord 表示文档本身:

doc_id
title
source
doc_type
product
access_scope
active_version_id
status

DocumentVersionRecord 表示某个版本:

version_id
doc_id
source
file_format
content_hash
normalized_text_hash
parser_name
parser_version
chunker_version
embedding_version
chunk_count
is_active
is_deleted
created_at

这个设计把“文档”和“文档版本”拆开了。文档可以有多个版本,但只有一个 active version。检索时应该只让 active 且未 deleted 的 chunk 进入候选池。

这也是上一篇评测里 expected_chunk_ids 能稳定工作的前提:chunk 背后有版本,版本背后有文档。

新版本创建版本记录

版本操作在 rag/_registry/version_ops.py。生成版本 ID 时,会把 doc_id 和时间戳组合起来:

prefix = f"{doc_id}::v{stamp}"
candidate = f"{prefix}_{sequence:03d}"

注册新版本时,会创建一条新的 DocumentVersionRecord。如果激活新版本,就把同一文档下其他版本标成非 active。

for version in versions.values():
    if version.doc_id == doc_id:
        version.is_active = version.version_id == version_id

这让回滚成为可能。如果新版本有问题,只要把旧 version 重新激活即可。

删除是生命周期状态

mark_document_deleted() 会把文档状态标成 deleted,并把对应版本设置为 inactive/deleted。

document.status = "deleted"
for version in versions.values():
    if version.doc_id == doc_id:
        version.is_active = False
        if version.version_id == document.active_version_id:
            version.is_deleted = True

这是一种软删除。它比直接物理删除更适合早期 RAG 项目。

原因很简单:RAG 的索引有多个副本。local JSONL 里有 chunk,BM25 里有索引,Chroma 里可能也有向量。软删除先把生命周期状态写进 metadata,再通过 reindex 或 filter 让它退出检索,风险更低。

后面如果要做彻底清理,再单独做物理清理任务也不迟。

local_store 同步 chunk 生命周期

本地 chunk 存储在 rag/_store/local.py 管理。它支持:

  • append_knowledge_chunks()
  • rewrite_knowledge_chunks()
  • load_knowledge_records()
  • set_active_document_version()
  • mark_document_deleted()

当文档回滚时,set_active_document_version(doc_id, version_id) 会遍历 local store 里的 chunk,把目标版本设置为 active,其他版本设为 inactive。

当文档删除时,mark_document_deleted(doc_id) 会给相关 chunk 写入:

{"is_active": False, "is_deleted": True}

这说明生命周期状态会同步存在于 registry 和 chunk metadata 里。检索过滤才能真正看到这些状态。

IndexManager 负责重建运行时索引

动态更新还需要同步运行时索引。代码在 rag/_knowledge/index_manager.py

IndexManager.rebuild() 会:

  1. 读取 local store 里的所有 records。
  2. 根据 registry 同步 is_activeis_deleted
  3. 根据 scope 过滤全量、某个 doc_id 或某个 document_version_id。
  4. 只取 active records。
  5. 清空并重建 BM25。
  6. 返回 index status。

核心判断在 _record_is_active()

if version:
    return bool(version.is_active) and not bool(version.is_deleted)
return is_active and not is_deleted

这让 BM25 只索引符合生命周期状态的 chunk。

index_status() 还会返回 local_store、bm25、chroma 的状态,包括 active chunk 数量、BM25 文档数和 Chroma 开关状态。这对排查知识库更新非常有用。

KnowledgeBase 把这些能力串起来

统一入口在 rag/_knowledge/base.py

上传文档时,KnowledgeBase.add_document() 会走 ingestion chain,成功后把 chunk 增量加入 BM25:

self._add_result_to_bm25(result)

删除文档时:

record = self.document_registry.mark_deleted(doc_id)
updated_chunks = mark_document_deleted(doc_id)
self.reindex()

回滚文档时:

version = self.document_registry.rollback(doc_id, version_id)
updated_chunks = set_active_document_version(doc_id, version.version_id)
self.reindex()

这里的重点是:registry、local_store、BM25 要协同变化。删除和回滚必须同时影响文档状态、chunk metadata 和运行时索引。

这样可以避免用户已经删除文档后,RAG 仍然召回旧证据。

前端知识库面板是治理入口

前端知识库管理页面在 frontend/src/features/knowledge/

这里覆盖文档列表、文档详情、版本、重建索引、搜索调试等组件。它对应的是知识库治理,侧重治理场景。

从 RAG 工程角度看,前端知识库面板应该能展示这些状态:

  • 当前文档列表。
  • 已删除文档。
  • 每个文档的版本数量。
  • 当前 active version。
  • 索引同步状态。
  • 删除或回滚后的检索结果变化。

这些能力会直接影响线上 RAG 的可信度。

这一步解决的内容和下一步

动态知识库更新解决的是“知识生命周期”问题。

它让 NexaRAG 从新增文档扩展到管理文档列表、版本、删除、回滚和重建索引。它把 lifecycle metadata 写进 chunk,并让 BM25 重建时只使用 active records。

生产级知识库后续还可以继续做:

  • 更强的权限体系。
  • Chroma 中旧向量的物理清理。
  • 增量索引任务队列。
  • 多格式文档解析。
  • 审核流和发布流。

对于一个施工记录系列来说,到这里已经形成了最小动态知识库闭环

小结

RAG 知识库会持续更新。NexaRAG 通过 DocumentRegistry 记录文档和版本,通过 local store 同步 chunk 生命周期,通过 IndexManager 重建运行时索引,通过 KnowledgeBase 把删除、回滚、reindex 串起来,再通过前端知识库页面暴露治理入口。

这一步让 RAG 证据链具备了生产侧最基本的生命周期能力。

下一篇进入高级 RAG 原型:在普通证据链已经稳定、可观察、可评测之后,再看 CRAG、Self-RAG、Mini GraphRAG 和 Agentic RAG 应该怎样接入。

参考材料:

  • NexaRAG api/knowledge_router/search.py
  • NexaRAG rag/_registry/records.py
  • NexaRAG rag/_registry/store.py
  • NexaRAG rag/_registry/version_ops.py
  • NexaRAG rag/_store/local.py
  • NexaRAG rag/_knowledge/index_manager.py
  • NexaRAG rag/_knowledge/base.py
  • NexaRAG frontend/src/features/knowledge/KnowledgePage.tsx
  • 原 RAG 系列 10-知识库动态更新-增量索引版本切换权限和回滚.md