不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。
01 介绍
Document loader(文档加载器),使用文档加载器将数据从数据源加载为 Document 对象的数据。
Document 是 LangChain 提供的一个类,包含一段文本和与文本关联的元数据。我们读取数据后就会转换成 Document 对象。
例如,有一些文档加载器用于加载简单的 .txt 文件、加载任何网页的文本内容,甚至用于加载 YouTube 视频。文档加载器提供了“ load ”方法,用于从配置的源加载数据,转换为 Document。它们还可以选择实现“延迟加载”,以便将数据延迟加载到内存中。
基于 LLM 的应用程序经常需要从数据库或文件(如 PDF)中提取数据,并将其转换为 LLM 可以使用的格式。在LangChain中,这通常涉及创建 Document 对象,这些对象封装了提取的文本(page_content)以及元数据(metadata 包含文档详细信息的字典),例如作者姓名或出版日期。
Document 对象通常被格式化为提示,这些提示被输入到 LLM 中,允许 LLM 使用 Document 中的信息来生成所需的响应(例如,总结文档)。Documents 既可以立即使用,也可以索引到向量存储中,以备将来检索和使用。
02 基础使用
这里以TextLoader为例
python复制代码from langchain_community.document_loaders import TextLoader
"""
file_path:要加载的文件的路径。
encoding:要使用的文件编码。如果“无”,将加载文件使用默认的系统编码。
autodetect_encoding:是否尝试自动检测文件编码如果指定的编码失败。
"""
loader = TextLoader("./index.txt",encoding='utf-8',autodetect_encoding=True)
loader.load()
"""
[
Document(
page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\n\n"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)\n\n"永远不要小看自己,因为你永远不知道自己有多强大。" ——《狮子王》(The Lion King, 1994)\n\n"我会回来的。" ——《终结者》(The Terminator, 1984)\n\n"你不能改变过去,但你可以改变未来。" ——《回到未来》(Back to the Future, 1985)\n\n"即使世界末日来临,我也要和你一起度过。" ——《泰坦尼克号》(Titanic, 1997)\n\n"不要让别人告诉你你能做什么,不能做什么。如果你有一个梦想,就去捍卫它。" ——《当幸福来敲门》(The Pursuit of Happyness, 2006)\n\n"我将永远爱你。" ——《保镖》(The Bodyguard, 1992)\n\n"这就是生活,小家伙。" ——《美丽人生》(Life is Beautiful, 1997)\n\n"我有一个梦想。" ——《我有一个梦想》(I Have a Dream, 1963)\n\n"你只需要跟随你的黄砖路。" ——《绿野仙踪》(The Wizard of Oz, 1939)\n\n这些台词不仅在电影中令人难忘,也在现实生活中激励着许多人。',
metadata={'source': './index.txt'}
)
]
"""
通过以上代码我们可以看到:
- load方法返回的是 Document 列表。
- Document 里面主要包含两个元素,page_content 和 metadata。
- metadata 是字典类型,这里包含 source 键,表示数据源位置。metadata内容和 Loader 对象有关,不同的对象,生成的 metadata 内容不同。
03 自定义Document Loader
在自定义之前,先了解几个组件
组件 | 描述 |
---|---|
Document | 包含文本内容和元数据 |
BaseLoader | 用于将源数据转换成Document |
Blob | 用于二进制数据的处理 |
BaseBlobParser | 将二进制数据转换成Document |
Standard Document Loader
标准文档加载器。文档加载器可以通过从 BaseLoader 进行子类化来实现,它提供了用于加载文档的标准接口。
自定义标准文档加载器必须实现的方法是:lazy_load
你说你还想实现其他方法,能不能行?当然能行!建议这样做吗?肯定不建议!为什么不建议这样做?源码里面有解释,大家可以自己跟一下源码,我这里就不贴了!😂
这里我会创建一个自定义的标准文档加载器,该加载器每次从文档中读取一行数据并创建 Document 对象返回。代码来源于官方文档,如下:
Python复制代码from typing import AsyncIterator, Iterator
from langchain_core.document_loaders import BaseLoader
from langchain_core.documents import Document
class CustomDocumentLoader(BaseLoader):
"""An example document loader that reads a file line by line."""
def __init__(self, file_path: str) -> None:
"""Initialize the loader with a file path.
Args:
file_path: The path to the file to load.
"""
self.file_path = file_path
def lazy_load(self) -> Iterator[Document]: # <-- Does not take any arguments
"""A lazy loader that reads a file line by line.
When you're implementing lazy load methods, you should use a generator
to yield documents one by one.
"""
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 is OPTIONAL.
# If you leave out the implementation, a default implementation which delegates to lazy_load will be used!
async def alazy_load(
self,
) -> AsyncIterator[Document]: # <-- Does not take any arguments
"""An async lazy loader that reads a file line by line."""
# Requires aiofiles
# Install with `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
使用方式如下:
python复制代码loader = CustomDocumentLoader("./custom_loader.txt")
loader.load()
打印结果如下:
python复制代码"""
[
Document(
page_content='这是第一行\n',
metadata={'line_number': 0, 'source': './custom_loader.txt'}
),
Document(
page_content='\n',
metadata={'line_number': 1, 'source': './custom_loader.txt'}
),
Document(
page_content='第二行是空白行,这是第三行',
metadata={'line_number': 2, 'source': './custom_loader.txt'})
]
"""
以上就是一个完整的自定义标准文档加载器实现,我们实现了lazy_load方法,同时也实现了alazy_load方法。这就呼应了我前面写的“可以全部方法都实现,但是不建议!”这里仅作示例说明。实际开发中如果能通过实现lazy_load一个方法解决问题的,就不要实现多个方法!
BaseBlobParser
BaseBlobParser 提供了接受 Blob 并输出 Document 对象列表的接口。
这里要说明一点:BaseBlobParser 与 BaseLoader 之间并不是割裂的,而是为了“组合复用”才有的 Blob 与 BaseBlobParser。 源码中很多的 XxxxLoader 内部的实现就是这样,下面贴出 PyPDFLoader 的源码图片用以说明。
我们将上面 Standard Document Loader 中的自定义示例做修改,用 Blob + BaseBlobParser 实现。代码来源官方,如下:
python复制代码from langchain_core.document_loaders import BaseBlobParser, Blob
from typing import Iterator
from langchain_core.documents import Document
class MyParser(BaseBlobParser):
"""A simple parser that creates a document from each line."""
def lazy_parse(self, blob: Blob) -> Iterator[Document]:
"""Parse a blob into a document line by line."""
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},
)
使用方式如下:
python复制代码blob = Blob.from_path("./custom_loader.txt")
parser = MyParser()
list(parser.lazy_parse(blob))
打印结果如下:
bash复制代码"""
[
Document(
page_content='这是第一行\r\n',
metadata={'line_number': 1, 'source': './custom_loader.txt'}),
Document(
page_content='\r\n',
metadata={'line_number': 2, 'source': './custom_loader.txt'}),
Document(
page_content='第二行是空白行,这是第三行',
metadata={'line_number': 3, 'source': './custom_loader.txt'})
]
"""
使用 Blob 还支持从内存中加载内容,而无需从文件中读取!示例如下:
python复制代码blob = Blob(data=b"one\n\nthree")
list(parser.lazy_parse(blob))
"""
[
Document(
page_content='one\n',
metadata={'line_number': 1, 'source': None}),
Document(
page_content='\n',
metadata={'line_number': 2, 'source': None}),
Document(
page_content='three',
metadata={'line_number': 3, 'source': None})
]
"""
Blob
这里对Blob常用API做简单介绍。
python复制代码blob = Blob.from_path("./custom_loader.txt", metadata={"name": "张三"})
print(blob.encoding)
"""
utf-8
"""
print(blob.as_bytes())
"""
b'\xe8\xbf\x99\xe6\x98\xaf\xe7\xac\xac\xe4\xb8\x80\xe8\xa1\x8c\r\n\r\n\xe7\xac\xac\xe4\xba\x8c\xe8\xa1\x8c\xe6\x98\xaf\xe7\xa9\xba\xe7\x99\xbd\xe8\xa1\x8c\xef\xbc\x8c\xe8\xbf\x99\xe6\x98\xaf\xe7\xac\xac\xe4\xb8\x89\xe8\xa1\x8c'
"""
print(blob.as_string())
"""
这是第一行
第二行是空白行,这是第三行
"""
print(blob.as_bytes_io())
"""
print(blob.as_bytes_io())
"""
print(blob.metadata)
"""
{'name': '张三'}
"""
print(blob.source)
"""
print(blob.source)
"""
Blob Loaders
BlobLoader 用于从指定位置加载 Blob,返回一个 Blob迭代器对象。目前 LangChain 中仅支持 FileSystemBlobLoader 与 YoutubeAudioLoader。前者用于文件系统,后者用于从 YouTube 视频网址提取音频文件。
以 FileSystemBlobLoader 为例,示例代码如下:
python复制代码from langchain_community.document_loaders.blob_loaders import FileSystemBlobLoader
# 当前文件目录下,所有以ipynb结尾的文件,进行解析
blob_loader = FileSystemBlobLoader(path=".", glob="*.ipynb", show_progress=True)
parser = MyParser()
for blob in blob_loader.yield_blobs():
for doc in parser.lazy_parse(blob):
# 因为我们的逻辑是按照行读取并处理成 Document,这里只打印第一行结果看效果
# 大家可以自行全部打印
print(doc)
break
"""
0%| | 0/9 [00:00<?, ?it/s]
page_content='{\r\n' metadata={'line_number': 1, 'source': 'Agent.ipynb'}
page_content='{\r\n' metadata={'line_number': 1, 'source': 'Chat Model.ipynb'}
page_content='{\r\n' metadata={'line_number': 1, 'source': 'Conversation Retrieval Chain.ipynb'}
page_content='{\r\n' metadata={'line_number': 1, 'source': 'Document loader.ipynb'}
page_content='{\r\n' metadata={'line_number': 1, 'source': 'LCEL.ipynb'}
page_content='{\r\n' metadata={'line_number': 1, 'source': 'Model.ipynb'}
page_content='{\r\n' metadata={'line_number': 1, 'source': 'Output parsers.ipynb'}
page_content='{\n' metadata={'line_number': 1, 'source': 'Prompt.ipynb'}
page_content='{\r\n' metadata={'line_number': 1, 'source': '【可能是全网最丝滑的LangChain教程】三、快速入门.ipynb'}
"""
Generic Loader
LangChain 有一个 GenericLoader 抽象,它由 BlobLoader 和 BaseBlobParser 组成。GenericLoader 旨在提供标准化的类方法,使现有 BlobLoader 实现易于使用。目前仅支持 FileSystemBlobLoader,因为支持单一,这里不做详细介绍,简单说下用法。
python复制代码from langchain_community.document_loaders.generic import GenericLoader
loader = GenericLoader.from_filesystem(
path=".", glob="*.txt", show_progress=True, parser=MyParser()
)
for idx, doc in enumerate(loader.lazy_load()):
print(doc)
"""
page_content='这是第一行\r\n' metadata={'line_number': 1, 'source': 'custom_loader.txt'}
page_content='\r\n' metadata={'line_number': 2, 'source': 'custom_loader.txt'}
page_content='第二行是空白行,这是第三行' metadata={'line_number': 3, 'source': 'custom_loader.txt'}
page_content='电影中的经典台词往往能够深入人心,成为人们记忆中的一部分。以下是一些电影中的经典台词:\r\n' metadata={'line_number': 1, 'source': 'index.txt'}
page_content='\r\n' metadata={'line_number': 2, 'source': 'index.txt'}
page_content='"生活就像一盒巧克力,你永远不知道你会得到什么。" ——《阿甘正传》(Forrest Gump, 1994)\r\n' metadata={'line_number': 3, 'source': 'index.txt'}
"""
04 常用 Loader 介绍
上部分我们已经知道了如何自定义 Loader,这里我们再看 LangChain 中的内置 Loader 那就是小儿科了。
因为我们已经知道了 Loader 的本质!
全部内置 Loader 如下:
bash复制代码class BaseLoader
class GenericLoader
class ConcurrentLoader
class WebBaseLoader
class AZLyricsLoader
class BlackboardLoader
class CollegeConfidentialLoader
class SitemapLoader
class DocusaurusLoader
class GitbookLoader
class HNLoader
class IMSDbLoader
class AcreomLoader
class AirbyteCDKLoader
class AirbyteHubspotLoader
class AirbyteStripeLoader
class AirbyteTypeformLoader
class AirbyteZendeskSupportLoader
class AirbyteShopifyLoader
class AirbyteSalesforceLoader
class AirbyteGongLoader
class AirbyteJSONLoader
class AirtableLoader
class UnstructuredBaseLoader
class UnstructuredFileLoader
class UnstructuredAPIFileLoader
class UnstructuredPDFLoader
class UnstructuredCSVLoader
class UnstructuredWordDocumentLoader
class UnstructuredEmailLoader
class UnstructuredCHMLoader
class UnstructuredEPubLoader
class UnstructuredExcelLoader
class UnstructuredHTMLLoader
class UnstructuredImageLoader
class UnstructuredMarkdownLoader
class UnstructuredODTLoader
class UnstructuredOrgModeLoader
class UnstructuredPowerPointLoader
class UnstructuredRSTLoader
class UnstructuredRTFLoader
class UnstructuredTSVLoader
class UnstructuredXMLLoader
class UnstructuredFileIOLoader
class UnstructuredAPIFileIOLoader
class UnstructuredLakeFSLoader
class S3FileLoader
class BasePDFLoader
class OnlinePDFLoader
class PyPDFLoader
class PyPDFium2Loader
class PDFMinerLoader
class PDFMinerPDFasHTMLLoader
class PyMuPDFLoader
class MathpixPDFLoader
class PDFPlumberLoader
class AmazonTextractPDFLoader
class DocumentIntelligenceLoader
class PyPDFDirectoryLoader
class ApifyDatasetLoader
class ArcGISLoader
class ArxivLoader
class AssemblyAIAudioTranscriptLoader
class AssemblyAIAudioLoaderById
class AstraDBLoader
class AsyncChromiumLoader
class AsyncHtmlLoader
class AthenaLoader
class AzureAIDataLoader
class AzureAIDocumentIntelligenceLoader
class AzureBlobStorageFileLoader
class AzureBlobStorageContainerLoader
class BSHTMLLoader
class BibtexLoader
class BigQueryLoader
class BiliBiliLoader
class CSVLoader
class TextLoader
class PythonLoader
class DirectoryLoader
class BlockchainDocumentLoader
class BraveSearchLoader
class BrowserbaseLoader
class BrowserlessLoader
class CassandraLoader
class ChatGPTLoader
class CoNLLULoader
class ConfluenceLoader
class CouchbaseLoader
class CubeSemanticLoader
class BaseDataFrameLoader
class DataFrameLoader
class PolarsDataFrameLoader
class XorbitsLoader
class DatadogLogsLoader
class DiffbotLoader
class DiscordChatLoader
class DocugamiLoader
class Docx2txtLoader
class DropboxLoader
class DuckDBLoader
class EtherscanLoader
class EverNoteLoader
class FacebookChatLoader
class FaunaLoader
class FigmaFileLoader
class FireCrawlLoader
class GCSFileLoader
class GCSDirectoryLoader
class GlueCatalogLoader
class GeoDataFrameLoader
class BaseGitHubLoader
class GitHubIssuesLoader
class GithubFileLoader
class GitLoader
class YoutubeLoader
class GoogleApiYoutubeLoader
class GoogleDriveLoader
class GoogleSpeechToTextLoader
class GutenbergLoader
class HuggingFaceDatasetLoader
class HuggingFaceModelLoader
class IFixitLoader
class ImageCaptionLoader
class IuguLoader
class JoplinLoader
class JSONLoader
class KineticaLoader
class LakeFSLoader
class LarkSuiteDocLoader
class LarkSuiteWikiLoader
class LLMSherpaFileLoader
class MastodonTootsLoader
class MHTMLLoader
class MWDumpLoader
class MaxComputeLoader
class MergedDataLoader
class ModernTreasuryLoader
class MongodbLoader
class NewsURLLoader
class NotebookLoader
class NotionDBLoader
class NotionDirectoryLoader
class OBSFileLoader
class OBSDirectoryLoader
class ObsidianLoader
class OneDriveFileLoader
class O365BaseLoader
class OneDriveLoader
class SharePointLoader
class OpenCityDataLoader
class OracleAutonomousDatabaseLoader
class OracleDocLoader
class OutlookMessageLoader
class PebbloSafeLoader
class PlaywrightURLLoader
class PsychicLoader
class PubMedLoader
class PySparkDataFrameLoader
class RSSFeedLoader
class ReadTheDocsLoader
class RecursiveUrlLoader
class RedditPostsLoader
class RoamLoader
class RocksetLoader
class S3DirectoryLoader
class SQLDatabaseLoader
class SRTLoader
class SeleniumURLLoader
class SlackDirectoryLoader
class SnowflakeLoader
class SpiderLoader
class SpreedlyLoader
class StripeLoader
class SurrealDBLoader
class TelegramChatFileLoader
class TelegramChatApiLoader
class TencentCOSFileLoader
class TencentCOSDirectoryLoader
class TensorflowDatasetLoader
class TiDBLoader
class ToMarkdownLoader
class TomlLoader
class TrelloLoader
class TwitterTweetLoader
class UnstructuredURLLoader
class VsdxLoader
class WeatherDataLoader
class WhatsAppChatLoader
class WikipediaLoader
class YuqueLoader
这些 Loader 可以加载哪些数据?基本我们生活中常见的数据形式都可以!
加载B站视频、加载百度搜索结果、加载URL数据、加载word、ppt、csv、pdf、html等常见文档、加载常见数据库文件、为图片配文字、为视频加字幕、加载视频内容…
写到这,我只能用一个字形容:强
05 总结
我们知道了 LangChain 中的 Loader,我们会用、会自定义,那么问题来了,我们学习 Loader 有什么用?大家总不能为了学 Loader 而学吧?!
既然最终的 Document 中包含 page_content 和 metadata,我们想办法把这些数据给到 LLM,然后让 LLM 根据这些数据做总结、做客服、做分析,这是不是就闭环了?!