文本切分 (Text Splitting)
将长文档加载进内存后,下一步是切分 (Chunking)。
为什么需要切分?
- 模型限制:Embedding 模型和 LLM 都有最大 Token 限制(如 8192 tokens)。
- 检索精度:切分得越细致,语义越集中。如果一个 Chunk 包含太多无关主题,检索的准确率会下降("大海捞针"效应)。
Golang 中的切分策略
1. 递归字符切分 (Recursive Character Splitter)
这是最常用的策略。它尝试按顺序使用分隔符列表(如 \n\n, \n, , "")进行切分,直到块的大小符合要求。这能最大程度保持段落的完整性。
Golang 实现注意事项: Go 语言中 len(string) 返回的是字节数,而不是字符数。对于中文环境,必须按 Rune(Unicode 码点)计算长度,否则会出现乱码或截断。
使用 langchaingo 的 textsplitter 包:
go
package main
import (
"fmt"
"github.com/tmc/langchaingo/textsplitter"
)
func main() {
text := "Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。\n\n它不同于Java和Python..."
// 创建 Splitter
splitter := textsplitter.NewRecursiveCharacter()
splitter.ChunkSize = 100 // 每个块大概 100 字符
splitter.ChunkOverlap = 20 // 重叠 20 字符,防止上下文丢失
// 执行切分
chunks, _ := splitter.SplitText(text)
for i, chunk := range chunks {
fmt.Printf("Chunk %d: %s\n---\n", i, chunk)
}
}2. Markdown 标题切分 (Markdown Header Splitter)
对于 Markdown 文档,按照标题层级(# H1, ## H2)切分效果最好,因为标题天然定义了语义边界。
langchaingo 目前对 Markdown Splitter 的支持正在完善中。我们可以实现一个简单的逻辑:
go
// 伪代码思路
func SplitMarkdown(text string) []string {
// 1. 正则匹配 # 标题
// 2. 将标题下的内容聚合为一个 Chunk
// 3. 将标题作为 Metadata 附加到 Chunk 中(非常重要!增强检索上下文)
}3. Token 切分
更精准的做法是按 Token 数切分(因为模型限制是按 Token 算的)。Go 中可以使用 tiktoken-go 库。
go
import "github.com/pkoukk/tiktoken-go"
func CountTokens(text string) int {
tkm, _ := tiktoken.GetEncoding("cl100k_base") // GPT-4 编码
token := tkm.Encode(text, nil, nil)
return len(token)
}切分参数最佳实践
- Chunk Size:
- 512 - 1024 tokens: 适合大多数问答场景。
- 256 tokens: 适合细粒度的事实检索。
- Chunk Overlap:
- 建议设置为 Chunk Size 的 10% - 20%。
- 例如:Size=500, Overlap=50。
- 作用:确保跨 Chunk 的句子不会被切断,保持语义连贯性。
总结
切分是 RAG 效果优化的低垂果实。
- 太小:缺乏上下文,模型看不懂。
- 太大:包含噪音多,检索不准。
- Golang 重点:时刻记住处理 UTF-8 字符问题,使用
[]rune而不是byte切片。
