08-动态知识库更新:新增、删除、版本回滚和权限过滤
前面讲完了评测体系。到这里,NexaRAG 已经有了一条比较完整的普通 RAG 证据链:能入库,能切 chunk,能检索,能 rerank,能构造 evidence,能引用和校验,也能用 Golden Set 做回归。
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()
真正的状态变化在 KnowledgeBase、DocumentRegistry、IndexManager 和 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() 会:
- 读取 local store 里的所有 records。
- 根据 registry 同步
is_active和is_deleted。 - 根据 scope 过滤全量、某个 doc_id 或某个 document_version_id。
- 只取 active records。
- 清空并重建 BM25。
- 返回 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