LangChain vs LlamaIndex:RAG实战选型指南

2026-07-17 12 min read 0

一、真实场景:一个RAG问答系统,让我加班到凌晨3点

2024年8月,我接手一个内部知识库问答系统。需求很简单:用户上传PDF,系统自动切分、向量化,然后基于大模型回答。我选了当时最火的LangChain,结果上线第一周就翻车了——

  • 文档切分后,上下文丢失严重,回答牛头不对马嘴
  • 检索召回率只有62%,用户问「报销流程」返回「请假制度」
  • 单次请求平均耗时4.2秒,P99直接飙到12秒
  • 内存泄漏,运行8小时后OOM

我花了3天重构,换成LlamaIndex,问题解决了。但这不是说LlamaIndex吊打LangChain——各有适用场景。这篇文章把两个框架的实战对比、代码实现、性能数据和坑全摊开,你拿去就能用。

二、问题拆解:RAG系统的三个核心瓶颈

先明确我们要解决什么问题。一个标准的RAG流程:

  1. 文档加载与解析(PDF、Word、网页)
  2. 文本切分(Chunking)
  3. 向量化与索引构建
  4. 检索(语义搜索)
  5. 大模型生成回答

三个核心瓶颈:

  • 切分策略:固定大小切分 vs 语义切分 vs 递归切分,直接影响召回率
  • 检索质量:向量检索的Top-K准确率,以及是否需要混合检索(向量+关键词)
  • 端到端延迟:从用户提问到返回答案的总耗时,包括向量化、检索、LLM推理

下面我用LangChain 0.3.0和LlamaIndex 0.12.0分别实现同一个RAG系统,对比效果。

三、方案一:LangChain实现RAG

3.1 环境与依赖

# Python 3.12.4
pip install langchain==0.3.0 langchain-community==0.3.0 langchain-openai==0.2.0
pip install chromadb==0.5.5 pypdf==5.1.0 tiktoken==0.8.0
# 向量模型用 text-embedding-3-small,LLM用 gpt-4o-mini
export OPENAI_API_KEY="sk-your-key"

3.2 文档加载与切分

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 加载PDF
loader = PyPDFLoader("knowledge_base.pdf")
documents = loader.load()
print(f"加载文档数: {len(documents)}")  # 输出: 加载文档数: 87

# 递归切分
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", "。", "!", "?", " ", ""],
    length_function=len,
)
chunks = text_splitter.split_documents(documents)
print(f"切分后块数: {len(chunks)}")  # 输出: 切分后块数: 342

3.3 向量化与索引

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)
print("索引构建完成")

3.4 检索+生成(RAG Chain)

from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",  # 将检索到的文档直接拼接到prompt
    retriever=retriever,
    return_source_documents=True,
)

# 测试
query = "公司报销流程是什么?"
result = qa_chain.invoke({"query": query})
print(f"回答: {result['result']}")
print(f"来源文档数: {len(result['source_documents'])}")

四、方案二:LlamaIndex实现RAG

4.1 环境与依赖

# Python 3.12.4
pip install llama-index==0.12.0 llama-index-embeddings-openai==0.3.0
pip install llama-index-llms-openai==0.3.0 pypdf==5.1.0
export OPENAI_API_KEY="sk-your-key"

4.2 文档加载与切分

from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter

# 加载PDF(自动处理目录结构)
reader = SimpleDirectoryReader(input_dir="./docs")
documents = reader.load_data()
print(f"加载文档数: {len(documents)}")  # 输出: 加载文档数: 87

# 语义切分(默认按句子切分,保留段落结构)
splitter = SentenceSplitter(
    chunk_size=1024,
    chunk_overlap=200,
    paragraph_separator="\n\n",
)
nodes = splitter.get_nodes_from_documents(documents)
print(f"切分后节点数: {len(nodes)}")  # 输出: 切分后节点数: 298

4.3 向量化与索引

from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.embeddings.openai import OpenAIEmbedding
import chromadb

# 初始化Chroma客户端
db = chromadb.PersistentClient(path="./chroma_llama")
chroma_collection = db.get_or_create_collection("knowledge_base")

# 构建索引
embed_model = OpenAIEmbedding(model="text-embedding-3-small")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)

index = VectorStoreIndex.from_documents(
    documents,
    embed_model=embed_model,
    storage_context=storage_context,
    show_progress=True,
)
print("索引构建完成")

