【LangChain系列】第二篇:文档拆分简介及实践

本文涉及的产品
交互式建模 PAI-DSW,每月250计算时 3个月
模型在线服务 PAI-EAS,A10/V100等 500元 1个月
模型训练 PAI-DLC,100CU*H 3个月
简介: 【5月更文挑战第15天】本文介绍了LangChain中文档拆分的重要性及工作原理。文档拆分有助于保持语义内容的完整性,对于依赖上下文的任务尤其关键。LangChain提供了多种拆分器,如CharacterTextSplitter、RecursiveCharacterTextSplitter和TokenTextSplitter,分别适用于不同场景。MarkdownHeaderTextSplitter则能根据Markdown标题结构进行拆分,保留文档结构。通过实例展示了如何使用这些拆分器,强调了选择合适拆分器对提升下游任务性能和准确性的影响。

[toc]


在上一篇博客中,我们学习了如何使用LangChain的文档加载器将文档加载为标准格式。加载文档后,下一步是将它们拆分为更小的块。这个过程乍一看似乎很简单,但有一些微妙之处和重要的考虑因素会显着影响下游任务的性能和准确性。

一、为什么文档拆分很重要

文档拆分至关重要,因为它可以确保语义相关的内容在同一块中组合在一起。在回答问题或执行依赖于文档中存在的上下文信息的其他任务时,这一点尤为重要。
image.png

考虑以下示例:假设我们有一句关于丰田凯美瑞及其规格的句子。如果我们天真地拆分这个句子,而不考虑上下文,我们最终可能会得到一个包含句子部分的块和另一个包含剩余部分的块。因此,当试图回答有关凯美瑞规格的问题时,我们都不会在任何一个块中获得完整的信息,从而导致答案不正确或不完整。

二、文档拆分在LangChain中是如何工作的?

LangChain中所有文本拆分器的基础是将文本拆分为指定大小的块,相邻块之间有可选的重叠。下图对此进行了说明:
image.png

对应于每个块的大小,可以用字符或标记来衡量。这是在连续块之间共享的文本的一部分,允许跨块维护上下文boundaries.chunk_sizechunk_overlap。

三、文本拆分类型

LangChain提供了几种类型的文本拆分器,每种都有自己的优势和用例。以下是一些最常用的分离器:
image.png

1.CharacterTextSplitter

一个基本的拆分器,它基于单个字符分隔符(如空格或换行符)拆分文本。在处理结构不清晰的文本或想要在特定点拆分文本时,此拆分器非常有用。

2.RecursiveCharacterTextSplitter

用于通用文本拆分,它根据分隔符的层次结构拆分文本,从双换行符开始,然后是单换行符 、空格,最后是单个字符。这种方法旨在通过优先考虑段落和句子等自然边界的拆分来保持文本的结构和连贯性。RecursiveCharacterTextSplitternnn

3.TokenTextSplitter

根据标记计数而不是字符计数拆分文本,因为许多语言模型都具有由标记计数而不是字符计数指定的上下文窗口。标记的长度通常约为四个字符,因此基于标记计数进行拆分可以更好地表示语言模型将如何处理文本。TokenTextSplitter

4.MarkdownHeaderTextSplitter

旨在根据标题结构拆分 Markdown 文档。它将标头元数据保留在生成的块中,从而允许上下文感知拆分和使用文档结构的潜在下游任务。MarkdownHeaderTextSplitter

四、上手实例

让我们探索一些示例,以更好地了解这些文本拆分器的工作原理以及如何有效地使用它们。

1.设置环境

通过导入必要的库并加载 OpenAI API 密钥来设置环境:

import os
from langchain_openai import OpenAI
from dotenv import load_dotenv, find_dotenv

_ = load_dotenv(find_dotenv())

client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY")
)

接下来,我们将导入两个最常用的文本拆分器:

from langchain_text_splitters import (
    CharacterTextSplitter,
    RecursiveCharacterTextSplitter,
)

2.使用CharacterTextSplitter和RecursiveCharacterTextSplitter拆分

让我们从定义一些示例开始,以了解这些分离器的工作原理:

chunk_size = 26
chunk_overlap = 4

r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=chunk_size, chunk_overlap=chunk_overlap
)
c_splitter = CharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)

