LlamaIndex
LlamaIndex
一、LlamaIndex 介绍
1.1 什么是大语言模型开发框架
所有开发框架(SDK)的核心价值,都是降低开发、维护成本。大语言模型开发框架是用于构建、训练、优化和部署大型语言模型的工具和库的集合,让开发者可以更方便地开发基于大语言模型的应用。
1.2 为什么使用大语言模型开发框架
简化开发流程,提高生产力和效率。可以帮助开发者:
- 第三方能力抽象。比如 LLM、向量数据库、搜索接口等
- 常用工具、方案封装
- 底层实现封装。比如流式接口、超时重连、异步与并行等
比如说:
与外部功能解依赖
- 比如可以随意更换 LLM 而不用大量重构代码
- 更换三方工具也同理
经常变的部分要在外部维护而不是放在代码里
- 比如 Prompt 模板
各种环境下都适用
- 比如线程安全
方便调试和测试
- 至少要能感觉到用了比不用方便吧
- 合法的输入不会引发框架内部的报错
1.3 什么是 LlamaIndex
LlamaIndex is a framework for building context-augmented generative AI applications with LLMs.
LlamaIndex 是一个框架,用于使用 llm 构建上下文增强的生成 AI 应用程序。LlamaIndex 和上下文增强的一些流行用例包括:
- Question-Answering (问答-检索增强 RAG)
- ChatBots(聊天机器人)
- Document Understanding and Data Extraction(文档理解和数据提取)
- Autonomous Agents(自主代理)
- Multi-modal applications(多模态应用)
- Fine-tuning(微调)
1.4 为什么使用 LlamaIndex
开源和社区支持
作为一个开源项目,LlamaIndex 不仅免费提供给用户使用,还拥有一个活跃的开发者社区。用户可以从社区获得支持、共享经验、并参与项目的开发和改进。
高效的索引和检索
- LlamaIndex: 提供了高效的索引和检索机制,能够显著加快查询速度。
- 原生大模型 API: 原生大模型 API 通常直接对大规模语言模型进行调用,每次查询都需要经过模型的完整处理流程,速度相对较慢。
灵活性和可扩展性
- LlamaIndex: 提供了灵活的模块化设计,支持插件和自定义扩展,可以与多种数据源和系统集成。
- 原生大模型 API: 固定的 API 接口和功能,灵活性较低,扩展性受限。
数据隐私和控制
- LlamaIndex: 数据在本地处理和存储,用户对数据有完全控制权,能够更好地保障数据隐私和安全。
- 原生大模型 API: 数据需要发送到第三方服务进行处理,存在一定的数据隐私和安全风险。
简单易用
易于集成到现有系统中,提供了简洁的 API 和详细的文档。支持多种编程语言和框架,便于开发和部署。
二、如何使用 LlamaIndex
2.1 安装 llama-index
pip install llama-index
2.2 把文档放在 data
的文件夹中
目录结构:
project_root
|-- data
| |-- pdf1.pdf
| |-- pdf2.pdf
|-- index.py
2.3 使用 LlamaIndex 进行问答
# index.py
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("Some question about the data should go here")
print(response)
三、LlamaIndex 中 RAG 的五个关键阶段
3.1 Loading(加载)
从数据源获取数据,无论是文本文件、pdf、另一个网站、数据库还是 API。LlamaHub 提供了数百种连接器可供选择。
相关概念:
- Nodes and Documents(节点和文档):
Document
是围绕任何数据源的容器——例如,PDF、API 输出或从数据库检索的数据。Node
是 LlamaIndex 中数据的原子单位,表示源Document
的一个“块”。节点拥有元数据,这些元数据将节点与它们所在的文档以及其他节点关联起来。 - Connectors(连接器): 数据连接器(通常称为
Reader
),将来自不同数据源和数据格式的数据摄取到Documents
和Nodes
中。
3.2 Indexing(索引)
通常使用向量模型建立索引。
相关概念:
- Indexes(索引):获取到数据后,LlamaIndex 会将数据索引到易于检索的结构中。通常会使用到文本向量(Embedding)和向量数据库。
- Embeddings(文本向量):实现用向量表示数据。在进行语义相关性查询时,LlamaIndex 会将查询转换成 Embedding 的形式,然后在向量数据库找到相似的数据,
3.3 Storing(存储)
将索引和元数据存储,避免重复索引
3.4 Querying(查询)
可以使用多种策略进行查询。
- Retrievers(检索器):检索器定义了在给定查询时如何有效地从索引检索相关上下文。检索策略是检索数据的相关性和效率的关键。
- Routers(路由器):路由器决定将使用哪个检索器从知识库检索相关上下文。更具体地说,
RouterRetriever
类负责选择一个或多个候选检索器来执行查询。 - Node Postprocessors(节点后处理器):节点后处理器接收一组检索到的节点,并对它们应用转换、过滤或重新排序逻辑。
- Response Synthesizers(响应合成器):响应合成器使用用户的查询和一组检索到的文本块,从 LLM 生成响应。
3.5 Evaluation(评估)
评估查询响应的准确性、速度
四、加载
4.1 本地加载文档
SimpleDirectoryReader
是一个简单的本地文件加载器。它会遍历指定目录,并根据文件扩展名自动加载文件(文本内容)。参考文档:SimpleDirectoryReader - LlamaIndex更换文件加载器:默认的
PDFReader
效果并不理想,我们可以更换文件加载器,配置加载器中的file_extractor
。参考文档:Simple directory reader - LlamaIndex
4.2 Data Connectors
用于处理更丰富的数据类型,并将其读取为 Document
的形式。
参考文档:Data Connectors (LlamaHub) - LlamaIndex
4.3 Ingestion Pipeline 自定义数据处理流程
IngestionPipeline 是一个数据处理架构,其核心在于利用 Transformations
的概念来处理输入数据。
它有以下好处:
- 支持将输入数据(文档)处理成节点返回或插入向量数据库。
- 可以进行本地或远程缓存管理。当遇到相同的处理请求时,可以直接从缓存中读取,避免了重复处理。
- 支持文档管理,可以主动查找重复的文档。
- 支持异步和并发调用。
- 模块化设计。
示例:
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=300, chunk_overlap=0), # 按句子切分
TitleExtractor(), # 利用 LLM 对文本生成标题
OpenAIEmbedding(), # 将文本向量化
],
vector_store=vector_store,
)
# Ingest directly into a vector db
pipeline.run(documents=documents)
# 创建索引
index = VectorStoreIndex.from_vector_store(vector_store)
# 本地保存 IngestionPipeline 的缓存
pipeline.persist("./pipeline_storage")
new_pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=300, chunk_overlap=0),
TitleExtractor(),
OpenAIEmbedding()
],
)
# 加载缓存
new_pipeline.load("./pipeline_storage")
这个 Pipeline 的一个显著特点是,它的每个子步骤是可以缓存(cache)的,即如果该子步骤的输入与处理方法不变(如上代码 3,4,5 行和 21,22,23 行),重复调用时会直接从缓存中获取结果,而无需重新执行该子步骤,这样即节省时间也会节省 token (如果子步骤涉及大模型调用)。
参考文档:Ingestion Pipeline - LlamaIndex
五、文本切分与解析(Chunking)
为方便检索,我们通常把 Document
切分为 Node
。节点解析器是一个简单的抽象,它接受一个文档列表,并将它们分成 Node
对象,这样每个节点都是父文档的特定块。
例子:
- 使用 TextSplitters 对文本做切分:Node Parser Modules - LlamaIndex
- 使用 NodeParsers 对有结构的文档做解析,如 markdown:Node Parser Modules - LlamaIndex
六、索引与检索
3.1 使用 VectorStoreIndex
构建索引
# 块构建 index = VectorStoreIndex(nodes) # 文档构建 index = VectorStoreIndex.from_documents(documents)
检索
# 获取 retriever vector_retriever = index.as_retriever( similarity_top_k=2 # 返回前两个结果 ) # 检索 results = vector_retriever.retrieve("Llama2有多少参数")
6.2 使用自定义的 Vector Store
- 创建 chroma_client,创建 chroma_collection
- 创建自定义的 vector_store
- 创建 storage_context,关联自定义的 vector_store
- 创建 index,通过 Storage Context 关联到自定义的 Vector Store
- 获取 retriever,检索
参考文档:Chroma - LlamaIndex
6.3 其他索引方式
七、查询
7.1 检索后处理
在 LlamaIndex 中,节点后处理器最常应用于查询引擎中,在节点检索步骤之后和响应合成步骤之前。
我们可以用不同模型对检索后的 Nodes
做重排序:
# 检索
nodes = vector_retriever.retrieve("Does llama 2 have a commercial license?")
# 检索后排序模型
postprocessor = SentenceTransformerRerank(
model="BAAI/bge-reranker-large", top_n=2
)
nodes = postprocessor.postprocess_nodes(nodes, query_str="Does llama 2 have a commercial license?")
八、生成回复
8.1 单轮问答
qa_engine = index.as_query_engine()
response = qa_engine.query("How many parameters does llama 2 have?")
print(response)
# 流式输出
response.print_response_stream()
参考文档:Query Engine - LlamaIndex
8.2 多轮问答
chat_engine = index.as_chat_engine()
response = chat_engine.chat("How many parameters does llama 2 have?")
print(response)
# 流式输出
streaming_response = chat_engine.stream_chat("How many parameters does llama 2 have?")
for token in streaming_response.response_gen:
print(token, end="")
九、底层接口
9.1 Prompt 模板
PromptTemplate 定义提示词模板
ChatPromptTemplate 定义多轮消息模板
参考文档:Prompts - LlamaIndex
9.2 LLM 模型
LlamaIndex 提供了一个统一的接口,用于定义 LLM 模块,无论是来自 OpenAI,Hugging Face,还是 LangChain,不必自己编写定义 LLM 接口的样板代码。
9.3 Embedding 模型
LlamaIndex 有许多 Embedding 模型可供选择,并提供了一个易于扩展的基类来实现自定义的 Embedding 模型。默认使用 OpenAI 的 text-embedding-ada-002
。