AI 知识库分块实战:按标题层级切文档,减少回答跑偏
来源:17golang原创
时间:2026-06-13 05:35:43 101浏览 收藏
很多 AI 知识库刚上线时看起来能回答问题,但一遇到跨段落说明、表格解释、版本差异,就容易出现“答了一半”“引用不到原文”“把两个章节混在一起”的情况。问题常常不在模型本身,而在文档入库前的分块策略。
本文用一个内部产品手册知识库做例子,拆解一套更稳的分块流程:先按标题层级切文档,再用小窗口补足上下文,最后把来源、章节、页码等元数据一起写入索引。这样做不追求复杂,但能明显减少回答跑偏。
摘要
本篇文章会完成三个目标:判断分块粒度是否合适、实现一个标题优先的分块函数、用问题回放修正常见命中失败。适合正在做 RAG、企业知识库、客服问答、技术文档问答的开发者阅读。
适合人群
- 已经能把文档写入向量库,但问答效果不稳定的同学。
- 需要处理 Markdown、产品手册、接口文档、制度文档的后端或 AI 应用开发者。
- 想把“能搜到”进一步优化成“搜得准、答得全”的工程团队。
目录
- 为什么不能只按固定字数切分
- 按标题层级切分:先保住语义边界
- 加重叠窗口和元数据:让检索结果可追踪
- 用问题回放修正分块策略
- 常见坑和总结
为什么不能只按固定字数切分
固定字数切分最大的优点是简单,但它容易把一个完整解释切断。例如“退款规则”的第一段解释条件,第二段解释例外,第三段给出处理流程。如果刚好按长度切开,检索时可能只命中条件,没有命中例外,最后回答就会偏。
更推荐先把文档看成有层级的内容树:一级标题是主题,二级标题是具体问题,段落和表格是证据。分块时先尊重这棵树,再用长度上限控制单块大小。

按标题层级切分:先保住语义边界
标题优先的分块逻辑可以概括为四步:
- 解析 Markdown 或 HTML 标题,记录当前章节路径。
- 把同一小节下的段落、列表、表格先合并成候选块。
- 候选块超过上限时,再按段落或句子拆成小块。
- 每个小块都带上标题路径,避免脱离上下文。
from dataclasses import dataclass
from typing import Iterable
@dataclass
class Chunk:
text: str
title_path: list[str]
source: str
page: int | None = None
def split_by_heading(lines: Iterable[str], source: str, max_chars: int = 900) -> list[Chunk]:
title_path: list[str] = []
buffer: list[str] = []
chunks: list[Chunk] = []
def flush() -> None:
if not buffer:
return
text = "\n".join(buffer).strip()
if not text:
buffer.clear()
return
while len(text) > max_chars:
head, text = text[:max_chars], text[max_chars:]
chunks.append(Chunk(text=head.strip(), title_path=title_path.copy(), source=source))
if text.strip():
chunks.append(Chunk(text=text.strip(), title_path=title_path.copy(), source=source))
buffer.clear()
for raw in lines:
line = raw.rstrip()
if line.startswith("#"):
flush()
level = len(line) - len(line.lstrip("#"))
title = line[level:].strip()
title_path = title_path[: level - 1] + [title]
else:
buffer.append(line)
flush()
return chunks
这段代码不是最终的生产版本,但它说明了关键思路:标题变化时先落盘当前候选块,每个块保留一份标题路径。后续拼上下文时,可以把标题路径一起放进提示词,让模型知道这段内容属于哪个主题。
加重叠窗口和元数据:让检索结果可追踪
分块不能太大,也不能太碎。太大会让检索结果带入噪声;太碎会丢上下文。一个常用折中是:单块控制在 500 到 1000 个中文字符,块与块之间保留 80 到 150 个字符的重叠窗口。实际数值要跟文档类型和模型上下文长度一起调。
元数据同样重要。至少建议保存:
- source:原始文件名或 URL,方便引用和排查。
- title_path:标题层级,例如“订单 / 退款 / 特殊场景”。
- page:PDF 页码或网页锚点,方便人工复核。
- chunk_index:同一文档内的块序号,用来找相邻上下文。
{
"text": "退款申请提交后,系统会先校验订单状态...",
"metadata": {
"source": "after-sale-guide.md",
"title_path": ["售后", "退款规则"],
"chunk_index": 12,
"page": null
}
}
有了这些元数据,回答里不仅能给出结论,还能返回“来自哪份文档、哪个章节”。当用户反馈答案不对时,开发者可以快速回看命中的原文,而不是只盯着模型输出猜原因。
用问题回放修正分块策略
分块策略是否合适,不能只看代码,要用真实问题回放。做法是准备 20 到 50 个高频问题,每个问题记录期望命中的章节。然后跑一遍检索,观察失败类型。

失败类型一:命中了同名章节,但不是同一产品
解决方式是把产品线、版本号、适用范围加入元数据,并在检索过滤条件里使用。例如客服知识库里可能有“企业版退款”和“个人版退款”,标题相似但规则不同。
失败类型二:命中了条件,没命中例外
这通常说明块太碎,或者重叠窗口太短。可以把相邻块一起回填给模型,或把分块上限从 500 提到 800,再观察命中结果。
失败类型三:命中了整页长文,答案被噪声干扰
这说明块太大,需要按二级或三级标题进一步拆分。对于表格、列表这类结构化内容,尽量不要拆断表头和关键行。
常见坑
- 只看向量相似度,不看来源。 知识库不是搜索框,回答必须能回到原文。
- 所有文档用同一粒度。 FAQ、接口文档、制度手册适合的分块大小并不一样。
- 忽略标题路径。 没有标题路径时,一个短段落很容易失去语义位置。
- 把图片文字直接丢掉。 如果重要流程藏在截图里,需要先做 OCR 或人工补录。
延伸阅读
如果要接入具体向量模型或分块库,建议以官方文档为准:OpenAI Embeddings 文档介绍了把文本转成向量表示的基础概念,LangChain Text Splitters 文档提供了常见文本切分器的设计参考。
总结
AI 知识库回答跑偏,很多时候不是“模型不够聪明”,而是入库时没有保住语义边界。更稳的做法是:按标题层级切分,控制块大小,保留重叠窗口和元数据,再用真实问题回放不断修正。这样构建出来的知识库,既更容易命中关键内容,也更容易解释答案来源。
-
284 收藏
-
387 收藏
-
328 收藏
-
426 收藏
-
147 收藏
-
233 收藏
-
174 收藏
-
339 收藏
-
260 收藏
-
438 收藏
-
152 收藏
-
232 收藏
-
280 收藏
-
152 收藏
-
102 收藏
-
247 收藏
-
306 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习