本文介绍如何为生成式 AI 应用程序生成非结构化数据管道。 非结构化管道对于检索增强生成 (RAG) 应用程序特别有用。
了解如何将文本文件和 PDF 等非结构化内容转换为 AI 代理或其他检索器可查询的矢量索引。 本文还将介绍如何试验和优化管道,以优化分块、索引和分析数据,使你能够排查管道故障和进行试验,以获得更好的结果。
非结构化数据管道笔记本
以下笔记本介绍了如何实现本文中的信息来创建非结构化数据管道。
Databricks 非结构化数据管道
数据管道的关键组件
任何具有非结构化数据的 RAG 应用程序的基础都是数据管道。 此管道负责以 RAG 应用程序可有效利用的格式来整理和准备非结构化数据。
此数据管道可能会根据用例而变得复杂,下面是首次生成 RAG 应用程序时需要考虑的关键组件:
- 语料库构成和引入:根据具体用例选择合适的数据源和内容。
- 数据预处理:将原始数据转换为适合嵌入和检索的整齐一致格式。
- 分块:将已分析的数据分解为较小的可管理区块,以便高效检索。
- 嵌入:将分块文本数据转换为可捕获其语义含义的数字矢量表示形式。
- 索引编制和存储:创建高效的矢量索引以优化搜索性能。
语料库构成和引入
RAG 应用程序无法检索在没有正确数据库的情况下回答用户查询所需的信息。 正确的数据完全取决于应用程序的具体要求和目标,因此花些时间了解可用数据的细微差别至关重要。 有关详细信息,请参阅生成式 AI 应用开发人员工作流。
例如,在生成客户支持机器人时,可以考虑包含以下内容:
- 知识库文档
- 常见问题解答 (FAQ)
- 产品手册和规格
- 故障排除指南
从任何项目一开始就让领域专家和利益干系人参与进来,以帮助识别和评判可提高数据语料库质量和覆盖范围的相关内容。 他们可以提供有关用户可能提交的查询类型的见解,并帮助确定要包含的最重要信息的优先级。
Databricks 建议你以可缩放的增量方式引入数据。 Azure Databricks 提供各种数据引入方法,包括适用于 SaaS 应用程序和 API 集成的全托管式连接器。 最佳做法是引入原始源数据并将其存储在目标表中。 此方法可确保数据得到持久保留、可追溯且可供审核。 请参阅 Lakeflow Connect 中的标准连接器。
数据预处理
引入数据后,必须清理原始数据并设置其格式,使其与适合嵌入和检索的格式保持一致。
分析
在确定检索器应用程序的适当数据源后,下一步是从原始数据中提取所需的信息。 此过程称为分析,涉及将非结构化数据转换为 RAG 应用程序可有效利用的格式。
使用的特定分析技术和工具取决于处理的数据类型。 例如:
- 文本文档(PDF、Word 文档):现成的库(如非结构化和 PyPDF2)可以处理各种文件格式并提供用于自定义分析过程的选项。
- HTML 文档:可以使用 BeautifulSoup 和 lxml 等 HTML 分析库从网页中提取相关内容。 借助这些库,可以浏览 HTML 结构,选择特定元素并提取所需的文本或属性。
- 图像和扫描文档:从图像中提取文本通常需要光学字符识别 (OCR) 技术。 流行的 OCR 库包括开源库(例如 Tesseract)或 SaaS 版本(例如 Amazon Textract、Azure AI Vision OCR 和 Google Cloud Vision API)。
分析数据的最佳做法
分析过程可确保数据整洁、结构化,已准备好用于嵌入生成和矢量搜索。 分析数据时,请考虑以下最佳做法:
- 数据清理:对提取的文本进行预处理,消除无关信息或干扰信息,例如页眉、页脚或特殊字符。 减少 RAG 链需要处理的不必要或格式不当的信息量。
- 处理错误和异常:实现错误处理和日志记录机制,以识别和解决分析过程中遇到的任何问题。 这可以帮助快速识别和解决问题。 这样做通常可发现源数据质量的上游问题。
- 自定义分析逻辑:根据数据的结构和格式,可能需要自定义分析逻辑以提取最相关的信息。 虽然它可能需要在前期付出额外的努力,但请投入时间执行此操作(如果需要),这通常可避免大量下游质量问题。
- 评估分析质量:通过人工审查输出样本,定期评估已分析数据的质量。 这有助于确定分析过程中的任何问题或需要改进的领域。
扩充
使用附加元数据来扩充数据并消除干扰信息。 尽管扩充是可选的,但它可以显著提高应用程序的整体性能。
元数据提取
生成和提取捕获有关文档内容、上下文和结构的重要信息的元数据可以显著提高 RAG 应用程序的检索质量和性能。 元数据提供额外的信号来提高相关性,实现高级筛选,并支持特定于领域的搜索要求。
虽然 LangChain 和 LlamaIndex 等库提供了能够自动提取关联标准元数据的内置分析器,但使用针对特定用例定制的元数据进行补充通常很有帮助。 此方法可确保捕获关键的领域特定信息,从而改善下游的检索和生成。 还可以使用大型语言模型 (LLM) 来自动实现元数据增强。
元数据类型包括:
- 文档级元数据:文件名、URL、作者信息、创建和修改时间戳、GPS 坐标以及文档版本。
- 基于内容的元数据:提取的关键字、摘要、主题、命名实体和特定于领域的标记(产品名称和类别,如 PII 或 HIPAA)。
- 结构元数据:章节标题、目录、页码和语义内容边界(章节或小节)。
- 上下文元数据:源系统、引入日期、数据敏感级别、原始语言或跨国指令。
将元数据与分块文档或其相应嵌入一起存储对于实现最佳性能至关重要。 这还有助于缩小检索的信息范围,并提高应用程序的准确性和可伸缩性。 此外,将元数据集成到混合搜索管道中(即,将矢量相似性搜索与基于关键字的筛选相结合)可以增强相关性,尤其是在大型数据集或特定搜索条件方案中。
重复项删除
根据使用的源,你最终可能会得到重复的文档或近乎重复的文档。 例如,如果你从一个或多个共享驱动器中拉取,则同一文档的多个副本可能存在于多个位置。 其中一些副本可能有细微的修改。 同样,知识库可能有产品文档的副本或博客文章的草稿副本。 如果这些重复项仍保留在语料库中,则最终索引中可能会出现高度冗余的区块,导致应用程序的性能下降。
可以单独使用元数据来消除一些重复项。 例如,如果某个项具有相同的标题和创建日期,但同时具有来自不同源或位置的多个条目,则你可以基于元数据对其进行筛选。
但是,这种做法可能还不够。 为了帮助根据文档内容识别和消除重复项,你可以使用一种称为“局部敏感哈希”的技术。 具体而言,称为 MinHash 的技术在这里可以发挥很好的作用,Spark ML 中已经提供了相关的 Spark 实现。 其工作原理是根据文档包含的字词为文档创建哈希,然后通过联接这些哈希有效地识别重复项或近乎重复项。 从较高层面讲,此过程分为四步:
- 为每个文档创建一个特征矢量。 如果需要,请考虑应用诸如干扰词去除、词干提取和词形还原等技术来改善结果,然后将其标记化为 n 元语法。
- 使用 MinHash 为 Jaccard 距离拟合 MinHash 模型并对矢量进行哈希处理。
- 使用这些哈希运行相似性联接,以便为每个重复或近乎重复的文档生成结果集。
- 筛选掉你不想保留的重复项。
基线重复项删除步骤可以任意选择要保留的文档(例如,保留每个重复项结果中的第一个文档,或在重复项之间随机选择)。 一项潜在的改进是使用其他逻辑(例如最近更新、发布状态或最权威来源)选择重复项的“最佳”版本。 另请注意,可能需要试验特征化步骤和 MinHash 模型中使用的哈希表数量,以改善匹配结果。
有关详细信息,请参阅 Spark 文档中的局部敏感哈希。
筛选
引入语料库中的某些文档可能对代理没有作用,因为它们与其目的无关、太旧或不可靠,或者因为包含有问题的内容,例如有害语言。 不过,其他文档可能包含你不想通过代理泄露的敏感信息。
因此,请考虑在管道中包含一个步骤,以使用任何元数据来筛选掉这些文档,例如将毒性分类器应用于文档,以生成可以用作筛选器的预测。 另一个示例是将个人身份信息 (PII) 检测算法应用于文档以筛选文档。
最后,馈送到代理中的任何文档源都可能是恶意行动者发起数据中毒攻击所凭借的攻击媒介。 你也可以考虑添加检测和筛选机制来帮助识别和消除这些问题。
分块
将原始数据分析成结构化程度更高的格式、删除重复项并筛选掉不需要的信息后,下一步是将其分解为更小且更易于管理的单元(称为区块)。 将大型文档分段为较小的语义集中区块可确保检索到的数据适合 LLM 的上下文,同时最大程度地减少干扰或不相关的信息。 对分块进行的选择将直接影响向 LLM 提供的检索数据,是 RAG 应用程序中的第一层优化之一。
对数据进行分块时,请考虑以下因素:
- 分块策略:用于将原始文本划分为区块的方法。 这可能涉及到使用按句子、段落、特定字符/标记计数以及特定于文档的更高级拆分策略进行拆分的基本技术。
- 区块大小:较小的区块可能侧重于具体详细信息,但会丢失一些相伴的上下文信息。 较大的区块可能捕获更多上下文,但可能包含不相关的信息或导致计算成本高昂。
- 区块之间的重叠:为了确保在将数据拆分为区块时不会丢失重要信息,请考虑在相邻区块之间包括一些重叠。 重叠可以确保跨区块的连续性和上下文保留,并改善检索结果。
- 语义连贯性:尽可能创建语义连贯的区块,其中包含相关信息,但可以独立代表有意义的文本单元。 这可以通过考虑原始数据的结构(例如段落、章节或主题边界)来实现。
- 元数据:相关元数据(例如源文档名称、章节标题或产品名称)可以改进检索。 这些附加信息可以帮助将检索查询与区块匹配。
数据分块策略
寻找适当的分块方法既需要反复迭代,又取决于上下文。 没有一种万能的方法。 最佳区块大小和方法取决于具体用例以及正在处理的数据的性质。 从广义上讲,分块策略可分为以下几种:
- 固定大小的分块:将文本拆分为预定大小(例如固定数量的字符或标记)的区块(例如 LangChain CharacterTextSplitter)。 虽然按任意数量的字符/标记进行拆分既快速又易于设置,但通常无法获得一致的语义连贯区块。 此方法一般不适用于生产级应用程序。
- 基于段落的分块:使用文本中的自然段落边界来定义区块。 此方法可以帮助保留区块的语义一致性,因为段落通常包含相关信息(例如 LangChain RecursiveCharacterTextSplitter)。
- 特定于格式的分块:markdown 或 HTML 等格式具有固有结构,可以定义区块边界(例如 markdown 标头)。 LangChain 的 MarkdownHeaderTextSplitter 或基于 HTML 标头/部分的拆分器等工具可用于此目的。
- 语义分块:可以应用主题建模等技术来识别文本中语义连贯的部分。 这些方法分析每个文档的内容或结构,以根据主题转变确定最合适的区块边界。 尽管与基本方法相比,语义分块更复杂,但它可以帮助创建更符合文本中自然语义划分的区块(有关示例,请参阅 LangChain SemanticChunker)。
示例:固定大小的分块
使用 LangChain 的 RecursiveCharacterTextSplitter 并设置 chunk_size=100 和 chunk_overlap=20 的固定大小分块示例。 ChunkViz 提供了一种交互式的方法,可以直观地展示不同区块大小和区块重叠值如何借助于 Langchain 的字符拆分器影响最终生成的区块。
嵌入
对数据进行分块后,下一步是使用嵌入模型将文本区块转换为矢量表示形式。 嵌入模型将每个文本区块转换为捕获其语义含义的矢量表示形式。 通过将区块表示为密集矢量,嵌入可以基于与检索查询的语义相似性快速准确地检索最相关的区块。 在查询时,将使用用于在数据管道中嵌入区块的同一嵌入模型来转换检索查询。
选择嵌入模型时,请考虑以下因素:
- 模型选择:每个嵌入模型都有细微差别,可用的基准可能无法捕获数据的特定特征。 选择已基于类似数据训练的模型至关重要。 探索任何专为特定任务而设计的可用嵌入模型也可能有益。 试验不同的现成嵌入模型,甚至是那些在标准排行榜上排名较低的模型,例如 MTEB。 可考虑的一些示例:
- 最大标记数:了解所选嵌入模型的标记数上限。 如果传递的区块超出此限制,它们会被截断,因而可能会丢失重要信息。 例如,bge-large-en-v1.5 的最大标记限制为 512。
- 模型大小:较大嵌入模型的表现一般更好,但需要更多计算资源。 根据具体用例和可用资源,需要在性能和效率之间取得平衡。
- 微调:如果 RAG 应用程序处理特定于域的语言(例如公司内部首字母缩略词或术语),请考虑根据特定于域的数据对嵌入模型进行微调。 这可以帮助模型更好地捕获特定域的细微差别和术语,并且通常可以提高检索性能。
索引编制和存储
管道中的下一步是在嵌入内容和前面步骤中生成的元数据上创建索引。 此阶段涉及到将高维矢量嵌入组织成高效的数据结构,以实现快速准确的相似性搜索。
当部署矢量搜索终结点和索引时,Mosaic AI 矢量搜索将使用最新的索引技术,以确保为矢量搜索查询提供快速高效的查找。 无需担心测试和选择最佳索引技术。
生成并部署索引后,可将其存储在系统中,以便为可缩放、低延迟的查询提供支持。 对于包含大型数据集的生产 RAG 管道,使用矢量数据库或可缩放的搜索服务来确保低延迟和高吞吐量。 将其他元数据与嵌入一起存储,以便在检索过程中实现有效的筛选。