当前位置: 首页 > news >正文

你的 RAG 为什么总答错?问题出在分块这一步

RAG 分块策略全攻略 —— 从"暴力切"到"语义切",一篇讲透

RAG 系列里,咱们一直用"按固定字数切块"——200 字一段,到点就切。这就像切面包不看纹路,上来就咔咔咔等分。结果呢?好好的一句话被劈成两半,上下文全断了。用户问"Django 怎么安装",检索到的块里只有个go,大模型能答对才怪。这篇咱们来认真聊聊分块这个事儿,从最粗暴到最聪明,5 种方案全给你对比一遍,每种都有完整可跑代码和模拟效果。分块这一步做好了,后面 Embedding、检索、生成全链路都受益。


一、为什么分块这么重要?

块太大:塞不进 LLM 的上下文窗口,而且噪音多——无关信息把有用的内容稀释了,大模型看了半天抓不住重点。你塞进去 2000 字,真正有用的可能就 100 字,剩下全是干扰项。

块太小:信息被切碎,上下文丢了。比如"年假 5 天"被切成"年假"和"5 天"两块,检索到其中一块也答不对。再比如"3 天以上病假需部门总监审批",切成"3 天以上"和"病假需部门总监审批",用户问"几天要总监批",检索逻辑直接懵了。

最佳状态是什么?一个块 = 一个完整的语义单元。可以是一个观点、一条规则、一个步骤。边界清晰,不拖泥带水。用户问什么,检索到的块就能直接回答什么,不用大模型去"猜"前后文。

说白了,分块是 RAG 的"天花板"。后面你怎么调 Embedding 模型、怎么优化检索策略、怎么改 prompt,都赢不了一个烂分块。这块没做好,后面全是白忙活。


二、准备测试文档

咱们用同一篇结构化的技术文档来对比 5 种分块策略。下面这篇"Python Web 框架对比"有标题、段落、代码块、列表,结构比较典型:`TEST_DOC = “”"
Python Web 框架对比(2024版)

一、Django
Django 是 Python 生态里最成熟的全栈框架,自带 ORM、Admin、认证系统。
适合中大型项目,学习曲线稍陡,但文档完善。
安装命令:pip install django

二、Flask
Flask 是轻量级微框架,核心只有路由和模板,其他功能靠扩展。
适合小型项目和 API 服务,灵活度高。
安装命令:pip install flask

三、FastAPI
FastAPI 基于类型注解,自动生成 OpenAPI 文档,性能接近 Go。
适合需要高性能 API 的场景,异步支持完善。
安装命令:pip install fastapi uvicorn

四、选型建议

  • 要快速上线:选 Django
  • 要灵活可控:选 Flask
  • 要高性能 API:选 FastAPI
    “”"`

后面所有策略都用这篇文档跑一遍,方便对比。选这篇是因为它有典型结构:标题、小节、列表、代码块。如果你的文档是纯长段落、或者全是表格,分块策略的差异会更明显,可以自己换一篇试试。


三、策略一:固定大小切块(咱们的老朋友)

原理很简单:每 N 个字符切一刀,不管内容。到 200 字就切,管你是在句子中间还是段落中间。很多教程的第一版 RAG 就是这么写的,因为实现起来只要一个 for 循环。`def simple_chunk(text, chunk_size=200):
“”“固定大小切块:到字数就切”“”
text = text.strip()
chunks = []
for i in range(0, len(text), chunk_size):
chunk = text[i:i + chunk_size]
if chunk.strip():
chunks.append(chunk)
return chunks

测试

chunks = simple_chunk(TEST_DOC, chunk_size=200)
for i, c in enumerate(chunks):
print(f"— 块 {i+1} —")
print(c[:80] + "…"if len© > 80else c)
print()`

运行效果:`— 块 1 —
Python Web 框架对比(2024版)

一、Django
Django 是 Python 生态里最成熟的全栈框架,自带 ORM、Admin、认证系统。
适合中大型项目,学习曲线稍陡,但文档完善。
安装命令:pip install djan…

— 块 2 —
go

二、Flask
Flask 是轻量级微框架,核心只有路由和模板,其他功能靠扩展。
适合小型项目和 API 服务,灵活度高。
安装命令:pip install flask

三、FastAPI
FastAPI 基于类型注解,自动生成 OpenAPI 文档,性能接近 Go。
适合需要高性能 API 的场景,异步支持完善。
安装命令:pip install fastapi uvicorn

四、选型建议

  • 要快速上线:选 Django
  • 要灵活可控:选 Flask
  • 要高性能 API:选 FastAPI`

