LangChain 与 LlamaIndex 实战教程
LangChain 和 LlamaIndex 是构建 LLM 应用最流行的两个框架。本文详细介绍它们的使用场景、核心概念和实战代码。
LangChain vs LlamaIndex:定位差异
| 特性 | LangChain | LlamaIndex |
|---|---|---|
| 核心理念 | 通用 LLM 应用开发框架 | 专门针对 RAG(检索增强生成) |
| 主要用途 | Agents、链式调用、多模态、工具调用 | 数据索引、检索、RAG 流水线 |
| 学习曲线 | 陡峭(组件多,概念复杂) | 平缓(专注于 RAG) |
| 灵活度 | 极高(可任意组合) | 中等(RAG 最佳实践预设) |
| 社区生态 | 庞大(2000+ 集成) | 快速增长(专注数据连接器) |
选择建议:
- Agent / Tool Use:选 LangChain
- RAG / 知识库问答:LlamaIndex(或 LangChain 的 RAG 模块)
- 混合:可以同时使用(LlamaIndex 做检索,LangChain 做下游处理)
LangChain 核心概念
LCEL(LangChain Expression Language)
LCEL 是 LangChain 0.1+ 推荐的编排方式,使用管道操作符 | 连接组件。
示例:简单问答链
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 1. 定义 Prompt 模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的助手。基于以下上下文回答问题:\n\n{context}"),
("human", "{question}")
])
# 2. 定义 LLM
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0)
# 3. 构建 RAG 链(检索 + 生成)
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# 4. 调用
response = rag_chain.invoke("什么是 RAG?")
print(response)为什么用 LCEL?
- 流式支持:天然支持流式输出
- 自动批处理:自动优化批量请求
- 异步支持:
ainvoke,astream - 可视化:
rag_chain.get_graph().draw_mermaid_png()
Agents(智能体)
Agent 让 LLM 能调用外部工具(搜索、计算、API 等)。
定义工具
python
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_tool_calling_agent
@tool
def search_web(query: str) -> str:
"""搜索网络获取最新信息"""
# 使用 Tavily, SerpAPI, 或自定义搜索
return search_tavily(query)
@tool
def calculate(expression: str) -> float:
"""计算数学表达式,如 '2+2*3'"""
return eval(expression)
tools = [search_web, calculate]创建 Agent
python
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent
llm = ChatOpenAI(model="gpt-4-turbo")
prompt = ChatPromptTemplate.from_messages([
("system", "你是有帮助的助手。使用工具回答问题。"),
("placeholder", "{chat_history}"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
result = agent_executor.invoke({"input": "今天北京天气如何?"})
print(result["output"])输出示例(Verbose=True):
> 进入 AgentExecutor 链...
思考:用户问天气,我需要使用搜索工具
行动:搜索 "北京天气"
观察:今天北京晴,15°C到25°C
回答:今天北京天气晴朗,温度在15°C到25°C之间。Memory(记忆)
LangChain 支持多种记忆类型:
python
from langchain.memory import ConversationBufferMemory, ConversationSummaryMemory
# 1. 缓冲记忆(记录所有对话)
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
# 2. 总结记忆(定期总结对话,节省 token)
memory = ConversationSummaryMemory(
llm=ChatOpenAI(),
memory_key="chat_history"
)使用示例:
python
from langchain.chains import ConversationChain
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True
)
conversation.predict(input="我叫小明")
# 输出:你好小明!
conversation.predict(input="我叫什么名字?")
# 输出:你叫小明。 (记忆生效)LlamaIndex 核心概念
LlamaIndex 专注于 RAG:从数据源 → 索引 → 检索 → 生成。
数据加载
python
from llama_index.core import SimpleDirectoryReader
# 加载本地文件(PDF, Markdown, TXT 等)
documents = SimpleDirectoryReader("data/").load_data()
# 或者使用特定加载器
from llama_index.readers.file import PDFReader
reader = PDFReader()
documents = reader.load_data(file="my_doc.pdf")索引构建
LlamaIndex 支持多种索引策略:
1. VectorStoreIndex(默认)
python
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_documents(documents)背后流程:
- 文档 → 文本分块(默认 256 tokens,重叠 10%)
- 每个块 → Embedding 模型(默认 text-embedding-ada-002)
- 向量存入向量数据库(默认内存,也可配置 Chroma/Milvus)
2. 树索引(Tree Index)
适用于文档内部有层次结构的情况:
python
from llama_index.core import TreeIndex
index = TreeIndex.from_documents(documents)3. 关键词索引(Keyword Index)
基于关键词快速召回,适合关键词搜索场景:
python
from llama_index.core import KeywordTableIndex
index = KeywordTableIndex.from_documents(documents)查询引擎
python
# 创建查询引擎(默认使用 VectorStoreIndex)
query_engine = index.as_query_engine(
similarity_top_k=3, # 检索前 3 个最相关 chunk
response_mode="tree_summarize" # 生成总结式回答
)
response = query_engine.query("什么是 RAG?")
print(response.response)
# 输出:RAG (Retrieval-Augmented Generation) 是一种结合检索和生成的技术...
# 查看来源
for node in response.source_nodes:
print(f"来源: {node.node.metadata['file_name']}, 相关性: {node.score:.2f}")实战:构建完整 RAG 应用
使用 LangChain 实现
python
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. 加载文档
loader = DirectoryLoader("./data/", glob="**/*.pdf")
documents = loader.load()
# 2. 文本分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
texts = text_splitter.split_documents(documents)
# 3. 向量化并存储
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(texts, embeddings, persist_directory="./chroma_db")
# 4. 检索器
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 5. 构建问答链(LCEL)
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from operator import itemgetter
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个有帮助的助手。基于以下上下文回答问题:\n\n{context}"),
("human", "{question}")
])
chain = (
{"context": retriever | (lambda docs: "\n\n".join(doc.page_content for doc in docs)),
"question": itemgetter("question")}
| prompt
| ChatOpenAI(model="gpt-4-turbo")
| StrOutputParser()
)
result = chain.invoke({"question": "什么是 LangChain?"})使用 LlamaIndex 实现
python
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter
# 1. 加载
documents = SimpleDirectoryReader("./data/").load_data()
# 2. 分块(更细粒度控制)
parser = SentenceSplitter(chunk_size=256, chunk_overlap=32)
nodes = parser.get_nodes_from_documents(documents)
# 3. 索引
index = VectorStoreIndex(nodes)
# 4. 查询
query_engine = index.as_query_engine(
similarity_top_k=5,
response_mode="compact" # 或 "tree_summarize", "refine"
)
response = query_engine.query("什么是 LlamaIndex?")
print(response.response)对比:
- LangChain 代码略多,但高度可定制,可以灵活组合各种工具
- LlamaIndex 更简洁,RAG 专用,内置很多 RAG 优化策略(如自动选择最佳检索模式)
高级功能
LangChain:Streaming(流式输出)
python
chain = ... # 前面的链
for chunk in chain.stream({"question": "给我写一首诗"}):
print(chunk, end="", flush=True)LlamaIndex:Hybrid Search(混合检索)
结合向量检索和关键词检索,提升召回率:
python
from llama_index.core.retrievers import HybridRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
# 两个检索器
vector_retriever = index.as_retriever(similarity_top_k=5)
keyword_retriever = index.as_retriever(similarity_top_k=5, vector_store=None)
hybrid_retriever = HybridRetriever(
vector_retriever=vector_retriever,
keyword_retriever=keyword_retriever,
weights=[0.6, 0.4] # 向量60%,关键词40%
)
query_engine = RetrieverQueryEngine.from_args(hybrid_retriever)
response = query_engine.query("你的问题")LlamaIndex:Citation 标注
让回答标注引用来源:
python
from llama_index.core.response.notebook_utils import display_response
query_engine = index.as_query_engine(
similarity_top_k=5,
response_mode="tree_summarize"
)
response = query_engine.query("解释一下注意力机制")
print(response.response)
print("\n--- 引用来源 ---")
for source in response.source_nodes:
print(f"文件: {source.node.metadata.get('file_name')}")
print(f"内容: {source.node.text[:100]}...")最佳实践建议
文档分块策略
- 按语义分割(Sentence/Tokens),不要跨段落
- 重叠 10-20% 避免信息断裂
- 特殊文档(法律、医学)考虑更大 chunk
Embedding 模型选择
- 通用场景:
text-embedding-ada-002(OpenAI) - 中文:
text2vec(BGE),m3e - 开源可自托管:
all-MiniLM-L6-v2,bge-large-zh
- 通用场景:
检索数量
- 通常 3-5 个 chunk 足够
- 如果信息分散,可增至 10-15
- 太多会引入噪音,降低质量
Rerank(重排序) 先用宽松策略召回 20-50 个,再用 Cross-Encoder 重排序取前 3-5:
pythonfrom llama_index.core.postprocessor import CohereRerank query_engine = index.as_query_engine( similarity_top_k=20, node_postprocessors=[CohereRerank(top_n=3)] )Prompt Engineering
- 系统提示明确指令: "你是一个有帮助的助手,基于上下文回答"
- 指示如答案不在上下文中,就说"我不知道"
- 限制幻觉: "只使用提供的上下文"
性能对比
| 框架 | 文档索引速度(100页 PDF) | 查询延迟(不含 LLM) | 易用性 |
|---|---|---|---|
| LangChain (Chroma) | ~5s | ~50ms | ⭐⭐⭐ |
| LlamaIndex | ~4s | ~40ms | ⭐⭐⭐⭐ |
| 纯手动 (Sentence Transformers) | ~3s | ~30ms | ⭐⭐ |
结论:性能差异不大,LlamaIndex 略快,LangChain 更灵活但稍复杂。
快速开始模板
LangChain RAG 模板
python
# requirements.txt
# langchain
# langchain-openai
# langchain-community
# chromadb
# openai
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# 加载 → 分块 → 向量化
loader = DirectoryLoader("./docs/", glob="**/*.md")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
texts = splitter.split_documents(docs)
vectorstore = Chroma.from_documents(
texts,
OpenAIEmbeddings(),
persist_directory="./chroma_db"
)
# 构建 QA 链
template = """你是一个有帮助的助手。基于以下上下文回答问题。如果不知道,就说不知道。
上下文:
{context}
问题:{question}
回答:"""
prompt = PromptTemplate.from_template(template)
qa = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4-turbo"),
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
chain_type_kwargs={"prompt": prompt}
)
# 使用
result = qa.invoke({"query": "你的问题"})
print(result["result"])LlamaIndex 模板
python
# requirements.txt
# llama-index
# llama-index-vector-stores-chroma
# openai
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter
from llama_index.llms.openai import OpenAI
# 1. 加载与索引
documents = SimpleDirectoryReader("./docs/").load_data()
parser = SentenceSplitter(chunk_size=256, chunk_overlap=32)
nodes = parser.get_nodes_from_documents(documents)
index = VectorStoreIndex(nodes)
# 2. 创建查询引擎
query_engine = index.as_query_engine(
similarity_top_k=3,
response_mode="compact",
llm=OpenAI(model="gpt-4-turbo")
)
# 3. 查询
response = query_engine.query("你的问题")
print(response.response)推荐:
- 快速原型 → LlamaIndex
- 复杂 Agent 应用 → LangChain
- 生产环境 RAG → 两者皆可,按团队熟悉度选择
总结
- LangChain 是"瑞士军刀",组件多,灵活但学习曲线陡
- LlamaIndex 是"专业工具",RAG 场景开箱即用
- 两者可以混合使用(如 LlamaIndex 检索 + LangChain 流式输出)
建议从一个简单 RAG 开始,逐步深入各个组件。
