第五章:语义被切断怎么办¶
5.1 问题的两个方向¶
上一章讨论的是粒度权衡。这一章关注它的直接后果:一段话被切开后,片段丢失了理解它所必需的上下文。
常见解法可以分成两个方向:
%%{init: {"flowchart": {"nodeSpacing": 12, "rankSpacing": 16, "padding": 8, "wrappingWidth": 110}}}%%
flowchart LR
P[语义被切断] --> D1[避免有害边界]
P --> D2[补足缺失语境]
边界类方法包括重叠切分、语义边界切分和结构化切分。语境类方法包括句子窗口检索、父子切分、命题化改写、上下文增强(Contextual Retrieval)和 Late Chunking。两组方法按保护的内容分类,并非要求语境恢复都在切分后发生:Late Chunking 先编码较大的上下文,再按块边界池化。
- 方向一是预防:在切分时尽量不破坏语义单元;
- 方向二是补足语境:句子窗口和父子切分在命中后扩展阅读材料;命题化、上下文增强和 Late Chunking 则在建库时改善片段表示。小片段是否检索更准仍需评测。
方向二把检索单元与阅读单元解耦,但不是免费消除权衡:父块扩展会增加 Token、噪声和权限核验成本,仍须在固定预算下验证收益。
5.2 方向一:切的时候别切错¶
5.2.1 重叠切分¶
最简单的兜底手段:相邻片段共享一部分内容,增加边界附近的短句在某个片段里完整出现的机会。
优点是实现简单;代价是重复嵌入、存储和召回。有限重叠既不能保证长句完整,也不能补全远距离指代。
5.2.2 结构化切分¶
按文档的天然结构(标题层级、条款编号、章节)切分。
文档结构可靠时,这是值得先试的基线;但标题不保证章节语义自足,跨节定义、脚注和引用仍需补齐。
5.2.3 语义边界切分¶
计算相邻句子的向量相似度,在相似度低谷处切断。
这种方法需要额外的句子编码和断点计算。如第四章所述,应在目标查询集上与固定或递归切分比较检索、生成质量及预处理成本,不能仅凭“语义”这个名字默认采用。
5.3 方向二:切完了把上下文补回来¶
5.3.1 句子窗口检索¶
做法是用小单元(句子或短片段)建索引参与检索,命中后返回它前后若干个单元一起送给模型。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 22, "padding": 8, "wrappingWidth": 160}}}%%
flowchart TB
Q[Query] --> IDX[小片段索引]
IDX --> HIT[命中片段 N]
HIT --> EXP[取 N-2 到 N+2]
EXP --> LLM[送给模型]
检索用小的,生成用大的——这就是方向二的核心范式。
适合叙述性、上下文连贯的文本。缺点是固定窗口大小不适应变化的语义单元长度。
5.3.2 父子切分(Parent-Child)¶
与命中后扩展固定窗口不同,这种方法通过明确的子片段到父片段映射查找上下文:
- 子片段(小):参与向量化和检索;
- 父片段(大):命中子片段后实际返回的单元,可以是整篇原文、一个章节,也可以是按长度或递归分隔生成的大块。
原文不必已有标题层级。例如,LangChain 的 ParentDocumentRetriever【366】 既支持直接以原文档为父片段,也支持先切出较大的父块。
| 对比 | 句子窗口 | 父子切分 |
|---|---|---|
| 扩展依据 | 位置(前后 N 个) | 映射到所属父文档或父块 |
| 边界是否语义完整 | 不保证 | 不保证;按真实结构构造的父块可能保留完整章节,但仍可能需要外部定义 |
| 是否依赖原文已有结构 | 否 | 否,但需要明确的子片段到父片段映射 |
父子切分适合定义、限定条件分布在同一章节的资料。代价包括更多子向量、父文档存取和更长的生成输入;返回父块时必须再次核验整个父块的 ACL,不能凭子块权限放行父块。
一个实用细节:多个子片段命中同一个父片段时要去重,否则会重复占用 Prompt 预算。
5.3.3 命题化改写¶
思路:用 LLM 把段落改写成一组独立自足的陈述句,每句话都不依赖上下文即可理解。
例如把:
「该金额不得超过前款规定的上限。」
只有原文前款确实包含以下事实时,才可以改写为(此处为假设示例):
「差旅住宿费报销金额不得超过员工月基本工资的百分之十五。」
这类方法在实体密集的问答任务上有明确的效果提升,因为每个命题都是自足的,向量表达非常聚焦。
代价也很明确:
- 每个片段都要过一次 LLM,建库成本显著上升;
- 改写过程有信息失真和幻觉风险;
- 改写后丢失了原文措辞,不利于精确匹配和引用溯源。
可以用命题参与检索,再取其对应原文作为证据,避免把改写句直接当成原件引用。是否能处理大语料取决于生成、核验与更新预算,不能仅凭规模否定这种方法。
5.3.4 上下文增强(Contextual Retrieval)¶
这是近年常用的方案之一。
思路:不改写原文,而是在每个 chunk 前拼接一段由 LLM 生成的、说明该片段在整篇文档中位置和背景的短说明,然后再做向量化和关键词索引。
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 22, "padding": 8, "wrappingWidth": 160}}}%%
flowchart TB
DOC[整篇文档] --> LLM[LLM 生成上下文说明]
CH[原始 chunk] --> LLM
LLM --> NEW[上下文说明 + 原始 chunk]
NEW --> EMB[向量化]
NEW --> BM[关键词索引]
它与命题化的关键区别是:原文一字不改,只是在前面加了一段背景说明。所以既补上了上下文,又保留了原始措辞。
Anthropic 公布的实验数据(注意:这是厂商工程博客,非同行评审论文):
| 方案 | Top-20 检索失败率 | 相对降幅 |
|---|---|---|
| 图示基线(普通嵌入) | 5.7% | — |
| + 上下文增强嵌入 | 3.7% | −35% |
| + 上下文增强 BM25 | 2.9% | −49% |
| + Rerank | 1.9% | −67% |
上表是 Anthropic 2024 年博客在所选数据域、Gemini Text 004 配置下的 1 − Recall@20,不是端到端回答错误率;最后一行先召回 150 个候选再重排到 20。相对降幅不能当作业务提升保证。
博客给出的 $1.02/百万文档 Token 是按当时价格估算生成上下文说明的成本:800 Token 的块、8k Token 的文档、50 Token 指令和 100 Token 生成说明,并使用前缀缓存。它不是当前报价,也不包括完整索引和查询成本;缓存命中仍有读取费用,不能理解为后续输入免费。
Prompt Caching 是「计算层」的优化,它降低的是重复输入的计算成本;上下文增强是「信息层」的改造,它改变的是被索引的内容。两者是配合关系,不是替代关系。
5.3.5 Late Chunking¶
思路更进一步:先编码能装进窗口的文档文本,得到 Token 级表示,再按 chunk 边界做池化。论文采用的双向编码器让 Token 表示能够结合窗口内前后文;如果使用因果注意力,每个位置只能读取此前内容,不能照搬“每个 Token 都看到全文”的解释。块边界仍需确定,只是池化放到编码之后。
传统方式
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 22, "padding": 8, "wrappingWidth": 160}}}%%
flowchart TB
D1[文档] --> C1[先切分]
C1 --> E1[各片段独立编码]
E1 --> V1[片段向量<br/>看不到全文]
Late Chunking
%%{init: {"flowchart": {"nodeSpacing": 16, "rankSpacing": 22, "padding": 8, "wrappingWidth": 160}}}%%
flowchart TB
D2[文档] --> E2[整篇长上下文编码]
E2 --> C2[按边界池化 Token 向量]
C2 --> V2[片段向量<br/>融合编码窗口内语境]
它的巧妙之处在于:上下文信息是在编码阶段自然注入的,不需要额外调用 LLM 生成说明。
局限:
- 必须能取得 Token 级隐状态,并按论文方式在编码后池化;仅有“长窗口、返回单向量”的 API 不够;
- 文档超过模型上限时仍需分段处理;
- 全文编码增加计算与峰值显存;文档一处改变可能影响多个块向量,须重算受上下文影响的编码窗口。
上下文增强和 Late Chunking 还会扩大权限依赖:如果一个块的说明或向量使用了受限章节,不能只按块自身的原文 ACL 放行。应在相同权限域内构建,或让派生物继承全部来源的访问限制;撤权时同步失效。它们改善了检索表示,也不代表送给生成器的短原文已补齐所有前提。
5.4 方案对比与选择¶
这些方法解决不同缺口,不能排成固定的精度或成熟度等级。保留原文指方法是否改写被检索的文本,所有派生表示都应另存原文与映射以便核验。
| 方案 | 选择时要核算什么 |
|---|---|
| 重叠切分 | 用重复嵌入、存储和 Token 换局部边界覆盖,不能补远距离指代 |
| 结构化切分 | 结构恢复成本,以及跨章节定义和脚注是否仍完整 |
| 语义边界切分 | 句子编码与阈值标定成本,主题断点是否真的改善召回 |
| 句子窗口 | 扩展读取和生成输入;位置邻近不等于语义充分 |
| 父子切分 | 子向量、父块存取与去重,同时核验父块 Token 和 ACL |
| 命题化改写 | 生成、事实核验和原文映射;不能把派生文本当作已验证事实 |
| 上下文增强 | 背景生成、索引载荷、来源权限与更新依赖;新增说明也可能错误 |
| Late Chunking | Token 级接口、长编码与相关窗口重算,不保证指代必然消解 |
推荐的实施顺序(按投入产出比):
- 结构化切分 + 合理的 size/overlap —— 结构已经可靠时实现成本较低,先建立基线;
- 父子切分 —— 发现上下文缺失时尝试,同时检查父块 Token 和权限预算;
- 上下文增强 —— 有一定成本,但有公开数据支撑,值得投入;
- 命题化 / Late Chunking —— 视场景和语料规模评估。
5.5 常见错误¶
5.5.1 只答重叠切分¶
重叠只解决「不要在边界上截断」的问题,不能保证语义完整。
5.5.2 不区分「预防」和「补救」两个方向¶
把六七种方法平铺罗列,设计和调参时就很难判断自己是在改善切分,还是在补上下文。
5.5.3 把 Prompt Caching 说成一种压缩或补上下文的方法¶
Prompt Caching 的机制、与 KV Cache/记忆压缩的边界及生命周期限制见Agent 记忆与上下文压缩;本节只讨论其在上下文增强索引成本中的作用。
它是计算层优化,不改变被索引的内容。混淆这一点是典型的概念不清。
5.5.4 把上下文增强和命题化混为一谈¶
前者保留原文只加说明,后者重写原文。在溯源和精确匹配上差别很大。
5.5.5 忽略父子切分的去重¶
多个子片段命中同一父片段时不去重,会浪费大量 Prompt 预算。
5.5.6 认为越复杂的方法越好¶
复杂方法可能增加预处理和维护开销。应先建立调好基础参数的简单基线,再在相同目标查询集与上下文预算下比较质量和成本,而不是把复杂度当成收益的证据。
5.6 本章总结¶
- 两个方向:预防(切的时候别切错)与补救(切完把上下文补回来),二者可组合;
- 核心范式:用小片段检索,用大上下文生成;
- 父子切分解耦检索和阅读粒度,但增加存取、Token 与权限校验成本;
- 上下文增强生成背景说明并保留原文;官方实验报告的是特定配置的检索失败率相对下降,成本与收益不能直接外推;
- Prompt Caching 是计算层优化,与信息层的上下文改造是互补关系;
- 命题化改写可补全独立事实表示,但增加生成与事实核验成本,并须保留原文映射;
- Late Chunking 在编码后按块池化,需要 Token 级表示接口,并考虑整段编码和更新成本;
- 实施顺序:结构化切分 → 父子切分 → 上下文增强 → 更前沿方案。
参考资料¶
本章的参考资料、阅读建议与来源说明见集中参考资料章节。