看到没?块 1 结尾是pip install djan,块 2 开头是godjango被活生生劈成两半。用户问"Django 怎么安装",检索到块 2 只有个孤零零的go,大模型能答对才怪。这种切法适合什么?快速验证想法、做个 Demo 给老板看,够用了。真要上线,别用。

优点:实现简单,速度快,不依赖任何库。缺点:完全不管语义,切到哪儿算哪儿。


四、策略二:固定大小 + 重叠(V2 升级)

既然一刀切会断上下文,那就让相邻块有重叠区域。块 1 的末尾和块 2 的开头重复 50 个字,这样django至少会完整出现在某个块里。overlap 一般设为 chunk_size 的 10%–20%,太小救不了断句,太大又浪费存储。`def overlap_chunk(text, chunk_size=200, overlap=50):
“”“固定大小 + 重叠:相邻块共享 overlap 个字符”“”
text = text.strip()
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
if chunk.strip():
chunks.append(chunk)
start = end - overlap # 下一块从 overlap 位置开始
return chunks

测试

chunks = overlap_chunk(TEST_DOC, chunk_size=200, overlap=50)
for i, c in enumerate(chunks):
print(f"— 块 {i+1} (len={len©}) —")
print(c[:100] + "…"if len© > 100else c)
print()`

运行效果:`— 块 1 (len=200) —
Python Web 框架对比(2024版)

一、Django
Django 是 Python 生态里最成熟的全栈框架,自带 ORM、Admin、认证系统。
适合中大型项目,学习曲线稍陡,但文档完善。
安装命令:pip install django…

— 块 2 (len=200) —
安装命令:pip install django

二、Flask
Flask 是轻量级微框架,核心只有路由和模板,其他功能靠扩展。
适合小型项目和 API 服务,灵活度高。
安装命令:pip install flask

三、FastAPI
FastAPI 基于类型注解,自动生成 OpenAPI 文档,性能接近 Go。
适合需要高性能 API 的场景,异步支持完善。
安装命令:pip install fastapi uvicorn…

— 块 3 (len=200) —
安装命令:pip install fastapi uvicorn

四、选型建议

  • 要快速上线:选 Django
  • 要灵活可控:选 Flask
  • 要高性能 API:选 FastAPI`

这次pip install django完整出现在块 1 和块 2 里了。重叠区减少了信息断裂,算是个小升级。但代价也很明显:每个块有 50 字是重复的,存进向量库会多占空间,检索时也可能把相似的两块都召回来——top_k=3 可能给你两块内容几乎一样的。而且 overlap 设多少合适?50?100?得自己试。如果切点刚好在句子中间,重叠也救不了,只是把"断点"往后挪了一点而已。


五、策略三:按段落/换行切

文档本身就有结构:双换行是段落,单换行是行内换行。按\n\n切,至少不会把段落劈开。写文档的人已经帮你分好段了,咱们就顺着这个结构来,比硬切靠谱。`def paragraph_chunk(text):
“”“按双换行切块,太短的合并,太长的二次切”“”
paragraphs = [p.strip() for p in text.split(“\n\n”) if p.strip()]
chunks = []
current = []
current_len = 0
max_len = 300# 单块最大长度

for p in paragraphs: if current_len + len(p) + 2 <= max_len and current: current.append(p) current_len += len(p) + 2 else: if current: chunks.append("\n\n".join(current)) if len(p) > max_len: # 段落太长,按句号再切 parts = p.replace("。", "。\n").split("\n") for part in parts: if part.strip(): chunks.append(part.strip()) current = [] current_len = 0 else: current = [p] current_len = len(p) if current: chunks.append("\n\n".join(current)) return chunks

测试

chunks = paragraph_chunk(TEST_DOC)
for i, c in enumerate(chunks):
print(f"— 块 {i+1} —")
print©
print()`

运行效果:`— 块 1 —
Python Web 框架对比(2024版)

一、Django
Django 是 Python 生态里最成熟的全栈框架,自带 ORM、Admin、认证系统。
适合中大型项目,学习曲线稍陡,但文档完善。
安装命令:pip install django

— 块 2 —
二、Flask
Flask 是轻量级微框架,核心只有路由和模板,其他功能靠扩展。
适合小型项目和 API 服务,灵活度高。
安装命令:pip install flask

— 块 3 —
三、FastAPI
FastAPI 基于类型注解,自动生成 OpenAPI 文档,性能接近 Go。
适合需要高性能 API 的场景,异步支持完善。
安装命令:pip install fastapi uvicorn

— 块 4 —
四、选型建议

  • 要快速上线:选 Django
  • 要灵活可控:选 Flask
  • 要高性能 API:选 FastAPI`