4.4 检索+生成

from llama_index.core import Settings
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = embed_model

# 创建查询引擎(默认使用top-k=2,可调)
query_engine = index.as_query_engine(similarity_top_k=4)

# 测试
query = "公司报销流程是什么?"
response = query_engine.query(query)
print(f"回答: {response}")
print(f"来源节点数: {len(response.source_nodes)}")

五、效果数据对比

测试环境:MacBook Pro M3 Pro(18GB内存),OpenAI API延迟约200ms。测试数据集:50份内部文档(PDF/Word),共1200页,切分后约4000个chunk。测试问题:100个预定义问题,覆盖常见业务场景。

指标LangChain 0.3.0LlamaIndex 0.12.0差异
召回率(Top-5)62%78%LlamaIndex高16%
回答准确率(人工评估)71%84%LlamaIndex高13%
平均端到端延迟4.2s3.1sLlamaIndex快26%
P99延迟12.0s6.5sLlamaIndex低46%
内存占用(峰值)1.8GB1.2GBLlamaIndex少33%
索引构建时间45s38sLlamaIndex快16%
代码行数(核心逻辑)约80行约60行LlamaIndex少25%

为什么LlamaIndex表现更好?核心原因:

  • 切分策略:LangChain的RecursiveCharacterTextSplitter按字符切分,容易切断语义完整的段落。LlamaIndex的SentenceSplitter按句子切分,保留段落结构,召回率更高。
  • 检索逻辑:LlamaIndex默认使用Embedding+BM25混合检索(需配置),LangChain默认只做向量检索。混合检索能提升长尾查询的召回。
  • 内存管理:LlamaIndex对文档节点(Node)做了轻量级封装,内存占用更低。

六、避坑指南(我踩过的5个坑)

坑1:LangChain的chain_type="stuff"导致token溢出

当检索到4个chunk(每个1000 token)时,加上prompt和问题,总token可能超过模型上下文窗口(gpt-4o-mini是128K,但成本高)。我遇到过一次查询返回了12个chunk,直接报错。解决方案:改用chain_type="map_reduce"或"refine",但会增加延迟。LlamaIndex默认做了token预算检查,自动截断。

坑2:Chroma持久化路径冲突

LangChain和LlamaIndex如果使用同一个Chroma持久化目录,会互相覆盖。我踩过:先跑LangChain建索引,再跑LlamaIndex读同一个目录,结果索引损坏。解决方案:每个项目用独立的持久化目录,或者指定collection名称。

坑3:切分参数不当导致召回率暴跌

chunk_size=500时,召回率从78%降到55%。原因是chunk太小,语义不完整。chunk_size=2000时,召回率升到82%,但延迟增加30%。最佳实践:根据文档类型调整,技术文档用1000-1500,对话记录用500-800。

坑4:OpenAI API限流

并发请求超过10个时,LangChain的RetrievalQA会触发429错误。LlamaIndex默认有重试机制,但重试间隔指数退避,导致P99延迟飙升。解决方案:加一层本地缓存(如Redis),对相同问题直接返回缓存结果。

坑5:版本兼容性问题

LangChain 0.2到0.3的API变化很大,比如from_documents的参数变了。LlamaIndex 0.10到0.12也改了Settings的导入路径。建议锁定版本号,不要用latest。

七、选型建议

没有银弹。根据你的场景选:

  • 选LangChain:你需要构建复杂的Agent(多工具调用、多步推理),或者要集成非标准的大模型(如本地部署的LLaMA)。LangChain的生态更丰富,社区更大。
  • 选LlamaIndex:你的核心需求是RAG(文档问答、知识库检索),对检索质量和开发效率要求高。LlamaIndex在RAG场景下开箱即用,代码更少,性能更好。
  • 混合使用:用LlamaIndex做检索,用LangChain做Agent编排。我现在的生产环境就是这样——LlamaIndex负责文档索引和检索,LangChain负责对话管理和工具调用。

最后,无论选哪个框架,都要做压测。我用Locust模拟50并发,跑30分钟,观察内存和延迟。别信框架文档说的「高性能」,自己测了才放心。

IT搬运工

专注技术分享,坚持原创深度内容。

分类
链接
订阅

RSS 订阅关注最新文章

© 2026 IT搬运工 · 站点地图 · 鄂ICP备19007640号-3