在使用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

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