热闻岛
Text Splitter:为什么知识库不能整篇文档直接丢给大模型?
AI新闻

Text Splitter:为什么知识库不能整篇文档直接丢给大模型?

2026年6月13日 23:0922 阅读
一、前言 Document Loader 只是把资料读进来。Text Splitter 才决定这些资料怎么进入知识库。 切分太大,检索像大海捞针。切分太小,语义被切碎。一个好的 Chunk,要能被召回,也要能独立表达完整意思。 这就是 Te

一、前言

Document Loader 只是把资料读进来。Text Splitter 才决定这些资料怎么进入知识库。

切分太大,检索像大海捞针。切分太小,语义被切碎。一个好的 Chunk,要能被召回,也要能独立表达完整意思。

这就是 Text Splitter 的价值:把长文档切成模型能吃、向量库能存、Retriever 能找、Prompt 能用的小块。

二、官方怎么定义 Text Splitter?

LangChain 官方文档把 Text Splitter 分成几类:递归切分、长度切分、按文档结构切分。长度切分简单稳定;结构切分适合 Markdown、HTML、JSON、代码;递归切分会尽量保留段落、句子、词等较大的语义单元。

官方递归切分文档也说明,RecursiveCharacterTextSplitter 默认按字符列表切分,长度默认按字符数计算;想直接得到字符串,用 split_text;想得到 Document 对象,用 create_documents。

这句话很关键:Text Splitter 不是简单字符串工具。它最终要服务 RAG,所以它必须把 Chunk 重新包装成 Document。

三、为什么知识库不能整篇文档直接入库?

第一,召回颗粒度太粗。用户问一个细节,向量库可能召回整篇大文档,模型被迫在一堆无关内容里找答案。

第二,上下文成本太高。Prompt 里塞进太多文本,Token 成本和延迟都会上升。

第三,答案容易跑偏。无关段落越多,模型越容易抓错重点。

第四,溯源不清晰。用户问“依据在哪”,整篇文档很难定位到具体页、具体段、具体标题。

所以,RAG 的第一性原理不是“把资料都塞进去”,而是“只把相关资料塞进去”。Text Splitter 正是把资料变成可召回颗粒的第一步。

四、TextSplitter 是所有切分器的骨架

从源码看,TextSplitter 继承 BaseDocumentTransformer。它不是一个普通工具类,而是一个“文档转换器”。它接收 Document,输出更多 Document。

TextSplitter 的构造方法里有几个硬约束:chunk_size 必须大于 0,chunk_overlap 不能小于 0,chunk_overlap 不能大于 chunk_size。源码一开始就做了参数保护。

class TextSplitter(BaseDocumentTransformer, ABC):
def __init__(
self,
chunk_size: int = 4000,
chunk_overlap: int = 200,
length_function: Callable[[str], int] = len,
keep_separator: bool | Literal["start", "end"] = False,
add_start_index: bool = False,
strip_whitespace: bool = True,
) -> None:
if chunk_overlap > chunk_size:
raise ValueError(...)
self._chunk_size = chunk_size
self._chunk_overlap = chunk_overlap
self._length_function = length_function

这里的重点不是默认值,而是抽象。TextSplitter 只规定切分器必须实现 split_text。至于怎么切,由具体子类决定。

五、split_documents:Document 是怎么被切成多个 Document 的?

split_documents 的源码非常短,但意义很大。它先遍历输入的 Document,把 page_content 和 metadata 拿出来,然后调用 create_documents。

def split_documents(self, documents: Iterable[Document]) -> list[Document]:
texts, metadatas = [], []
for doc in documents:
texts.append(doc.page_content)
metadatas.append(doc.metadata)
return self.create_documents(texts, metadatas=metadatas)

这说明一件事:切分不是丢掉原始信息。每个 Chunk 都应该继承原始 metadata,比如来源文件、页码、标题、业务标签、版本号。

create_documents 会继续调用 split_text,把每个文本切成 chunk;然后把每个 chunk 包成新的 Document。开启 add_start_index 后,还会把 chunk 在原文中的起始位置写入 metadata。

for chunk in self.split_text(text):
metadata = copy.deepcopy(metadatas_[i])
if self._add_start_index:
metadata["start_index"] = index
new_doc = Document(page_content=chunk, metadata=metadata)
documents.append(new_doc)

