在构建 rag 应用时,读取文档后的下一步是对文档进行分块,之所以分块。理由如下:
- llms 一次输入有限:prompt 无法接受太多输入,拿 4 K 大小的模型来说,大概可以输入 4 K*1.5=6000 左右的汉字,过长的上下文不仅会出现截断问题,还导致推理耗时增加
- 语义混乱:一次性输入过长的上下文,llms 可能回答不准确,通过语义筛选相关文档后,使得上下文的语义更集中
一个合理的切分方式,每个 chunk 语义完整,长度适中,过长:虽然 chunk 减少,使得向量数据库检索更快,但是 llm 推理成本上升,过短:语义被切割,一些精准的问题无法召回 chunk
目前没有一套通用的分块方式,但是一般使用以下方法
1 2 3 4 5 6
| from llama_index.core.node_parser import TokenTextSplitter text="""东汉末年,朝政腐败,再加上连年灾荒,老百姓的日子非常困苦。 巨鹿人张角见人民怨恨官府,便与他的弟弟张梁、张宝在河北、河南、山东、湖北、江苏等地,招收了五十万人,举行起义,一起向官兵进攻。 """ text_splitter=TokenTextSplitter(chunk_size=30,chunk_overlap=10) text_splitter.split_text(text)
|
1 2 3 4 5 6
| ['东汉末年,朝政腐败,再加上连年灾荒,老百姓', '灾荒,老百姓的日子非常困苦。\n巨鹿人张角见', '巨鹿人张角见人民怨恨官府,便与他的弟弟', ',便与他的弟弟张梁、张宝在河北、河南、山东、', '北、河南、山东、湖北、江苏等地,招收了五十万人,举', '收了五十万人,举行起义,一起向官兵进攻。']
|
1 2 3
| from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter=RecursiveCharacterTextSplitter(separators=["。",""],chunk_size=30,chunk_overlap=10) text_splitter.split_text(text)
|
1 2 3 4 5
| ['东汉末年,朝政腐败,再加上连年灾荒,老百姓的日子非常困苦', '。\n巨鹿人张角见人民怨恨官府,便与他的弟弟张梁、张宝在河北、', '弟张梁、张宝在河北、河南、山东、湖北、江苏等地,招收了五十万', '苏等地,招收了五十万人,举行起义,一起向官兵进攻', '。']
|
1 2 3
| from llama_index.core.text_splitter import SentenceSplitter text_splitter=SentenceSplitter(chunk_size=30,chunk_overlap=10) text_splitter.split_text(text)
|
1 2 3 4 5 6
| ['东汉末年,朝政腐败,再加上连年灾荒,老百姓', '灾荒,老百姓的日子非常困苦。\n巨鹿人张角见', '巨鹿人张角见人民怨恨官府,便与他的弟弟', ',便与他的弟弟张梁、张宝在河北、河南、山东、', '北、河南、山东、湖北、江苏等地,招收了五十万人,举', '收了五十万人,举行起义,一起向官兵进攻。']
|
ChunkViz 提供一种可视化的方式查看切分效果,效果如下
总结:针对不同的文档有不同的切分方案,比如文档包含:图片、表格、公式时,就不能使用简单的切分思路。一个是要考虑切分效果,另一个要考虑构建索引的效率。