一、两个代码共同实现的核心流程
无论是 LangChain 还是 LlamaIndex,本质上实现的都是标准 RAG(Retrieval-Augmented Generation,检索增强生成)流程。核心思想不是让大模型直接凭记忆回答,而是先从知识库中检索相关资料,再把检索结果作为上下文交给大模型生成答案。
文档读取 → 文档切分 → Embedding 向量化 → 向量数据库/向量索引 → 问题检索 Top-K 文档块 → 拼接 context → 大模型生成答案
|--------|-----------------------|---------------------------------------------------|
| 步骤 | 作用 | 关键对象 |
| 文档读取 | 把 txt、PDF、网页等资料读入程序 | TextLoader / SimpleDirectoryReader |
| 文档切分 | 把长文档切成可检索的小块 | RecursiveCharacterTextSplitter / SentenceSplitter |
| 向量化 | 把文本转换为语义向量 | HuggingFaceEmbeddings / OpenAIEmbedding |
| 向量存储 | 保存文本块向量并支持相似度检索 | Chroma / FAISS / VectorStoreIndex |
| 检索增强 | 根据问题召回相关片段作为 context | similarity_search / Retriever |
| 答案生成 | 让 LLM 基于 context 生成答案 | ChatOpenAI / QueryEngine |
二、LangChain 代码知识点
1. 文档读取:TextLoader
loader = TextLoader("./藜麦.txt", encoding="utf-8")
documents = loader.load()
TextLoader 用于读取本地 txt 文件。Windows 中文系统默认编码可能是 GBK,而很多文本文件实际是 UTF-8,因此必须显式设置 encoding="utf-8",否则容易出现 UnicodeDecodeError。
|------------------|-------------------------------|
| 对象/参数 | 含义 |
| loader | 文档加载器,负责读取本地文本文件 |
| documents | Document 对象列表,虽然只有一个文件,也会返回列表 |
| page_content | Document 中的正文内容 |
| metadata | Document 中的元数据,例如 source 文件路径 |
| encoding="utf-8" | 指定文本编码,避免 Windows 默认 GBK 解码失败 |
2. 文档切分:RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=256,
chunk_overlap=50,
separators="\\n\\n", "\\n", "。", "!", "?", ";", ",", " "
)
RAG 不能简单地把整篇文档作为一个整体检索。切分的目的是把长文档变成多个语义较完整的小块,使检索更精准。
|------------------|------------------------------|
| 参数 | 作用 |
| chunk_size=256 | 每个文本块大约 256 个字符,适合短中文文档 |
| chunk_overlap=50 | 相邻文本块保留 50 个字符重叠,避免重要句子被截断 |
| separators | 指定中文分隔符,优先按段落、句号、问号、分号、逗号等切分 |
3. 生成切分后的文本块
texts = text_splitter.create_documents(
documents\[0.page_content],
metadatas=documents\[0.metadata]
)
texts 是切分后的 Document 块列表。后续构建向量库时应使用 texts,而不是原始 documents。因为 RAG 检索的基本单位是 chunk,而不是整篇文章。
4. Embedding 模型:HuggingFaceEmbeddings
embedding = HuggingFaceEmbeddings(
model_name="moka-ai/m3e-small",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True}
)
Embedding 的作用是把文本转成高维向量,使计算机能够基于语义相似度进行检索。例如"藜一般几月播种"和"藜麦什么时候种植"文字不同,但语义相近,向量距离也应较近。
|--------------------------------|---------------------|
| 参数 | 含义 |
| model_name="moka-ai/m3e-small" | 中文语义向量模型,适合中文检索 |
| device="cpu" | 使用 CPU 推理,小模型可以正常运行 |
| normalize_embeddings=True | 对向量归一化,便于相似度检索 |
5. 向量数据库:Chroma
db = Chroma.from_documents(
documents=texts,
embedding=embedding
)
Chroma 用于保存"文本块 + 向量 + 元数据"。当用户输入问题时,系统会先把问题转为向量,再在 Chroma 中寻找语义最相似的文本块。
注意:这里 documents=texts 是正确写法。如果用 documents=documents,则相当于把整篇文章作为一个大块存入向量库,检索粒度会变粗。
6. 相似度检索:similarity_search
search_result = db.similarity_search(question, k=4)
|----------|---------------|
| 参数 | 作用 |
| question | 用户输入的问题 |
| k=4 | 返回最相似的 4 个文本块 |
RAG 答错时,首先应检查 similarity_search 返回的片段是否相关。如果检索结果不相关,大模型后续再强也难以给出正确答案。
7. 大模型配置:ChatOpenAI
llm = ChatOpenAI(
model="deepseek-v3",
temperature=0,
max_retries=2,
api_key="...",
base_url="https://api.apiyi.com/v1"
)
ChatOpenAI 可以调用 OpenAI 兼容接口,不一定只调用 OpenAI 官方模型。这里通过 APIyi 的 base_url 调用 deepseek-v3。RAG 场景中 temperature 通常设为 0,使回答更稳定、更少发散。
安全建议:不要把真实 API Key 写死在代码中,更推荐使用环境变量,例如 api_key=os.getenv("APIYI_API_KEY")。
8. RAG Prompt 模板
prompt = PromptTemplate.from_template("""
你是一个严谨的知识库问答助手。
请只根据给定资料回答问题。
如果资料中没有相关信息,请回答"根据现有资料无法确定"。
资料:
{context}
问题:
{question}
答案:
""")
Prompt 的作用是约束大模型:只能根据检索到的 context 回答,如果资料中没有答案,就明确回答无法确定。这是减少幻觉的关键。
9. LCEL 链式调用
chain = prompt | llm | StrOutputParser()
这是 LangChain 新版 LCEL 写法,表示 PromptTemplate → LLM → 字符串输出解析器。相比旧版 LLMChain,这种写法更推荐,也更符合新版 LangChain 的设计。
10. RAG 问答函数
def rag_answer(question: str, top_k: int = 4):
docs = db.similarity_search(question, k=top_k)
context = "\n\n".join(f"资料{i + 1}:{doc.page_content}" for i, doc in enumerate(docs))
answer = chain.invoke({"context": context, "question": question})
return answer, docs
该函数完成完整 RAG:输入问题 → 检索相关文本块 → 拼接 context → 调用大模型 → 返回答案和参考片段。返回 source_docs 可以帮助检查答案依据。
三、LlamaIndex 代码知识点
1. LlamaIndex 的定位
LlamaIndex 更偏向"数据接入 + 索引构建 + 查询引擎"的 RAG 框架。相比 LangChain 手写流程,LlamaIndex 封装程度更高,尤其适合文档问答、PDF 解析、索引管理、查询引擎和 Agent 工具调用。
|-----------------|-----------------------|
| 模块 | 作用 |
| Data Connectors | 读取本地文件、PDF、网页、数据库等数据源 |
| Data Indexes | 对文档进行切分、向量化和索引构建 |
| Query Engines | 基于索引回答用户问题 |
| Chat Engines | 支持带上下文和记忆的聊天 |
| Data Agents | 让大模型根据任务选择工具并调用 |
2. Settings 全局配置
Settings.llm = OpenAI(...)
Settings.embed_model = OpenAIEmbedding(...)
Settings 是 LlamaIndex 的全局配置,用来指定默认 LLM 和 Embedding 模型。后续创建索引和查询时,如果没有单独指定,就会默认使用 Settings 中的模型配置。
3. 文档读取:SimpleDirectoryReader / PyMuPDFReader / LlamaParse
LlamaIndex 支持多种数据读取方式。SimpleDirectoryReader 适合读取目录下的 txt、pdf 等文件;PyMuPDFReader 适合读取普通 PDF;LlamaParse 适合复杂 PDF、表格、论文和版式复杂文档。
|------------------------|-------------------------------|
| Reader | 适用场景 |
| SimpleDirectoryReader | 读取本地目录中的多种文件 |
| PyMuPDFReader | 读取普通 PDF 文本 |
| BeautifulSoupWebReader | 读取网页内容 |
| LlamaParse | 解析复杂 PDF、表格、论文、报告,输出 Markdown |
4. 文档切分:SentenceSplitter
splitter = SentenceSplitter(
chunk_size=1024,
chunk_overlap=100,
paragraph_separator="\n\n"
)
nodes = splitter.get_nodes_from_documents(documents)
LlamaIndex 中切分后的文本块叫 nodes,对应 LangChain 中的 texts。nodes 是后续构建 VectorStoreIndex、SummaryIndex 和 QueryEngine 的基础。
5. 向量索引:VectorStoreIndex
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("问题")
VectorStoreIndex 会自动完成文档切分、向量化、索引构建等流程。相比 LangChain 中手动构建 Chroma,LlamaIndex 的写法更简洁。
6. FAISS 与 StorageContext
faiss_index = faiss.IndexFlatL2(embed_dim)
vector_store = FaissVectorStore(faiss_index=faiss_index)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
FAISS 是高性能向量检索库,适合大规模向量检索。StorageContext 是 LlamaIndex 中统一管理存储后端的对象,用于指定向量存储、文档存储、索引存储等。
7. Retriever 与 QueryEngine
vector_retriever = VectorIndexRetriever(index=vector_index, similarity_top_k=5)
query_engine = RetrieverQueryEngine.from_args(retriever=vector_retriever)
Retriever 负责检索相关 nodes,QueryEngine 负责把检索结果组织成上下文并调用 LLM 生成答案。这相当于 LangChain 中自己写的 rag_answer 函数。
8. SummaryIndex 与全文总结
summary_index = SummaryIndex(nodes)
summary_query_engine = summary_index.as_query_engine(
response_mode="tree_summarize",
use_async=True
)
SummaryIndex 用于全文总结。tree_summarize 会先对多个文本块分别总结,再递归合并成最终总结,适合长文档概括。use_async=True 可以提高长文档总结效率。
9. Agent 工具调用:vector_tool 与 summary_tool
LlamaIndex 可以把查询函数或查询引擎包装成工具。例如 vector_tool 负责具体问题检索,summary_tool 负责全文总结。Agent 可以根据用户问题自动判断调用哪个工具。
|--------------|-----------------------------|
| 工具 | 适合问题 |
| vector_tool | 查询具体事实,例如"第几页讲了什么""某个指标是多少" |
| summary_tool | 总结全文,例如"概括这篇文章""总结主要内容" |
四、LangChain 与 LlamaIndex 对比
|-----------|--------------------------------|------------------------------------------------|
| 对比项 | LangChain | LlamaIndex |
| 文档读取 | TextLoader 等 Loader | SimpleDirectoryReader、PyMuPDFReader、LlamaParse |
| 切分对象 | texts | nodes |
| 切分器 | RecursiveCharacterTextSplitter | SentenceSplitter |
| Embedding | HuggingFaceEmbeddings | OpenAIEmbedding / 自定义 Embedding |
| 向量库 | Chroma | FAISS / VectorStoreIndex / 其他 VectorStore |
| 检索方式 | db.similarity_search() | VectorIndexRetriever.retrieve() |
| 问答方式 | 手写 rag_answer + chain.invoke | query_engine.query() |
| Prompt 控制 | PromptTemplate | system_prompt / query engine 参数 |
| 工具调用 | 可用 Tool/Runnable 自定义 | FunctionTool / QueryEngineTool 更方便 |
| 特点 | 灵活、流程清楚、适合学习底层 | 封装高、适合复杂文档问答系统 |
五、学习 RAG 时必须掌握的关键点
1. RAG 的关键不是"让模型更聪明",而是"让模型基于外部知识回答"。
私有文档、课程资料、企业知识库、论文报告等通常不在大模型训练数据中,因此需要通过检索把相关资料喂给模型。
2. Chunk 质量直接影响检索质量
chunk 太小会导致语义不完整,chunk 太大会导致检索不精准。中文短文档可以使用 256-500 字符,长 PDF 或报告可以使用 800-1200 字符,并配合适当 overlap。
3. Embedding 决定语义匹配能力
如果中文资料使用不适合中文的 Embedding,检索结果可能偏差较大。常见中文模型包括 m3e-small、bge-small-zh、bge-base-zh 等。
4. 检索结果要可检查
调试 RAG 时应打印检索到的参考片段。只有确认检索片段相关,才能继续判断模型回答是否正确。
5. Prompt 必须限制模型不要胡编
例如"请只根据给定资料回答;如果资料中没有相关信息,请回答无法确定"。这是知识库问答中非常重要的安全约束。
6. API Key 不应写死在代码里
真实项目中应使用环境变量或配置文件管理 API Key,避免泄露。
六、推荐排错顺序
|--------|------------------|--------------------------------------|
| 顺序 | 检查内容 | 常见问题 |
| 1 | 文件是否读取成功 | 路径错误、编码错误、文件为空 |
| 2 | 文本是否切分成功 | chunk 太大或太小、中文分隔符不合适 |
| 3 | Embedding 是否加载成功 | 模型下载失败、网络问题、设备配置错误 |
| 4 | 向量库是否构建成功 | Chroma/FAISS 安装或版本问题 |
| 5 | 检索片段是否相关 | Embedding 不合适、top_k 设置不合理、chunk 策略不佳 |
| 6 | Prompt 是否合理 | 没有限制模型只基于资料回答 |
| 7 | LLM 接口是否成功 | API Key、base_url、model 名称或权限问题 |
七、最终结论
LangChain 代码适合学习 RAG 的底层流程,能清楚看到读取、切分、向量化、检索、拼接 context 和调用大模型的每一步。LlamaIndex 代码更适合快速搭建文档问答系统,尤其在复杂 PDF 解析、索引构建、查询引擎、总结工具和 Agent 调用方面更方便。
两者的核心思想完全一致:先检索,再生成。真正决定 RAG 效果的关键不是单一大模型,而是文档切分质量、Embedding 检索质量、向量库召回效果、Prompt 约束和结果可追溯性。