核心模块(Modules)
数据连接(DataConnection)
文档加载器(DocumentLoaders)
自定义(Custom)

自定义文档加载器

概述

基于 LLMs 的应用程序通常涉及从数据库或文件(如 PDF)中提取数据,并将其转换为 LLMs 可以利用的格式。在 LangChain 中,这通常涉及创建文档对象,该对象封装了提取的文本(page_content)以及包含有关文档详细信息的元数据 - 一个包含有关文档的作者姓名或发布日期等详细信息的字典。

Document 对象通常被格式化为提示,然后被馈送到 LLM 中,允许 LLM 使用 Document 中的信息生成所需的响应(例如,对文档进行总结)。Documents 可以立即使用,也可以索引到矢量存储以供将来检索和使用。

文档加载的主要抽象为:

组件描述
Document包含 text 和 metadata
BaseLoader用于将原始数据转换为 Documents
Blob二进制数据的表示,可以位于文件或内存中
BaseBlobParser逻辑解析 Blob 以产生 Document 对象

本指南将演示如何编写自定义文档加载和文件解析逻辑;具体来说,我们将看到如何:

  1. 通过从 BaseLoader 继承而创建标准文档加载器。
  2. 使用 BaseBlobParser 创建解析器,并将其与 Blob 和 BlobLoaders 一起使用。这在处理文件时非常有用。

标准文档加载器

文档加载器可通过从 BaseLoader 继承实现,后者提供了加载文档的标准接口。

接口

方法名解释
lazy_load用于惰性逐个加载文档。用于生产代码。
alazy_loadlazy_load 的异步变体
load用于急性加载所有文档到内存中。用于原型设计或交互工作。
aload用于急性加载所有文档到内存中。用于原型设计或交互工作。 添加于 LangChain 的 2024 年 04 月。
  • load 方法仅适用于原型设计工作 - 它只调用 list(self.lazy_load())。
  • alazy_load 有一个默认实现,会委托给 lazy_load。如果您使用异步操作,建议重写默认实现并提供本机异步实现。

重要:在实现文档加载器时,不要通过 lazy_load 或 alazy_load 方法传递参数。预期所有配置通过初始化程序(__init__)传递。这是 LangChain 的设计选择,以确保一旦实例化文档加载器,就会具有加载文档所需的所有信息。

实现

让我们创建一个标准文档加载器的示例,该加载器会读取文件,并为文件中的每一行创建一个文档。

from typing import AsyncIterator, Iterator
 
from langchain_core.document_loaders import BaseLoader
from langchain_core.documents import Document
 
 
class CustomDocumentLoader(BaseLoader):
    """读取文件的示例文档加载器,每行创建一个文档。"""
 
    def __init__(self, file_path: str) -> None:
        """使用文件路径初始化加载器。
 
        Args:
            file_path: 要加载的文件的路径。
        """
        self.file_path = file_path
 
    def lazy_load(self) -> Iterator[Document]:  # <-- 不带任何参数
        """逐行读取文件的懒加载器。
 
        在实现惰性加载方法时,应使用生成器逐个生成文档。
        """
        with open(self.file_path, encoding="utf-8") as f:
            line_number = 0
            for line in f:
                yield Document(
                    page_content=line,
                    metadata={"line_number": line_number, "source": self.file_path},
                )
                line_number += 1
 
    # alazy_load 是可选的。
    # 如果省略实现,则将使用一个委托给 lazy_load 的默认实现!
    async def alazy_load(
        self,
    ) -> AsyncIterator[Document]:  # <-- 不带任何参数
        """逐行读取文件的异步懒加载器。"""
        # 需要 aiofiles
        # 使用 `pip install aiofiles` 安装
        # https://github.com/Tinche/aiofiles
        import aiofiles
 
        async with aiofiles.open(self.file_path, encoding="utf-8") as f:
            line_number = 0
            async for line in f:
                yield Document(
                    page_content=line,
                    metadata={"line_number": line_number, "source": self.file_path},
                )
                line_number += 1

测试 🧪

要测试文档加载器,我们需要一个包含一些优质内容的文件。

with open("./meow.txt", "w", encoding="utf-8") as f:
    quality_content = "meow meow🐱 \n meow meow🐱 \n meow😻😻"
    f.write(quality_content)
 
loader = CustomDocumentLoader("./meow.txt")
## 测试懒加载接口
for doc in loader.lazy_load():
    print()
    print(type(doc))
    print(doc)
