ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

BISHENG 知识空间 AI 问答检索权限过滤(F029):双层 view_file 过滤架构与实现解析

BISHENG 知识空间 AI 问答检索权限过滤(F029):双层 view_file 过滤架构与实现解析 BISHENG 知识空间 AI 问答检索权限过滤F029双层 view_file 过滤架构与实现解析【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng知识空间 AI 问答是 BISHENG 中用户最常接触的 RAG 入口但在 v2.6.0 之前问答检索只依赖空间级权限存在列表里看不到的文件却能通过 AI 问答检索到的越权读取风险。本文基于 F029 spec 及其在仓库中的落地实现完整讲解这套索引层粗过滤 结果层精过滤的双层权限过滤方案包括 4 个问答入口的接入方式、KnowledgeFileVisibilityService的策略决策IN / NOT-IN / none / empty、自适应检索扩张循环、角标溯源接口的整条剔除改造以及配套的配置项、验收标准与性能约束。读完本文你将掌握 BISHENG 知识空间列表 UI 可见性 AI 问答可见性这一安全不变量INV-7的工程实现全貌。1. 背景为什么 AI 问答会成为越权读取通道知识空间knowledge_space是 BISHENG 中多人协作的知识库组织单元空间内的文件、文件夹通过 ReBACOpenFGA与 Fine-grained permission_id 双重体系控制可见性。列表 UI 的可见性语义是用户必须对文件持有view_file权限_filter_visible_child_items且该权限沿文件 → 祖先文件夹 → 空间的 lineage 逐层解析。然而在 F029 之前知识空间 AI 问答的检索路径存在两类缺口整空间 / 文件夹问答只校验空间级view_space/view_folder检索时把整个空间的文件全部送入 Milvus / ES 召回未按文件级view_file过滤首页 / 工作台多 KB 检索WorkStationService.queryChunksFromDB依赖前端 KB 选择器提供的kb_id_whitelist且check_authFalse跳过 per-KB 鉴权用户只要绕过下拉框构造请求就能检索自己无权访问的空间与文件历史会话角标溯源CitationResolveService._has_file_access通过RoleAccessDaoAccessType.KNOWLEDGE只做空间级判定且无权时仅隐藏 URL 仍返回文件名等结构化字段历史会话因此成为新的信息泄漏面。F029P0v2.6.0的目标正是把这三种路径全部对齐到列表 UI 可见性语义任何 AI 问答入口都不得让用户无view_file权限的文件出现在模型上下文、回答引用或角标溯源响应中。该不变量被正式登记为 release-contract.md 中的INV-7。2. 范围边界本次纳入与明确排除F029 的边界划分非常清晰spec §1 范围边界理解它能避免误判哪些入口已经安全本次纳入知识空间 AI 助手的 4 个问答入口整空间问答、文件夹问答、文件预览页问答、首页 工作台多 KB 检索新版角标溯源接口POST /api/v1/citations/resolve批量、GET /api/v1/citations/{citation_id}单条。明确排除spec §3 边界情况「不支持」工作流KNOWLEDGE_RETRIEVER节点workflow/nodes/knowledge_retriever/涉及流程节点执行上下文中的运行用户身份问题工作流 owner vs caller留待独立 feature对外 RPC/api/v2/filelib/retrieve以默认 operator身份运行而非真实终端用户需要先定义代用户检索协议如on_behalf_of_user_idOpenFGA 模型变更不新增view_fileReBAC 关系复用现有can_read与view_filepermission_idchunk 元数据 ACL 索引化超大规模方案单空间单用户可见文件 10 万时的索引层 ACL 字段方案不在本期本期通过双层过滤 检索次数封顶在 10 万规模内提供可接受性能历史接口/api/v1/qa/chunk与/api/v1/qa/keyword新版引用 UI 已切换到/api/v1/citations/resolve两个旧接口本期完全不动待后续统一下线匿名调用方login_user is None典型为 share link / 公开场景本期保持现状不引入新鉴权留待 share-link 专项处理。错误码方面权限缺失一律复用现有SpacePermissionDeniedError18040不新增任何错误码。3. 架构决策双层过滤AD-01 ~ AD-08F029 的核心是 8 条架构决策spec §4其中最关键的是权限语义基线与索引层策略3.1 AD-01双层过滤——can_read粗滤 view_file精滤选项结论理由A: 仅用 ReBACcan_read❌会导致列表看不到但问答能查到越权B: 仅用 fine-grainedview_file❌10 万规模冷启动需要对每个文件单独 OpenFGA tuple 读取一次性解析耗时不可接受C: 双层can_read索引层粗滤 view_file结果层精滤✅用can_read在 Milvus/ES 端把候选缩到有 ReBAC 读权限的文件一次list_objects 缓存再在召回结果的 unique file_id一般 ≤ 30 个上跑 fine-grainedview_file解析保证送进 LLM 的 chunks 必然满足列表 UI 可见性3.2 AD-02索引层过滤策略自适应以可见集合大小K与空间内主版本文件总数N为决策依据K ≤ 5000→INdocument_id in [...]N − K ≤ 5000→NOT IN排除集合 非主版本 ∪ 不可见二者均 5000 →不过滤none仅靠扩大候选 结果层精滤兜底用户无任何可见文件 →empty直接跳过检索。阈值 5000 写为可配置常量KnowledgeQAFilterConf.index_filter_threshold。选择依据ESterms默认上限 65536Milvus 长表达式解析在 ≤ 5000 内表现稳定自适应让 95% 业务走快路径。3.3 AD-03结果层不够 top_k 时的扩展策略首轮按top_k × 3retrieval_initial_multiplier召回若精滤后 top_k且首轮命中文件数 0进行至多 1 次扩张到top_k × 10retrieval_expansion_multiplier仓库实际默认 6仍不足则返回已有结果不再扩张——检索循环次数硬上限 2AC-26避免稀疏访问场景的无限循环。3.4 其余决策速览AD-04 历史引用越权剔除方式整条剔除不返回 placeholder、不暴露文件名/URL/source接口仍 HTTP 200AD-05 不引入新 ReBAC 关系沿用can_readview_file避免 OpenFGA 模型变更与 tuple 迁移的巨大 blast radiusAD-06 工作流节点与 RPC延后独立 featurespec §3 明确标注不支持AD-07 索引层缓存复用PermissionCache现有 10s TTL invalidate_user不引入专属缓存AD-08 结果层并发复用列表 UI 的_build_child_permission_context含tuple_cachefine_grained_concurrency8信号量并发。4. 核心实现KnowledgeFileVisibilityServiceF029 的落地核心是新建的 knowledge_file_visibility_service.py它把可见文件集合的解析集中到一处被KnowledgeSpaceChatService、WorkStationService、CitationResolveService三个调用方共享spec §7.1。4.1 返回值对象IndexFilterdataclass class IndexFilter: Index-layer filter to be injected into Milvus / ES search_kwargs. strategy: str # in | notin | none | empty milvus_expr: str | None None es_filter: list | None None accessible_size: int 0 excluded_ids: list[int] field(default_factorylist) property def is_empty(self) - bool: return self.strategy empty四种strategy语义in——document_id in [visible ids]小可见集notin——document_id not in [excluded ids]几乎全部可见none—— 不加过滤admin 或两侧集合都过大仅靠结果层精滤兜底empty—— 用户在该空间可见文件数为 0调用方必须直接跳过检索。4.2 三个核心方法方法输入输出职责build_index_prefilter(space_id, candidate_file_ids)space_id 候选集合None整空间IndexFilterAD-02 索引层策略决策post_filter_visible_files(space_id, file_ids)space_id 召回 chunk 抽出的 file_id 集合满足view_file的 file_id 子集AD-01/08 结果层精滤is_space_visible(space_id)space_idbool首页多 KB 跳过逻辑AC-11build_index_prefilter的实现要点源码 L118-L208调PermissionService.list_accessible_ids(user_id, can_read, knowledge_file, login_user)拿到用户 tenant 范围内的可读文件集合与该空间的主版本文件集合求交集缩放到当前查询空间若存在业务候选集合文件夹 / tag 范围再取交集交集为空 → 返回empty按k ≤ threshold判断 IN / NOT-IN / none。其中有一个容易被忽略的正确性细节业务候选集合folder/tag 边界是正确性而非优化因此即使两侧集合都超过阈值、即使调用方是 admin只要有candidate_file_ids就必须强制下推到索引层IN 或 NOT-IN绝不能走none捷径泄漏范围外的文件源码注释与测试test_build_index_prefilter_large_candidate_never_falls_to_none均验证了这一点。post_filter_visible_files的实现要点源码 L214-L274admin 短路返回输入集空输入短路通过KnowledgeSpaceService._get_child_item_effective_permission_ids与列表 UI 同一个原语逐文件解析 effective permissions保证聊天路径与列表路径对什么可见的判断一致INV-6委托调用带nearest_binding_winsTrue语义使文件级 revoke 能覆盖空间 membership 默认值——这正是revoke 后文件仍泄漏bug 的回归修复点对应测试test_post_filter_visible_files_regression_revoke_overrides_membershipasyncio.Semaphore(fine_grained_concurrency)限流并发。5. 配置项KnowledgeQAFilterConf新增配置块写入 core/config/settings.py挂在Settings顶层为可选块YAML 缺省时按默认值class KnowledgeQAFilterConf(BaseModel): Knowledge space AI QA retrieval permission filter (F029). index_filter_threshold: int Field( default5000, ge1, descriptionAD-02 threshold: visible/excluded count this → IN / NOT-IN clause; otherwise post-filter only., ) retrieval_initial_multiplier: int Field( default3, ge1, descriptionAD-03 first attempt: initial recall fetches top_k * this multiplier., ) retrieval_expansion_multiplier: int Field( default6, ge1, descriptionAD-03 capped expansion: single retry recalls top_k * this multiplier; no further expansion., ) fine_grained_concurrency: int Field( default8, ge1, le64, descriptionAD-08 concurrency: semaphore limit for per-file view_file resolution., ) model_validator(modeafter) def validate(self): if self.retrieval_expansion_multiplier self.retrieval_initial_multiplier: raise ValueError(retrieval_expansion_multiplier must be retrieval_initial_multiplier) return self注意与 spec 的一个实现差异spec 草案中retrieval_expansion_multiplier默认 10仓库落地实现调整为6注释说明6 - k600 at base_k100以约束重试搜索成本Milvus wrapper 会相应提高 ef。配置校验器保证扩展倍数 ≥ 初始倍数否则启动即报错。6. 三个检索路径的接入改造6.1 知识空间问答KnowledgeSpaceChatService_retrieve_and_filter是 chat_folder / space_rag 共用的双层过滤检索循环完整实现了 AD-03 流程1. build_index_prefilter(space.id, candidate_file_ids) → 若 is_empty 直接返回 [] 2. 首轮: k base_k(100) × initial_multiplier(3)注入 milvus_expr / es_filter → retriever_tool.ainvoke(query) 召回 → 抽 unique file_id → post_filter_visible_files → 按 view_file 子集筛 chunks 3. 若 survivors 为空且首轮命中文件数 0 → 扩张到 × expansion_multiplier重复步骤 2 4. 每次尝试写一条结构化 permission_filter 日志AC-27关键点检索循环封顶 2 次if survivors: break不无限扩张每次尝试都输出结构化日志strategy/accessible_ids_size/prefilter_candidate_size/retrieval_attempts/post_filter_dropped_count便于事后区分该空间下用户无可见文件与用户有可见文件但与 query 不相关space_rag的 prompt 拼装与流式渲染被抽取为_render_rag_response复用不改变原有回答路径单文件问答chat_single_file已有_require_file_view_permission门禁仅增加 DEBUG 日志记录已通过 view_file 检查作为防御性确认AC-09。6.2 首页 / 工作台多 KB 检索WorkStationService.queryChunksFromDBspec §7.2b 定义了三阶段改造仓库中体现为两个新的 classmethodworkstation_service.pyStage 1 ——_filter_visible_space_kb_idsview_space 二次校验AC-11对space_bucket的每个 KB 调is_space_visible(kb_id)不通过则从循环中静默剔除记录 INFO 日志skipped_kb_idX reasonno_view_space探测异常如 OpenFGA 抖动按 fail-closed 处理为不可见org_bucketlegacyknowledge_library原样放行AC-14。Stage 2 —— 索引层粗滤构造MultiRetriever的search_kwargs时注入build_index_prefilter(kb_id, None)的产物不显式预检测该 KB 是否有可见文件——若accessible_ids与 KB 文件集合交集为空Milvus/ES 查询自然返回 0 chunks。Stage 3 ——_post_filter_kb_docs_by_view_file结果层精滤KnowledgeRetrieverTool.ainvoke返回后抽 uniquedocument_id走post_filter_visible_files只保留 survivorssurvivors 为空时该 KB 不进入最终结果、不计入kb_succeedAC-12 自然跳过语义。这里有一个关键的设计取舍AC-12无 view_file 文件不是显式检测而是通过 Stage 2 返回 0 chunks 或 Stage 3 砍光 survivors 自然实现排障依赖 AC-27 日志字段区分原因。这与 spec 中不抛SpacePermissionDeniedError、不阻塞其他 KB的语义完全一致。6.3 角标溯源CitationResolveServicecitation_resolve_service.py是改造最彻底的一个文件spec §7.3删除了旧的_has_file_accessRoleAccessDaoAccessType.KNOWLEDGE空间级判定arch-guard RULE-8 VIOLATION替换为文件级view_file精滤。新流程_resolve_rag_space_pairs按knowledge_id分组收集 RAG citation 的documentIdknowledgeId缺失时通过KnowledgeFileDao.query_by_id_sync反查与 enrich 路径一致无法解析的归入space_id0待剔除_permitted_file_ids登录用户按空间分组调post_filter_visible_files得到允许的 file_id 集合匿名调用方login_user is None返回None表示不加门控AC-20保持 share-link 现状_apply_tier_filtertypeweb的 citation 永远放行AC-19per_userRAG citation 无view_file则整条剔除sharedcitationF041 引入的开关关闭的知识空间来源保留但 enrich 阶段不给完整文件 URLresolve_citations批量先过滤再并发 enrich无权 citation 不出现在返回的items中——等价于该文件不存在于引用注册表AC-16/AC-17resolve_citation单条RAG 类型且登录用户无view_file→ 抛NotFoundErrorAC-18与citation 不存在语义一致。对应的端点 citation.py 使用get_optional_login_user依赖JWT 解码失败返回None而非抛错请求 / 响应 schemaResolveCitationRequest/ResolveCitationResponse完全不变仅登录用户收到的items可能变短。7. API 契约与响应示例7.1 端点变更清单spec §6MethodPath本特性变更POST/api/v1/knowledge/space/{space_id}/chat/file/{file_id}无外部契约变化内部新增结果层精滤防御性确认POST/api/v1/knowledge/space/{space_id}/chat/folder无外部契约变化内部接入双层过滤folder_id0 即整空间POST工作台search_kb工具无独立 HTTP 端点queryChunksFromDB内部按view_file过滤无view_space的 KB 静默跳过POST/api/v1/citations/resolve签名不变_has_file_access改造为文件级精滤无权整条剔除GET/api/v1/citations/{citation_id}同上单条无权返回NotFoundErrorPOST/api/v1/qa/chunk/GET/api/v1/qa/keyword不修改待后续下线7.2 批量溯源部分无权示例AC-16请求POST /api/v1/citations/resolve { citationIds: [cit_A_visible, cit_B_invisible, cit_C_visible] }响应cit_B_invisible对应的文件用户无view_file整条不返回{ status_code: 200, status_message: SUCCESS, data: { items: [ { citationId: cit_A_visible, type: rag, sourcePayload: { knowledgeId: 5, knowledgeName: Q1 报告库, documentId: 1234, documentName: report-Q1.pdf, snippet: ..., previewUrl: ..., downloadUrl: ..., items: [{ itemId: ..., bbox: ... }] } }, { citationId: cit_C_visible, type: rag, sourcePayload: { ...: ... } } ] } }注意items只包含view_file ∈ effective_permissions的 citation无权 citation整条不存在不返回documentName/knowledgeId/snippet/ 任何 placeholdertypeweb的 citation 不受影响。7.3 整空间问答流式响应AC-02服务端 SSE 流结尾事件示意{ type: end_cover, category: answer, message: { answer: ..., source_documents: [ { file_id: 1234, file_name: report-Q1.pdf, score: 0.91 } ] } }source_documents[*].file_id必为当前用户view_file ∈ effective_permissions的子集结果层精滤保证可能为空数组AC-03 触发模型仍按 prompt 给出未找到相关内容类回答HTTP 200 不报错。7.4 错误码表不新增错误码HTTP StatusCodeError Class场景关联 AC200body18040SpacePermissionDeniedError复用用户访问无view_space/view_folder/view_file的资源AC-01, AC-05, AC-08200body——用户有view_*但可见集合为空resolve 时 items 全部被过滤AC-03, AC-12, AC-17200body现有码NotFoundError单条 citation 无view_file与不存在语义一致AC-188. 验收标准全景AC 速查组关键 AC验收要点整空间问答AC-01 ~ AC-04无view_space返回 18040 不检索有view_space仅检索有view_file的主版本文件空间内无任何view_file文件 → 空检索 200 prompt 提示权限收回后 TTL ≤ 10s 内收敛文件夹问答AC-05 ~ AC-07无view_folder返回 18040无view_folder的子分支整体剔除其下直接授权的view_file文件也不可见与列表 UI 对齐tags 过滤取交集文件预览页问答AC-08 ~ AC-09URL 直构造无view_file→ 18040有权限则限定document_id file_id非主版本自动排除首页 / 工作台AC-10 ~ AC-14下拉框沿用view_space过滤仅回归验证无view_space的 KB 静默跳过无可见文件的 KB 自然产生 0 docsorg_bucket不变角标溯源AC-15 ~ AC-20全可见照常返回部分无权整条剔除全部无权返回[]单条无权NotFoundErrorweb 类型不受影响匿名调用方保持现状实时性AC-21 ~ AC-22authorize/revoke同步触发invalidate_userTTL 上限 10s 自动收敛性能AC-23 ~ AC-26热缓存 ≤ 5000 可见文件新增 ≤ 80ms冷缓存 ≤ 500ms10 万可见集 ≤ 200msNOT-IN/后过滤检索循环 ≤ 2 次可观测AC-27日志含permission_filter结构化字段strategy/accessible_ids_size/prefilter_candidate_size/retrieval_attempts/post_filter_dropped_count9. 前端影响与 i18n后端变更对前端契约透明spec §84 个问答入口的响应 schema 不变前端零改动角标溯源面板items数组可能变短无权 citation 被剔除前端按数组渲染即可单条接口无权返回NotFoundError前端复用现有该来源已不可访问提示唯一需要前端联调验证整空间 / 文件夹页面在用户对全部文件均无view_file时AC-03/AC-12空检索 模型按 prompt 回答的流式渲染应正常无 chunks 时不应渲染已引用 0 篇卡片若现状无对应文案需在 platform 前端 locales 的knowledge.json与 client 前端 locales 的translation.json中补充knowledge.qa.noVisibleContentkey中 / 英 / 日三语。不涉及新增组件、新增路由、新增 store。10. 边界情况与降级行为空间内无任何可见文件AC-03/AC-12空检索 200由 prompt 决定回答不视为错误文件夹递归深度≤ 现有 8 层限制仅按祖先文件夹 / 文件链 用户绑定解析权限无需展开全部子树即可剔除整段无权子树多版本文件检索集合永远只考虑当前主版本find_non_primary_file_ids_by_knowledge_ids权限按主版本文件 ID 判断解析中 / 解析失败文件不参与检索沿用file_status过滤权限过滤在其后执行管理员list_accessible_ids对 admin 返回None短路本特性据此跳过过滤OpenFGA 不可达list_accessible_ids降级为返回空 / 回退 scope结果等同于无可见文件AC-03 行为记录 ERROR 日志便于排障引用已删除文件无权检查在文件存在性检查之后已删除文件本就不在响应中行为与现状一致。11. 安全、性能与可观测性保障安全双层过滤保证列表 UI 可见性 AI 问答可见性AD-01结果层精滤不可绕过——它是最终送 LLM 前的必经步骤角标溯源对登录用户严格按view_file文件级精滤无权 citation 整条剔除而非仅隐藏 URL删除CitationResolveService._has_file_access对旧 RBAC 的直接依赖消除一个 arch-guard RULE-8 VIOLATION沿用 PermissionService 五级短路super_admin → 租户隔离 → 租户 admin → ReBAC → RBAC。性能spec §10 非功能要求热缓存下单次问答新增权限过滤耗时 ≤ 80msAC-23冷缓存首轮 ≤ 500ms10s 内追问回到热缓存AC-2410 万可见集下走 NOT-IN / 后过滤新增 ≤ 200msAC-25检索循环硬上限 2 次最坏额外延迟 ≤ 400msAC-26。可观测单条问答日志新增permission_filter结构化字段AC-27解决为什么这条问答没拿到我以为能看到的内容OpenFGAlist_objects失败时保留 ERROR 日志并额外打点权限过滤策略退化为空集以便监控。12. 测试验证与文件清单F029 落地后配套了完整的单元测试可从测试用例反推行为契约test_knowledge_file_visibility_service.py覆盖is_space_visible三种分支、build_index_prefilter的 empty / in / notin / none / admin 短路 / 候选交集 / 大候选不下推 none 等 8 个场景以及post_filter_visible_files的 admin 短路、空输入短路、并发压力与revoke 覆盖 membership 的回归用例test_knowledge_space_chat_service_visibility.py验证 chat_folder / space_rag 双层过滤行为test_query_chunks_visibility.py验证工作台 KB 的 view_space 跳过与 view_file 精滤test_citation_resolve_visibility.py验证批量剔除、单条 NotFound、web 放行、匿名保持现状。实现文件清单spec §9新建src/backend/bisheng/knowledge/domain/services/knowledge_file_visibility_service.py新 serviceAD-01/02/08修改src/backend/bisheng/knowledge/domain/services/knowledge_space_chat_service.py_build_folder_search_kwargs拆分 chat_folder 接入双层过滤 _retrieve_and_filter扩展循环 结构化日志src/backend/bisheng/workstation/domain/services/workstation_service.pyqueryChunksFromDB三阶段改造src/backend/bisheng/citation/domain/services/citation_resolve_service.py删除_has_file_access新增_filter_visible_rag_items/_apply_tier_filter/_permitted_file_idssrc/backend/bisheng/knowledge/api/dependencies.py与src/backend/bisheng/citation/api/dependencies.py注入KnowledgeFileVisibilityService依赖src/backend/bisheng/core/config/settings.pyKnowledgeQAFilterConf配置块前端 locales 文件补 i18n key、release-contract.md登记 INV-7明确不修改knowledge/api/endpoints/qa.py历史 qa 接口、workflow/nodes/knowledge_retriever/工作流节点、open_endpoints/api/endpoints/filelib.py与citation.py的 v2 RPC、OpenFGA 模型定义与PermissionService.authorize/revoke路径、列表 UI 的_filter_visible_child_items/_scan_visible_child_items。13. 总结F029 用一次list_accessible_idsReBACcan_read索引层粗滤Redis 10s 缓存 结果层view_file精滤复用列表 UI 的_build_child_permission_context上下文与 8 并发信号量的双层方案在不改 OpenFGA 模型、不动外部契约、不新增错误码的前提下把知识空间 AI 问答的可见性严格对齐到列表 UI 语义INV-7。管理员 revoke 权限后 TTL ≤ 10s 自动收敛冷启动与 10 万规模均有明确的性能预算所有过滤决策通过permission_filter结构化日志可观测、可排障。对于需要排查问答检索到无权内容类安全问题的开发者这套索引层粗滤 结果层精滤 检索次数封顶的架构是可直接借鉴的范本。【免费下载链接】bishengBISHENG is an open LLM devops platform for next generation Enterprise AI applications. Powerful and comprehensive features include: GenAI workflow, RAG, Agent, Unified model management, Evaluation, SFT, Dataset Management, Enterprise-level System Management, Observability and more.项目地址: https://gitcode.com/GitHub_Trending/bi/bisheng创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表