开源 AI 食谱文档
使用自定义非结构化数据构建 RAG
并获得增强的文档体验
开始使用
使用自定义非结构化数据构建 RAG
如果您是 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 中的受限模型,无论是嵌入模型还是文本生成模型,您都需要使用您的 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 智能体并创建一个自定义检索器工具,如这篇 Notebook 中所述。
- 您可以做很多事情来改善搜索结果。例如,您可以使用混合搜索而不是单一的相似性搜索检索器。混合搜索结合了多种搜索算法,以提高搜索结果的准确性和相关性。通常,它是基于关键字的搜索算法与向量搜索方法的组合。
祝您使用非结构化数据构建 RAG 应用愉快!
< > 在 GitHub 上更新