夜雨聆风学习资料网

ARTICLE · 1133598

06-RAG 文档chunk实战(下)

06-RAG 文档chunk实战(下)
上一篇讲了基础分块和结构感知分块。但现实里总有些"难啃"的文档——既没有清晰结构,话题又跳来跳去:上一段还在讲产品功能,下一段突然跳到售后政策。这种按字符切、按结构切都不好使。

这一篇(下),我们上更聪明的招:语义分块、主题分块、高级策略(小大分块 + 代理式分块)、混合分块,最后给一套"到底怎么选"的决策方法。全程配代码。


三、语义分块与主题分块

1)语义分块:按"意思变了没"来切

固定字符切分的问题在于——它不懂内容。如果一段话从"产品功能"突然跳到"售后政策",按字符切很可能把两个不相关的话题切进同一块,语义就乱了。

语义分块的思路:把句子逐个转成向量,计算相邻句子的相似度;相似度高就合并到一块,相似度明显下降(话题变了)就在这里切开。

句1 —(相似度 0.85)— 句2 —(0.82)— 句3 —(0.38 ↓ 掉了!)— 句4                    合并成一块                切开

控制它的关键是一个相似度阈值:阈值越低,切得越细;越高,块越大。建议从 70% 左右起步再调。

from langchain_experimental.text_splitter import SemanticChunkerfrom langchain_huggingface import HuggingFaceEmbeddingsemb = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")splitter = SemanticChunker(    embeddings=emb,    breakpoint_threshold_type="percentile",   # 按分位数判断"相似度骤降"    breakpoint_threshold_amount=70# 阈值,越低切得越细)chunks = splitter.create_documents([text])

注意:语义分块要对每个句子做 Embedding,比字符切分慢(还要先加载模型),但它切出来的块话题内聚,适合话题跳跃的长文。

2)主题分块:识别每段的主导主题

主题分块会识别每段文本的主导主题,在主题切换处分块。它适合长文档,但效果不太稳定、且往往要配不少自定义清洗,生产环境用得较少,更适合探索性分析。了解即可,别一上来就上。


四、高级策略:小大分块与代理式分块

先记住一个绕不开的矛盾:

优点
缺点
小块
检索精准(命中率高)
上下文太少,生成时信息不足
大块
上下文丰富
检索有噪声,命中率下降

1)小大分块(父文档检索)——鱼与熊掌兼得

思路很巧:用小块去检索(保证精准),命中后返回它所属的大块给大模型(保证上下文充足)。

小块(用于检索,精准命中) ──属于──▶ 大块(父块,返回给大模型作答)

LangChain 里 ParentDocumentRetriever 已经封装好了这套逻辑:

from langchain.retrievers import ParentDocumentRetrieverfrom langchain.text_splitter import RecursiveCharacterTextSplitterparent_splitter = RecursiveCharacterTextSplitter(chunk_size=1000)  # 大块:给上下文child_splitter  = RecursiveCharacterTextSplitter(chunk_size=200)   # 小块:用于检索retriever = ParentDocumentRetriever(    vectorstore=vectorstore,          # 存小块的向量    docstore=docstore,                # 存大块的原文    child_splitter=child_splitter,    parent_splitter=parent_splitter,)retriever.add_documents(docs)# 检索时:小块命中 → 自动返回对应的大块results = retriever.invoke("公司的核心技术是什么")

既保住检索精度,又提供充足上下文,这是生产里非常实用的一招。

2)代理式分块(Agentic Chunking)——让大模型自己切

最前沿的思路:直接让大语言模型读原文,动态地按语义/知识点切分。它的语义理解比机械切分强得多,但成本高、延时大,只适合小规模、高价值的知识库(科研文献、企业核心专利等),通用场景不划算。

from langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplatellm = ChatOpenAI(model="……", base_url="……", api_key="……")prompt = ChatPromptTemplate.from_template("""你是顶尖的文档分析师。请把下面的文本,按“独立的知识点/主题”拆分成若干块,每块输出:标题 + 内容。文本:{doc}""")chunks = (prompt | llm).invoke({"doc": text})# 模型会按自己的理解,把长文提取成 N 个知识块(各带标题与内容)

🤔 想一想:代理式分块效果最好,为什么不所有场景都用它? —— 因为它每切一次都要调大模型,成本和延时都很高,通用大规模知识库根本扛不住这个账。


五、混合分块:现实文档往往要"组合拳"

真实文档常常是结构 + 超长内容的混合体:比如企业年报,有章节标题,但标题下又是超长段落。单一策略不够用,这时候上混合分块:

先按结构切,再对超长的块做二次递归切分。

from langchain.text_splitter import (    MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter)# 第一步:按 Markdown 标题做结构切分(保留标题元数据)md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=[("#","H1"),("##","H2")])sections = md_splitter.split_text(markdown_doc)# 第二步:对超过阈值的大块,再用递归切分做二次细分recursive = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=50)final_chunks = []for sec in sections:iflen(sec.page_content) > 400:                 # 超阈值才二次切for piece in recursive.split_text(sec.page_content):            final_chunks.append((piece, sec.metadata))  # 继承标题元数据else:        final_chunks.append((sec.page_content, sec.metadata))

既保留了原始结构(标题溯源),又避免单块过大,特别适合技术白皮书、年报、多格式混合文档。


六、到底怎么选?三条核心认知

讲了这么多策略,最后给你一套决策方法:

认知
说明
① 没有银弹
没有任何一种方法通吃所有场景,要按数据类型、业务、成本权衡
② 先简单后优化
先用递归字符分块跑出一个基线,再逐步引入结构/语义/混合策略;别一上来就上复杂的
③ 成本 + 数据说话
高价值小规模知识库才考虑代理式分块;关键是把分块当成可实验、可优化的工程模块,用 A/B 测试比命中率、答案准确率,用数据决定

一张决策速查表:

文档类型
推荐策略
通用/不确定
递归字符分块(首选基线)
句子完整性高(法律/新闻)
句子分块
有标题结构(Markdown/手册/网页)
结构感知(标题切)+ 元数据
对话/客服/访谈
按发言轮次切
无结构、话题跳跃
语义分块
检索要精又要上下文
小大分块(父文档检索)
小规模高价值
代理式分块
结构+超长混合(年报/白皮书)
混合分块

小结

分块两篇到这里收官。核心记住三句:

  1. 递归字符分块是通用首选,先用它跑基线;
  2. 有结构就用结构、话题跳就用语义、要精度+上下文就用小大分块、复杂文档上混合分块;
  3. 分块是可优化的工程模块,最终用 A/B 测试(命中率、准确率)说话,别凭感觉。

分块讲透了,RAG 系统的"下限"就稳了。这个系列后面还会继续更新——LangChain 介绍与实战、RAG + Agent 的应用案例,把 RAG 从"能用"做到"好用"。

关注不迷路。

相关学习资料