【人工智能】《从零搭建AI问答助手项目(六):Chunk + Overlap 优化》
本文探讨了优化RAG问答系统的关键方法——Chunk分块与Overlap重叠技术。文章指出,合理的文本分块能解决大模型处理长文本时的计算限制,而重叠设计可避免语义割裂。作者分享了分块大小的选择原则(适中+重叠)、不同文档类型的适配方案,并提供了Python实现代码。通过对比优化前后的检索效果,展示了该方法如何提升问答准确性。文章还总结了实践中的常见问题(如分块过大导致检索不准)及解决方案(调整分块
引言
上一篇我们完成了本地知识库的文档解析、文本向量化和向量库入库,正式搭建好了 RAG 问答助手的底层知识库。
但很多人搭完向量库后会发现一个痛点:文档切块太生硬、关键语义被强行割裂,上下文断层,进而导致检索不准、问答答案残缺或逻辑断裂。
想要从根源提升 RAG 问答的准确率,Chunk 切块大小 + Overlap 上下文重叠 是最基础也最关键的优化点。合适的块大小能保留完整语义,合理的重叠度能避免知识点被切分割裂,从源头减少检索漏配、答非所问的问题。
本篇就结合 AI 问答助手实战,讲解 Chunk 大小选型、Overlap 重叠原理、不同文档类型的适配方案,以及实操中的优化经验,给后续检索和问答打好底层基础。
版本目标
让本地向量库的chunk块按合适的大小进行切分,提升RAG的检索准确率,让AI问答助手更“智能”。
现在的“知识库切分方式”是:一整段 or 一整行,导致检索不精准、上下文不完整、回答容易“偏” 。
什么是Chunk
大模型一次能处理的信息长度有限(即上下文窗口,例如 128K tokens)。当处理长文章时,系统会先把原文切成许多个小片段,每个小片段就是一个 Chunk。
Chunk = 把大文本切成“小块”
为什么需要Chunk
1. 上下文窗口的硬性限制
大模型一次能处理的 token 数量是有上限的(比如 4k、128k 甚至 1M token)。如果原始文本(例如一本小说、一份财报)超过了这个长度,模型根本无法一次性读完。
分块就是把超长文档切成若干个模型能一口“吞下”的小片段。
2. 降低计算成本与延迟
即便模型拥有很长的上下文窗口(比如 1M token),一次性输入整本《三体》也会导致:
- 计算量爆炸:注意力机制的计算复杂度随长度呈平方级增长(O(n²)),处理 1M token 的成本极高。
- 响应缓慢:用户等不了几十秒才出一个字。
分块后,只把最相关的几个块送给模型,大幅节省算力和时间。
3. RAG(检索增强生成)的必然要求
这是目前最普遍的应用场景。当需要让模型回答基于海量私有知识(如公司全部合同)的问题时,不可能把所有知识都塞进 prompt(太长、太贵)。
可行的做法: 预先将所有文档切成 Chunk,建立索引。用户提问时,系统先快速检索出最相关的 3~5 个 Chunk,再把“问题 + 这些 Chunk”一起给模型。
没有 Chunk,就无法实现高效、精准的检索。
4. 提升长文本处理的注意力质量
研究与实践表明,当文本极长时,模型对位于中间位置的信息容易“遗忘”或关注不足(称为“中间迷失”现象)。将长文本分块后:
可以对每个块单独做摘要或提取关键信息,再汇总这些结果,得到更可靠的整体输出。
分块相当于把一个大任务分解成多个互不干扰的小任务,降低模型的认知负担。
现在的本地知识库Chunk是这样的,例如:
Java是一种面向对象编程语言,具有平台无关性,由Sun公司开发...
(很长一段)
优化后变成:
[块1] Java是一种面向对象编程语言
[块2] Java具有平台无关性
[块3] Java由Sun公司开发
这样优化的好处是:检索更精准、更容易命中关键内容
什么是Overlap
在文本分块(Chunking)的上下文中,Overlap(重叠) 指的是相邻两个 Chunk 之间共享的那一部分文本。
Overlap = 块之间“重叠一部分”
简单来说就是块内容的冗余,比如:
块1:Java是一种面向对象编程语言
块2:面向对象编程语言具有封装继承多态
为什么需要Overlap?
Overlap是为了避免“信息被切断”,如果完全没有冗余,那么切分后根本分不清楚哪个块跟哪个块原本是连接在一起的。
比如:一个完整的句子“张三在2025年成为了CEO”,如果切分点恰好落在“张三在”和“2025年成为了CEO”之间,那么:
前一个 Chunk 只包含“张三在”
后一个 Chunk 只包含“2025年成为了CEO”
当用户问“谁成为了CEO?”时,检索系统可能只命中后一个 Chunk,缺少主语“张三”,导致模型无法给出完整答案。
怎么设计Chunk大小
最核心原则:
Chunk 太大,检索不准
Chunk 太小,上下文不完整
最优:“适中 + 有重叠”
代码实现
第一步:新增文本切分函数
def split_text(text, chunk_size=100, overlap=20):
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
chunks.append(chunk)
start += chunk_size - overlap
return chunks
上面这段文本切分函数的效果,举个例子:
原文:1000字
chunk_size=100
overlap=20
调用该文本切分函数后会被切分成:
chunk1: 0-100
chunk2: 80-180
chunk3: 160-260
...
第二步,接入本地知识库,修改 load_knowledge()
def load_knowledge():
with open("data/knowledge.txt", "r", encoding="utf-8") as f:
text = f.read()
chunks = split_text(text)
return chunks
第三步,效果对比
原来
问题:Java特点?
命中:整段大文本
→ 噪声很多
现在命中率大幅提升:
问题:Java特点?
命中:
✔ 面向对象
✔ 平台无关性
注意
改了 chunk 后,必须删除旧的向量库,让程序重新构建本地向量库。其实就是删除项目data目录下的docs.pkl和faiss.index这两个文件,直接在项目里删除不行,就右键进入资源管理器目录下删除。
进阶优化
按“语义切分”(比长度更高级)
比如按句号、段落、标题。
不同类型用不同 chunk_size
| 内容 | chunk_size |
|---|---|
| FAQ | 小 |
| 文档 | 中 |
| 论文 | 大 |
踩过的坑
分不清答案到底是直接把原文一整段拿出来还是多个 chunk 命中后拼接的
我测试的命中结果是:Java的特点包括:是一种面向对象的编程语言,具有平台无关性,由Sun公司开发。
我的知识库原文:Java是一种面向对象的编程语言,具有平台无关性,由Sun公司开发。
我如何能判定它不是取的知识库原文一大段,而是命中了多个切片后组装的呢?
排查分析方案
加日志,是的,把目前的整个RAG过程从输入到输出都打印出来,尤其是要把检索结果打印出来。
找到vector_db.py中的search方法
# ===== 4. 搜索 =====
def search(self, query, top_k=3):
query_vec = model.encode([query])
distances, indices = self.index.search(
np.array(query_vec).astype("float32"),
top_k
)
print("命中的 chunks:")
for i in indices[0]:
print(self.docs[i])
return [self.docs[i] for i in indices[0]]
更进一步验证,在 prompt 前打印上下文,这是LLM实际看到的东西。
def ask(question: str) -> str:
# 用向量检索代替关键词匹配
related_docs = vector_db.search(question)
context = "\n".join(related_docs)
print("\n最终拼接的上下文:")
print(context)
解决方案
根据前面加了日志打印运行后控制台打印出来的内容,可进行以下修改:
如果本地知识库很小
比如我们测试用的本地知识库,只有几行,文本量很少。
解决方案就是扩充知识库,把测试用的知识库扩充到100条甚至几百条文本。
chunk_size太大
比如chunk_size = 500,文本只有100字,那么切了也只有一个chunk
解决方案就是减小 chunk_size
split_text(text, chunk_size=20, overlap=5)
结论
判断RAG是否真的生效,不是看“答案”,而是看检索命中的chunks。
因为答案是LLM生成的(可能“猜对”),只有chunk命中才是真正“基于知识库”。
更多推荐



所有评论(0)