(附代码)Langchain中其他文档分割器的使用与介绍

在 LangChain 中,除了常用的文档分割器,还设计了一些适用于特定场景的分割器,涵盖了以下类型:基于 HTML 标题/段的分割器、Markdown 标题分割器、递归 JSON 分割器、基于 Token 计数的分割器等。这些分割器的使用方式与字符文本分割器非常接近,主要针对特定格式的文档进行优化处理。

2.1 HTML/Markdown 标题与段分割器

LangChain 提供了针对 HTML 类型文档的分割器——HTMLHeaderTextSplitterHTMLSectionSplitter,它们的作用如下:

HTMLHeaderTextSplitter
  • 按照 HTML 文档中的元素级别进行分割。
  • 在分割时会查找每一块文本的内容以及所有关联的标题。
  • 为每个相关的标题块提供元数据,顺序逐层向上查找,直到找到所有嵌套层级的标题。
  • 这种分割器适合提取文本及其完整的标题层级结构。
HTMLSectionSplitter
  • 按照 HTML 文档中的元素级别进行分割。
  • 在分割时会查找每一块文本的内容及其最近的副标题。
  • 查找规则是顺序逐层向上查找,一旦找到最近的副标题就停止。
  • 这种分割器适合提取文本及其局部的副标题信息。
层级关系说明

理解这些分割器的工作原理非常简单,层级关系并不是 HTML 元素的嵌套结构,而是类似目录导航的层级关系。例如:

  • 在文档的侧边栏或导航栏中,标题可能分为一级标题、二级标题和三级标题。
  • 某段内容所在的目录层级决定了它被认为属于哪个标题下,而并不依赖 HTML 的实际嵌套结构。

例如:

这意味着这段内容在分割后,会被归类到与该标题相关的层级下。

更多详细文档分割器的使用说明,可以参考以下链接:LangChain 文档分割器翻译文档
资料推荐

示例代码:

from langchain_text_splitters import HTMLHeaderTextSplitter

html_string = """
<!DOCTYPE html>
<html>
<body>
    <div>
        <h1>标题1</h1>
        <p>关于标题1的一些介绍文本。</p>
        <div>
            <h2>子标题1</h2>
            <p>关于子标题1的一些介绍文本。</p>
            <h3>子子标题1</h3>
            <p>关于子子标题1的一些文本。</p>
            <h3>子子标题2</h3>
            <p>关于子子标题2的一些文本。</p>
        </div>
        <div>
            <h3>子标题2</h2>
            <p>关于子标题2的一些文本。</p>
        </div>
        <br>
        <p>关于标题1的一些结束文本。</p>
    </div>
</body>
</html>
"""
headers_to_split_on = [
    ("h1", "一级标题"),
    ("h2", "二级标题"),
    ("h3", "三级标题"),
]
html_splitter = HTMLHeaderTextSplitter(headers_to_split_on)
html_header_splits = html_splitter.split_text(html_string)

print(html_header_splits)

输出内容:

[
Document(page_content='标题1'), 
Document(metadata={'一级标题': '标题1'}, page_content='关于标题1的一些介绍文本。  \n子标题1 子子标题1 子子标题2'), 
Document(metadata={'一级标题': '标题1', '二级标题': '子标题1'}, page_content='关于子标题1的一些介绍文本。'), 
Document(metadata={'一级标题': '标题1', '二级标题': '子标题1', '三级标题': '子子标题1'}, page_content='关于子子标题1的一些文本。'), 
Document(metadata={'一级标题': '标题1', '二级标题': '子标题1', '三级标题': '子子标题2'}, page_content='关于子子标题2的一些文本。'), 
Document(metadata={'一级标题': '标题1'}, page_content='子标题2'),
Document(metadata={'一级标题': '标题1', '三级标题': '子标题2'}, page_content='关于子标题2的一些文本。'), 
Document(metadata={'一级标题': '标题1'}, page_content='关于标题1的一些结束文本。')
]

另外在 LangChain 中除了 HTML 类型的文档可以使用这套分割规则,Markdown 类的文件也有类似的分割规则,可以使用 Markdown 标题分割器—— MarkdownHeaderTextSplitter 完成同样的文档分割。

详细文档:https://imooc-langchain.shortvar.com/docs/how_to/markdown_header_metadata_splitter/

2.2 递归 JSON 分割器

对于 JSON 类的数据,在 LangChain 中也封装了一个递归 JSON 分割器——RecursiveJsonSplitter,这个分割器会按照深度优先的方式遍历 JSON 数据,并构建较小的 JSON 块,而且尽可能保持嵌套 JSON 对象完整,但如果需要保持文档块大小在最小块大小和最大块大小之间,则会将它们拆分。

JSON 数据中,如果值不是嵌套的 JSON,而是一个非常大的字符,则不会对该字符串进行拆分,可以配合 递归字符文本分割器 强制性拆分字符串,确保块大小在限制的范围内。