<class 'langchain_core.documents.base.Document'>
page_content='meow meow🐱 \n' metadata={'line_number': 0, 'source': './meow.txt'}

<class 'langchain_core.documents.base.Document'>
page_content=' meow meow🐱 \n' metadata={'line_number': 1, 'source': './meow.txt'}

<class 'langchain_core.documents.base.Document'>
page_content=' meow😻😻' metadata={'line_number': 2, 'source': './meow.txt'}
## 测试异步实现
async for doc in loader.alazy_load():
    print()
    print(type(doc))
    print(doc)
<class 'langchain_core.documents.base.Document'>
page_content='meow meow🐱 \n' metadata={'line_number': 0, 'source': './meow.txt'}

<class 'langchain_core.documents.base.Document'>
page_content=' meow meow🐱 \n' metadata={'line_number': 1, 'source': './meow.txt'}

<class 'langchain_core.documents.base.Document'>
page_content=' meow😻😻' metadata={'line_number': 2, 'source': './meow.txt'}

提示:load() 在诸如 jupyter 笔记本之类的交互环境中很有用。避免用于生产代码,因为急性加载假定所有内容都可以适合内存,而在某些情况下(特别是企业数据),情况并非总是如此。

loader.load()
[Document(page_content='meow meow🐱 \n', metadata={'line_number': 0, 'source': './meow.txt'}),
 Document(page_content=' meow meow🐱 \n', metadata={'line_number': 1, 'source': './meow.txt'}),
 Document(page_content=' meow😻😻', metadata={'line_number': 2, 'source': './meow.txt'})]

使用文件

许多文档加载器涉及解析文件。这些加载器之间的区别通常在于如何解析文件而不是如何加载文件。例如,您可以使用 open 读取 PDF 文件或标记文件的二进制内容,但您需要不同的解析逻辑将二进制数据转换为文本。

因此,将解析逻辑与加载逻辑分离可能非常有用,这样一来,无论数据如何加载,都可以更容易地重用给定的解析器。

BaseBlobParser

BaseBlobParser 是一个接口,接受一个 blob 并输出一个 Document 对象列表。blob 是一个表示数据的实体,可以在内存中或文件中存在。LangChain python 具有 Blob 原语,受 Blob WebAPI 规范 (opens in a new tab) 的启发。

from langchain_core.document_loaders import BaseBlobParser, Blob
 
class MyParser(BaseBlobParser):
    """一个将每行转换为文档的简单解析器。"""
 
    def lazy_parse(self, blob: Blob) -> Iterator[Document]:
        """将一个 blob 逐行解析为文档。"""
        line_number = 0
        with blob.as_bytes_io() as f:
            for line in f:
                line_number += 1
                yield Document(
                    page_content=line,
                    metadata={"line_number": line_number, "source": blob.source},
                )

使用 blob API 还允许从内存直接加载内容,而无需从文件中读取!

blob = Blob.from_path("./meow.txt")
parser = MyParser()
list(parser.lazy_parse(blob))
[Document(page_content='meow meow🐱 \n', metadata={'line_number': 1, 'source': './meow.txt'}),
 Document(page_content=' meow meow🐱 \n', metadata={'line_number': 2, 'source': './meow.txt'}),
 Document(page_content=' meow😻😻', metadata={'line_number': 3, 'source': './meow.txt'})]

使用 blob API 还可以直接从内存加载内容,而无需从文件中读取!

blob = Blob(data=b"some data from memory\nmeow")
list(parser.lazy_parse(blob))
[Document(page_content='some data from memory\n', metadata={'line_number': 1, 'source': None}),
 Document(page_content='meow', metadata={'line_number': 2, 'source': None})]

Blob

让我们简要地浏览一下 Blob API 的一些内容。

blob = Blob.from_path("./meow.txt", metadata={"foo": "bar"})
blob.encoding
'utf-8'
blob.as_bytes()
b'meow meow\xf0\x9f\x90\xb1 \n meow meow\xf0\x9f\x90\xb1 \n meow\xf0\x9f\x98\xbb\xf0\x9f\x98\xbb'
blob.as_string()
'meow meow🐱 \n meow meow🐱 \n meow😻😻'
blob.as_bytes_io()
<contextlib._GeneratorContextManager at 0x743f34324450>
blob.metadata
{'foo': 'bar'}
blob.source
'./meow.txt'

Blob Loaders

虽然解析器封装了将二进制数据解析为文档所需的逻辑,但是 blob loaders 封装了从给定存储位置加载 blob 所需的逻辑。

