在构建企业级RAG系统时,尤其是在处理内部Wiki和PDF文档的场景下,选择合适的搜索组件并设计合理的工程方案至关重要。本文将深入探讨组件选型、多租户权限、文档粒度以及一套可落地的生产级配置。
首先要明确:“搜索组件”具体指什么?
在RAG系统中,“搜索”并不仅指向量检索。一个成熟的生产环境通常需要整合至少三种核心检索能力:
-
关键词检索 (Keyword / BM25)
- 适用场景:错误码、专有名词、人名、版本号、制度编号等精确匹配。
- 常用组件:Elasticsearch / OpenSearch。
-
向量检索 (Vector Search)
- 适用场景:用户提问口语化、存在同义表达或概念性查询时进行语义匹配。
- 常用组件:Milvus、pgvector、ES kNN、Pinecone。
-
重排 (Rerank)
- 适用场景:从初步检索出的“看似相关”的候选结果中,筛选出“真正最相关”的部分,显著降低噪声。
- 常用组件:bge-reranker、Cohere Rerank,或使用小型LLM进行评审。
针对“内部Wiki + PDF”的工程方案建议:
- 追求组件少、运维简单:采用 OpenSearch/ES 一把梭(同时承担关键词与向量检索)。
- 追求向量性能与扩展性:采用 ES (关键词) + Milvus (向量) + Rerank 的组合方案。
1)多租户场景的核心:权限过滤设计(避免“串库”)
在多租户RAG系统中,首要任务不是追求极致的召回效果,而是确保数据隔离,绝不越权。
1.1 两种主流的隔离策略:索引隔离 vs 元数据过滤
A. 索引/集合隔离 (强隔离,运维成本较高)
- 做法:为每个租户(tenant)创建独立的索引(ES Index)或集合(Milvus Collection)。
- 优点:天然物理隔离、审计清晰、实现逻辑简单。
- 缺点:租户数量庞大时,索引数量会爆炸式增长,导致运维成本和存储成本高昂,数据迁移也变得复杂。
B. 单一大型库 + 元数据过滤 (更常见,对工程能力要求高)
- 做法:所有租户的数据存储在同一个库中,但每条文本块(Chunk)都附带
tenant_id、org_id、ACL(访问控制列表)等元数据字段。检索时必须先进行过滤(如 tenant_id = ? AND (ACL允许)),再进行召回。
- 优点:运维简单、资源利用率高。
- 缺点:一旦过滤逻辑出现漏洞即是严重的安全事故;此外,过滤条件若设计不当可能拖慢检索性能。
实践建议:
- 租户数量少、对隔离性要求极高:采用方案A。
- 租户数量多、对成本敏感:采用方案B,但必须将权限过滤构建为平台底层“不可绕过”的核心能力。
1.2 权限过滤必须落实在“检索层”,而非仅依赖Prompt
切勿指望通过提示词(如“你只能使用本租户的资料”)来实现权限控制。正确的做法是:在检索阶段,系统就只返回当前租户权限内的文本块,确保大模型在生成答案时根本“看不到”其他租户的数据。
工程落地的必备元数据字段(每条Chunk都应包含):
tenant_id (租户标识)
source_type (数据源类型,如 wiki/pdf)
doc_id, chunk_id (文档及块标识)
updated_at (用于基于时效性的过滤)
acl (可选,用于部门、角色、用户等更细粒度的权限控制)
source_url / page (用于追溯和引用)
2)理解“粒度”:通常涉及两个层面
当讨论“粒度较细”时,在RAG工程中通常指以下两个维度:
2.1 文档粒度 (Document Granularity)
指权限控制和数据隔离的最小单位是什么?是按整篇文档管理,还是更细?
在企业环境中,通常权限管理在文档粒度较为合理,即一整篇Wiki文章或一个PDF文件拥有统一的访问权限。这样做的好处是权限管理模型简单,避免了同一文档部分内容可见、部分不可见的复杂情况。
2.2 切片粒度 (Chunk Granularity)
指将文档切分成多大尺寸的文本块用于索引和召回。
“粒度较细”通常意味着Chunk的尺寸更小。
- 好处:语义匹配可能更精准。
- 坏处:单个Chunk可能丢失上下文信息、需要召回更多数量才能覆盖问题、可能引入更多噪声、索引和检索成本更高。
工程实践中的默认建议(非常实用):
- Wiki文档:按照标题层级(如H2/H3)进行切分,每块控制在300–600个tokens。
- PDF文档:先按自然段落或小节切分,每块控制在350–700个tokens。
- 重叠率(Overlap):建议设置为 10–15%,以保持块之间的上下文连贯性。
如果追求“更细的粒度”,不建议直接无限缩小Chunk尺寸。下面介绍一个生产级的优化方案。
3)生产级方案:子块召回 + 父块供给(兼顾细粒度与上下文完整性)
这个方法能有效解决“既要匹配精准,又要上下文完整”的矛盾,特别适合Wiki和PDF场景。
具体做法:
在索引阶段,生成两种关联的文本块:
- 父块 (Parent Chunk):较大的文本块(例如500–900 tokens),用于最终组合并喂给大模型,保证生成答案时的上下文完整性。
- 子块 (Child Chunk):较细的文本块(例如150–300 tokens),用于精准召回。每个子块需要存储其所属父块的ID (
parent_id)。
查询流程:
- 使用子块进行检索,获取Top K个最相关的结果。
- 将这些子块映射回其对应的父块,并根据父块进行去重。
- 将去重后的Top N个父块组合起来,拼接到Prompt中供大模型生成答案。
核心收益:
- 召回更准:利用细粒度的子块进行相似度计算,匹配精度高。
- 生成更稳:最终提供给模型的是完整的父块,上下文信息充足。
- 特别有效:对于PDF中的表格、法律条款等前后文依赖性强的内容,此方法优势明显。
4)Wiki 与 PDF 的差异化处理建议
4.1 内部Wiki(结构性强,标题清晰)
- 推荐检索组合:
- 关键词检索:非常重要,用于精确匹配标题、术语、制度编号。
- 向量检索:用于处理同义表达和口语化提问。
- 重排(Rerank):锦上添花,能显著去除噪声,提升Top结果质量。
- 实践要点:
- 切分Chunk时保留标题路径(如
公司制度 > 财务 > 报销 > 交通费),这对检索结果的可解释性和最终答案的引用都极有帮助。
- 对用户查询(Query)进行规范化预处理:去除多余空格、统一中英文标点、标准化数字格式等。
4.2 PDF文档(结构相对弱,解析风险大)
- 推荐检索组合:
- 首要任务是把解析做好:使用高质量的PDF解析库(如
pdfplumber、PyMuPDF),糟糕的解析结果会让后续搜索组件无能为力。
- 重排(Rerank)价值更大:PDF解析产生的文本块往往噪声更高,重排模型能有效筛选出真正相关的部分。
- 实践要点:
- 对于表格内容,优先将其转换为Markdown表格格式或生成“表格摘要文本”后再入库,这能极大提升检索和理解的准确性。
- 必须存储页码信息(
page_number),以便在生成答案时提供精确的引用来源。
5)一套“开箱即用”的多租户RAG默认配置(V0起步版)
你可以将以下配置作为初始版本,上线后通过分析日志和用户反馈进行迭代优化。
-
索引策略
- 存储:单一大库 + 元数据过滤(
tenant_id为必需字段)。
- 块设计:采用子块(160–220 tokens) + 父块(600–900 tokens)的双层结构。
- 重叠率:10–15%。
-
检索策略
- 方式:混合检索(关键词 + 向量)。
- 先过滤:
tenant_id + ACL + doc_status=active。
- 召回:使用子块检索Top K=40个候选。
- 映射与去重:映射到父块后,选取Top N=6个(去重后)最相关的父块。
-
生成策略
- 输出格式:鼓励使用结构化输出(如JSON),并对引用的内容进行校验,确保其来自提供的证据(Evidence)。
- 处理未知:当检索到的信息不足以回答问题,应主动触发澄清流程(
ask_clarify),而非强行生成可能错误的答案。
-
观测与审计(多租户必需)
- 记录每次请求:
tenant_id、query、retrieved_chunk_ids、doc_ids、source_urls、prompt_version。
- 安全审计:定期抽样检查日志,确认“跨租户的Chunk是否被召回”(此情况应为0)。安全是云栈社区技术讨论中永恒的重点。
|