检索增强从零落地:检索增强系统的索引、召回与评测
检索增强从零落地检索增强系统的索引、召回与评测一、RAG 不是向量库加聊天框RAG 经常被简化成“文档切片、写入向量库、检索后塞给模型”。这个流程能做演示但很难支撑生产。真实系统里文档会更新权限会变化用户问题会含糊召回结果会夹杂噪声模型还可能把不相关片段拼成看似合理的答案。RAG 的目标不是让模型“知道更多”而是让答案有依据、可追溯、可评估。一个能落地的 RAG 系统至少包含五层文档处理、索引构建、召回排序、上下文组装、答案评测。少任何一层后期都会变成排障黑盒。最常见的问题是召回质量差。用户问的是“退款审核超时怎么处理”系统召回到“退款规则介绍”和“审核人员排班”却漏掉“超时补偿流程”。模型只能基于错误材料回答。此时继续调 Prompt 没意义应该先回到索引和召回层。二、RAG 链路答案质量由检索上限决定flowchart LR A[原始文档] -- B[清洗与切片] B -- C[元数据标注] C -- D[向量索引] C -- E[关键词索引] F[用户问题] -- G[查询改写] G -- H[混合召回] D -- H E -- H H -- I[重排序] I -- J[上下文组装] J -- K[LLM 生成] K -- L[引用与评测]文档切片要保留结构。标题、章节、更新时间、权限范围、业务线都应该进入元数据。只保存纯文本会让后续过滤和排序非常困难。切片大小也要结合内容类型。制度文档可以按章节切FAQ 可以按问答对切代码文档可以按函数或模块切。召回建议使用混合策略。向量召回适合语义相近的问题关键词召回适合专有名词、编号和接口名。两者结合后再用重排序模型或规则排序。生产环境里单纯向量召回很容易漏掉精确词。三、实现要点索引版本和引用链路下面是一个文档切片结构。关键是把元数据放进索引而不是只存正文。type Chunk struct { ID string DocID string Title string Content string SectionPath []string Version string UpdatedAt time.Time Permission string } type SearchResult struct { ChunkID string Score float64 Content string Source string } func BuildPrompt(question string, results []SearchResult) string { var b strings.Builder b.WriteString(请只基于给定资料回答。资料不足时说明缺口。\n\n) b.WriteString(用户问题) b.WriteString(question) b.WriteString(\n\n资料\n) for i, r : range results { fmt.Fprintf(b, [%d] source%s score%.3f\n%s\n\n, i1, r.Source, r.Score, r.Content) } b.WriteString(回答必须标注引用编号。) return b.String() }提示词里明确要求“资料不足时说明缺口”是为了减少模型硬答。更重要的是每段资料都带source。答案必须标注引用编号否则无法审计。用户质疑答案时系统要能回到具体文档和具体切片。索引版本也不能忽略。文档更新后旧索引可能仍在服务。如果没有版本号线上答案会出现“明明文档改了回答还是旧规则”的问题。建议每次构建索引都有版本并在答案日志里记录使用的索引版本。四、评测与边界RAG 失败时不要只调模型RAG 评测至少要分三层。第一是召回评测看正确资料是否进入 top-k。第二是生成评测看模型是否基于资料回答。第三是端到端评测看答案是否解决用户问题。只看最终回答会掩盖召回和生成的责任边界。RAG 不适合处理强实时数据。比如库存、余额、订单状态应优先调用实时接口而不是依赖文档检索。RAG 也不适合处理权限复杂但没有元数据的资料。没有权限过滤召回越准泄露风险越高。上下文长度不是越大越好。塞入过多片段会增加成本也会引入噪声。更好的方式是提高召回和排序质量让进入模型的上下文更干净。需要记住一句话RAG 的上限通常由检索决定下限由评测决定。五、总结RAG 落地的关键是检索、引用和评测。向量库只是其中一环。文档切片要保留结构和元数据召回要结合语义与关键词答案要带引用日志要记录索引版本。建议从小范围文档开始建设评测集。先让正确资料稳定进入 top-k再优化生成效果。不要把所有问题都交给 Prompt。RAG 是检索系统和生成系统的组合只有两端都可观测答案才可控。