
RAG 框架选型避坑LangChain、LlamaIndex 和自研方案一、RAG 技术回顾与框架生态RAGRetrieval-Augmented Generation检索增强生成是解决 LLM 幻觉、知识过时、缺乏领域知识等问题的主流方案。RAG 的核心思想是在生成回答前先从外部知识库检索相关信息然后将检索结果提供给 LLM辅助其生成准确、有据可依的回答。RAG 的基本流程索引阶段将文档进行加载、分块、Embedding、存储到向量数据库。检索阶段将用户问题转换为 Embedding在向量数据库中检索最相关的文档块。生成阶段将检索到的文档块与用户问题组合成 Prompt发送给 LLM 生成回答。RAG 框架生态随着 RAG 技术的普及出现了多个 RAG 开发框架旨在简化 RAG 系统的构建。主流框架包括LangChain提供了完整的 RAG 模块RetrievalQA、ConversationalRetrievalChain支持多种向量数据库和 LLM。LlamaIndex原 GPT Index专注于数据索引和检索提供了丰富的索引结构List Index、Tree Index、Vector Store Index 等。Haystack由 deepset 开发面向生产环境的 RAG 框架支持灵活的 Pipeline 配置。RAGatouille专注于 RAG 评估和优化提供了先进的 RAG 技术如 HyDE、Cross-Encoder Reranker。自研方案基于 LLM API 和向量数据库自行实现 RAG 逻辑。每个框架都有其设计哲学和适用场景选型时需要仔细评估。# RAG 基本流程示例不使用框架原生实现 import openai import numpy as np from typing import List, Dict import json class SimpleRAG: 极简 RAG 实现 def __init__(self, embedding_model: str text-embedding-ada-002): self.embedding_model embedding_model self.documents [] # 存储文档块 self.embeddings [] # 存储 Embedding def index_documents(self, documents: List[str], chunk_size: int 500): 索引文档 # 1. 文档分块简化示例按字符数分块 chunks [] for doc in documents: for i in range(0, len(doc), chunk_size): chunks.append(doc[i:ichunk_size]) # 2. 生成 Embedding for chunk in chunks: embedding self._get_embedding(chunk) self.documents.append(chunk) self.embeddings.append(embedding) print(f索引完成{len(self.documents)} 个文档块) def _get_embedding(self, text: str) - List[float]: 获取文本的 Embedding response openai.Embedding.create( modelself.embedding_model, inputtext ) return response[data][0][embedding] def retrieve(self, query: str, top_k: int 3) - List[str]: 检索相关文档 # 1. 将查询转换为 Embedding query_embedding self._get_embedding(query) # 2. 计算相似度余弦相似度 similarities [] for doc_embedding in self.embeddings: similarity self._cosine_similarity(query_embedding, doc_embedding) similarities.append(similarity) # 3. 选择 top-k top_indices np.argsort(similarities)[-top_k:][::-1] retrieved_docs [self.documents[i] for i in top_indices] return retrieved_docs def _cosine_similarity(self, vec1: List[float], vec2: List[float]) - float: 计算余弦相似度 dot_product np.dot(vec1, vec2) norm1 np.linalg.norm(vec1) norm2 np.linalg.norm(vec2) return dot_product / (norm1 * norm2) def generate(self, query: str, top_k: int 3) - str: 生成回答 # 1. 检索相关文档 retrieved_docs self.retrieve(query, top_k) # 2. 构建 Prompt context \n\n.join(retrieved_docs) prompt f根据以下参考文档回答问题。如果参考文档中没有相关信息请说我不知道。 参考文档 {context} 问题{query} 回答 # 3. 调用 LLM response openai.ChatCompletion.create( modelgpt-3.5-turbo, messages[ {role: user, content: prompt} ] ) return response[choices][0][message][content] # 使用示例 rag SimpleRAG() # 索引文档 documents [ 公司年假政策入职满 1 年有 10 天年假满 3 年有 15 天满 5 年有 20 天。, 报销流程填写报销单附上发票提交给部门经理审批然后交财务部门。, 工作时间上午 9:00 到下午 6:00午休 12:00-13:00。 ] rag.index_documents(documents) # 提问 query 年假有多少天 answer rag.generate(query) print(f问题{query}) print(f回答{answer}) # 这个极简实现只有 100 行代码但实现了 RAG 的核心功能。 # 优点完全可控易于理解。 # 缺点缺乏优化如更好的分块策略、Hybrid Search、Rerank 等。二、LangChain RAG功能全面但黑盒化LangChain 提供了多种 RAG 实现方式从简单的RetrievalQA到复杂的ConversationalRetrievalChain覆盖了大部分应用场景。核心优势开箱即用几行代码就能搭建一个 RAG 系统。from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings from langchain.chains import RetrievalQA from langchain.llms import OpenAI # 1. 创建向量库假设已经有了 documents embeddings OpenAIEmbeddings() vectorstore Chroma.from_documents(documents, embeddings) # 2. 创建 QA 链 qa_chain RetrievalQA.from_chain_type( llmOpenAI(), chain_typestuff, retrievervectorstore.as_retriever() ) # 3. 使用 result qa_chain.run(年假有多少天)支持多种检索策略stuff、map_reduce、refine、map_rerank等链类型适应不同长度的文档。集成丰富支持几乎所有主流向量数据库Chroma、Pinecone、Weaviate、Qdrant、Milvus 等。支持对话历史ConversationalRetrievalChain可以维护多轮对话上下文。主要问题黑盒化严重LangChain 封装了 RAG 的全部细节。开发者往往会用但不懂。例如chain_typestuff具体是怎么组合的 Prompt检索到的文档如何排序如何优化这些问题难以干预。Prompt 不透明LangChain 使用了大量预设 Prompt这些 Prompt 可能不适合特定场景。虽然可以自定义但需要深入理解 LangChain 的内部机制。性能优化困难检索优化如 Hybrid Search、Rerank、分块策略优化等在 LangChain 中难以实现。需要绕过 LangChain直接操作底层组件。版本兼容性LangChain 迭代速度快API 变化频繁。代码可能在一两个月后就过时。使用建议快速原型使用 LangChain 快速验证 RAG 可行性。学习 RAG通过阅读 LangChain 源码理解 RAG 的实现细节。生产环境在 LangChain 基础上进行大量定制或考虑混合方案。# LangChain RAG 的透明度问题示例 from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings from langchain.chains import RetrievalQA from langchain.llms import OpenAI # 创建 RAG 系统 embeddings OpenAIEmbeddings() vectorstore Chroma.from_documents(documents, embeddings) qa_chain RetrievalQA.from_chain_type( llmOpenAI(), chain_typestuff, retrievervectorstore.as_retriever(search_kwargs{k: 3}) ) # 问题Prompt 是什么 # 我们不知道 LangChain 内部使用了什么 Prompt 来组合检索结果和用户问题。 # 解决方法查看源码或自定义 Prompt from langchain.prompts import PromptTemplate template 使用以下参考文档回答问题。如果参考文档中没有相关信息请说我不知道。 参考文档 {context} 问题{question} 回答 prompt PromptTemplate( templatetemplate, input_variables[context, question] ) # 使用自定义 Prompt qa_chain RetrievalQA.from_chain_type( llmOpenAI(), chain_typestuff, retrievervectorstore.as_retriever(), chain_type_kwargs{prompt: prompt} # 自定义 Prompt ) # 但即使这样依然不知道检索的具体逻辑、相似度计算方法等。三、LlamaIndex专注检索灵活性强LlamaIndex原 GPT Index是一个专注于数据索引和检索的框架。与 LangChain 的全家桶思路不同LlamaIndex 更加聚焦于 RAG 的检索环节提供了丰富的索引结构和检索策略。核心优势索引结构丰富LlamaIndex 提供了多种索引结构适应不同场景VectorStoreIndex基于向量的索引适合语义搜索。ListIndex顺序索引适合逐块阅读。TreeIndex树形索引适合多层次摘要。KeywordTableIndex关键词索引适合关键词匹配。KnowledgeGraphIndex知识图谱索引适合关系查询。检索策略灵活支持多种检索策略如 Top-k、Similarity、MMR、Metadata Filter以及组合检索如 Vector Keyword Hybrid Search。Node 概念LlamaIndex 将文档分块后封装为 NodeNode 包含文本、元数据、关系等信息。这种设计使得文档块的粒度更细便于管理。可解释性强LlamaIndex 提供了详细的检索日志可以看到每个 Node 的相似度得分、为何被检索出来等信息。与 LangChain 集成LlamaIndex 可以作为 LangChain 的检索器使用两者互补。主要问题学习曲线LlamaIndex 的概念Index、Node、Retriever、Response Synthesizer较多需要时间理解。文档质量部分功能的文档不够详细需要查看源码或示例。性能开销LlamaIndex 的抽象层也引入了一定开销但比 LangChain 轻量。使用建议检索需求复杂如果需要根据不同场景使用不同检索策略LlamaIndex 是好的选择。需要可解释性如果需要知道为何检索出某些文档LlamaIndex 提供了更详细的日志。与 LangChain 配合使用用 LlamaIndex 做检索用 LangChain 做 Agent。# LlamaIndex 使用示例多种索引结构 from llama_index import VectorStoreIndex, SimpleDirectoryReader from llama_index import ListIndex, TreeIndex from llama_index import KeywordTableIndex import os # 1. 加载文档 documents SimpleDirectoryReader(data/).load_data() # 2. 创建不同类型的索引 # VectorStoreIndex适合语义搜索 vector_index VectorStoreIndex.from_documents(documents) # ListIndex适合顺序阅读如逐章总结 list_index ListIndex.from_documents(documents) # TreeIndex适合多层次摘要从叶子节点向上汇总 tree_index TreeIndex.from_documents(documents) # KeywordTableIndex适合关键词查询 keyword_index KeywordTableIndex.from_documents(documents) # 3. 使用不同索引进行查询 # VectorStoreIndex 查询语义相似 vector_query_engine vector_index.as_query_engine() response vector_query_engine.query(年假有多少天) # ListIndex 查询顺序阅读 list_query_engine list_index.as_query_engine() response list_query_engine.query(总结第一章的主要内容) # TreeIndex 查询从底层向上汇总 tree_query_engine tree_index.as_query_engine() response tree_query_engine.query(总结整个文档) # 4. 高级功能组合检索Hybrid Search from llama_index.retrievers import VectorIndexRetriever, KeywordTableSimpleRetriever from llama_index.query_engine import RetrieverQueryEngine from llama_index.schema import NodeWithScore # 创建多个检索器 vector_retriever VectorIndexRetriever(indexvector_index, similarity_top_k3) keyword_retriever KeywordTableSimpleRetriever(indexkeyword_index) # 组合检索简单示例按顺序检索然后合并 class HybridRetriever: def __init__(self, retrievers): self.retrievers retrievers def retrieve(self, query_str): all_nodes [] for retriever in self.retrievers: nodes retriever.retrieve(query_str) all_nodes.extend(nodes) # 去重根据 Node ID seen_ids set() unique_nodes [] for node in all_nodes: if node.node.node_id not in seen_ids: seen_ids.add(node.node.node_id) unique_nodes.append(node) return unique_nodes hybrid_retriever HybridRetriever([vector_retriever, keyword_retriever])# LlamaIndex 的 Node 和 Metadata 使用示例 from llama_index import Document, VectorStoreIndex from llama_index.schema import MetadataMode # 1. 创建带有元数据的 Document documents [ Document( text公司年假政策入职满 1 年有 10 天年假。, metadata{source: handbook.pdf, page: 5, category: HR} ), Document( text报销流程填写报销单附上发票。, metadata{source: handbook.pdf, page: 8, category: Finance} ) ] # 2. 创建索引 index VectorStoreIndex.from_documents(documents) # 3. 检索并使用元数据过滤 from llama_index.retrievers import VectorIndexRetriever retriever VectorIndexRetriever( indexindex, similarity_top_k5, filters{category: HR} # 只检索 HR 类别的文档 ) nodes retriever.retrieve(年假有多少天) for node in nodes: print(f文本: {node.node.get_content()}) print(f元数据: {node.node.metadata}) print(f相似度得分: {node.score}) print() # 4. Node Postprocessor对检索结果进行后处理 from llama_index.postprocessor import SimilarityPostprocessor # 过滤掉相似度低的节点 postprocessor SimilarityPostprocessor(similarity_cutoff0.7) query_engine index.as_query_engine( node_postprocessors[postprocessor] ) response query_engine.query(年假有多少天)四、自研 RAG完全掌控的代价与 Function Calling 类似许多团队也选择自研 RAG 系统。自研 RAG 可以完全掌控检索逻辑、Prompt 设计、性能优化但也需要投入大量工程资源。自研 RAG 的优势完全掌控检索策略、分块逻辑、Prompt 模板、后处理逻辑都可以精确控制。轻量级无需引入庞大框架系统更轻量、更易维护。可优化性强可以针对特定场景深度优化如特定领域的分块策略、特定的 Rerank 模型。无依赖风险不依赖第三方框架避免版本兼容、框架停止维护等风险。自研 RAG 的挑战开发成本高从零实现 RAG 需要完成文档加载、分块、Embedding、向量存储、检索、Prompt 构建、生成、评估等全部环节。功能不完整前期往往只实现了基础功能缺乏高级功能如 Hybrid Search、Rerank、多模态检索。缺乏最佳实践框架凝聚了社区的智慧和踩坑经验。自研容易重复造轮子甚至引入已知问题。评估体系复杂RAG 的效果评估检索准确率、回答准确率、幻觉率需要建立完整的评估体系和 benchmark。自研 vs 框架决策因素数据敏感度高如果数据极其敏感如医疗记录、金融数据不能发送到第三方服务自研可以更好地控制数据流向。性能要求高如果 QPS 要求极高如每秒数千次查询自研可以进行极致优化。检索逻辑特殊如果业务需要特殊的检索逻辑如多阶段检索、跨语言检索、多模态检索框架可能难以支持。团队能力强团队对 RAG 原理有深入理解能够快速迭代和优化。# 自研 RAG 的高级功能示例Hybrid Search Rerank import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sentence_transformers import CrossEncoder class AdvancedRAG: 自研 RAG支持 Hybrid Search 和 Rerank def __init__(self, embedding_model: str text-embedding-ada-002): self.embedding_model embedding_model self.documents [] self.embeddings [] self.tfidf_vectorizer TfidfVectorizer() self.tfidf_matrix None # Rerank 模型Cross-Encoder self.reranker CrossEncoder(cross-encoder/ms-marco-MiniLM-L-6-v2) def index_documents(self, documents: List[str]): 索引文档包含向量索引和关键词索引 self.documents documents # 1. 生成向量 Embedding self.embeddings [self._get_embedding(doc) for doc in documents] # 2. 构建 TF-IDF 矩阵用于关键词检索 self.tfidf_matrix self.tfidf_vectorizer.fit_transform(documents) print(f索引完成{len(self.documents)} 个文档块) def retrieve_hybrid(self, query: str, top_k: int 10, alpha: float 0.5) - List[tuple]: Hybrid Search结合向量检索和关键词检索 Args: alpha: 向量检索权重0-11 表示只用向量检索0 表示只用关键词检索 # 1. 向量检索 query_embedding self._get_embedding(query) vector_scores [] for doc_embedding in self.embeddings: similarity self._cosine_similarity(query_embedding, doc_embedding) vector_scores.append(similarity) # 2. 关键词检索TF-IDF query_tfidf self.tfidf_vectorizer.transform([query]) tfidf_scores np.array(self.tfidf_matrix.dot(query_tfidf.T).toarray().flatten()) # 3. 归一化分数 vector_scores np.array(vector_scores) vector_scores (vector_scores - vector_scores.min()) / (vector_scores.max() - vector_scores.min() 1e-8) tfidf_scores (tfidf_scores - tfidf_scores.min()) / (tfidf_scores.max() - tfidf_scores.min() 1e-8) # 4. 加权融合 hybrid_scores alpha * vector_scores (1 - alpha) * tfidf_scores # 5. 选择 top-k top_indices np.argsort(hybrid_scores)[-top_k:][::-1] return [(i, hybrid_scores[i]) for i in top_indices] def retrieve_with_rerank(self, query: str, top_k: int 3, retrieve_k: int 10): 检索并使用 Rerank 模型重新排序 两阶段检索 1. 使用 Hybrid Search 检索 top-k 候选 2. 使用 Cross-Encoder Rerank 模型重新排序 # 第一阶段检索候选 candidates self.retrieve_hybrid(query, top_kretrieve_k) # 第二阶段Rerank pairs [[query, self.documents[i]] for i, score in candidates] rerank_scores self.reranker.predict(pairs) # 按 Rerank 分数排序 reranked sorted(zip(candidates, rerank_scores), keylambda x: x[1], reverseTrue) # 返回 top-k top_results [(candidates[i][0], rerank_scores[i]) for i, (candidate, score) in enumerate(reranked[:top_k])] return top_results def _get_embedding(self, text: str) - List[float]: 获取 Embedding简化示例 import openai response openai.Embedding.create( modelself.embedding_model, inputtext ) return response[data][0][embedding] def _cosine_similarity(self, vec1: List[float], vec2: List[float]) - float: 计算余弦相似度 dot_product np.dot(vec1, vec2) norm1 np.linalg.norm(vec1) norm2 np.linalg.norm(vec2) return dot_product / (norm1 * norm2 1e-8) # 这个自研 RAG 实现了 Hybrid Search 和 Rerank检索效果优于基础的向量检索。 # 但这些功能在 LlamaIndex 中也都有提供自研的必要性需要评估。五、总结RAG 框架选型需要综合考虑功能需求、团队技能、性能要求和长期维护成本。LangChain、LlamaIndex 和自研方案各有优劣没有最好的选择。关键要点LangChain 适合快速原型和简单场景。但需要注意其黑盒化问题在生产环境中可能需要大量定制。LlamaIndex 在检索灵活性和可控性上更优。特别是需要 Hybrid Search、Rerank、复杂索引结构时LlamaIndex 是更好的选择。自研 RAG 适合有特殊需求或极高性能要求的场景。但需要投入大量工程资源且需要团队对 RAG 有深入理解。组合使用是推荐方案用 LlamaIndex 做检索用 LangChain 做 Agent用自研代码处理特定优化点。关注 RAG 评估无论使用哪个框架都需要建立 RAG 评估体系检索准确率、回答准确率、幻觉率持续监控和优化。RAG 技术快速演进除了基础 RAG还有 Advanced RAG如 HyDE、Multi-Query Retrieval、Ensemble Retrieval和 Modular RAG如 Agentic RAG。选型时需要考虑框架是否支持这些高级技术。RAG 不是一劳永逸的解决方案。随着业务场景变化、数据量增长、用户反馈积累需要持续迭代和优化 RAG 系统。参考资料LangChain RAG 文档https://python.langchain.com/docs/modules/chains/popular/retrieval_qaLlamaIndex 文档https://docs.llamaindex.ai/Advanced RAG Techniques (AI Engineer Worlds Fair, 2024)RAGatouille GitHubhttps://github.com/bclavie/RAGatouille《RAG 实战手册》电子书本文基于作者的 RAG 实践经验。具体选型请结合实际场景和需求决定。