text1 = "abcdefghijklmnopqrstuvwxyz"
print(r_splitter.split_text(text1))  
# Output: ['abcdefghijklmnopqrstuvwxyz']

text2 = "abcdefghijklmnopqrstuvwxyzabcdefg"
print(r_splitter.split_text(text2))  
# Output: ['abcdefghijklmnopqrstuvwxyz', 'wxyzabcdefg']

text3 = "a b c d e f g h i j k l m n o p q r s t u v w x y z"
print(r_splitter.split_text(text3))  
# Output: ['a b c d e f g h i j k l m', 'l m n o p q r s t u v w x', 'w x y z']
print(c_splitter.split_text(text3))  
# Output: ['a b c d e f g h i j k l m n o p q r s t u v w x y z']

# Set the separator for CharacterTextSplitter
c_splitter = CharacterTextSplitter(
    chunk_size=chunk_size, chunk_overlap=chunk_overlap, separator=" "
)
print(c_splitter.split_text(text3))  
# Output: ['a b c d e f g h i j k l m', 'l m n o p q r s t u v w x', 'w x y z']

这些示例演示了如何根据指定的 和 拆分文本,而如何基于单个字符分隔符(在本例中为空格)拆分文本。

3.真实示例

尝试拆分一些真实世界的例子:

some_text = """When writing documents, writers will use document structure to group content. \
This can convey to the reader, which idea's are related. For example, closely related ideas \
are in sentances. Similar ideas are in paragraphs. Paragraphs form a document. \\n\\n  \
Paragraphs are often delimited with a carriage return or two carriage returns. \
Carriage returns are the "backslash n" you see embedded in this string. \
Sentences have a period at the end, but also, have a space.\
and words are separated by space."""

c_splitter = CharacterTextSplitter(chunk_size=450, chunk_overlap=0, separator=" ")
r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=450, chunk_overlap=0, separators=["\n\n", "\n", " ", ""]
)

chunks = c_splitter.split_text(some_text)
print("Chunks: ", chunks)
print("Length of chunks: ", len(chunks))
# Chunks:  ['When writing documents, writers will use document structure to group content. This can convey to the reader, which idea\'s are related. For example, closely related ideas are in sentances. Similar ideas are in paragraphs. Paragraphs form a document. \n\n Paragraphs are often delimited with a carriage return or two carriage returns. Carriage returns are the "backslash n" you see embedded in this string. Sentences have a period at the end, but also,', 'have a space.and words are separated by space.']
# Length of chunks:  2

chunks = r_splitter.split_text(some_text)
print("Chunks: ", chunks)
print("Length of chunks: ", len(chunks))
# Chunks:  ["When writing documents, writers will use document structure to group content. This can convey to the reader, which idea's are related. For example, closely related ideas are in sentances. Similar ideas are in paragraphs. Paragraphs form a document.", 'Paragraphs are often delimited with a carriage return or two carriage returns. Carriage returns are the "backslash n" you see embedded in this string. Sentences have a period at the end, but also, have a space.and words are separated by space.']
# Length of chunks:  2

在此示例中,它基于空格拆分文本,而第一个尝试拆分双换行符,然后是单换行符、空格,最后是单个字符。CharacterTextSplitterRecursiveCharacterTextSplitterCharacterTextSplitterRecursiveCharacterTextSplitter

我们还可以拆分真实世界的文档,例如 PDF 和 Notion 数据库:

from langchain.document_loaders import PyPDFLoader, NotionDirectoryLoader

# Load a PDF document
loader = PyPDFLoader("docs/cs229_lectures/MachineLearning-Lecture01.pdf")
pages = loader.load()

text_splitter = CharacterTextSplitter(
    separator="\n", chunk_size=1000, chunk_overlap=150, length_function=len
)
docs = text_splitter.split_documents(pages)

print("Pages in the original document: ", len(pages))
print("Length of chunks after splitting pages: ", len(docs))
# Pages in the original document:  22
# Length of chunks after splitting pages:  353

此代码使用 加载 PDF 文档,将页面拆分为较小的块,并打印原始页数和生成的块数。PyPDFLoaderCharacterTextSplitter

# Load a Notion database
loader = NotionDirectoryLoader("docs/Notion_DB")
notion_db = loader.load()

