【AI原生开发实战专栏】5.5 RAG高级技巧:从Naive RAG到生产级系统
学习目标
- 理解RAG的三种技术范式演进
- 掌握Query Rewriting、Hybrid Search、Reranking等核心技巧
- 能够在实际项目中实现高级RAG Pipeline
- 理解Chunk策略对检索效果的影响
- 为构建生产级RAG系统奠定基础
一、RAG技术范式演进
1.1 从Naive到Advanced的演进
RAG技术经历了三个发展阶段:
┌─────────────────────────────────────────────────┐ │ RAG范式演进 │ ├─────────────────────────────────────────────────┤ │ Naive RAG → Advanced RAG → Modular RAG │ │ 2020 2023-2024 2025-2026 │ ├─────────────────────────────────────────────────┤ │ • 简单检索 • 预检索优化 • 模块可插拔 │ │ • 直接拼接 • 后检索优化 • 多模态支持 │ │ • 效果一般 • 质量提升 • Agent集成 │ └─────────────────────────────────────────────────┘1.2 Naive RAG痛点
| 问题 | 表现 | 影响 |
|---|---|---|
| 检索不准 | 语义匹配差 | 答非所问 |
| 上下文丢失 | 大块截断 | 关键信息遗漏 |
| 幻觉严重 | LLM乱编 | 信任度下降 |
| 效率低下 | 检索慢 | 用户体验差 |
二、预检索优化(Pre-Retrieval)
2.1 Query Rewriting
将用户模糊/口语化的query重写为更适合检索的形式。
fromlangchain.chat_modelsimportChatOpenAIfromlangchain.promptsimportChatPromptTemplatefromlangchain.schemaimportStrOutputParser# Query重写Promptrewrite_prompt=ChatPromptTemplate.from_template(""" 你是一个查询优化专家。将用户的问题改写为更适合向量检索的形式。 原始问题:{query} 要求: 1. 提取核心关键词 2. 去除口语化表达 3. 补充必要的上下文 4. 可以生成2-3个相似查询 改写结果: """)# 重写链rewrite_chain=rewrite_prompt|ChatOpenAI(temperature=0)|StrOutputParser()# 使用示例original_query="那篇讲transformer的论文讲的啥来着"rewritten=rewrite_chain.invoke({"query":original_query})# 输出可能:# - Transformer论文的核心内容# - Attention is All You Need主要贡献# - BERT和GPT的基础架构2.2 HyDE(Hypothetical Document Embeddings)
用LLM生成"假设答案",用假设答案去检索。
fromlangchain.chainsimportHypotheticalDocumentEmbedderfromlangchain.embeddingsimportOpenAIEmbeddings# HyDE嵌入器hyde_embeddings=HypotheticalDocumentEmbedder.from_llm(llm=ChatOpenAI(temperature=0),base_embeddings=OpenAIEmbeddings(),prompt_key="web_search"# 指定生成假设文档的prompt)# 应用到向量存储vectorstore=Chroma.from_documents(documents=chunks,embedding=hyde_embeddings# 使用HyDE)# 检索时使用假设答案results=vectorstore.similarity_search(query,k=5)2.3 Hybrid Search
结合BM25(关键词搜索)和向量搜索的优势。
fromlangchain.retrieversimportEnsembleRetrieverfromlangchain_community.retrieversimportBM25Retrieverfromlangchain.vectorstoresimportChroma# 1. BM25检索器bm25_retriever=BM25Retriever.from_texts(texts=chunk_texts,metadatas=chunk_metadatas)bm25_retriever.k=5# 2. 向量检索器vectorstore=Chroma.from_texts(texts=chunk_texts,embedding=OpenAIEmbeddings())vector_retriever=vectorstore.as_retriever(search_kwargs={"k":5})# 3. 集成检索器ensemble_retriever=EnsembleRetriever(retrievers=[vector_retriever,bm25_retriever],weights=[0.6,0.4]# 向量搜索权重更高)# 4. 使用集成检索器results=ensemble_retriever.invoke("transformer架构详解")三、检索优化(Retrieval)
3.1 Multi-Query Retrieval
从多个角度生成查询变体,扩大召回范围。
fromlangchain.retrievers.multi_queryimportMultiQueryRetriever# 自动生成多个查询multi_query_retriever=MultiQueryRetriever.from_llm(retriever=base_retriever,llm=ChatOpenAI(temperature=0),include_original=True# 保留原始查询)# 检索results=multi_query_retriever.get_relevant_documents("Transformer中的Positional Encoding是如何工作的?")3.2 Parent Document Retriever
保持父子文档关系,平衡检索精度和上下文完整性。
fromlangchain.retrieversimportParentDocumentRetrieverfromlangchain.text_splitterimportRecursiveCharacterTextSplitterfromlangchain.vectorstoresimportChroma# 大块(父文档)parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000,chunk_overlap=200)# 小块(检索用)child_splitter=RecursiveCharacterTextSplitter(chunk_size=200,chunk_overlap=50)# 父文档检索器parent_retriever=ParentDocumentRetriever(vectorstore=Chroma(),docstore=InMemoryStore(),child_splitter=child_splitter,parent_splitter=parent_splitter,)# 添加文档时会自动切分parent_retriever.add_documents(documents)# 检索时返回父文档(完整上下文)results=parent_retriever.invoke("attention mechanism")3.3 Reranking
用更精确的模型对初筛结果重排序。
fromsentence_transformersimportCrossEncoder# 1. 初筛:快速向量检索Top-20initial_results=vectorstore.similarity_search(query,k=20)# 2. 重排:用Cross-Encoder精细排序reranker=CrossEncoder('BAAI/bge-reranker-base')pairs=[[query,doc.page_content]fordocininitial_results]scores=reranker.predict(pairs)# 按分数排序ranked_results=[docfor_,docinsorted(zip(scores,initial_results),reverse=True)][:5]# 取Top-5# 3. 使用重排后的结果context="\n\n".join([doc.page_contentfordocinranked_results])四、Chunk策略优化
4.1 固定窗口Chunk
fromlangchain.text_splitterimportCharacterTextSplitter# 简单但可能切断语义text_splitter=CharacterTextSplitter(separator="\n",chunk_size=500,chunk_overlap=50,length_function=len,)4.2 语义分块(推荐)
fromlangchain_experimental.text_splitterimportSemanticChunkerfromlangchain_openai.embeddingsimportOpenAIEmbeddings# 基于语义相似度切分semantic_chunker=SemanticChunker(embeddings=OpenAIEmbeddings(),breakpoint_threshold_type="percentile",# 基于百分位breakpoint_threshold_amount=95,# 相似度低于95%分位点时切分)chunks=semantic_chunker.create_documents([long_text])4.3 Markdown/JSON结构化分块
defchunk_by_markdown(content:str)->list[str]:""" 按Markdown标题层级分块 """lines=content.split('\n')chunks=[]current_chunk=[]current_level=0forlineinlines:ifline.startswith('#'):level=len(line)-len(line.lstrip('#'))# 遇到更高级别标题时保存当前chunkiflevel<current_levelandcurrent_chunk:chunks.append('\n'.join(current_chunk))current_chunk=[]current_level=level current_chunk.append(line)ifcurrent_chunk:chunks.append('\n'.join(current_chunk))return[cforcinchunksifc.strip()]五、后检索处理
5.1 Contextual Compression
压缩检索结果,保留关键信息。
fromlangchain.retrieversimportContextualCompressionRetrieverfromlangchain_community.document_compressorsimportLLMChainExtractor# 压缩器:用LLM提取关键信息compressor=LLMChainExtractor.from_llm(ChatOpenAI(temperature=0))compression_retriever=ContextualCompressionRetriever(base_retriever=base_retriever,document_compressor=compressor)# 检索时自动压缩compressed_docs=compression_retriever.invoke(query)5.2 MMR(最大边际相关性)
避免检索结果过于相似,增加多样性。
# 在向量存储中配置MMRvectorstore=Chroma.from_texts(texts,embedding=embeddings)# 检索时使用MMRresults=vectorstore.max_marginal_relevance_search(query,k=5,# 最终返回数量fetch_k=20,# 初始候选数量lambda_mult=0.5# 0=只顾多样性, 1=只顾相关性)六、完整RAG Pipeline实现
fromtypingimportOptionalfromlangchain.chat_modelsimportChatOpenAIfromlangchain.promptsimportChatPromptTemplatefromlangchain.schemaimportStrOutputParserfrompydanticimportBaseModelclassAdvancedRAGPipeline:def__init__(self,vectorstore,llm:ChatOpenAI,embedder):self.vectorstore=vectorstore self.llm=llm# 1. Query重写self.rewrite_prompt=ChatPromptTemplate.from_template(""" 将这个问题改写为更适合检索的形式: {query} """)self.rewrite_chain=(self.rewrite_prompt|self.llm|StrOutputParser())# 2. HyDE生成假设答案selfhyde_embeddings=HypotheticalDocumentEmbedder.from_llm(llm=self.llm,base_embeddings=embedder)# 3. 集成检索self.bm25_retriever=BM25Retriever.from_texts(...)self.vector_retriever=vectorstore.as_retriever(k=10)self.ensemble_retriever=EnsembleRetriever(retrievers=[self.vector_retriever,self.bm25_retriever],weights=[0.7,0.3])# 4. 重排器self.reranker=CrossEncoder('BAAI/bge-reranker-base')# 5. 生成self.qa_prompt=ChatPromptTemplate.from_template(""" 基于以下上下文回答问题。如果上下文中没有相关信息,请如实说明。 上下文: {context} 问题:{question} """)defretrieve(self,query:str,top_k:int=5)->list:"""检索阶段"""# 重写Queryrewritten=self.rewrite_chain.invoke({"query":query})# 集成检索initial_results=self.ensemble_retriever.invoke(rewritten)# 重排pairs=[[query,doc.page_content]fordocininitial_results]scores=self.reranker.predict(pairs)ranked=sorted(zip(scores,initial_results),reverse=True)return[docfor_,docinranked[:top_k]]defgenerate(self,question:str,context_docs:list)->str:"""生成阶段"""context="\n\n".join([doc.page_contentfordocincontext_docs])chain=self.qa_prompt|self.llm|StrOutputParser()returnchain.invoke({"question":question,"context":context})definvoke(self,query:str)->dict:"""完整RAG Pipeline"""docs=self.retrieve(query)answer=self.generate(query,docs)return{"answer":answer,"source_documents":docs,"retrieval_query":query}七、评估与调优
7.1 RAGAS指标
fromragas.metricsimport(faithfulness,answer_relevancy,context_relevancy,)fromragasimportevaluate# 评估数据集eval_dataset=[{"question":"...","answer":"...","contexts":["..."],}]# 运行评估result=evaluate(eval_dataset,metrics=[faithfulness,answer_relevancy,context_relevancy],)result.to_pandas()7.2 调优参数
| 参数 | 调优范围 | 影响 |
|---|---|---|
| chunk_size | 200-2000 | 太大丢失精度,太小缺上下文 |
| top_k | 3-10 | 太多噪声,太少缺信息 |
| retrieval_weight | 0.3-0.7 | 向量vs BM25的权重 |
| rerank_top_n | 5-20 | 重排候选数量 |
| mmr_lambda | 0-1 | 相关性与多样性平衡 |
八、总结
高级RAG技巧的核心是全流程优化:
- 预检索:Query Rewriting/HyDE提升召回
- 检索层:Hybrid Search + Parent Doc平衡精度
- 后处理:Reranking + Compression提升精度
- 评估:RAGAS等指标量化效果
实践建议:
- 从Naive RAG开始,逐步迭代
- 每个优化点单独验证效果
- 记录不同配置的效果对比
习题
- 实现一个支持MMR的RAG系统
- 对比不同Chunk策略的检索效果
- 使用RAGAS评估现有RAG系统
- 尝试组合多种优化技巧
参考文献
- LangChain RAG文档
- RAGAS Evaluation Framework
- BAAI/bge-reranker模型
