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

向量数据库横评:Chroma vs Pinecone vs Weaviate vs Milvus

做 RAG 系统绕不开向量数据库的选型。这篇文章从工程角度做个横评。 核心功能对比 Chroma Pinecone Weaviate Milvus 部署方式 本地/云 纯云服务 本地/云 本地/云 开源 ✓ ✗ ✓ ✓ Python SDK ✓ ✓ ✓ ✓ 混合检索 部分 ✓ ✓ ✓ 适合规模 小-中 中-大 中-大 大 Chroma:本地开发首选 import chromadb client = chromadb.PersistentClient(path="./chroma_db") collection = client.get_or_create_collection( name="my_docs", metadata={"hnsw:space": "cosine"} ) # 添加文档 collection.add( documents=["RAG 是检索增强生成", "向量数据库存储高维向量"], ids=["doc1", "doc2"] ) # 查询 results = collection.query( query_texts=["什么是检索增强?"], n_results=3 ) 适合场景:本地开发、原型验证、数据量 < 100 万条。 优点:零配置启动,和 LangChain 深度集成。 缺点:性能和功能不适合大规模生产。 Pinecone:托管云服务 from pinecone import Pinecone, ServerlessSpec pc = Pinecone(api_key="your-key") pc.create_index( name="my-index", dimension=1536, metric="cosine", spec=ServerlessSpec(cloud="aws", region="us-east-1") ) index = pc.Index("my-index") # 插入向量 index.upsert(vectors=[ ("id1", [0.1, 0.2, ...], {"text": "原始文本", "source": "doc.pdf"}), ]) # 查询 results = index.query( vector=[0.1, 0.2, ...], top_k=5, filter={"source": "doc.pdf"}, # 元数据过滤 include_metadata=True ) 适合场景:不想运维、快速上线、预算充足。 ...

2023-10-30 · 1 min · Kada Liao

RAG 系统从零搭建:检索增强生成的原理与实践

LLM 有两个核心局限:知识有截止日期、无法访问私有数据。RAG(Retrieval-Augmented Generation)是目前解决这两个问题最主流的方案。 RAG 的基本流程 文档 → 切块 → Embedding → 存入向量库 ↓ 用户问题 → Embedding → 向量检索 → 召回相关块 → 组合 Prompt → LLM → 回答 四个核心步骤:文档处理、向量化、检索、生成。 文档切块 文档太长无法直接塞给模型,需要切成小块: from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, # 相邻块有重叠,避免信息割裂 separators=["\n\n", "\n", "。", ",", " "] ) chunks = splitter.split_documents(docs) chunk_overlap 是个容易忽略的参数——它让相邻的块有内容重叠,避免一句话被硬切断导致语义丢失。 Embedding 与向量存储 from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embeddings = OpenAIEmbeddings(model="text-embedding-3-small") # 将文档块向量化并存入 Chroma vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db" ) 向量数据库本质是做高维空间的近邻搜索。Chroma 适合本地开发,生产环境可以考虑 Pinecone、Weaviate 或自建 Milvus。 检索与生成 from langchain.chains import RetrievalQA retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4} # 召回最相关的 4 个块 ) qa_chain = RetrievalQA.from_chain_type( llm=ChatOpenAI(model="gpt-3.5-turbo"), retriever=retriever, return_source_documents=True ) result = qa_chain.invoke({"query": "公司的请假政策是什么?"}) print(result["result"]) 几个影响效果的关键点 切块策略:chunk_size 太小,单块缺乏上下文;太大,引入噪声。通常 300-600 tokens 是个比较合适的范围。 ...

2023-08-15 · 1 min · Kada Liao

Embedding 模型选型:OpenAI vs BGE vs 其他开源方案

在做 RAG 系统时,Embedding 模型的选型是个绕不过去的问题。选错了,后面调再多参数也是事倍功半。 什么是 Embedding Embedding 模型把文本转成高维向量,语义相近的文本在向量空间里距离更近。RAG 的检索质量,本质上取决于 Embedding 模型对语义的理解能力。 OpenAI text-embedding 系列 from openai import OpenAI client = OpenAI() response = client.embeddings.create( model="text-embedding-3-small", # 或 text-embedding-3-large input="什么是 RAG 系统?" ) vector = response.data[0].embedding # 1536 维向量 text-embedding-3-small: 维度:1536 价格:$0.02 / 1M tokens 性价比最高,大多数场景足够用 text-embedding-3-large: 维度:3072 价格:$0.13 / 1M tokens 效果更好,但贵 6 倍,仅在对检索质量要求极高时考虑 优点:接口简单,效果稳定,中英文都好。 缺点:按量计费,数据需要发到 OpenAI,有隐私顾虑。 BGE 系列(智源) BGE(BAAI General Embedding)是智源研究院开源的中文 Embedding 模型,中文效果出色: from FlagEmbedding import FlagModel model = FlagModel( "BAAI/bge-large-zh-v1.5", query_instruction_for_retrieval="为这个句子生成表示以用于检索相关文章:", use_fp16=True ) # 对查询加 instruction(重要!BGE 的查询和文档编码方式不同) query_embedding = model.encode_queries(["什么是 RAG?"]) # 文档不需要 instruction doc_embeddings = model.encode(["RAG 是检索增强生成..."]) BGE-large-zh-v1.5: ...

2023-06-12 · 1 min · Kada Liao