Skip to content

[Feature] 知识库文档管理支持多选删除 #10564

Open

Description

@kano0222

功能描述

目前知识库的文档管理页面只能逐个删除文档。希望增加多选删除功能,方便一次清理多个不再需要的文档。

建议支持:

  • 勾选多个文档,并支持当前页全选。
  • 点击“删除所选文档”后,显示待删除数量并进行二次确认。
  • 删除完成后刷新列表和文档数量;如果部分删除失败,提示失败结果,并允许重试。

此前 PR #8630 曾包含文档批量删除功能,但该 PR 已关闭且未合并。本 issue 希望单独讨论和实现这项功能,保持改动范围集中。

使用场景

批量更新知识库资料时,需要先删除一批旧文档;或上传了重复、错误的文件,需要集中清理。逐个点击删除并确认较为繁琐,多选删除可以减少重复操作。

检查清单

  • 我已在 Issue 列表中搜索过相关功能,找到曾包含此功能但已关闭的 PR feat(kb): [WIP]overhaul knowledge base workflows and retrieval #8630,未找到专门跟踪该功能的 issue。
  • 我建议的功能与 AstrBot 本体相关,而非在建议某一插件的功能。
  • 我已阅读并同意本项目的贡献者行为准则。
  • (可选)我愿意提交 PR 以帮助实现该功能。

Activity

  1. LIghtJUNction commented on Oct 15, 2026

    @LIghtJUNction
    Contributor

    基于当前默认分支 master@db4ce0c4 和关联 PR #10565 的最新提交 128b5104 做了源码核对。结论是:当前 DocumentsTab.vue 确实只有单文档删除;#10565 已经覆盖了本 issue 的主体需求,包括当前页选择/全选、二次确认、知识库归属校验、500 个文档 ID 的请求上限、分批清理 chunk,以及上传与删除共用写锁。实现方向基本合理,但合并前建议再收紧两个失败路径,否则“允许重试”的用户可观察语义仍不完全成立。

    1. FAISS 写盘失败后,磁盘索引被保护了,但进程内索引没有回滚

    相关位置:

    • astrbot/core/db/vec_db/faiss_impl/vec_db.py:FaissVecDB.delete_documents()
    • astrbot/core/db/vec_db/faiss_impl/embedding_storage.py:EmbeddingStorage.delete()、save_index()、_write_index()

    确认的调用链是:

    1. FaissVecDB.delete_documents() 分批调用 EmbeddingStorage.delete(..., persist=False)。
    2. faiss.Index.remove_ids() 会立刻修改 self.index。
    3. 所有批次完成后才调用 save_index();_write_index() 通过临时文件 + replace 保护已有磁盘文件。
    4. 如果这里发生磁盘满、权限或 I/O 错误,异常会阻止后续 chunk/FTS 和 KBDocument 删除,因此元数据仍在,磁盘索引也仍是旧版本;但是当前进程的 self.index 已经少了这些向量。
    5. API 会返回失败,界面仍能看到文档,但在重试或重启前,检索使用的是已被修改的内存索引,可能暂时检索不到这些文档。

    因此现有“保留索引文件、保留文档记录、可重试”的测试只验证了磁盘和元数据,还缺少运行中检索一致性。建议至少在 save_index() 失败时从原索引文件重新加载 self.index 后再抛出;更完整的做法是对工作索引做 copy-on-write:在副本上删除并成功原子写盘后,再替换活动索引。伪代码可以是:

    original = self.embedding_storage.index
    working = clone_index(original)
    try:
        remove_ids_from(working)
        persist_atomically(working)
    except Exception:
        self.embedding_storage.index = original
        raise
    self.embedding_storage.index = working

    如果考虑大索引复制的内存成本,最小修复也应是捕获写盘异常并立即从未被替换的磁盘文件恢复活动索引;同时需要明确检索是否要与写锁共享读写边界,避免读取到删除过程中的中间状态。

    2. 统计刷新失败会把“已删除”误报成“删除失败”

    相关位置:

    • astrbot/core/knowledge_base/kb_helper.py:KBHelper.delete_documents()
    • astrbot/core/knowledge_base/kb_db_sqlite.py:delete_documents_by_ids()、update_kb_stats()
    • astrbot/dashboard/services/knowledge_base_service.py:KnowledgeBaseService.delete_documents()
    • dashboard/src/views/knowledge-base/components/DocumentsTab.vue:deleteSelectedDocuments()

    这里先完成向量、chunk/FTS、媒体及 KBDocument 删除,之后才调用 update_kb_stats()。如果只有统计刷新失败,删除已经提交,但异常仍经 API 返回通用失败;前端会显示“批量删除失败”,随后 finally 又刷新列表,用户会看到文档消失。#10565 的 test_statistics_failure_is_retryable_after_content_cleanup 已证明后台可通过重试修复计数,但没有覆盖这段相互矛盾的 API/界面反馈。

    建议让 service 返回分阶段结果,例如 deleted_count、stats_refreshed 和可选 warning。内容及元数据删除已完成时应报告“删除成功,但统计刷新待修复”,而不是把整批标为失败;只有内容清理尚未完成、元数据仍保留时,才保留失败状态并让当前选择可重试。这样也能准确覆盖 issue 中的“部分失败提示”。

    建议补充的验证

    • 注入 save_index() 失败后,同时断言磁盘文件字节、进程内 index.ntotal、实际检索结果、chunk/FTS、KBDocument 均保持删除前状态;随后重试成功且只保存一次最终索引。
    • 删除已提交但 update_kb_stats() 失败时,路由返回结构化警告而非通用失败;前端刷新后列表不再显示已删除文档,并明确提示统计刷新状态。
    • 内容清理中途失败时,文档记录和选择仍保留;再次提交同一批 ID 能幂等完成。
    • 在删除写盘失败与并发检索同时发生时,检索只能观察到删除前或删除后的完整索引,不应长期停留在“元数据存在但向量已从内存移除”的状态。

    完成以上两处后,用户侧效果会更稳定:成功、可重试失败和“删除完成但统计待修复”三种状态可区分,且磁盘异常不会让当前进程的检索结果与持久化数据发生长期偏离。

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions