RAG由浅入深02
第一部分分块策略 —— 如何优雅地切分长文档分块Chunking是RAG的地基工程——分块太大检索噪声多分块太小语义不完整。核心目标是在保持语义完整性和控制检索粒度之间找到最佳平衡。 五大主流分块策略1. 固定字符分块最基础deffixed_size_chunking(text,chunk_size500,overlap50): 按固定字符数切分带重叠区域防止语义断裂 chunks[]start0whilestartlen(text):endstartchunk_size chunks.append(text[start:end])startend-overlap# 重叠区域保留上下文returnchunks优点实现简单计算开销小缺点可能切断句子或段落语义不完整适用场景对质量要求不高的快速原型2. 递归字符分块LangChain默认方式fromlangchain.text_splitterimportRecursiveCharacterTextSplitter splitterRecursiveCharacterTextSplitter(chunk_size500,chunk_overlap50,separators[\n\n,\n,。,,,, ,]# 优先级递减)chunkssplitter.split_text(document)核心思想优先在段落、句子边界切分保持自然语义单元优点比固定切分更智能保留更多语义完整性适用场景通用场景的首选方案3. 语义分块基于句子嵌入fromsentence_transformersimportSentenceTransformerimportnumpyasnpdefsemantic_chunking(sentences,similarity_threshold0.7): 计算句子间的语义相似度相似度低的切分 modelSentenceTransformer(all-MiniLM-L6-v2)embeddingsmodel.encode(sentences)chunks[]current_chunk[sentences[0]]foriinrange(1,len(sentences)):# 计算当前句子与上一句的相似度simnp.dot(embeddings[i],embeddings[i-1])/(np.linalg.norm(embeddings[i])*np.linalg.norm(embeddings[i-1]))ifsimsimilarity_threshold:chunks.append( .join(current_chunk))current_chunk[sentences[i]]else:current_chunk.append(sentences[i])ifcurrent_chunk:chunks.append( .join(current_chunk))returnchunks优点能智能识别主题边界分块质量高缺点计算开销大需要调用模型适用场景对检索精度要求高的生产环境4. 文档结构感知分块针对特定格式defmarkdown_aware_chunking(markdown_text): 按Markdown的标题层级切分保持文档结构 importre# 按标题# ## ###分割patternr(#{1,3}\s[^\n]\n)sectionsre.split(pattern,markdown_text)chunks[]current_headercurrent_content[]foriteminsections:ifre.match(r^#{1,3}\s,item):# 是标题保存之前的块ifcurrent_headerandcurrent_content:chunks.append(current_header\n.join(current_content))current_headeritem current_content[]else:current_content.append(item)ifcurrent_headerandcurrent_content:chunks.append(current_header\n.join(current_content))returnchunks适用场景技术文档、法律文书、财报等结构化文档5. 大小块混合策略进阶方案defhybrid_chunking(document): 同时建立大块用于上下文和小块用于检索 # 大块提供完整上下文large_chunksRecursiveCharacterTextSplitter(chunk_size1000,chunk_overlap100).split_text(document)# 小块用于精确检索small_chunksRecursiveCharacterTextSplitter(chunk_size200,chunk_overlap50).split_text(document)return{large_chunks:large_chunks,small_chunks:small_chunks,strategy:父文档检索Parent Document Retrieval}核心思想检索时用小块精确定位返回答案时附带大块上下文优点兼顾检索精度和回答质量 分块策略选择决策表文档类型推荐策略chunk_sizeoverlap短文章500字不切分整文使用--技术博客/文章递归字符分块500-80050-100法律/政策文档结构感知分块按章节-财报/年报语义分块动态-多主题综合文档混合策略200/100050/100用户评论/对话固定字符分块200-30030-50 第二部分检索优化 —— 让AI找到最相关的信息分块只是第一步更关键的是如何让检索系统精准找到用户问题的相关片段。 检索优化策略矩阵策略1多路召回 重排序Reranking核心思想先快速召回多个候选再用更精细的模型重新排序。fromsentence_transformersimportCrossEncoderimportnumpyasnpdefrerank_search(query,chunks,top_k10): 多路召回 交叉编码器重排 # 1. 第一轮快速召回20个候选用普通嵌入embeddingsOpenAIEmbeddings()query_embembeddings.embed_query(query)chunk_embs[embeddings.embed_query(chunk)forchunkinchunks]# 计算余弦相似度取前20similaritiesnp.dot(chunk_embs,query_emb)top_indicesnp.argsort(similarities)[-20:][::-1]candidates[chunks[i]foriintop_indices]# 2. 第二轮用交叉编码器精细排序rerankerCrossEncoder(cross-encoder/ms-marco-MiniLM-L-6-v2)scoresreranker.predict([(query,chunk)forchunkincandidates])# 返回排序后的top_krerankedsorted(zip(candidates,scores),keylambdax:x[1],reverseTrue)return[chunkforchunk,scoreinreranked[:top_k]]性能提升检索精度提升20-30%适用场景大规模知识库10万文档策略2HyDE假设文档嵌入核心思想让大模型先想象一个理想答案然后用这个假想答案去检索。defhyde_search(query,llm,knowledge_base): HyDE先让AI生成假设答案再检索 # 1. 生成假设答案不要求准确只求相关概念hypothesisllm.invoke(f假设你是专家请针对以下问题写一段可能的答案草稿{query})# 2. 用假设答案进行检索而非原始问题hypothesis_embedembeddings.embed_query(hypothesis.content)resultsknowledge_base.similarity_search_by_vector(hypothesis_embed)returnresults优点能召回与问题语义相近但关键词不同的文档缺点增加LLM调用开销约100-300ms策略3多查询查询Multi-Query核心思想将用户问题改写为多个不同视角的问题分别检索后合并去重。defmulti_query_search(query,llm,knowledge_base): 生成5个不同视角的问题合并检索结果 # 1. 生成多个查询变体promptf请将以下问题从不同角度改写为5个不同的问题保持原意{query}variantsllm.invoke(prompt).content.split(\n)[:5]# 2. 每个变体分别检索all_results[]forqin[query]variants:resultsknowledge_base.similarity_search(q,k3)all_results.extend(results)# 3. 去重并合并基于内容相似度unique_resultsdeduplicate_documents(all_results)returnunique_results适用场景用户问题模糊、多义性强的场景策略4元数据过滤Metadata Filtering核心思想为文档块附加元数据来源、日期、类型等检索时先按条件过滤。defmetadata_aware_retrieval(query,metadata_filters,knowledge_base): 带元数据过滤的检索 # 例只检索2024年以后的文档且类型为政策filters{year:{$gte:2024},doc_type:policy}resultsknowledge_base.similarity_search(query,k5,filterfilters# 先过滤再检索)returnresults优点大幅减少无关候选提升精度适用场景结构化知识库如企业文档库策略5自适应检索Adaptive Retrieval核心思想根据问题的复杂度动态调整检索策略。defadaptive_search(query,llm,knowledge_base): 先判断问题复杂度再选择策略 # 1. 问题复杂度判断complexity_promptf请判断以下问题的复杂度简单/中等/复杂{query}levelllm.invoke(complexity_prompt).content.strip()# 2. 动态策略选择iflevel简单:returnknowledge_base.similarity_search(query,k3)eliflevel中等:# 使用多路召回returnmulti_query_search(query,llm,knowledge_base)else:# 复杂# 使用HyDE或链式检索returnhyde_search(query,llm,knowledge_base) 实战完整优化方案代码示例fromlangchain.retrieversimportContextualCompressionRetrieverfromlangchain.retrievers.document_compressorsimportLLMChainExtractordefbuild_optimized_rag(): 构建一个整合了多种优化策略的RAG系统 # 1. 优化分块策略text_splitterRecursiveCharacterTextSplitter(chunk_size600,chunk_overlap80,separators[\n\n,\n,。,,,, ,])docstext_splitter.split_documents(documents)# 2. 创建向量库embeddingsOpenAIEmbeddings()vectorstoreChroma.from_documents(docs,embeddings,collection_metadata{hnsw:space:cosine}# 使用余弦相似度)# 3. 基础检索器多路召回base_retrievervectorstore.as_retriever(search_typemmr,# MMR算法平衡相关性和多样性search_kwargs{k:20,fetch_k:50})# 4. 重排序压缩LLM链式压缩llmChatOpenAI(modelgpt-3.5-turbo)compressorLLMChainExtractor.from_llm(llm)# 5. 最终检索器final_retrieverContextualCompressionRetriever(base_compressorcompressor,base_retrieverbase_retriever)returnfinal_retriever# 使用优化后的检索器retrieverbuild_optimized_rag()relevant_docsretriever.get_relevant_documents(公司年假政策是什么) 优化效果与性能权衡优化策略精度提升延迟增加实现难度推荐指数递归字符分块10%5ms⭐⭐⭐⭐⭐⭐语义分块15%50ms⭐⭐⭐⭐⭐⭐⭐多路召回重排20-30%100-200ms⭐⭐⭐⭐⭐⭐⭐⭐⭐HyDE15-25%200-300ms⭐⭐⭐⭐⭐⭐元数据过滤25%10ms⭐⭐⭐⭐⭐⭐⭐自适应检索30%50-300ms⭐⭐⭐⭐⭐⭐⭐⭐ 最佳实践建议从小规模开始先在1000个文档的规模测试分块策略建立评测集准备50-100个标准问题及期望答案量化评估每个优化策略的效果分层优化先优化分块影响基础再优化检索锦上添花监控关键指标关注召回的NDCG10和回答的BLEU/ROUGE分数渐进式部署在生产环境先用简单策略逐步迭代复杂优化分块和检索优化没有银弹最佳实践是根据你的文档类型、用户查询模式、性能要求做AB测试找到最适合的组合。