这就是为什么企业知识库一定要重视 metadata。没有 metadata,答案就没法引用;没法引用,用户就不敢信。

六、_merge_splits:chunk_size 和 chunk_overlap 的真相

_merge_splits 是源码里最值得细看的地方。它不是简单截断字符串,而是把一堆小片段合并成中等大小的 Chunk。

逻辑很直接:不断往 current_doc 里塞片段;如果加上新片段会超过 chunk_size,就先输出当前 Chunk;然后从头弹出旧片段,直到保留的重叠部分不超过 chunk_overlap。

if total + len_ + separator_len > self._chunk_size:
doc = self._join_docs(current_doc, separator)
docs.append(doc)
while total > self._chunk_overlap or still_too_long:
total -= length(current_doc[0])
current_doc = current_doc[1:]
current_doc.append(d)

这段源码背后的核心思想是:不要让 Chunk 太长,也不要让相邻 Chunk 完全断开。overlap 就是两个 Chunk 之间的“缝合线”。

七、RecursiveCharacterTextSplitter:为什么它通常是默认首选?

普通 CharacterTextSplitter 更像“按某个分隔符切”。RecursiveCharacterTextSplitter 更聪明。它会拿一组分隔符,从大结构到小结构逐层尝试。

源码默认分隔符是:段落、换行、空格、空字符串。也就是先按段落切;段落太长,再按换行;还太长,再按空格;最后兜底按字符切。

self._separators = separators or ["\n\n", "\n", " ", ""]
def split_text(self, text: str) -> list[str]:
return self._split_text(text, self._separators)

源码里的 _split_text 会先找到当前文本里能匹配的 separator,然后切开。如果切出来的某个片段还超过 chunk_size,就带着剩下的 separator 递归继续切。

for s_ in separators:
if re.search(separator_, text):
separator = s_
new_separators = separators[i + 1:]
break
for s in splits:
if self._length_function(s) < self._chunk_size:
good_splits.append(s)
else:
final_chunks.extend(self._split_text(s, new_separators))

这就是它适合通用文档的原因:它尽量不破坏自然结构。先保段落,再保句子,再保词。实在没办法,才保长度。

八、参数怎么调?先看这张图

参数没有银弹。知识库越专业,越不能只靠默认值。要用真实问题集做评测,看召回命中率、答案准确率、引用完整性和成本。

一般经验:普通中文文章可以从 500 到 1000 字级别开始;技术文档优先按标题结构切;合同、公告、研报要保留页码、章节、发布日期;代码文档要用代码结构切分。

九、MarkdownHeaderTextSplitter:结构化文档不要盲目按长度切

如果文档本身有标题结构,比如 Markdown 的 #、##、###,更好的方式是先按标题切。LangChain 的 MarkdownHeaderTextSplitter 会把标题信息写入 metadata。

这对 RAG 很重要。因为同一句话放在“安装教程”下面,和放在“故障排查”下面,含义完全不同。标题就是上下文。

headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]
markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
chunks = markdown_splitter.split_text(markdown_document)

源码思路并不复杂:扫描每一行,维护当前标题栈,把内容聚合到相同 metadata 的块里。最后输出 Document(page_content=内容, metadata=标题路径)。

这类结构切分器适合技术文档、产品手册、接口文档、知识库页面。它的目标不是“切得均匀”,而是“切得有语义”。

十、企业级项目里,Text Splitter 应该怎么落地?

企业项目不要把切分写成一次性脚本。建议做成可配置、可重跑、可追踪的流水线。

Java 主服务负责上传、鉴权、任务状态、数据源管理。

Python AI 服务负责文档解析、清洗、切分、Embedding、向量入库。

每个 Chunk 要有 metadata:source_id、file_name、page、section、chunk_index、version、hash。

切分策略要可配置:普通文档、Markdown、HTML、代码、PDF 表格,不能一套参数走天下。

切分结果要可回溯:用户点答案引用时,能跳回原文位置。

十一、最容易踩的坑

坑一:只调 chunk_size,不看召回效果。结果 Chunk 看起来整齐,答案却不准。

坑二:metadata 丢失。答案能生成,但没法溯源。生产环境这是硬伤。

坑三:PDF 直接切。很多 PDF 有页眉、页脚、断行、表格错位,必须先清洗。

坑四:overlap 过大。召回结果重复,Prompt 变长,成本上升。