docs = text_splitter.split_documents(notion_db)

print("Pages in the original notion document: ", len(notion_db))
print("Length of chunks after splitting pages: ", len(docs))
# Pages in the original notion document:  52
# Length of chunks after splitting pages:  353

类似地,我们可以加载一个 Notion 数据库,将文档拆分为块,并打印原始文档的数量和生成的块。NotionDirectoryLoader

五、Token-based拆分

除了基于字符的拆分之外,LangChain还支持基于令牌的拆分,这在使用具有由令牌计数指定的上下文窗口的语言模型时非常有用:

from langchain.text_splitter import TokenTextSplitter

text_splitter = TokenTextSplitter(chunk_size=1, chunk_overlap=0)
text1 = "foo bar bazzyfoo"
print(text_splitter.split_text(text1))  
# Output: ['foo', ' bar', ' b', 'az', 'zy', 'foo']

text_splitter = TokenTextSplitter(chunk_size=10, chunk_overlap=0)
docs = text_splitter.split_documents(pages)
print(docs[0])  
# Output: Document(page_content='MachineLearning-Lecture01  \n', metadata={'source': 'docs/cs229_lectures/MachineLearning-Lecture01.pdf', 'page': 0})
print(pages[0].metadata)
# Output: {'source': 'docs/cs229_lectures/MachineLearning-Lecture01.pdf', 'page': 0}

在此示例中,我们使用 to 根据令牌计数拆分文本。我们可以调整 and 参数来控制拆分行为。TokenTextSplitterchunk_sizechunk_overlap

六、Context-aware拆分

LangChain还提供了上下文感知拆分的工具,旨在在拆分过程中保留文档结构和语义上下文。它根据文档的标题结构拆分 Markdown 文档,并将标题元数据保留在生成的块中:MarkdownHeaderTextSplitter

from langchain.document_loaders import NotionDirectoryLoader
from langchain.text_splitter import MarkdownHeaderTextSplitter

markdown_document = """# Title\n\n \
## Chapter 1\n\n \
Hi this is Jim\n\n Hi this is Joe\n\n \
### Section \n\n \
Hi this is Lance \n\n 
## Chapter 2\n\n \
Hi this is Molly"""

headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]

markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(markdown_document)

print(md_header_splits[0])  
# Output: Document(page_content='Hi this is Jim  \nHi this is Joe', metadata={'Header 1': 'Title', 'Header 2': 'Chapter 1'})
print(md_header_splits[1])  
# Output: Document(page_content='Hi this is Lance', metadata={'Header 1': 'Title', 'Header 2': 'Chapter 1', 'Header 3': 'Section'})

在此示例中,我们定义一个带有标题的 Markdown 文档,并根据标题结构拆分文档。生成的块保留标头元数据,这对于利用文档结构的下游任务非常有用。MarkdownHeaderTextSplitter

我们还可以将此拆分器Notion 数据库:

loader = NotionDirectoryLoader("docs/Notion_DB")
docs = loader.load()
txt = " ".join([d.page_content for d in docs])

headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(txt)

print(md_header_splits[0])

此代码加载一个 Notion 数据库,将文档内容联接到单个字符串中,拆分字符串,并打印第一个生成的块。MarkdownHeaderTextSplitter

小结

文档拆分是LangChain流水线中的关键步骤,因为它确保语义相关的内容在同一块中组合在一起。LangChain提供了各种文本拆分器,每个拆分器都有自己的优势和用例,允许您根据自己的特定需求选择最合适的拆分器。

无论您是处理通用文本、Markdown 文档、代码片段还是其他类型的内容,LangChain 的文本拆分器都提供了灵活性和自定义选项,可以有效地拆分您的文档。通过了解文档拆分中涉及的细微差别和注意事项,可以优化语言模型和下游任务的性能和准确性。

