前情回顾
在 上一篇文章 中,我们掌握了强大的递归字符分块法,它能智能地按段落和句子来分割通用文本,效果拔群。对于大多数文章、文档类的处理,它已经足够优秀。
但现实世界是复杂的。作为开发者,我们经常需要让AI理解的,不仅仅是纯文本,还有:
- 我们自己写的项目代码我们整理的技术笔记(Markdown格式)
如果继续用之前的方法来处理这些带有鲜明结构化特征的内容,会发生什么?
想象一下,一个完整的Python函数被从中间拦腰截断,或者一个Markdown的二级标题和它的内容被分到两个不同的块里。这无疑是一场灾难,会严重误导后续的检索和生成。
所以,今天我们的任务就是:为特定格式的内容,找到专属的、更优雅的分块方法。
针对特定语言的分割器 (Language-Specific Splitters)
幸运的是,像 LangChain
这样的框架已经为我们考虑到了这一点。它内置了针对多种主流编程语言的分割器。
其核心思想是:不再基于通用的标点符号,而是基于编程语言本身的语法结构(如类、函数、导入语句等)来进行分割。
我们以 Python 为例,直接上代码。
# 导入针对Python的递归字符分块器from langchain.text_splitter import ( RecursiveCharacterTextSplitter, Language,)# 一段Python代码示例python_code = """import osimport sysclass MyClass: def __init__(self, name): self.name = name def say_hello(self): print(f"Hello, {self.name}!")def my_function(x, y): return x + yif __name__ == "__main__": instance = MyClass("World") instance.say_hello() result = my_function(5, 3) print(f"Result is {result}")"""# 使用 RecursiveCharacterTextSplitter.from_language() 方法# 传入语言枚举值 Language.PYTHONpython_splitter = RecursiveCharacterTextSplitter.from_language( language=Language.PYTHON, chunk_size=150, chunk_overlap=0)# 进行分块chunks = python_splitter.split_text(python_code)# 我们来看看分块结果for i, chunk in enumerate(chunks): print(f"--- Chunk {i+1} ---") print(chunk) print(f"(长度: {len(chunk)})\n")
输出结果:
--- Chunk 1 ---import osimport sys(长度: 19)--- Chunk 2 ---class MyClass: def __init__(self, name): self.name = name def say_hello(self): print(f"Hello, {self.name}!")(长度: 129)--- Chunk 3 ---def my_function(x, y): return x + y(长度: 38)--- Chunk 4 ---if __name__ == "__main__": instance = MyClass("World") instance.say_hello() result = my_function(5, 3) print(f"Result is {result}")(长度: 141)
效果分析
太漂亮了!看看这个分割结果:
- 导入语句(import) 被单独分为一块。整个
MyClass
类,包含它的构造函数和方法,被完整地保留在了一个块里。独立的 my_function
函数 也自成一块。主执行模块 if __name__ == "__main__"
的内容也被聚合在一起。这种基于语法的分割,完美地保留了代码的逻辑单元。当用户提问“MyClass
类有什么功能?”时,检索系统能精准地定位到Chunk 2,为大模型提供最相关的、最完整的上下文。
除了Python,LangChain
还支持 CPP
, GO
, JAVA
, JS
, PHP
, PROTO
, RUBY
, RST
, SCALA
, SWIFT
, MARKDOWN
, LATEX
, HTML
等多种语言和格式。你只需要在 language
参数中传入对应的枚举值即可,非常方便。
Markdown文件的分割
Markdown是我们写技术文档和笔记的利器。它的标题层级(#
, ##
, ###
)天然就是内容的逻辑分割线。
RecursiveCharacterTextSplitter
在处理 Language.MARKDOWN
时,会智能地将这些标题符号作为高优先级的分割符。
# Markdown文本示例markdown_text = """# RAG系统概述RAG(Retrieval-Augmented Generation)是一种强大的AI框架。## 核心组件RAG系统主要包含两个核心组件:1. **检索器 (Retriever)**:负责从知识库中快速找到相关文档。2. **生成器 (Generator)**:基于检索到的信息生成答案。### 检索器的技术细节检索器通常使用向量数据库实现..."""# 使用针对Markdown的分割器markdown_splitter = RecursiveCharacterTextSplitter.from_language( language=Language.MARKDOWN, chunk_size=100, chunk_overlap=0)chunks = markdown_splitter.split_text(markdown_text)for i, chunk in enumerate(chunks): print(f"--- Chunk {i+1} ---") print(chunk) print(f"(长度: {len(chunk)})\n")
输出结果:
--- Chunk 1 ---# RAG系统概述RAG(Retrieval-Augmented Generation)是一种强大的AI框架。(长度: 57)--- Chunk 2 ---## 核心组件RAG系统主要包含两个核心组件:1. **检索器 (Retriever)**:负责从知识库中快速找到相关文档。2. **生成器 (Generator)**:基于检索到的信息生成答案。(长度: 99)--- Chunk 3 ---### 检索器的技术细节检索器通常使用向量数据库实现...(长度: 34)
可以看到,分割完全按照 h1
, h2
, h3
的标题层级进行,保持了文档的章节结构,非常符合预期。
总结与预告
今日小结:
处理代码、Markdown等结构化文本时,不要使用通用的文本分割器。应该使用针对特定语言的分割器 (Language-Specific Splitters),它能基于语法和标记(如函数、类、Markdown标题)进行更智能、更符合逻辑的分割。在
LangChain
中,通过RecursiveCharacterTextSplitter.from_language()
方法可以轻松实现这一点。
到目前为止,我们已经掌握了如何将各种类型的文档“优雅地”切分成高质量的文本块 (Chunks)。
我们的“原材料”准备好了。下一步,就是要将这些文本块转化成机器能够理解和比较的数学形式——向量 (Vectors)。
这个过程,就是 Embedding。选择什么样的Embedding模型,将直接决定你的RAG系统能否“读懂”文本的深层含义。
明天预告:RAG 每日一技(四):不止是切分,如何让机器读懂文本的“灵魂”——初探Embedding
准备好进入向量的世界了吗?明天我们不见不散!