快速上手:用 LlamaIndex 构建第一个 RAG 应用
本文将手把手带您快速上手LlamaIndex,通过一个本地问答Demo体验其核心流程。目标是在几行代码内完成从文档加载、索引构建到查询问答的闭环,让您对LlamaIndex有直观认识。我将介绍安装配置步骤,运行一个“五行代码”的本地问答示例,并详细剖析示例代码的工作原理。通过本文,您将能够搭建起第一个简单的RAG应用,为后续深入学习各模块打下基础。
目标与背景说明
本文将手把手带您快速上手LlamaIndex,通过一个本地问答Demo体验其核心流程。目标是在几行代码内完成从文档加载、索引构建到查询问答的闭环,让您对LlamaIndex有直观认识。我将介绍安装配置步骤,运行一个“五行代码”的本地问答示例,并详细剖析示例代码的工作原理。通过本文,您将能够搭建起第一个简单的RAG应用,为后续深入学习各模块打下基础。
背景方面,许多读者初次接触LlamaIndex往往不知道从何下手。本文通过Hello LlamaIndex示例帮助您扫清入门障碍,包括环境安装、API密钥配置等细节。同时,我还将讨论在不同环境(脚本、Notebook、Web服务)中使用LlamaIndex的要点,并汇总新手常见的问题及解决方法。读完后,您应能独立运行LlamaIndex的基本功能,并对它的用法有初步理解。
大家有什么问题可以直接写在评论区,一起学习讨论。
写这篇文章时,刚好在整理自己混乱的账号与密码。最近在用 “我的密馆” app
记录和管理这些重要信息。整体体验很安静,也更有秩序感。如果你也有类似困扰,可以了解看看。【我的密馆 (VaultLib)】
五行代码的本地问答 Demo
让我们直接来看一个五行Python代码实现的本地问答Demo,感受LlamaIndex的强大。我们将:
• 加载样例文本:使用LlamaIndex内置的 SimpleDirectoryReader 从本地读取文件。
• 构建索引并查询:调用 VectorStoreIndex.from_documents 一步完成索引创建和问题查询。
示例代码:
from llama_index import VectorStoreIndex, SimpleDirectoryReader
# 1. 从当前目录加载所有文本文件为 Document 列表
documents = SimpleDirectoryReader(input_dir="./data").load_data()
# 2. 创建向量索引并立即执行一次查询
index = VectorStoreIndex.from_documents(documents)
response = index.query("请问这个资料的主要结论是什么?")
print(response)
以上代码仅五行,却完成了一个问答系统的关键过程:
数据加载: 使用 SimpleDirectoryReader 加载指定目录下的文本文件。只需提供目录路径,load_data() 会读取目录中各种支持格式的文件(txt、PDF、docx等)并返回一个 Document 列表。
索引构建与查询: 调用 VectorStoreIndex.from_documents(documents) ,LlamaIndex会在后台完成两步:首先对每个Document生成Embedding向量,然后将这些向量存入内部向量索引。紧接着,我们直接使用 .query(…) 在索引上执行查询 。这一高层API实现了“一键构建索引并检索”的便捷操作 。
打印结果: response 是LLM返回的回答(通常包含答案文本和引用信息)。打印它即可看到对于提问的答案。这段代码运行时将输出对于给定问题的答案。例如,如果 ./data 目录下有一篇研究报告文本,问题询问其主要结论,LLM可能返回简要的结论内容,并附带引用段落。
背后机制: 在 .from_documents() 中,默认LlamaIndex会使用OpenAI的 text-embedding-ada-002 模型将文档embedding为向量(1536维) 。然后建立一个简单的向量索引(内置 SimpleVectorStore )存储这些向量。 index.query(question) 时,LlamaIndex将问题也embedding成向量,在向量索引中找出与之最相似的若干文本段(默认Top-K=3),最后将这些段落和问题一起送入LLM(默认gpt-3.5-turbo)生成答案。因此短短五行代码实际上完成了“加载->索引->检索->回答”的完整闭环。
这展示了LlamaIndex开箱即用的便捷性:无需繁琐配置,便能让LLM获取本地知识来回答问题。当然,真实应用需要更多控制和优化。
接下来我将指导您如何配置环境以运行上述示例。
环境安装与配置
在运行Demo前,需要正确安装LlamaIndex及其依赖,并准备LLM/Embedding的API或模型。本节逐步说明:
安装 LlamaIndex: 最简单的方法是通过pip。确保您的Python版本在3.9–3.11范围内,然后执行:
pip install llama-index
这会从PyPI安装最新版本的 llama-index 库(本文假定v0.14.x)。安装完成后,您即可在Python中 import llama_index 。建议在虚拟环境或Poetry环境中安装,以避免与其他项目依赖冲突。
有些附加功能在基础包中没有,需安装扩展插件。
例如:
• 要使用BM25检索器:运行 pip install llama index-retrievers-bm25 。
• 要使用某些向量数据库支持:安装对应子包,如 pip install llama-indexvectorstores[faiss] 以支持FAISS 。
• 更多插件如 llama-index-embeddings-huggingface(HuggingFace本地embedding支持)等,可根据需要安装。
配置 OpenAI API 密钥: 默认情况下,LlamaIndex使用OpenAI的GPT-3.5-Turbo作为LLM、Ada-002作为Embedding模型 。因此需要提供OpenAI API Key。
有两种设置方式:
• 在环境变量中设置:在终端运行 export OPENAI_API_KEY="sk-..." 或在Python中os.environ["OPENAI_API_KEY"]="sk-..." 。
• 在代码中初始化ServiceContext时传入。
初学者建议先申请免费的OpenAI API Key以跑通Demo。
(可选)安装其他依赖: SimpleDirectoryReader 对多种文件格式有内置支持,但有些格式需要额外库:
• PDF:需要安装 PyMuPDF ( pip install PyMuPDF ) 或pdfplumber等。不安装时只能读取纯文本PDF,复杂PDF解析可能失败 。
• 图片:如需OCR识别图片文字,可安装 easyocr 或 Pillow 等,LlamaIndex能通过这些把图片转文本。
• Word/PPT:安装 python-docx 、 python-pptx 支持分别读取Word、PPT文件。
如果懒得手动装,可以安装LlamaIndex提供的“快开套件”: pip install llama-index[jieba,pymupdf] 等,一次性带上常用解析库。
测试安装: 完成以上后,在Python REPL或脚本中输入:
from llama_index import VectorStoreIndex, SimpleDirectoryReader
若无错误则安装成功。接下来可尝试加载一个简单文本文件并查询,验证API Key配置是否正确(若忘记配置,调用OpenAI接口会报错)。配置好环境后,就可以运行上面的Demo代码。请确保OpenAI Key有效,否则将在 index.query() 时出现认证错误。下一节我们将深入剖析Demo代码发生了什么,以及默认配置下使用了哪些模型。
Hello LlamaIndex:最小示例剖析
现在我们详细拆解上一节Demo代码的每一步,深入了解LlamaIndex默认行为和核心流程:
数据加载(SimpleDirectoryReader): 这一行:
documents = SimpleDirectoryReader(input_dir=“./data”).load_data()
使用了LlamaIndex内置的文件读取器。 SimpleDirectoryReader 会扫描指定目录下的所有文件,并自动选择合适的解析器处理每种格式 。
例如:
• .txt / .md 文本:直接读取为字符串。
• .pdf :如安装了PyMuPDF,则调用其解析PDF文本 。
• .docx :用 python-docx 解析文本。
• .jpg / .png 图像:尝试OCR提取文字等。
支持的类型非常丰富,包括Markdown、PDF、Word、PPT、图片、音频、视频等 。因此SimpleDirectoryReader可轻松处理混合类型文件夹。甚至可以将整个知识库目录扔给它,一行代码获取
所有文档内容 。此Demo中,我们假定 ./data 目录下有文本文件,load_data返回一个列表,里面每个元素是一个Document对象。
Document 对象包含两个主要属性: text (文本内容)和 metadata (元数据字典) 。
SimpleDirectoryReader 默认会将文件路径存入每个Document的metadata,如 {“filename”: “data/xxx.pdf”} ,以备后用。这样,哪怕后续文本被切碎为Node,我们仍能知道它来自哪个文件。
索引构建与查询(VectorStoreIndex): 这一行:
index = VectorStoreIndex.from_documents(documents)
是高层便捷方法,同时完成了索引创建和初步查询的准备。在幕后,它执行了以下步骤:
• Node Parser 切分: LlamaIndex会先将每个Document解析为若干Node。默认使用SentenceSplitter 按语义句子切分长文本 (或按固定token长度)。切分后,每个Node保留了Document的metadata,例如文件名、段落位置等 。Demo中因文本可能较短,此步影响不明显,但对长文档很重要。
• Embedding 计算: 然后,对于每个Node的文本,调用Embedding模型获取向量表示。默认embedding模型为OpenAI的Ada-002(text-embedding-ada-002),得到一个1536维的浮点向量。如果没有设置OpenAI Key,这步会失败。Ada模型速度快、效果好,是业界常用的embedding方案 。LlamaIndex通过默认 ServiceContext 封装OpenAI Embedding API调用,将文本批量发送获取向量。
• 存入向量存储: LlamaIndex创建一个内置的 SimpleVectorStore (纯Python实现的向量数据库)来存储向量索引。如果documents数量很大,它也可以连接外部向量数据库,但默认情况下不传则用内存存储 。每个向量与其对应Node的id、文本、metadata等关联保存。
• 构建索引对象: VectorStoreIndex实例中保存了向量存储和一些查询参数配置,如默认Top-K值等。索引对象还持有一个默认的Retriever。完成这些后, index 已经可以用于查询。
Demo下一行:
response = index.query("请问这个资料的主要结论是什么?")
这实际上是 index 的快捷查询接口。等价于:
query_engine = index.as_query_engine()
response = query_engine.query("<问题>")
也就是先拿到一个默认QueryEngine,再执行查询 。默认QueryEngine内部会使用 VectorIndexRetriever来基于向量相似度检索节点 。其过程如下:
• 问题Embedding: QueryEngine将用户问题用同一embedding模型编码为一个查询向量。向量检索: 在索引的向量存储中,执行最近邻搜索,找出与查询向量距离最近的 Top-K 个节点(默认K=3)。这利用高维向量相似度(通常内积或余弦)计算实现。如果设置了相似度阈值也会考虑,但默认主要用固定Top-K 。这一过程由Retriever封装。以Demo而言,它会返回与问题语义最相关的3个文本段。
• 答案合成: 将检索到的节点内容连同原始问题,一起塞入LLM的提示(prompt)。LlamaIndex默认的ResponseSynthesizer会构造一个提示,例如:“根据以下内容回答问题:<节点文本>… 问题:…。请给出答案并引用来源。” 。然后调用OpenAI GPT-3.5模型(或我们配置的LLM)生成回答文本。LLM应该会综合这3段内容给出答案。由于提示要求引用来源,LLM通常会在答案后附上诸如“(来源: 文件X,第Y页)”的信息,或至少输出节点的引用ID。
• 返回Response对象: QueryEngine将LLM的回答封装为一个Response对象,其中包含 response (答案文本)和 source_nodes (用到的源节点列表) 。我们打印response时,会调用其 str 或repr 方法,通常会展示答案内容以及可能的来源标识。
默认LLM是OpenAI gpt-3.5-turbo ,如果未特别配置,LlamaIndex会自动使用此模型(需API Key)。默认embedding和LLM的搭配可以让入门者快速见效,但也意味着每次查询会调用OpenAI API两次(一次embedding,一次LLM)。
输出解读: print(response) 通常会打印答案文本。如果要获取更细粒度的信息,可以访问
response.response 属性得到纯答案字符串, response.source_nodes 得到一个列表,其中每个元素
有 .node 属性指向对应源Node。通过Node的metadata可以提取如文件名、段落id等信息,用于在UI上显示来源出处。在Demo中,我们简单打印response,可能会看到类似:
这个资料的主要结论是: … (来源: example.pdf 第10页)
具体格式取决于默认prompt和LLM输出风格。
总之, VectorStoreIndex.from_documents().query() 让我们体验了最小化RAG流程:数据->向量->检索-LLM回答。LlamaIndex将其中复杂步骤都封装好了。这对新手非常友好,但在实际应用中,我们往往需要自定义各步骤的细节,如更换embedding模型、本地化LLM、调整检索策略等。
值得一提的是, .from_documents() 这种高级接口适合小规模实验。如果数据量很大,建议分步构建索引(先Index.from_documents 保存索引,下次直接加载)或使用IngestionPipeline优化嵌入计算和缓存。但作为入门示例,它展示了LlamaIndex默认参数的威力:几乎零配置即可得到有用结果。
默认配置总结:
• LLM 模型:gpt-3.5-turbo 。
• Embedding模型:text-embedding-ada-002(OpenAIada 1536维) 。
• Text Splitter:SentenceSplitter,默认chunk大小约512 tokens,chunk_overlap=0(可在Settings调整) 。
• 检索Top-K:默认为3(可通过 query_engine = index.as_query_engine(retriever_mode=“simple”, retriever_kwargs={“top_k”:5}) 修改)。
• 输出答案带引用:默认prompt会让模型在答案中包含引用标记 。这就是我们常看到的答案附上文档名称或编号。
理解这些有助于您迅速上手并开始修改参数。例如,如果希望每次检索更多段落,可以将top_k调大;想使用GPT-4提高答案质量,可配置Settings.llm为OpenAI(model=“gpt-4”)。
在不同环境中运行:CLI、Notebook 和 Web服务
LlamaIndex作为Python库,可以在多种环境下使用。这里我们介绍三种典型环境及其注意事项:
本地脚本/CLI 模式: 您可以将LlamaIndex集成到Python脚本或命令行程序中。例如编写一个 ask.py :
ask.py
import sys
from llama_index import VectorStoreIndex, SimpleDirectoryReader
query = sys.argv[1] if len(sys.argv)>1 else "Hello"
documents = SimpleDirectoryReader('./docs').load_data()
index = VectorStoreIndex.from_documents(documents)
print(index.query(query))
然后在命令行运行:
$ python ask.py "人工智能的定义是什么?"
这会读取 ./docs 目录并回答问题。注意: 每次运行该脚本都会重新加载和重建索引,开销较大。对于快速CLI查询工具,可以预先构建索引保存到磁盘,脚本启动时加载索引而非重建,以提高响应
速度。
Jupyter Notebook 交互式: 在Notebook里使用LlamaIndex非常方便,可即时查看中间结果。典型流程是
在Notebook中:
用 SimpleDirectoryReader 加载文档。 调用 index =VectorStoreIndex.from_documents(docs) 创建索引。 然后多次调用 index.query("问题") 进行交互式问答。 Notebook的好处是可以inspect每步。您可以打印部分 documents 内容看看格式,或 for node in index.index_struct.nodes: print(node.text[:50]) 查看索引节点摘要。不过要注意,不要在Notebook中打印整个索引内容,尤其数据很大时,以免输出刷屏甚至崩溃。另一个注意点是:首次运行查询时,LlamaIndex可能需要下载依赖资源,如打开AI21模型时下载tokenizer等。这些在Notebook中会有提示。常见还有NLTK资
源下载的问题(如第一次用SentenceSplitter会下载punkt模型),如果遇到“Resource not found”提示,根据提示运行nltk.download()即可 。
Web服务(FastAPI): 将LlamaIndex封装成Web API是常见需求,可供前端或其他系统调用。FastAPI框架非常适合此场景,原因是其支持异步IO,可并发处理向量检索和LLM API请求 。一个简易FastAPI问答服务
如下:
from fastapi import FastAPI, Query
from llama_index import VectorStoreIndex, SimpleDirectoryReader
app = FastAPI()
# 全局构建索引,启动时加载数据(避免每请求重复加载)
documents = SimpleDirectoryReader('./docs').load_data()
index = VectorStoreIndex.from_documents(documents)
@app.get("/ask")
async def ask(q: str = Query(..., description="用户问题")):
#调用异步查询(LlamaIndex支持异步API)
response = await index.aquery(q)
#返回JSON,包括答案和来源
return {
"answer": str(response.response),
"sources": [node.node.metadata.get("filename") for node in response.source_nodes]
}
上述服务定义了一个 GET /ask 接口,接收查询参数 q (问题),返回包含答案和文档来源的JSON。
这里我们利用了LlamaIndex的异步接口 index.aquery() 。LlamaIndex 内部许多操作(如OpenAI API调
用)都提供了 async 版本,结合FastAPI可以提升并发性能 。另外我们将索引构建放在全局,作为单例对象,避免每次请求重新读取文件构建索引 。
运行这个FastAPI应用后,前端或HTTP客户端即可请求: GET http://localhost:8000/ask?q=问题 得到回答。部署时,推荐使用Uvicorn这样支持异步的服务器,充分利用FastAPI优势 。
线程安全与状态: 默认情况下,VectorStoreIndex等对象主要读操作多,可在多线程/异步下并发查询。但如果在查询同时修改索引(插入文档),可能需要锁控制。本例索引在启动时构建,后续只读,所以是安全的。如果要动态更新索引,在Web服务中需设计好同步机制(如所有更新操作通过队列串行执
行)。
通过以上三种环境,您可以在脚本模式快速验证、小批量问答,在Notebook模式探索调试,在服务模式集成到产品系统并开放接口。LlamaIndex在不同环境下用法基本一致,关键是考虑性能:脚本/Notebook适合原型开发,生产环境应使用持久化索引+异步服务来支撑高并发。
更多推荐



所有评论(0)