一、两个代码共同实现的核心流程

无论是 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 约束和结果可追溯性。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