目前,LangChain 仅支持 FileSystemBlobLoader。

您可以使用 FileSystemBlobLoader 来加载 blob,然后使用解析器对其进行解析。

from langchain_community.document_loaders.blob_loaders import FileSystemBlobLoader
 
blob_loader = FileSystemBlobLoader(path=".", glob="*.mdx", show_progress=True)
parser = MyParser()
for blob in blob_loader.yield_blobs():
    for doc in parser.lazy_parse(blob):
        print(doc)
        break
0%|          | 0/8 [00:00<?, ?it/s]

page_content='# Microsoft Office\n' metadata={'line_number': 1, 'source': 'office_file.mdx'}
page_content='# Markdown\n' metadata={'line_number': 1, 'source': 'markdown.mdx'}
page_content='# JSON\n' metadata={'line_number': 1, 'source': 'json.mdx'}
page_content='---\n' metadata={'line_number': 1, 'source': 'pdf.mdx'}
page_content='---\n' metadata={'line_number': 1, 'source': 'index.mdx'}
page_content='# File Directory\n' metadata={'line_number': 1, 'source': 'file_directory.mdx'}
page_content='# CSV\n' metadata={'line_number': 1, 'source': 'csv.mdx'}
page_content='# HTML\n' metadata={'line_number': 1, 'source': 'html.mdx'}

Generic Loader

LangChain 在 GenericLoader 中组合了 BlobLoader 和 BaseBlobParser 的抽象。

GenericLoader 旨在提供标准化的类方法,使使用现有的 BlobLoader 实现变得容易。目前,只支持 FileSystemBlobLoader。

from langchain_community.document_loaders.generic import GenericLoader
 
loader = GenericLoader.from_filesystem(
    path=".", glob="*.mdx", show_progress=True, parser=MyParser()
)
 
for idx, doc in enumerate(loader.lazy_load()):
    if idx < 5:
        print(doc)
 
print("... output truncated for demo purposes")
0%|          | 0/8 [00:00<?, ?it/s]

page_content='# Microsoft Office\n' metadata={'line_number': 1, 'source': 'office_file.mdx'}
page_content='\n' metadata={'line_number': 2, 'source': 'office_file.mdx'}
page_content='>[The Microsoft Office](https://www.office.com/) suite of productivity software includes Microsoft Word, Microsoft Excel, Microsoft PowerPoint, Microsoft Outlook, and Microsoft OneNote. It is available for Microsoft Windows and macOS operating systems. It is also available on Android and iOS.\n' metadata={'line_number': 3, 'source': 'office_file.mdx'}
page_content='\n' metadata={'line_number': 4, 'source': 'office_file.mdx'}
page_content='This covers how to load commonly used file formats including `DOCX`, `XLSX` and `PPTX` documents into a document format that we can use downstream.\n' metadata={'line_number': 5, 'source': 'office_file.mdx'}
... output truncated for demo purposes

Custom Generic Loader

如果您喜欢创建类,可以创建一个类来封装一起的逻辑。

您可以从这个类继承以使用现有的加载器加载内容。

from typing import Any
 
 
class MyCustomLoader(GenericLoader):
    @staticmethod
    def get_parser(**kwargs: Any) -> BaseBlobParser:
        """Override this method to associate a default parser with the class."""
        return MyParser()
loader = MyCustomLoader.from_filesystem(path=".", glob="*.mdx", show_progress=True)
 
for idx, doc in enumerate(loader.lazy_load()):
    if idx < 5:
        print(doc)
 
print("... output truncated for demo purposes")
0%|          | 0/8 [00:00<?, ?it/s]

page_content='# Microsoft Office\n' metadata={'line_number': 1, 'source': 'office_file.mdx'}
page_content='\n' metadata={'line_number': 2, 'source': 'office_file.mdx'}
page_content='>[The Microsoft Office](https://www.office.com/) suite of productivity software includes Microsoft Word, Microsoft Excel, Microsoft PowerPoint, Microsoft Outlook, and Microsoft OneNote. It is available for Microsoft Windows and macOS operating systems. It is also available on Android and iOS.\n' metadata={'line_number': 3, 'source': 'office_file.mdx'}
page_content='\n' metadata={'line_number': 4, 'source': 'office_file.mdx'}
page_content='This covers how to load commonly used file formats including `DOCX`, `XLSX` and `PPTX` documents into a document format that we can use downstream.\n' metadata={'line_number': 5, 'source': 'office_file.mdx'}
... output truncated for demo purposes