RAG 进阶优化:提升检索质量的七个方向

上一篇文章介绍了基础 RAG 的搭建。基础 RAG 跑起来之后,你会发现效果差强人意——召回的内容不够准、回答有时候答非所问。这篇文章梳理提升 RAG 效果的常见优化方向。 方向 1:切块策略优化 基础的固定大小切块太粗糙,几个更好的策略: 按语义切块(Semantic Chunking): from langchain_experimental.text_splitter import SemanticChunker from langchain_openai import OpenAIEmbeddings splitter = SemanticChunker( OpenAIEmbeddings(), breakpoint_threshold_type="percentile" ) chunks = splitter.split_text(document) 语义切块基于 Embedding 相似度判断段落边界,比按字符数切更合理。 父子文档(Parent-Child):小块用于检索,大块用于生成: from langchain.retrievers import ParentDocumentRetriever retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=InMemoryStore(), child_splitter=RecursiveCharacterTextSplitter(chunk_size=200), parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000), ) 小块召回精准,但上下文不足;大块提供足够上下文,但噪声多。父子文档两全其美。 方向 2:Query 改写 用户的提问往往不是最优的检索 query: async def rewrite_query(query: str) -> list[str]: prompt = f""" 生成 3 个不同角度的检索查询,帮助从文档库中找到回答以下问题的信息。 原始问题:{query} 输出格式:每行一个查询 """ response = await llm.ainvoke(prompt) queries = response.content.strip().split("\n") return [query] + queries # 原始查询 + 改写的查询 用多个 query 检索,再合并去重,召回率显著提升。 方向 3:HyDE(假设文档嵌入) 让模型先生成一个"假设的答案文档",用它来检索: async def hyde_retrieve(query: str) -> list[Document]: # 让模型生成一个假设的答案 hypothetical_doc = await llm.ainvoke( f"写一段简短的文章,回答以下问题(即使你不确定):{query}" ) # 用假设答案的向量来检索,而不是用问题的向量 docs = vectorstore.similarity_search(hypothetical_doc.content, k=4) return docs 假设答案的 Embedding 比问题的 Embedding 更接近文档的分布,检索效果往往更好。 ...

2024-05-22 · 2 min · Kada Liao