Llama-index RAG避坑指南:最新实战经验
在使用Llama-index构建RAG问答系统的时候总是遇到各种各样的问题,网上看了很多的资料,要么过时了要么完全不对根本跑不通。问GPT等LLM也是一样的问题,由于这个库本身更新较快而且似乎有过大的结构更新导致AI的回答也几乎都过时了。官方文档尽管可以参考,但是也存在着一些不清不楚的地方。在这里我将总结我遇到的问题,还有一些不明显写出来的包都列举在下面(如果需要可以私信我获取requirements.txt文件),来帮助各位少走一些弯路。
这里是官方的参考网站:LlamaIndex - LlamaIndex

在这里找到Examples - LlamaIndex地方,里面有很多很多示例。你在这里找到你想要做的方面,仔细看怎么导入包并使用就可以了。
pip install llama-index
(Llamaindex库经过几次大更新有些包已经更换了位置,就平时使用下来你问GPT这个包在哪里,如果不行的话加入一个llama_index.core的路径就可以使用。另外使用这个语句安装的llama库并不全,使用其他组件的时候需要另外pip,我这里举一个例子)

比如你要使用bm25做检索的依据,那你就还需要另外执行下面这条语句来装上llama对应的库,之后才能使用这个库的方法。如果在尝试的时候老是显示找不到import的包或者其他报错,都可以通过在路径上加一个.core(如上)或者查看官方文档是否少pip了一个组件解决。
pip install llama-index-retrievers-bm25
# pip过后才能正常使用这个库
from llama_index.retrievers.bm25 import BM25Retriever
# 常用的几个需要单独pip的包
pip install llama-index-vector-stores-faiss
pip install llama-index-embeddings-huggingface
pip install llama-index
pip install llama-index-postprocessor-cohere-rerank
pip install llama-index-postprocessor-voyageai-rerank
pip install llama-index-postprocessor-flag-embedding-reranker
pip install llama-index-retrievers-rerank
pip install faiss-cpu
pip install langchain_huggingface
最后,将你的llama-index库更新到最新的版本,就可以开始快乐(痛苦)使用啦~
我使用的是本地部署的LLM,所以全篇没有配置连接OPENAI模型密钥。大家可以通过使用自定义的API接口或者本地部署的LLM服务,也挺方便的。比如这样:
import requests
import json
def llm_client(content:str="你好,介绍一下你自己?"):
url = "http://127.0.0.1:8000/v1/chat/completions"
headers = {"Content-Type": "application/json", "Authorization": "Bearer "}
data = {
"model": "/workspace/models/Qwen3-0.6B",
"messages": [{"role": "user", "content": content}],
"temperature": 0.7,
}
response = requests.post(url, headers=headers, data=json.dumps(data))
print("Status code:", response.status_code)
1. 文档处理(读取分块)
(如果你的llama_index找不到SentenceSplitter或者SimpleDirectoryReader,很有可能是上述的问题)使用RAG前首先需要准备你的知识数据并且做预处理(主要就是分文档划结构分块等方面做文章)。我这里就使用最简单的txt文本和按照句子分类。data_path是一个文件夹,data目录下存放着你所有的txt文件(我看官方介绍似乎docx,excel,pdf也能读取,不过还没有尝试),它会自动读取并拆分为nodes(注意nodes非常重要,这就是你直接嵌入和检索的文本块)。
def load_and_chunk(data_path="./data"):
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter
documents = SimpleDirectoryReader(data_path).load_data()
print(f"[load_and_chunk] Loaded {len(documents)} documents.")
splitter = SentenceSplitter(
chunk_size=256,
chunk_overlap=20,
)
nodes = splitter.get_nodes_from_documents(documents)
print(f"[load_and_chunk] Parsed {len(nodes)} nodes.")
return documents, nodes
2.嵌入并构建索引
把文本分好块以后就可以使用嵌入模型获得嵌入向量并构建索引了。这里我使用的是bge的中文模型做词嵌入(也可以不指定它会下载默认模型给你做)。
def embedding_and_index():
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import Settings
documents, nodes = load_and_chunk()
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-large-zh",model_kwargs={"device": "cuda"},encode_kwargs={"normalize_embeddings": True},)
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_documents(documents) # 这里我通过documents构建索引,它会使用默认方法自动分块,之后我会介绍使用nodes构建索引的
return index
那么在得到嵌入向量和索引的同时,我们也需要考虑持久化的问题。(之前我采用MongoDB发现不行,似乎是需要云平台才可以,所以最后换成了faiss,只需要pip install faiss就可以使用)d是嵌入向量维度,这是你的嵌入模型决定的。下面的代码就根据nodes来构建并保存到本地,默认会保存在/storage文件夹,如果需要指定的话可以在index.storage_context.persist("这里指定你的保存位置")
def build_and_persist_index(
data_path="./data",
):
import faiss
d = 1024
faiss_index = faiss.IndexFlatL2(d)
from langchain_huggingface import HuggingFaceEmbeddings # 这里我发现有多个库的方法可以使用,有的时候可以有的时候不行。如果你遇到不行的情况就换做llama_index中的这个方法(注意需要另外pip一个包,可以通过官方文档找到)
from llama_index.core import Settings, ServiceContext, StorageContext
from llama_index.core import VectorStoreIndex
from llama_index.core import SimpleDirectoryReader
from llama_index.vector_stores.faiss import FaissVectorStore
documents, nodes = load_and_chunk(data_path)
# 构建 Faiss 向量存储
vector_store = FaissVectorStore(faiss_index=faiss_index)
# 保存索引
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 构建索引
index = VectorStoreIndex(nodes=nodes, storage_context=storage_context)
index.storage_context.persist() # 保存索引
return index
保存过后将来使用索引只需要这样就可以了。
def load_index_from_fassi():
from llama_index.core import StorageContext,load_index_from_storage
from llama_index.vector_stores.faiss import FaissVectorStore
vector_store = FaissVectorStore.from_persist_dir("./storage")
storage_context = StorageContext.from_defaults(
vector_store=vector_store, persist_dir="./storage"
)
index = load_index_from_storage(storage_context=storage_context)
return index
3.通过索引查询你的知识块:
(在这里我也遇到了llama_index.core.retrievers导入不了的问题,还是之前那个原因)这里我的查找是没有调用默认的大模型进行总结和推理的,仅仅把知识库中的文本块检索出来。results就是它保存的Documents类的列表。可以通过我注释的方法把内容content提取出来。
def query_index(
index,
query_text,
similarity_top_k=3,
):
from llama_index.core.retrievers import VectorIndexRetriever
retriever = VectorIndexRetriever(
index=index,
similarity_top_k=similarity_top_k,
)
results = retriever.retrieve(query_text)
# for i, node in enumerate(results):
# content = node.node.get_content()
# print(f"\n--- 匹配块 {i+1} ---")
# print(content)
# print(f"长度: {len(content)} 字符")
# print(results,"\n",len(results))
return results
4.将问题查找后的结果送给llm进行推理:
这里我是有一个爬虫爬下来的介绍http协议的知识,所以我就用这个问题进行了测试(你们使用什么知识库就问相关的问题即可)。通过下面的方法再加上前面定义的函数,就可以看到模型在使用RAG前后回复的差异。
def compare_response(query: str = "请你为我详细介绍http协议"):
result1 = llm_client(query)
content1 = get_content(result1)
index = load_index_from_fassi()
related_docs = query_index(index, query, similarity_top_k=6)
reference_text = "\n".join([doc.node.get_content() for doc in related_docs])
prompt2 = f"{query},请你为我详细解答。以下是该问题的相关资料,请你首先学习资料,并作出回复:\n" + reference_text
result2 = llm_client(prompt2)
content2 = get_content(result2)
with open("compare.txt", "w", encoding="utf-8") as f1:
f1.write("=== LLM Client Response ===\n")
f1.write(content1 + "\n\n")
f1.write("=== LlamaIndex Query Response ===\n")
f1.write(content2 + "\n\n")
f1.write("=== Searched Knowledge ===\n")
f1.write(reference_text + "\n")
return result1, result2
到此位置,你已经完成了一个最基本的RAG框架并可以投入使用。但是在现实中我们往往还需要对这个过程进行更多的优化,来让模型拥有更好的回答。所以下面我将介绍几种简单的优化方法,给大家一个方向。
优化方法
优化一:使用bm25和段落嵌入联合检索,避免单种检索方法无法覆盖所有相关知识。
def build_and_persist_bm25_retriever(
data_path="./data",
similarity_top_k=2,
):
_, nodes = load_and_chunk(data_path)
from llama_index.retrievers.bm25 import BM25Retriever
# We can pass in the index, docstore, or list of nodes to create the retriever
bm25_retriever = BM25Retriever.from_defaults(
nodes=nodes,
similarity_top_k=similarity_top_k,
)
bm25_retriever.persist("./bm25_retriever")
return bm25_retriever
def load_retriever_from_bm25():
from llama_index.retrievers.bm25 import BM25Retriever
return BM25Retriever.from_persist_dir("./bm25_retriever")
同样,我们可以使用nodes来构建bm25算法的索引信息并将检索器持久化保存下来。在使用的时候就可以通过load加载出来。通过下面这个函数就可以同时使用两种方法检索啦。
def query_index(
vector_index,
bm25_retriever,
query_text,
similarity_top_k=20,
):
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.postprocessor.flag_embedding_reranker import FlagEmbeddingReranker
from llama_index.core.schema import QueryBundle
# 构建两个 retriever
vector_retriever = VectorIndexRetriever(
index=vector_index,
similarity_top_k=similarity_top_k,
)
# 构建查询对象(QueryBundle)
query_bundle = QueryBundle(query_str=query_text)
# 执行检索
vector_nodes = vector_retriever.retrieve(query_bundle)
bm25_nodes = bm25_retriever.retrieve(query_bundle)
# 合并并按 node_id 去重
all_nodes = list({
node.node.node_id: node for node in vector_nodes + bm25_nodes
}.values())
return all_nodes
优化二:使用ReRank重排模型,获取最相关的知识块进行输入。
在此之上,我们还可以使用重排模型来对检索的知识进行筛选。比如这里我使用的就是中文的重排模型,可以进一步提高模型的回答准确率。
def query_index(
vector_index,
bm25_retriever,
query_text,
similarity_top_k=20,
rerank_top_n=5,
):
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.postprocessor.flag_embedding_reranker import FlagEmbeddingReranker
rerank = FlagEmbeddingReranker(model="BAAI/bge-reranker-base", top_n=rerank_top_n)
from llama_index.core.schema import QueryBundle
# 构建两个 retriever
vector_retriever = VectorIndexRetriever(
index=vector_index,
similarity_top_k=similarity_top_k,
)
# 构建查询对象(QueryBundle)
query_bundle = QueryBundle(query_str=query_text)
# 执行检索
vector_nodes = vector_retriever.retrieve(query_bundle)
bm25_nodes = bm25_retriever.retrieve(query_bundle)
# 合并并按 node_id 去重
all_nodes = list({
node.node.node_id: node for node in vector_nodes + bm25_nodes
}.values())
reranked_nodes = rerank.postprocess_nodes(all_nodes, query_bundle)
# 返回结果节点
return reranked_nodes
优化三:使用mini-LLM对查询进行优化,分析查询意图让检索更加容易。
优化四:使用mini_LLM对知识块进行内容整理总结,避免因为分块导致的语义混乱和不相关。
这两个方法其实非常类似,就是在中间步骤的时候使用LLM对文本再优化一下,可以使用类似之前的llm调用来优化就可以。
import requests
import json
def llm_client(content:str="这里传入你需要优化的文本"):
url = "http://127.0.0.1:8000/v1/chat/completions"
headers = {"Content-Type": "application/json", "Authorization": "Bearer "}
data = {
"model": "/workspace/models/Qwen3-0.6B",
"messages": [{"role": "user", "content": f"这里加入你想让模型做的事的提示词:{content}"],
"temperature": 0.7,
}
response = requests.post(url, headers=headers, data=json.dumps(data))
print("Status code:", response.status_code)
优化五:使用更好的分块方法,让每一个知识块都有明显的语义特征和结构。
这里的优化方法见仁见智,主要是根据你的具体任务和知识库决定。如果你是excel数据那当然就希望结构化输入,没有固定的方法。
def load_and_chunk(data_path="./data"):
"""
这里就使用你自己定义的方法来进行处理,最终目标就是将文本数据分块保存嘛。
"""
documents,nodes = your_special_process(data_path)
return documents,nodes
更多推荐



所有评论(0)