开源 AI 食谱文档
使用自定义非结构化数据构建 RAG
并获得增强的文档体验
开始使用
使用自定义非结构化数据构建 RAG
作者: Maria Khalusova
如果您是 RAG 的新手,请先在 另一个 notebook 中探索 RAG 的基础知识,然后再回到这里学习如何使用自定义数据构建 RAG。
无论您是构建自己的基于 RAG 的个人助理、宠物项目还是企业 RAG 系统,您都会很快发现大量重要知识以各种格式存储,例如 PDF、电子邮件、Markdown 文件、PowerPoint 演示文稿、HTML 页面、Word 文档等等。
如何预处理所有这些数据,以便您可以将其用于 RAG?在本快速教程中,您将学习如何构建一个 RAG 系统,该系统将整合来自多种数据类型的数据。您将使用 Unstructured 进行数据预处理,使用 Hugging Face Hub 的开源模型进行嵌入和文本生成,使用 ChromaDB 作为向量存储,并使用 LangChain 将所有内容整合在一起。
让我们开始吧!我们将首先安装所需的依赖项
!pip install -q torch transformers accelerate bitsandbytes sentence-transformers unstructured[all-docs] langchain chromadb langchain_community
接下来,让我们获取各种文档。假设,我想构建一个 RAG 系统,以帮助我管理花园中的害虫。为此,我将使用涵盖 IPM(综合虫害管理)主题的各种文档
- PDF:
https://www.gov.nl.ca/ecc/files/env-protection-pesticides-business-manuals-applic-chapter7.pdf
- Powerpoint:
https://ipm.ifas.ufl.edu/pdfs/Citrus_IPM_090913.pptx
- EPUB:
https://www.gutenberg.org/ebooks/45957
- HTML:
https://blog.fifthroom.com/what-to-do-about-harmful-garden-and-plant-insects-and-pests.html
您可以随意使用您自己选择主题的文档,这些文档类型受 Unstructured 支持: .eml
、 .html
、 .md
、 .msg
、 .rst
、 .rtf
、 .txt
、 .xml
、 .png
、 .jpg
、 .jpeg
、 .tiff
、 .bmp
、 .heic
、 .csv
、 .doc
、 .docx
、 .epub
、 .odt
、 .pdf
、 .ppt
、 .pptx
、 .tsv
、 .xlsx
。
!mkdir -p "./documents"
!wget https://www.gov.nl.ca/ecc/files/env-protection-pesticides-business-manuals-applic-chapter7.pdf -O "./documents/env-protection-pesticides-business-manuals-applic-chapter7.pdf"
!wget https://ipm.ifas.ufl.edu/pdfs/Citrus_IPM_090913.pptx -O "./documents/Citrus_IPM_090913.pptx"
!wget https://www.gutenberg.org/ebooks/45957.epub3.images -O "./documents/45957.epub"
!wget https://blog.fifthroom.com/what-to-do-about-harmful-garden-and-plant-insects-and-pests.html -O "./documents/what-to-do-about-harmful-garden-and-plant-insects-and-pests.html"
非结构化数据预处理
您可以使用 Unstructured 库逐个预处理文档,并编写自己的脚本来遍历目录,但使用本地源连接器来提取给定目录中的所有文档更容易。Unstructured 可以从本地目录、S3 存储桶、Blob 存储、SFTP 以及您的文档可能存储的许多其他位置提取文档。从这些来源的提取非常相似,主要区别在于身份验证选项。这里您将使用本地源连接器,但请随时在 Unstructured 文档中探索其他选项。
可选地,您还可以为处理后的文档选择一个 目标位置 - 这可以是 MongoDB、Pinecone、Weaviate 等。在本 notebook 中,我们将所有内容都保留在本地。
# Optional cell to reduce the amount of logs
import logging
logger = logging.getLogger("unstructured.ingest")
logger.root.removeHandler(logger.root.handlers[0])
>>> import os
>>> from unstructured.ingest.connector.local import SimpleLocalConfig
>>> from unstructured.ingest.interfaces import PartitionConfig, ProcessorConfig, ReadConfig
>>> from unstructured.ingest.runner import LocalRunner
>>> output_path = "./local-ingest-output"
>>> runner = LocalRunner(
... processor_config=ProcessorConfig(
... # logs verbosity
... verbose=True,
... # the local directory to store outputs
... output_dir=output_path,
... num_processes=2,
... ),
... read_config=ReadConfig(),
... partition_config=PartitionConfig(
... partition_by_api=True,
... api_key="YOUR_UNSTRUCTURED_API_KEY",
... ),
... connector_config=SimpleLocalConfig(
... input_path="./documents",
... # whether to get the documents recursively from given directory
... recursive=False,
... ),
... )
>>> runner.run()
INFO: NumExpr defaulting to 2 threads.
让我们仔细看看我们在这里的配置。
ProcessorConfig
控制处理管道的各个方面,包括输出位置、工作进程数、错误处理行为、日志记录详细程度等等。这里唯一必需的参数是 output_dir
- 您想要存储输出的本地目录。
ReadConfig
可用于自定义不同场景下的数据读取过程,例如重新下载数据、保留下载的文件或限制处理的文档数量。在大多数情况下,默认的 ReadConfig
将会起作用。
在 PartitionConfig
中,您可以选择是在本地还是通过 API 对文档进行分区。此示例使用 API,因此需要 Unstructured API 密钥。您可以从 这里 获取您的密钥。免费的 Unstructured API 限制为 1000 页,并且为基于图像的文档提供比本地安装 Unstructured 更好的 OCR 模型。如果您删除这两个参数,文档将在本地处理,但如果文档需要 OCR 和/或文档理解模型,您可能需要安装其他依赖项。即,在这种情况下,您可能需要安装 poppler 和 tesseract,您可以使用 brew 获取它们
!brew install poppler
!brew install tesseract
如果您使用的是 Windows,您可以在 Unstructured 文档中找到替代安装说明。
最后,在 SimpleLocalConfig
中,您需要指定原始文档所在的位置,以及是否要递归遍历目录。
文档处理完成后,您将在 local-ingest-output
目录中找到 4 个 json 文件,每个文件对应一个处理过的文档。Unstructured 以统一的方式对所有类型的文档进行分区,并返回带有文档元素的 json。
文档元素 具有类型,例如 NarrativeText
、 Title
或 Table
,它们包含提取的文本以及 Unstructured 能够获取的元数据。某些元数据对于所有元素都是通用的,例如元素来自的文档的文件名。其他元数据取决于文件类型或元素类型。例如,Table
元素将在元数据中包含表作为 html 的表示形式,电子邮件的元数据将包含有关发送者和接收者的信息。
让我们从这些 json 文件中导入元素对象。
from unstructured.staging.base import elements_from_json
elements = []
for filename in os.listdir(output_path):
filepath = os.path.join(output_path, filename)
elements.extend(elements_from_json(filepath))
现在您已经从文档中提取了元素,您可以将它们分块以适应嵌入模型的上下文窗口。
分块
如果您熟悉将长文本文档拆分为较小块的分块方法,您会注意到 Unstructured 的分块方法略有不同,因为分区步骤已经将整个文档划分为其结构元素:标题、列表项、表格、文本等。通过这种方式对文档进行分区,您可以避免不相关的文本片段最终出现在同一元素,然后是同一块中的情况。
现在,当您使用 Unstructured 对文档元素进行分块时,单个元素已经很小,因此只有当它们超过所需的最大块大小时才会被拆分。否则,它们将保持原样。您还可以选择性地组合连续的文本元素,例如列表项,这些元素将一起适合块大小限制。
from unstructured.chunking.title import chunk_by_title
chunked_elements = chunk_by_title(
elements,
# maximum for chunk size
max_characters=512,
# You can choose to combine consecutive elements that are too small
# e.g. individual list items
combine_text_under_n_chars=200,
)
这些块已准备好用于 RAG。要将它们与 LangChain 一起使用,您可以轻松地将 Unstructured 元素转换为 LangChain 文档。
from langchain_core.documents import Document
documents = []
for chunked_element in chunked_elements:
metadata = chunked_element.metadata.to_dict()
metadata["source"] = metadata["filename"]
del metadata["languages"]
documents.append(Document(page_content=chunked_element.text, metadata=metadata))
设置检索器
此示例使用 ChromaDB 作为向量存储和 BAAI/bge-base-en-v1.5
嵌入模型,您可以随意使用任何其他向量存储。
from langchain_community.vectorstores import Chroma
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import utils as chromautils
# ChromaDB doesn't support complex metadata, e.g. lists, so we drop it here.
# If you're using a different vector store, you may not need to do this
docs = chromautils.filter_complex_metadata(documents)
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-base-en-v1.5")
vectorstore = Chroma.from_documents(documents, embeddings)
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3})
如果您计划使用 Hugging Face Hub 中的 gated 模型,无论是嵌入模型还是文本生成模型,您都需要使用您的 Hugging Face 令牌进行身份验证,您可以在 Hugging Face 个人资料的设置中获取该令牌。
from huggingface_hub import notebook_login
notebook_login()
使用 LangChain 进行 RAG
让我们将所有内容整合在一起,并使用 LangChain 构建 RAG。在此示例中,我们将使用 Meta 的 Llama-3-8B-Instruct
。为了确保它可以在 Google Colab 的免费 T4 运行时中顺利运行,您需要对其进行量化。
from langchain.prompts import PromptTemplate
from langchain.llms import HuggingFacePipeline
from transformers import pipeline
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from langchain.chains import RetrievalQA
model_name = "meta-llama/Meta-Llama-3-8B-Instruct"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16
)
model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config)
tokenizer = AutoTokenizer.from_pretrained(model_name)
terminators = [tokenizer.eos_token_id, tokenizer.convert_tokens_to_ids("<|eot_id|>")]
text_generation_pipeline = pipeline(
model=model,
tokenizer=tokenizer,
task="text-generation",
temperature=0.2,
do_sample=True,
repetition_penalty=1.1,
return_full_text=False,
max_new_tokens=200,
eos_token_id=terminators,
)
llm = HuggingFacePipeline(pipeline=text_generation_pipeline)
prompt_template = """
<|start_header_id|>user<|end_header_id|>
You are an assistant for answering questions using provided context.
You are given the extracted parts of a long document and a question. Provide a conversational answer.
If you don't know the answer, just say "I do not know." Don't make up an answer.
Question: {question}
Context: {context}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
"""
prompt = PromptTemplate(
input_variables=["context", "question"],
template=prompt_template,
)
qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever, chain_type_kwargs={"prompt": prompt})
结果和后续步骤
现在您已经有了 RAG 链,让我们询问一下关于蚜虫的问题。蚜虫是我的花园里的害虫吗?
question = "Are aphids a pest?"
qa_chain.invoke(question)["result"]
输出
Yes, aphids are considered pests because they feed on the nutrient-rich liquids within plants, causing damage and potentially spreading disease. In fact, they're known to multiply quickly, which is why it's essential to control them promptly. As mentioned in the text, aphids can also attract ants, which are attracted to the sweet, sticky substance they produce called honeydew. So, yes, aphids are indeed a pest that requires attention to prevent further harm to your plants!
看起来开局不错!现在您已经了解了为 RAG 预处理复杂非结构化数据的基本知识,您可以继续改进此示例。以下是一些想法
- 您可以连接到不同的源来提取文档,例如 S3 存储桶。
- 您可以在
qa_chain
参数中添加return_source_documents=True
,使链返回作为上下文传递给提示的文档。这有助于了解哪些来源用于生成答案。 - 如果您想在检索阶段利用元素元数据,请考虑使用 Hugging Face agents 并创建自定义检索器工具,如 另一个 notebook 中所述。
- 您可以做很多事情来改进搜索结果。例如,您可以使用混合搜索而不是单一的相似性搜索检索器。混合搜索结合了多种搜索算法,以提高搜索结果的准确性和相关性。通常,它是基于关键字的搜索算法与向量搜索方法的组合。
祝您使用非结构化数据构建 RAG 应用程序玩得开心!
< > 在 GitHub 上更新