每个块都是一个完整的小节,语义边界清晰。这种切法特别适合结构规整的文档:产品说明书、API 文档、有明确章节的技术文章。问题是:有的文档段落特别长(比如一整段代码、一整段法律条文),有的特别短(比如只有标题、选型建议")。所以加了合并和二次切割逻辑——太短就拼一起,太长就按句号再切一刀。如果你的文档是 Markdown 写的,段落结构清晰,按段落切往往比固定字数靠谱得多。


六、策略四:递归字符切块(LangChain 的方案)

这是当前工程里很常见的方案。思路是:按一组分隔符的优先级递归切。先按\n\n切,切完发现某块太长?再按\n切。还太长?按句号.切。还太长?按空格切。总之尽量在"自然边界"切,实在不行才硬切。相当于把"按段落切"和"按字数切"结合了,既尊重结构,又控制块大小。`# 用 LangChain 的实现
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=30,
length_function=len,
separators=[“\n\n”, “\n”, “。”, “.”, " ", “”]
)
chunks = splitter.split_text(TEST_DOC)

纯 Python 手写版(不依赖 LangChain)

def recursive_chunk(text, chunk_size=200, overlap=30, separators=None):
if separators isNone:
separators = [“\n\n”, “\n”, “。”, “.”, " ", “”]

def _split(s, seps): ifnot s.strip(): return [] if len(s) <= chunk_size: return [s] if s.strip() else [] sep = seps[0] if seps else"" if sep == "": return [s[i:i+chunk_size] for i in range(0, len(s), chunk_size - overlap) if s[i:i+chunk_size].strip()] parts = s.split(sep) chunks = [] current = "" for i, p in enumerate(parts): piece = p + (sep if i < len(parts) - 1else"") if len(current) + len(piece) <= chunk_size: current += piece else: if current.strip(): chunks.append(current.strip()) if len(piece) > chunk_size and len(seps) > 1: chunks.extend(_split(piece, seps[1:])) current = "" else: current = piece if current.strip(): chunks.append(current.strip()) return chunks return _split(text.strip(), separators)

测试

chunks = recursive_chunk(TEST_DOC, chunk_size=200, overlap=30)
for i, c in enumerate(chunks):
print(f"— 块 {i+1} —")
print(c[:120] + "…"if len© > 120else c)
print()`

运行效果:`— 块 1 —
Python Web 框架对比(2024版)

一、Django
Django 是 Python 生态里最成熟的全栈框架,自带 ORM、Admin、认证系统。
适合中大型项目,学习曲线稍陡,但文档完善。
安装命令:pip install django

— 块 2 —
二、Flask
Flask 是轻量级微框架,核心只有路由和模板,其他功能靠扩展。
适合小型项目和 API 服务,灵活度高。
安装命令:pip install flask

— 块 3 —
三、FastAPI
FastAPI 基于类型注解,自动生成 OpenAPI 文档,性能接近 Go。
适合需要高性能 API 的场景,异步支持完善。
安装命令:pip install fastapi uvicorn

— 块 4 —
四、选型建议

  • 要快速上线:选 Django
  • 要灵活可控:选 Flask
  • 要高性能 API:选 FastAPI`

和按段落切的结果很像,但递归切块能处理更复杂的文档。有代码块、有长段落、有短标题混在一起时,它会自动选更合适的分隔符。先试\n\n,不行试\n,再不行试句号,最后才硬切。它的优势在于兼顾了语义边界和大小限制,所以在生产里非常常见。LangChain 的RecursiveCharacterTextSplitter默认就是这么干的,你可以直接用,separators也能按文档特点微调,比如中文文档把放前面。


七、策略五:语义分块(Semantic Chunking)

前面四种都是"看字符、看标点",语义分块是"看意思"。思路是:把文本拆成句子,每句话算一个 Embedding,然后算相邻句子的余弦相似度。相似度高,通常说明两句话还在聊同一件事;相似度骤降,往往意味着话题换了,就可以考虑在那儿切一刀。比如"安装命令:pip install django"和"二、Flask"之间,语义往往会有明显跳变,那就是一个很自然的候选切点。`import os
import numpy as np
from openai import OpenAI

client = OpenAI(api_key=os.getenv(“OPENAI_API_KEY”))

def get_embeddings(texts):
“”“批量获取 Embedding,避免一条句子调一次 API”“”
r = client.embeddings.create(model=“text-embedding-3-small”, input=texts)
return [np.array(item.embedding) for item in r.data]

def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

def semantic_chunk(text, threshold=0.5, min_chunk_len=50, max_chunk_len=400):
“”“语义分块:在相似度骤降处切割”“”
import re
sentences = re.split(r’[。\n.!?]', text)
sentences = [s.strip() for s in sentences if len(s.strip()) > 5]

if len(sentences) < 2: return [text] if text.strip() else [] embeddings = get_embeddings(sentences) similarities = [cosine_similarity(embeddings[i], embeddings[i+1]) for i in range(len(embeddings)-1)] cut_points = [0] for i, sim in enumerate(similarities): if sim < threshold: cut_points.append(i + 1) cut_points.append(len(sentences)) chunks = [] for i in range(len(cut_points) - 1): start, end = cut_points[i], cut_points[i + 1] chunk_text = "。".join(sentences[start:end]) if len(chunk_text) > max_chunk_len: chunks.append(chunk_text[:max_chunk_len]) chunks.append(chunk_text[max_chunk_len:]) elif len(chunk_text) >= min_chunk_len: chunks.append(chunk_text) return chunks

测试(需要 OPENAI_API_KEY)

chunks = semantic_chunk(TEST_DOC, threshold=0.5)
for i, c in enumerate(chunks):
print(f"— 块 {i+1} —")
print©
print()`

模拟相似度曲线(示意):句子对: 1-2 2-3 3-4 4-5 5-6 6-7 7-8 ... 相似度: 0.85 0.82 0.45 0.88 0.79 0.52 0.91 ... ↑ ↑ 切点 切点

在 3-4 和 6-7 之间相似度明显下降,说明话题切换了,就在这些位置切。切出来的块会更接近"语义单元",上限通常更高。这种方案适合什么?文档没有明显段落结构、或者段落很长很杂、你需要尽可能精准地切出"一个观点一块"的场景。比如法律条款、医疗指南、长篇技术博客。

优点:真正按语义切,上限更高。缺点:要额外算 Embedding,有成本;处理速度比简单规则切块慢;还要调threshold这个超参数,设高了切得太碎,设低了块太大。工程上通常更适合离线批处理,而不是临时在线切。


八、实测对比:同一个问题,5 种分块谁最准?

光说不练假把式。咱们用同一批文档、同一个 Embedding 模型、同一个问题"FastAPI 怎么安装?",分别建 5 个向量库,看谁召回的块最相关。这个测试你本地跑一遍就能复现,结论会很有说服力。`import chromadb
from chromadb.config import Settings

def build_and_query(chunks, question, top_k=2):
“”“建库 + 检索”“”
client = chromadb.Client(Settings(anonymized_telemetry=False))
coll = client.create_collection(“test”, metadata={“hnsw:space”: “cosine”})
embeddings = [get_embedding© for c in chunks]
coll.add(ids=[str(i) for i in range(len(chunks))], embeddings=embeddings, documents=chunks)
q_emb = get_embedding(question)
results = coll.query(query_embeddings=[q_emb], n_results=top_k)
return results[“documents”][0]

question = “FastAPI 怎么安装?”

5 种策略分别测试

strategies = [
(“固定大小”, simple_chunk(TEST_DOC, 200)),
(“固定+重叠”, overlap_chunk(TEST_DOC, 200, 50)),
(“按段落”, paragraph_chunk(TEST_DOC)),
(“递归切块”, recursive_chunk(TEST_DOC, 200, 30)),
(“语义分块”, semantic_chunk(TEST_DOC, 0.5)),
]

for name, chunks in strategies:
retrieved = build_and_query(chunks, question)
print(f"【{name}】召回:“)
for r in retrieved:
print(f” - {r[:60]}…")
print()`

结果对比:

策略召回内容是否包含 “pip install fastapi uvicorn”
固定大小可能只召回含 “fastapi” 的碎片,安装命令被切断
固定+重叠能召回完整命令,但可能带多余重叠内容
按段落能召回完整 FastAPI 段落 ✓
递归切块能召回完整段落 ✓
语义分块能召回最精准的语义块 ✓

固定大小切块最容易翻车:块 2 可能刚好是 “go\n\n二、Flask…” 这种四不像,和"FastAPI 安装"的语义距离说不清。重叠切块能救回来一部分,但块会变多,检索时重复内容也多。按段落和递归切块都能稳定召回完整段落,语义分块在文档更复杂时优势会更明显。你可以拿自己的文档多试几个问题,感受一下差异。


九、五种策略对比表

策略原理复杂度效果适合场景
固定大小按字数切最低快速验证 / Demo
固定+重叠按字数 + 重叠区一般简单项目
按段落按换行切较好结构清晰的文档
递归切块多级分隔符中等生产环境通用
语义分块Embedding 相似度上限高高精度 RAG

十、实用建议

大多数项目,先用递归切块就够了。实现简单,效果稳定,LangChain 也有现成实现,不用自己造轮子。先把递归切块跑起来,再根据实际效果决定要不要上语义分块。

如果文档有明确的标题、章节结构,按标题切会比按段落更好。比如 Markdown 的#####,可以优先按这些切。很多 RAG 框架支持 MarkdownHeaderTextSplitter,会按标题层级切,切出来的块自带"这是第几章"的元数据,检索时还能按章节过滤。

语义分块适合"精度比成本重要"的场景,比如法律文书、医疗文档。要算好 Embedding 的调用量和延迟。如果文档量不大(几百篇以内),可以离线跑一次语义分块,建好索引后检索阶段和普通分块一样快。

不管用哪种策略,chunk_size的选择都要结合 Embedding 模型、文档类型和问题类型一起看。很多项目会先从 256–512 token 这个量级试起,中文粗略可以理解成 150–300 字左右,但这不是铁律。别拍脑袋定个 2000,也别切得太碎。更稳妥的方式是先拿一批真实问题做召回测试,再调大小和 overlap。


十一、写在最后

分块是 RAG 里最不起眼但影响很大的环节。很多人花大量时间调 Embedding 模型、调检索策略、调 Rerank,结果分块方式还是第一天写的那个 200 字硬切。检索不准的时候,先回去看看你的块是怎么切的,是不是一句话被劈成两半,是不是一个完整规则被拆到了三个块里。先把分块做对了,很多"检索不准"的问题会先消掉一大半。

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

http://www.cnnetsun.cn/news/1524176.html

相关文章:

  • 让Windows 11运行如飞:Win11Debloat优化工具全面指南
  • QuickRecorder高效解决方案:从基础到进阶的macOS录屏全指南
  • 别再为选哪个大模型头疼了!用AI Ping这个免费工具,5分钟搞定性能对比
  • BL999温湿度传感器单总线驱动库深度解析与工业实践
  • miniCOIL:为BM25添加语义
  • 【深度解析】Claude Auto Dream:从“短期对话”到“项目级心智模型”的记忆系统升级
  • FPGA商用级ISP(二):镜头阴影校正(LSC)的网格增益插值与并行硬件架构实现
  • Vault 密钥管理实践:从部署到使用
  • 如何安装龙虾
  • Easy-Scraper:Rust 构建的现代化网页数据采集解决方案
  • SEO_网站SEO优化常见问题及解决办法(273 )
  • GAT的注意力真的‘智能’吗?可视化分析它在节点分类任务中到底关注了谁
  • 基于Python的律师事务所案件管理系统毕业设计
  • OCR-VQA数据集下载避坑指南:解决URL失效和图片格式问题
  • 风扇噪音优化与智能温控:FanControl全方位解决方案
  • [具身智能-124]:惯性测量单元(Inertial Measurement Unit,简称 IMU),测量物体在三维空间中运动状态的核心传感器。
  • RTOS选型与设计:实时系统核心技术解析
  • Vue3项目救星:我是如何用Cursor的‘项目规则’功能,让团队新人一天上手的
  • SAM2赋能ComfyUI-Impact-Pack:实时交互分割技术的落地与创新
  • EVA-02企业内网部署方案:安全隔离与高可用架构
  • AB Download Manager完整指南:告别杂乱下载,体验高效文件管理
  • Qwen3-0.6B-FP8一文详解:FP8显存优化原理、Streamlit界面定制与CoT解析机制
  • 用LDA模型挖掘微信聊天秘密:Gensim实战教程(含pyLDAvis可视化)
  • AB Download Manager:提升下载效率的5个实用技巧完整指南
  • 解密Qwen的FunctionCall机制:从XML标签到JSON解析的完整流程拆解
  • Gitlab API实战:如何用Java统计团队代码提交量(附完整SpringBoot代码)
  • 3步解锁MSG文件高效提取:免费工具让邮件处理效率提升10倍
  • 像素时装锻造坊用户调研:92%美术从业者认为其比传统SD WebUI更易上手的原因分析
  • ZephyrOS--实战Bluetooth LE心率监测
  • 实战指南:基于快马平台,从零到一构建可部署上线的“榕树钱小乐”全栈应用