RecursiveJsonSplitter 的参数非常简单,只需传递 max_chunk_sizemin_chunk_size(可选) 即可。
资料推荐

例如这里有一个很大的 json 文本(https://api.smith.langchain.com/openapi.json),使用递归 JSON 分割器对其进行拆分,
示例代码:

import requests
from langchain_text_splitters import RecursiveJsonSplitter

# 1.获取并加载json
url = "https://api.smith.langchain.com/openapi.json"
json_data = requests.get(url).json()

# 2.递归JSON分割器
text_splitter = RecursiveJsonSplitter(max_chunk_size=300)

# 3.分割json数据并创建文档
json_chunks = text_splitter.split_json(json_data=json_data)
chunks = text_splitter.create_documents(json_chunks)

for chunk in chunks[:3]:
    print(chunk)

输出内容:

page_content='{"openapi": "3.1.0", "info": {"title": "LangSmith", "version": "0.1.0"}, "paths": {"/api/v1/sessions/{session_id}": {"get": {"tags": ["tracer-sessions"], "summary": "Read Tracer Session", "description": "Get a specific session."}}}}'
page_content='{"paths": {"/api/v1/sessions/{session_id}": {"get": {"operationId": "read_tracer_session_api_v1_sessions__session_id__get", "security": [{"API Key": []}, {"Tenant ID": []}, {"Bearer Auth": []}]}}}}'
page_content='{"paths": {"/api/v1/sessions/{session_id}": {"get": {"parameters": [{"name": "session_id", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid", "title": "Session Id"}}, {"name": "include_stats", "in": "query", "required": false, "schema": {"type": "boolean", "default": false, "title": "Include Stats"}}, {"name": "accept", "in": "header", "required": false, "schema": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Accept"}}]}}}}'

RecursiveJsonSplitter 分割器的运行流程其实也非常简单,这个分割器会按照 深度优先 的方式遍历整个 JSON,即一层一层往下读取数据,然后将对应的数据提取生成一个新的 JSON,直到数据大小接近块大小(极端情况下还是会超过预设的块大小,例如 JSON 数据中的 Key 很长,亦或者 Value 很长,甚至出现单条数据就超过了预设大小)。

所以如果要使用该分割器,一般会结合 RecursiveCharacterTextSplitter 降低单条数据超过预设大小的风险,思路就是将递归 JSON 分割器生成的文档列表进行二次分割。

2.3 基于标记的分割器

对于大语言模型来说,上下文的长度计算应该通过 token 进行计算,而不是通过字符长度 len() 函数,在 OpenAIGPT 模型中,一个汉字大约等于 1.5Token,一个单词为 1Token,所以使用 len() 函数可能会导致很大的误差。
资料推荐

在开发中,不同的模型对于 Token 的计算并不相同,但是可以使用 tiktoken 这个包来大致计算文本的 token 数,误差也相对较小,首先安装 tiktoken 包,

pip install -U tiktoken

接下来定义一个基于 tiktoken 的长度计算函数,如下:

def calculate_token_count(query: str) -> int:
    """计算传入文本的token数"""
    encoding = tiktoken.encoding_for_model("text-embedding-3-large")
    return len(encoding.encode(query))

然后将该函数传递给分割器的 length_function,代码如下:

import tiktoken
from langchain_community.document_loaders import UnstructuredFileLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 1.定义加载器和文本分割器
loader = UnstructuredFileLoader("./科幻短篇.txt")
text_splitter = RecursiveCharacterTextSplitter(
    separators=[
        "\n\n",
        "\n",
        "。|!|?",
        "\.\s|\!\s|\?\s",  # 英文标点符号后面通常需要加空格
        ";|;\s",
        ",|,\s",
        " ",
        ""
    ],
    is_separator_regex=True,
    chunk_size=500,
    chunk_overlap=50,
    length_function=calculate_token_count,
)

# 2.加载文档并执行分割
documents = loader.load()
chunks = text_splitter.split_documents(documents)

# 3.循环打印分块内容
for chunk in chunks:
    print(f"块大小: {len(chunk.page_content)}, 元数据: {chunk.metadata}")

输出内容:

块大小: 334, 元数据: {'source': './科幻短篇.txt'}
块大小: 409, 元数据: {'source': './科幻短篇.txt'}
块大小: 372, 元数据: {'source': './科幻短篇.txt'}
块大小: 95, 元数据: {'source': './科幻短篇.txt'}

LangChain 中,除了传递 length_function 方法,还可以直接调用分割器的类方法 from_tiktoken_encoder() 来快速创建基于 tiktoken 分词器的文本分割器,例如:

RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    model_name="gpt-4",
    chunk_size=500,
    chunk_overlap=50,
    separators=[
        "\n\n",
        "\n",
        "。|!|?",
        "\.\s|\!\s|\?\s",  # 英文标点符号后面通常需要加空格
        ";|;\s",
        ",|,\s",
        " ",
        ""
    ],
    is_separator_regex=True,
)

资料推荐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ai大师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值