1 项目背景与主要工作
本项目面向一家拥有约十万份PDF技术手册、产品规格书与内部制度文档的中大型企业,构建一个供研发、售前与运维人员使用的知识问答助手。业务方的核心诉求有三:检索召回率不低于90%;每条回答必须可追溯到原文的具体章节与页码;不同部门与职级的用户只能检索到其有权访问的文档范围。
笔者在该项目中担任检索链路的技术负责人,主要工作包括:(1)主导文档解析与分块流水线的设计与选型,解决扫描版PDF与复杂表格的结构化提取问题;(2)设计"稠密向量+BM25稀疏检索+Cross-Encoder重排"的混合检索体系,并完成Embedding模型与Reranker的选型评测;(3)实现基于向量数据库标量过滤的检索阶段权限隔离方案,确保无权文档在ANN召回之前即被排除;(4)建立以Recall@K与NDCG@10为核心的离线评测集,驱动检索策略的迭代调优。
技术栈上,编排采用LangChain,向量数据库选用Milvus(生产环境),Embedding模型为BAAI/bge-m3,稀疏检索基于BM25,重排采用BAAI/bge-reranker-v2-m3。
2 RAG完整链路与幻觉成因分析
2.1 完整链路拆解
RAG系统可分为离线数据工程 与在线推理问答两个阶段。离线阶段完成"原始文档→数据清洗→文档分块→向量化→构建索引→存入向量数据库"的管道;在线阶段执行"查询改写→多路召回→重排序→上下文注入→LLM生成→引用溯源"的推理链路。
文档解析是将PDF、DOCX、HTML等非结构化格式转换为可处理文本的入口环节。纯文字版PDF使用PyMuPDF即可高效处理,但复杂排版(多栏布局、合并单元格表格、图表标注)需要版面感知解析器(如MinerU、Docling)提取出Markdown结构,明确保留标题层级与表格边界。
分块直接决定检索质量的上限。切分过粗会将大段无关噪声带入生成上下文;切分过细则会割裂跨块语义。业界普遍采用200---500 token的块大小并保留10%---15%的重叠,同时为每个chunk附着来源文档、页码、章节标题、时间戳等元数据,这些元数据在后续过滤与引用溯源中不可替代。
向量化将文本块映射到稠密语义空间。中文场景下,BGE系列模型(bge-m3、bge-large-zh-v1.5)是实践中的常见选择;垂直领域(医疗、法律、特定技术术语体系)则往往需要对通用Embedding进行领域微调,才能显著提升相似度计算的判别力。
检索的主流形态已从单一向量相似度搜索演变为混合检索。稠密向量擅长语义泛化,但对精确术语、错误码、产品型号的匹配存在系统性弱点;BM25稀疏检索恰恰在这些精确匹配场景中形成互补。两者融合(常用Reciprocal Rank Fusion,RRF)在社区实践中可将召回率提升15%---30%。
重排是在粗召回之后施加的第二道精度控制。Cross-Encoder将query与候选文档联合编码,能够捕获比双塔余弦相似度更细粒度的相关性信号。实践中常以top-20至top-50的粗召回结果作为重排输入,输出top-5送入生成阶段;BGE-Reranker-v2-m3在此环节可带来6%---8%的准确率提升,代价是约30ms的额外延迟。
生成阶段将重排后的上下文与用户问题组装为提示模板,由LLM产出答案。工程上必须要求模型在答案中标注引用来源,这既是可追溯性的保障,也是发现上游分块或检索问题的诊断信号。
2.2 大模型幻觉的成因
RAG系统中的幻觉不能简单归咎于"模型编造",需要从两个层面区分归因。
第一层:参数化知识的内在缺陷。 大模型在预训练中习得的知识是压缩的、有损的,且存在明确的训练截止边界。当问题落在知识盲区或训练数据覆盖薄弱的区域时,模型倾向于用语言流畅性"填补"事实空缺。研究进一步表明,即使在微调中注入新知识,也可能诱发对原有已知事实的幻觉------新知识的学习会削弱模型对输入中关键实体的注意力分配。幻觉的根源不限于知识缺失:训练激励鼓励模型利用预训练中的统计捷径与虚假关联,推理阶段的"潜在推理路径偏置"同样可以导致输出偏离事实,即便相关知识存在于参数之中。
第二层:检索失败引发的"无据生成"。 这是RAG场景中更具工程可控性的幻觉来源。当检索环节没有召回真正相关的文档块,或召回的内容被无关噪声稀释,生成模型面对的是一个"空上下文"或"坏上下文"。此时模型的行为退化为纯参数化生成,RAG的" grounding"承诺落空。有研究将这一机制概括为"幻觉本质上是检索问题而非知识问题":解码器架构的叠加干扰、长上下文中的注意力稀释、以及RLHF校准带来的过度自信,共同使得模型在检索供给不足时更倾向于编造而非拒答。换言之,检索精度是幻觉控制的第一道防线;在检索不准的前提下堆叠模型参数,只会放大幻觉的破坏力。
3 项目实践:知识库构建、检索优化与权限隔离
3.1 知识库构建:以"可检索性"为设计目标的数据工程
本项目的知识库构建遵循一个原则:每一个入库决策都以"下游能否精准检索到它"为检验标准,而非以"是否完整存入"为目标。
解析层 放弃纯文本流抽取,对技术文档采用版面感知解析器提取Markdown结构,保留H1---H3的章节树状路径。每个chunk继承其所在的章节标题路径(breadcrumb),作为元数据的一部分写入。分块层 采用父子分块(Parent-Child Chunking):子块控制在200---300 token,粒度小、主题单一,专门用于Embedding与相似度匹配,避免语义向量被长文本稀释;命中子块后,向上回溯其所属的父块(1000---2000 token或整节内容)作为生成阶段的上下文,从而在"检索精度"与"生成完整性"之间取得平衡。这一设计直接回应了固定长度切分的两难:小块提精度但丢上下文,大块保上下文但降精度,父子结构将两个需求解耦。每个chunk绑定的元数据包括doc_id、page_num、section_path、file_url、security_level与department_scope,前五项支撑引用溯源,后两项服务于权限过滤。
增量更新机制同样不可忽略:向量库以文档内容hash为键执行"删除旧chunk---写入新chunk"的同步,避免用户检索到已被修订或废止的过时内容。
3.2 检索策略优化与召回精度提升
本项目的检索体系采用三层架构:查询侧扩展 → 混合多路召回 → 重排精筛。
查询侧首先解决"用户语言与文档语言之间的语义鸿沟"。用户输入往往是口语化的("那个客户上次说的API的事情"),直接用于向量检索命中率很低。系统先用LLM将原始query改写为检索友好的表述,并对简短或模糊的查询启用HyDE------让模型先生成一段"假设性答案",再用假设答案的Embedding去检索,因为假设答案的文本风格更接近真实文档,向量空间中的距离更近。在公开评测中,HyDE在混合检索体系中对BM25的改善幅度可达26.7%。对于涉及多实体或多跳推理的问题,进一步启用多查询扩展与子问题分解,生成3---5个不同视角的检索query并行召回。
召回层执行稠密向量与BM25的并行检索,再以RRF融合两路排序。向量侧使用bge-m3,负责语义匹配;BM25侧负责捕获错误码(如APIG.0402)、精确参数名、产品型号等术语密集型信号------这是纯稠密检索最容易"泛化脱靶"的场景。两路各自取top-50进入融合池。
重排层对融合后的候选集使用bge-reranker-v2-m3进行Cross-Encoder精排,输出top-5送入生成。在项目自建的评测集(覆盖3000段技术文档语料与150条人工标注问句)上,混合检索+重排的组合将Recall@1从纯向量基线的0.79提升至0.91以上,NDCG@10提升约9个百分点。值得注意的是,重排的增益高度依赖于粗召回的质量:当粗召回top-50中已包含正确文档时,Cross-Encoder能够有效将其推至前列;但若正确文档未被粗召回捕获,重排无力回天。因此迭代重心始终放在"扩大有效召回"而非"过度打磨重排"。
评测驱动是上述优化的闭环保障。离线评测集按问题类型分层标注(事实型、比较型、多跳型、精确匹配型),分别追踪Recall@5/10/20与NDCG@10。每一次分块参数调整、Embedding模型替换或检索策略变更,都必须在该评测集上产生可比较的指标变动,而非依赖主观感受。
3.3 权限隔离:将权限嵌入检索的数据面
企业级RAG与Demo级问答的分水岭在于:权限不能是检索之后的外挂校验,而必须是检索本身的一部分。本项目采用检索阶段前置过滤方案,核心链路如下:
用户发起查询时,请求携带租户ID与用户身份。业务权限服务(PermissionService)首先解析该用户在文件粒度上的可读文档ID集合,该集合综合了用户直接授权、部门继承、用户组授权与文件夹继承规则。权限编译层将可读ID集合转化为Milvus可执行的标量过滤表达式(document_id in [...]),向量数据库先执行标量过滤缩小候选空间,再在合规子集内执行ANN向量召回。这样,无权文档的向量根本不会进入相似度竞争,也不会进入应用内存或LLM上下文。
这一设计直接规避了两种常见但有害的反模式。反模式一:Prompt约束 ------先召回top-K再在提示词中告诉模型"不要回答无权内容"。当无权chunk已经被召回并拼入上下文时,泄露已经在数据面发生,模型最终是否在正文中输出并不改变信息已经进入系统的事实;引用、摘要、日志字段都可能成为泄露通道。反模式二:全量召回后过滤------安全上可靠,但严重伤害召回率:如果top-10中前9条均来自无权文档,过滤后仅剩1条,系统可能给出"未找到"的错误答案。
对于可读文档数量极大的场景(如管理者可访问数万文件),IN表达式的长度可能超出数据库限制,此时结合租户分区(Milvus partition)与NOT IN排除少量无权文档的策略,控制过滤条件的规模。召回结果进入生成之前,业务层再对文件权限执行一次复核;最终前端回填引用内容时执行第三道校验,形成"前置过滤---召回复核---展示兜底"的三层链路。审计日志记录每次检索的过滤条件与命中文档,为合规审查提供完整轨迹。