坑五:结构化文档按长度硬切。标题和正文断开,语义就断了。

坑六:不做评测。没有命中率、准确率、成本指标,参数就是玄学。

十二、最后总结

Text Splitter 是 RAG 的地基。地基切歪了,Embedding 再强、向量库再快、模型再贵,也救不回来。

它的源码并不神秘:split_documents 负责处理 Document,create_documents 负责重建 Document,split_text 交给子类实现,_merge_splits 负责控制大小和重叠,RecursiveCharacterTextSplitter 负责按结构逐层递归。

真正的关键只有一句:不要为了切分而切分,要为了检索、引用和回答质量而切分。

相关推荐

LangChain 系列之Tools:让大模型真正连接业务系统
AI新闻

LangChain 系列之Tools:让大模型真正连接业务系统

前面几章,我们把 RAG 的底层链路讲完了:文档进来,切分,向量化,入库,检索,重排,最后把上下文交给模型。 但这还不够。 RAG 让模型会“查资料”。Tools 让模型能“办事情”。 没有 Tools,大模型只是一个会聊天的脑子。它能分析

11 分钟前
LangChain 系列:从 0 搭一个企业知识库问答系统
AI新闻

LangChain 系列:从 0 搭一个企业知识库问答系统

这一章不再单讲一个组件,而是把前面所有 RAG 组件合起来:从文件上传,到向量入库,再到用户提问、检索证据、模型回答、日志追踪。 01 先搞清楚:企业知识库问答不是 Demo Demo 只要能回答。企业系统要能上传、能解析、能检索、能引用、

91 小时前
Tensor:PyTorch 世界里的一切都是张量
AI新闻

Tensor:PyTorch 世界里的一切都是张量

1. Tensor 是 PyTorch 的基本单位 PyTorch 里,模型吃进去的是 Tensor,参数保存的是 Tensor,梯度也是 Tensor,GPU 上跑的还是 Tensor。 你可以把 Tensor 理解成“加强版数组”。但这

61 小时前
Rerank 与上下文压缩:为什么召回 TopK 后还要重排?
AI新闻

Rerank 与上下文压缩:为什么召回 TopK 后还要重排?

RAG 最容易踩的坑,不是“没有召回”。 而是召回了一堆看起来相关、实际会干扰模型的资料。 所以,成熟的 RAG 链路不会把 TopK 直接塞进 Prompt。 它会先召回,再重排,再压缩。 1. 为什么召回 TopK 后还要重排? 向量召

101 小时前
环境安装与开发姿势:CPU、CUDA、ROCm、MPS 怎么选
AI新闻

环境安装与开发姿势:CPU、CUDA、ROCm、MPS 怎么选

学 PyTorch,第一个门槛不是模型。是环境。环境装错,代码还没开始跑,报错已经堆满屏。 这一章只讲一件事:把 PyTorch 环境一次性讲清楚。不是背安装命令,而是看懂安装背后的逻辑。 因为命令会随着版本变化。逻辑不会。你只要能判断 C

53 小时前
LangChain 系列之Retriever:RAG 的核心不是生成,而是检索
AI新闻

LangChain 系列之Retriever:RAG 的核心不是生成,而是检索

01 前言 Retriever 是 RAG 的入口。它接收一个 query,返回一组 List[Document]。模型不是先出场,资料先出场。 前面几章我们讲了 Loader、Splitter、Embedding、VectorStore。

103 小时前

阅读补充

一句话看懂

一、前言 Document Loader 只是把资料读进来。Text Splitter 才决定这些资料怎么进入知识库。

事件背景

这篇内容围绕“Text”展开,热闻岛基于公开信息整理事件背景、主要进展与可继续关注的方向。

事件时间线

发布

相关信息进入公开传播

更新

热闻岛对内容进行整理与补充。

看点

  • · Text的最新进展是什么
  • · 相关信息对用户或行业会带来哪些影响
  • · 后续是否会有新的回应或处理结果

后续关注

  • · 后续官方回应或权威通报
  • · 相关主体的进一步说明
  • · 事件对普通用户和行业的持续影响

免责声明:本文仅代表作者观点,不构成投资建议、法律建议、医疗建议。财经类内容尤其需要注意风险;爆料类信息请以权威通报为准。

评论 (0)

登录后即可发表评论

去登录
暂无评论,快来抢沙发