第10章:RAG(2)

环境准备

1 安装依赖

我们在《第02章-模型调用》章节已经通过requirements.txt 文件安装过课程的依赖。当时考虑到本章 RAG模块涉及的依赖较多且大,所以不在之前的依赖文件中。所以,这里大家需要补充安装RAG涉及到 的依赖。完整版文件见《02-资料\requirements_full.txt》


复制代码
 pip install -r requirements_full.txt

检查冲突

复制代码
pip check

如果环境安装正确,则日志如下

2 准备数据

将 knowledge.txt 置于项目根目录下

将 asset文件夹 解压后置于项目根目录下

文档加载器 Document Loaders

数据源可能包含多种格式的文件,如文本文档、Markdown,PDF 等。LangChain 实现和集成了众多文 档加载器(https://docs.langchain.com/oss/python/integrations/document_loaders ),方便从不同 格式的文件中加载数据。

常用 Loaders:

  • TextLoader - 文本文件
  • CSVLoader - CSV 文件
  • PyPDFLoader - PDF 文件
  • WebBaseLoader - 网页

LangChain的设计:对于 Source 中多种不同的数据源,我们可以用一种统一的形式读取、调用。上述 每一个文档加载器,都要继承自 BaseLoader 基类,此类提供了通用的 load (一次加载所有文档) 与 lazy_load (以延迟方式加载文档) 方法,用于从数据源加载数据并处理为 Document 对象 。

1 加载txt

复制代码
from langchain_community.document_loaders import TextLoader

loader = TextLoader(
    file_path="../asset/load/01-langchain-utf-8.txt",
    encoding="utf-8",
)

docs = loader.load()

print(docs)

Document(metadata={'source': '../asset/load/01-langchain-utf-8.txt'}, page_content='LangChain 是一个用于构建基于大语言模型(LLM)应用的开发框架,旨在帮助开发者更高效地集成、管理和增强大语言模型的能力,构建端到端的应用程序。它提供了一套模块化工具和接口,支持从简单的文本生成到复杂的多步骤推理任务')

Documment对象中有两个重要的属性

  • page_content:真正的文档内容,字符串类型

  • metadata:文档内容的原数据,字典类型

    print(type(docs[0]))

    print(docs[0].metadata)
    print(docs[0].page_content)

2 加载CSV

举例:加载csv所有列

复制代码
from langchain_community.document_loaders import CSVLoader

loader = CSVLoader(
    file_path="../asset/load/02-load.csv",
)

docs = loader.load()
print(docs)

3 加载JSON

LangChain提供的JSON格式的文档加载器是 JSONLoader 。在实际应用场景中,JSON格式的数据占有 很大比例,而且JSON的形式也是多样的。我们需要特别关注。

JSONLoader 使用指定的 jq结构 来解析 JSON 文件。jq是一个轻量级的命令行 JSON 处理器 ,可以对 JSON 格式的数据进行各种复杂的处理,包括数据过滤、映射、减少和转换,是处理 JSON 数据的 首选 工具之一 。

复制代码
# 在requirements_full.txt中已经安装
pip install jq

常见 jq schema 参考:

详细用法可参考 https://jqlang.org/manual/#basic-filters。

举例1:使用JSONLoader文档加载器加载

复制代码
# 1.导入依赖
from  langchain_community.document_loaders import JSONLoader
from rich import print as rprint

# 2.定义JSONLoader对象
# 情况1
# json_loader=JSONLoader(
#     file_path="../asset/load/03-load.json",
#     jq_schema=".", #直接提取完整的JSON对象(包括所有字段)
#     text_content=False #保持原始 JSON 结构,将提取的数据转换为JSON字符串存入page_content字段中
# )

# 情况2
# .messages[].content:遍历.messages[]中所有元素 从每一个元素中提取.content字段
json_loader=JSONLoader(
    file_path="../asset/load/03-load.json",
    jq_schema=".messages[].content"
)

# 3.加载
docs = json_loader.load()
rprint(docs)

举例2:提取03-response.json文件中指定的文本

复制代码
# 1.导入相关依赖
from langchain_community.document_loaders import JSONLoader
from rich import print as rprint

# 2.定义json文件的路径
file_path = '../asset/load/03-response.json'

# 3.定义JSONLoader对象
# 需求1:提取data.items中的数据
# loader = JSONLoader(
#     file_path=file_path,  # 文件路径
#     jq_schema=".data.items[]",
#     text_content=False,  # 提取内容是否为字符串格式
# )


# 需求2:提取data.items[].content中的数据
# loader = JSONLoader(
#     file_path=file_path,  # 文件路径
#     jq_schema=".data.items[].content",
# )


# 需求3:提取data.items中指定字段的数据
loader = JSONLoader(
    file_path=file_path,  # 文件路径
    jq_schema="""
        .data.items[] | {
            author,
            created_at,
            content: (.title + "\n" + .content)
        }
    """,
    text_content=False,  # 提取内容是否为字符串格式
)


# 4.加载
data = loader.load()
rprint(data)

4 加载pdf

PDF 存在多种来源格式,包括扫描版(图片 PDF)、电子文本版、混合版。并且布局格式也多种多样, 包括单列布局、双列布局甚至竖排文本布局。并且包含段落、标题、页眉页脚、表格、数学公式、化学 式、特殊符号、图片等各种元素

因此,PDF 解析存在很多挑战。对于复杂 PDF,需要进行文本提取、布局检测、表格解析、公式识别等 处理。

LangChain加载PDF文件使用的是pypdf,先安装

复制代码
# 在requirements_full.txt中已经安装
pip install pypdf

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader(
    # 文件路径,支持本地文件和在线文件链接
    # file_path="../asset/load/04-sample.pdf",
    file_path="https://arxiv.org/pdf/alg-geom/9202012",
    # 提取模式:控制如何从 PDF 文件中解析和提取文本结构。
    #   plain 提取文本,默认值
    #   layout 布局感知提取模式,通常会通过插入大量的空格、换行符,来模拟原文档中的多栏、缩进和间距(适用场景:学术论文(如 arXiv 论文)、多栏报刊杂志、带有左右分栏的合同)
    extraction_mode="plain",
)

docs = loader.load()

print(docs)
print(len(docs))

5 加载word

可使用 UnstructuredWordDocumentLoader加载 Word 文件,需要 unstructured 包。(已在 requirements_full.txt文件中安装)

复制代码
from langchain_community.document_loaders import UnstructuredWordDocumentLoader

loader = UnstructuredWordDocumentLoader(
    # 文件路径
    file_path="../asset/load/05-sgg_chat.docx",
    # 加载模式:
    #   single 返回单个Document对象
    #   elements 按标题等元素切分文档
    mode="single",
)

docs = loader.load()

print(len(docs))
print(docs)

6 加载Markdown

可使用 UnstructuredMarkdownLoader 加载 Markdown 文件,需要 unstructured 包。(已在 requirements_full.txt文件中安装)

复制代码
# 1.导入相关的依赖
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from pprint import pprint

# 2.定义UnstructuredMarkdownLoader对象
loader = UnstructuredMarkdownLoader(
    file_path="../asset/load/06-load.md",
    # 加载模式:
    #   single 返回单个Document对象
    #   elements 按标题等元素切分文档
    mode= "single",
    # 解析策略:
    #   "fast"(快速模式),它会以最快的速度提取文本,不进行复杂的版面分析
    #   "hi_res" 高分辨率模式
    strategy="fast"
)

# 3.加载
docs = loader.load()

# 4.打印
print(len(docs))
pprint(docs)

举例2:精细分割文档,保留结构信息

将Markdown文档按语义元素(标题、段落、列表、表格等)拆分成多个独立的小文档( Element 对 象),而不是返回单个大文档。通过指定 mode="elements" 轻松保持这种分离。

复制代码
# 1.导入相关的依赖
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from pprint import pprint

# 2.定义UnstructuredMarkdownLoader对象
md_loader = UnstructuredMarkdownLoader(
    file_path="../asset/load/06-load.md",
    # 加载模式:
    #   single 返回单个Document对象
    #   elements 按标题等元素切分文档
    mode= "elements",
    # 解析策略:
    #   "fast"(快速模式),它会以最快的速度提取文本,不进行复杂的版面分析
    #   "hi_res" 高分辨率模式
    strategy="fast"
)

# 3.加载
docs = md_loader.load()

print(len(docs))
# 4.打印
for doc in docs:
    # pprint(doc)
    pprint(doc.page_content)

7 加载HTML(了解)

复制代码
# 1.导入相关的依赖
from langchain_community.document_loaders import UnstructuredHTMLLoader

# 2.定义UnstructuredHTMLLoader对象
# strategy:
#   "fast" 解析加载html文件速度是比较快(但可能丢失部分结构或元数据)
#   "hi_res": (高分辨率解析) 解析精准(速度慢一些)
#   "ocr_only"  强制使用ocr提取文本,仅仅适用于图像(对HTML无效)

# mode :one of `{'paged', 'elements', 'single'}
#    "elements"  按语义元素(标题、段落、列表、表格等)拆分成多个独立的小文档

loader = UnstructuredHTMLLoader(
    file_path="../asset/load/07-load.html",
    mode="elements",
    strategy="fast"
)

# 3.加载
docs = loader.load()

print(len(docs))  # 16

# 4.打印
for doc in docs:
    pprint(doc)

8 加载File Directory(了解)

除了上述的单个文件加载,我们也可以批量加载一个文件夹内的所有文件。

复制代码
# 1.导入相关的依赖
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.document_loaders import PythonLoader
from pprint import pprint

# 2.定义DirectoryLoader对象,指定要加载的文件夹路径、要加载的文件类型和是否使用多线程
directory_loader = DirectoryLoader(
    path="../asset/load",
    glob="*.py", # 文件匹配模式(过滤器)。使用标准的 Unix 路径通配符。
    use_multithreading=True, # 是否启用多线程。填 True 意味着 LangChain 会同时并发读取多个文件。
    show_progress=True, # 是否显示进度条。填 True 时,控制台在加载文件时会弹出一个进度条
    loader_cls=PythonLoader # 指定底层核心加载器
)

# 3.加载
docs = directory_loader.load()

# 4.打印
print(len(docs))
for doc in docs:
    pprint(doc)

9 了解:BaseLoader、Document类

一方面:LangChain在设计时,要保证Source中多种不同的数据源,在接下来的流程中可以用一种统一 的形式读取、调用。

另一方面:为什么 PDFloader 和 TextLoader 等Document Loader 都使用 load() 去加载,且都使 用 .page_content 和 .metadata 读取数据。

【解答】每一个在LangChain中集成的文档加载器,都要继承自 BaseLoader(文档加载器) , BaseLoader提供了一个名为"load"的公开方法,用于从配置的不同 数据源 加载数据,全部作为 Document 对象。实现逻辑如下所示:

BaseLoader类分析

BaseLoader类定义了如何从不同的数据源加载文档,每个基于不同数据源实现的loader,都需要继承 BaseLoader 。Baseloader要求不多,对于任何具体实现的loader,最少都要实现 load方法。

复制代码
from abc import ABC
from typing import List, Optional

from langchain_core.documents import Document
from langchain_text_splitters import TextSplitter, RecursiveCharacterTextSplitter


class BaseLoader(ABC):
    """
    文档加载器基类。

    作用:
        将各种来源的数据加载成 LangChain 的 Document 对象。

    设计要求:
        子类应该实现 lazy_load(),通过生成器的方式逐个返回 Document,
        而不是一次性把所有文档加载到内存中。

    注意:
        load() 和 load_and_split() 主要是为了方便用户使用,
        子类一般不应该重写这两个方法。
    """

    def load(self) -> List[Document]:
        """
        将数据加载为 Document 对象列表。

        实际上调用的是 lazy_load(),
        然后通过 list() 一次性将生成器中的所有 Document 转换成列表。

        Returns:
            List[Document]: 文档对象列表。
        """
        return list(self.lazy_load())

    async def aload(self) -> list[Document]:
        """
        异步加载数据。

        是 load() 的异步版本。

        Returns:
            list[Document]: 文档对象列表。
        """
        return [document async for document in self.alazy_load()]

    def load_and_split(
        self,
        text_splitter: Optional[TextSplitter] = None,
    ) -> List[Document]:
        """
        加载文档并将文档切分成多个文本块。

        参数:
            text_splitter:
                文本切分器。
                如果没有传入,则默认使用
                RecursiveCharacterTextSplitter。

        Returns:
            List[Document]:
                切分后的 Document 对象列表。

        注意:
            这个方法主要是为了方便使用,
            不建议子类重写。
        """

        # 如果用户没有传入文本切分器,
        # 默认使用 RecursiveCharacterTextSplitter
        if text_splitter is None:
            _text_splitter = RecursiveCharacterTextSplitter()
        else:
            _text_splitter = text_splitter

        # 第一步:加载原始文档
        docs = self.load()

        # 第二步:对 Document 进行文本切分
        return _text_splitter.split_documents(docs)

BaseLoader 把数据加载成 Documents object ,存到 Documents 类中的 page_content 中。

Document类分析

Document 允许用户与文档的内容进行交互,可以查看文档内容。

其继承体系如下

复制代码
Serializable
↑
BaseMedia
├── id
├── metadata
↑
Document
├── page_content
├── type = "Document"

from typing import Any, Literal

from langchain_core.documents import BaseMedia


class Document(BaseMedia):
    """
    用于存储一段文本以及与文本相关的元数据。

    Document 主要用于:
        - 文档加载
        - 文档切分
        - 向量检索
        - RAG

    注意:
        Document 主要用于「检索工作流」,
        而不是用于和 LLM 进行聊天时的输入输出。

        如果需要给 LLM 发送聊天消息,
        应该使用 langchain.messages 中的消息类型。

    Example:
        from langchain_core.documents import Document

        document = Document(
            page_content="Hello, world!",
            metadata={
                "source": "https://example.com"
            }
        )
    """

    # 文档的正文内容
    page_content: str

    # Document 的类型标识
    type: Literal["Document"] = "Document"

    def __init__(
        self,
        page_content: str,
        **kwargs: Any,
    ) -> None:
        """
        创建 Document 对象。

        参数:
            page_content:
                文档正文。

            **kwargs:
                其他字段,例如 metadata、id 等。

        例如:

            Document(
                page_content="Hello",
                metadata={"source": "test.txt"}
            )
        """

        # 调用父类 BaseMedia 的初始化方法。
        #
        # BaseMedia 基于 Pydantic,
        # 会负责字段校验以及对象初始化。
        super().__init__(
            page_content=page_content,
            **kwargs,
        )

class BaseMedia:
    """
    LangChain 媒体对象的基础类。
    """

    @classmethod
    def is_lc_serializable(cls) -> bool:
        """
        判断当前类是否支持 LangChain 序列化。

        Returns:
            True:表示可以被 LangChain 序列化。
        """
        return True

    @classmethod
    def get_lc_namespace(cls) -> list[str]:
        """
        获取 LangChain 对象的命名空间。

        Returns:
            LangChain 对象所属的命名空间。
        """

        return [
            "langchain",
            "schema",
            "document",
        ]

    def __str__(self) -> str:
        """
        将 Document 转换成字符串。

        这里只展示:
            - page_content
            - metadata

        不展示其他字段。
        """

        # 如果存在 metadata
        if self.metadata:
            return (
                f"page_content='{self.page_content}' "
                f"metadata={self.metadata}"
            )

        # 如果没有 metadata,
        # 只显示 page_content
        return f"page_content='{self.page_content}'"
相关推荐
爱学堂IT分享1 小时前
图灵Java互联网架构师六期
java·开发语言
传奇开心果编程1 小时前
【Rust入门知识点学与练】第3课:String 与 &str 的区别
开发语言·学习·rust
2601_962299491 小时前
使用 Python 将 CSV 转为 PDF(含表格样式美化)
python·pdf·csv·spire.xls·表格样式
秋名RG1 小时前
Java 核心特性一览
java·开发语言
2601_962077981 小时前
利用Python,四步掌握机器学习
python·机器学习·数据挖掘·数据分析·编程
axinawang2 小时前
requests--爬取网页内容
python
平头哥AI2 小时前
Day 01 | go run 跑通第一个 Go 程序,go build 留下一个能拷走的 exe
开发语言·后端·golang
盼小辉丶2 小时前
PyTorch强化学习实战——融合人类示范数据的高效强化学习
人工智能·pytorch·python·深度学习·强化学习
葡萄城技术团队3 小时前
工业数据可视化:使用活字格 AIcoding + Three.js 构建轻量化三维设备监控大屏
开发语言·javascript·信息可视化