相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
目录
相关文章
|
4月前
|
运维 分布式计算 监控
Dataphin深度评测:企业级数据中台的智能实践利器
Dataphin是一款以全链路治理、智能提效和高兼容性为核心的企业级数据中台工具,特别适用于中大型企业的复杂数据场景。其流批一体能力、资源监控工具及行业化模板库可显著提升数据治理水平并降低运维成本。通过周期补数据功能,历史数据修复效率提升约60%;智能建模功能使建模时间缩短50%。尽管在数据源支持(如SAP HANA、DB2)和用户体验上仍有改进空间,但其强大的功能使其成为构建企业级数据中台的优选工具,尤其适合零售、金融等行业需要高效数据治理与实时分析的企业。
|
3月前
|
存储 人工智能 自然语言处理
LangChain RAG入门教程:构建基于私有文档的智能问答助手
本文介绍如何利用检索增强生成(RAG)技术与LangChain框架构建基于特定文档集合的AI问答系统。通过结合检索系统和生成机制,RAG能有效降低传统语言模型的知识局限与幻觉问题,提升回答准确性。文章详细展示了从环境配置、知识库构建到系统集成的全流程,并提供优化策略以改进检索与响应质量。此技术适用于专业领域信息检索与生成,为定制化AI应用奠定了基础。
624 5
LangChain RAG入门教程:构建基于私有文档的智能问答助手
|
6月前
|
关系型数据库 OLAP API
非“典型”向量数据库AnalyticDB PostgreSQL及RAG服务实践
本文介绍了非“典型”向量数据库AnalyticDB PostgreSQL及其RAG(检索增强生成)服务的实践应用。 AnalyticDB PostgreSQL不仅具备强大的数据分析能力,还支持向量查询、全文检索和结构化查询的融合,帮助企业高效构建和管理知识库。
338 19
|
7月前
|
数据采集 供应链 搜索推荐
商业案例 I AllData数据中台商业版落地实践
杭州奥零数据科技有限公司成立于2023年,专注于数据中台业务,维护开源项目AllData并提供商业版解决方案。AllData提供数据集成、存储、开发、治理及BI展示等一站式服务,支持AI大模型应用,助力企业高效利用数据价值。
|
8月前
|
JSON 数据可视化 NoSQL
基于LLM Graph Transformer的知识图谱构建技术研究:LangChain框架下转换机制实践
本文介绍了LangChain的LLM Graph Transformer框架,探讨了文本到图谱转换的双模式实现机制。基于工具的模式利用结构化输出和函数调用,简化了提示工程并支持属性提取;基于提示的模式则为不支持工具调用的模型提供了备选方案。通过精确定义图谱模式(包括节点类型、关系类型及其约束),显著提升了提取结果的一致性和可靠性。LLM Graph Transformer为非结构化数据的结构化表示提供了可靠的技术方案,支持RAG应用和复杂查询处理。
464 2
基于LLM Graph Transformer的知识图谱构建技术研究:LangChain框架下转换机制实践
|
9月前
LangChain-06 RAG With Source Doc 通过文档进行检索增强
LangChain-06 RAG With Source Doc 通过文档进行检索增强
80 3
|
11月前
|
自然语言处理 搜索推荐 机器人
langchain 简介
langchain 简介
672 1
|
11月前
|
存储 自然语言处理 算法
【LangChain】如何本地部署基于chatGPT的实时文档和表格数据的助手,在自己的数据上构建chatGPT?
本文介绍了如何使用LangChain库和FAISS工具在本地部署一个基于chatGPT的实时文档和表格数据助手,详细阐述了项目原理、搭建步骤、环境配置、代码修改和运行流程,以及如何在自己的数据上构建和使用chatGPT。
147 1
|
11月前
|
机器学习/深度学习 自然语言处理 搜索推荐
LangChain在个性化内容生成中的实践
【8月更文第3天】随着人工智能技术的发展,个性化内容生成已经成为许多应用的核心竞争力。LangChain 是一种开源框架,旨在简化语言模型的应用开发,尤其是针对自然语言处理任务。本文将探讨 LangChain 如何帮助开发者根据用户的偏好生成定制化的内容,从挑战到实践策略,再到具体的案例分析和技术实现。
808 1
|
存储 人工智能 机器人
LangChain结合LLM做私有化文档搜索
我们知道LLM(大语言模型)的底模是基于已经过期的公开数据训练出来的,对于新的知识或者私有化的数据LLM一般无法作答,此时LLM会出现“幻觉”。针对“幻觉”问题,一般的解决方案是采用RAG做检索增强。
LangChain结合LLM做私有化文档搜索

热门文章

最新文章