摄取管道(Ingestion Pipeline)
概念解释
摄取管道(IngestionPipeline)使用了一种称为“转换”(Transformations)的概念,这些转换应用于输入数据。这些转换应用于你的输入数据,结果节点要么返回,要么插入到向量数据库(如果提供了的话)。每个节点+转换组合都会被缓存,因此后续运行(如果缓存被持久化)使用相同的节点+转换组合时可以使用缓存结果,节省时间。
要查看摄取管道实际使用的交互式示例,请查看RAG CLI。
使用模式
最简单的用法
实例化一个摄取管道如下:
from llama_index.core import Document
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import TitleExtractor
from llama_index.core.ingestion import IngestionPipeline, IngestionCache
# 创建带有转换的管道
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=25, chunk_overlap=0),
TitleExtractor(),
OpenAIEmbedding(),
]
)
# 运行管道
nodes = pipeline.run(documents=[Document.example()])
注意,在实际场景中,你会从 SimpleDirectoryReader
或其他来自 Llama Hub 的读取器获取文档。
连接到向量数据库
运行摄取管道时,你还可以选择自动将结果节点插入到远程向量存储中。
然后,你可以稍后从该向量存储构建索引。
from llama_index.core import Document
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import TitleExtractor
from llama_index.core.ingestion import IngestionPipeline
from llama_index.vector_stores.qdrant import QdrantVectorStore
import qdrant_client
client = qdrant_client.QdrantClient(location=":memory:")
vector_store = QdrantVectorStore(client=client, collection_name="test_store")
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=25, chunk_overlap=0),
TitleExtractor(),
OpenAIEmbedding(),
],
vector_store=vector_store,
)
# 直接摄取到向量数据库
pipeline.run(documents=[Document.example()])
# 创建你的索引
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_vector_store(vector_store)
在管道中计算嵌入
注意,在上面的示例中,嵌入作为管道的一部分进行计算。如果你将管道连接到向量存储,嵌入必须是管道的一个阶段,否则稍后实例化索引时会失败。
如果你不连接到向量存储,可以从管道中省略嵌入,即只生成节点列表。
缓存
在摄取管道中,每个节点 + 转换组合都会被哈希并缓存。这节省了使用相同数据进行后续运行的时间。
以下部分描述了一些基本的缓存使用方法。
本地缓存管理
一旦你有了管道,你可能想要存储和加载缓存。
# 保存
pipeline.persist("./pipeline_storage")
# 加载并恢复状态
new_pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=25, chunk_overlap=0),
TitleExtractor(),
],
)
new_pipeline.load("./pipeline_storage")
# 由于缓存,运行速度会很快
nodes = pipeline.run(documents=[Document.example()])
如果缓存变得太大,你可以清除它:
# 删除所有缓存内容
cache.clear()
远程缓存管理
我们支持多种远程存储后端用于缓存:
- RedisCache
- MongoDBCache
- FirestoreCache
以下是使用 RedisCache 的示例:
from llama_index.core import Document
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import TitleExtractor
from llama_index.core.ingestion import IngestionPipeline, IngestionCache
from llama_index.storage.kvstore.redis import RedisKVStore as RedisCache
ingest_cache = IngestionCache(
cache=RedisCache.from_host_and_port(host="127.0.0.1", port=6379),
collection="my_test_cache",
)
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=25, chunk_overlap=0),
TitleExtractor(),
OpenAIEmbedding(),
],
cache=ingest_cache,
)
# 直接摄取到向量数据库
nodes = pipeline.run(documents=[Document.example()])
这里不需要持久化步骤,因为所有内容都会在你进行时缓存在指定的远程集合中。
异步支持
摄取管道也支持异步操作:
nodes = await pipeline.arun(documents=documents)
文档管理
将文档存储附加到摄取管道将启用文档管理。
使用 document.doc_id
或 node.ref_doc_id
作为基础点,摄取管道将主动查找重复文档。
它的工作原理是:
- 存储
doc_id -> document_hash
的映射 - 如果附加了向量存储:
- 如果检测到重复的
doc_id
,并且哈希已更改,文档将被重新处理并更新 - 如果检测到重复的
doc_id
,并且哈希未更改,节点将被跳过
- 如果检测到重复的
- 如果未附加向量存储:
- 检查所有现有哈希的每个节点
- 如果找到重复项,节点将被跳过
- 否则,节点将被处理
注意:如果我们不附加向量存储,我们只能检查并删除重复的输入。
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.storage.docstore import SimpleDocumentStore
pipeline = IngestionPipeline(
transformations=[...], docstore=SimpleDocumentStore()
)
完整的演练可以在我们的演示笔记本中找到。
还可以查看另一个指南,使用 Redis 作为我们的整个摄取堆栈。
并行处理
摄取管道的 run
方法可以与并行进程一起执行。它通过使用 multiprocessing.Pool
将节点批次分发到处理器来实现这一点。
要使用并行处理执行,请将 num_workers
设置为你希望使用的进程数:
from llama_index.core.ingestion import IngestionPipeline
pipeline = IngestionPipeline(
transformations=[...],
)
pipeline.run(documents=[...], num_workers=4)
转换
转换是接受节点列表作为输入并返回节点列表的东西。每个实现转换基类的组件都有同步的 __call__()
定义和异步的 acall()
定义。
目前,以下组件是转换对象:
- TextSplitter
- NodeParser
- MetadataExtractor
- Embeddingsmodel(查看我们支持的嵌入列表)
使用模式
虽然转换最好与摄取管道一起使用,但它们也可以直接使用。
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import TitleExtractor
node_parser = SentenceSplitter(chunk_size=512)
extractor = TitleExtractor()
# 直接使用转换
nodes = node_parser(documents)
# 或异步使用转换
nodes = await extractor.acall(nodes)
与索引结合
转换可以传递到索引或整体全局设置中,并在调用 from_documents()
或 insert()
时使用。
from llama_index.core import VectorStoreIndex
from llama_index.core.extractors import (
TitleExtractor,
QuestionsAnsweredExtractor,
)
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import TokenTextSplitter
transformations = [
TokenTextSplitter(chunk_size=512, chunk_overlap=128),
TitleExtractor(nodes=5),
QuestionsAnsweredExtractor(questions=3),
]
# 全局设置
from llama_index.core import Settings
Settings.transformations = [text_splitter, title_extractor, qa_extractor]
# 每个索引设置
index = VectorStoreIndex.from_documents(
documents, transformations=transformations
)
自定义转换
你可以通过实现基类来实现任何转换。
以下自定义转换将删除文本中的任何特殊字符或标点符号:
import re
from llama_index.core import Document
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.schema import TransformComponent
class TextCleaner(TransformComponent):
def __call__(self, nodes, **kwargs):
for node in nodes:
node.text = re.sub(r"[^0-9A-Za-z ]", "", node.text)
return nodes
这些可以直接使用或在任何摄取管道中使用。
# 在管道中使用
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=25, chunk_overlap=0),
TextCleaner(),
OpenAIEmbedding(),
],
)
nodes = pipeline.run(documents=[Document.example()])
通过这些详细的解释和示例,希望你能更好地理解和使用 LlamaIndex 中的摄取管道功能。