首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我的 AI 知识库开源!RAG命中率从60%到78% 经验分享

我的 AI 知识库开源!RAG命中率从60%到78% 经验分享

作者头像
码哥字节
发布2026-07-11 08:21:36
发布2026-07-11 08:21:36
170
举报
文章被收录于专栏:后端架构师后端架构师

我花了1个月搭了一套自建 RAG 知识库,240 篇技术文章灌进去,向量库跑起来一切正常。

然后我用"Redis Cluster 怎么扩容"搜了一下。

返回的结果前三篇分别是:《Redis 为什么快》、《Go 语言的并发模型》、《我们团队的代码规范》。每一篇回答了我的问题。

问题不在 embedding 模型,不在向量数据库。问题出在一个大多数教程一笔带过、很多工程师压根没认真对待的环节——分块策略

同样的向量数据库、同样的 embedding 模型,分块策略不同,检索命中率能差 20% 以上。这不是夸大,是我实打实测出来的。

三种分块策略核心差异速览
三种分块策略核心差异速览

三种分块策略核心差异速览

先给答案:三种策略怎么选

不想看完整分析的话,这张表够用了:

你的内容类型

推荐策略

预期命中率

一句话理由

技术教程、专栏、架构文档

策略 A:结构感知分块

~78%

保留标题层级,chunk 自带上下文

设计模式、最佳实践、精炼总结

策略 B:模板/段落分块

~72%

轻量快速,适合内容本身已结构化

FAQ、社区问答、踩坑笔记

策略 C:Q&A 对分块

规划中

保证问题和答案不被拆散

如果你用的还是固定 500 字符硬切,命中率大概率在 60% 左右徘徊——这跟没做 RAG 区别不大。

上面这张表看起来简洁,但背后有一个很多人不知道的事实:策略 A 和策略 B 本质上是在「内容入库时」就已经决定了检索质量的上下限。chunk 边界一旦写入索引,之后所有下游操作——换 embedding 模型、加 reranker、调检索参数——都只能在这组已切好的候选上重新排序。如果你的 chunk 本身就是残缺的(一句话被腰斩、代码块被截断、Q&A 被拆散),后面做再多优化也是白搭。

换句话说:分块定义了「检索对象是什么」,embedding 只决定「怎么找到它」。想通这一层,你就知道为什么分块策略值得花时间细调。

如果你对自己的内容类型拿不准,直接看后面的详细对比。如果内容结构比较复杂——比如既有一堆 Markdown 教程又有一批 FAQ 问答——可以考虑进阶方案:Small-to-Big(父子文档)。用小块做精准召回(chunk_size=300-500),召回后自动扩展到父级大块喂给 LLM。社区 benchmark 显示这套方案的 recall 能达到 0.86,是目前生产环境实测的最优策略之一。

为什么分块策略被低估了

大部分 RAG 教程的流程是:装向量库 → 选 embedding 模型 → 调检索参数 → 上线。分块这一步,教程里通常就一行 chunk_size=500,随手一设就过了。

但实际上,分块决定了 embedding 向量的信息密度

一个 chunk 太大(比如 2000 字符),包含多个不相关的技术概念,embedding 向量就会变成一个"平均脸"——什么都像,什么都检索不准。一个 chunk 太小(比如 200 字符),信息被切碎,检索时会召回一堆零散片段,LLM 得自己拼图。

说白了,分块策略在 RAG 系统里的地位相当于数据库的索引设计。索引没建对,换什么存储引擎都是白搭。

换一个角度想:你写技术文章的时候会先搭标题结构,读者阅读时会根据标题找自己关心的章节。那为什么给向量库灌内容的时候,要把标题结构扔掉、当成一坨平文本切?

这就是结构感知分块的核心思路。

还有一个反直觉的数据点:Adobe 在 2025 年做了一个大规模实验,在他们内部数千工程师使用的技术支持系统上对比了多种分块策略。结果让很多人意外——400-token 的固定分块(加 20% overlap)在某些场景下反而击败了语义分块和分层分块,准确率提升了 20%。这说明一个道理:分块策略没有银弹,内容类型决定一切。如果文档本身格式规整、段落清晰,简单的段落切分可能比复杂的语义分块更有效。

策略 A:结构感知分块(我的主力策略)

原理:按 Markdown 的标题层级(H2 → H3 → H4)逐级切分,保证每个 chunk 是一个完整的"小节",而不是一段被腰斩的文字。

关键点有两条:

  1. 保留父级标题作为上下文前缀。比如一篇讲 Redis Cluster 的文章,H3 小节"3.4.1 Redis Cluster 是什么"下面的内容在切分时,chunk 的 metadata 会带上 Redis Cluster 集群 > 3.4.1 Redis Cluster 是什么。检索时不仅能匹配内容本身,还能利用标题路径做语义锚定。
  2. **max_size 不是一刀切,而是"软上限"**。我的设置是 800 字符——大部分 H3 小节本身就在 500-800 字之间,少数超长的会自动在 H4 级别进一步拆分,但绝对不会在一段代码或一个完整概念中间断开。

来看一下实现代码:

代码语言:javascript
复制
from langchain.text_splitter import MarkdownHeaderTextSplitter

headers_to_split_on = [
    ("##", "h2"),
    ("###", "h3"),
    ("####", "h4"),
]

splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=headers_to_split_on,
    strip_headers=False,  # 保留标题文本在 chunk 中
)

# 实际使用:读取 markdown 文件后切分
with open("redis-cluster-deep-dive.md", "r") as f:
    markdown_content = f.read()

chunks = splitter.split_text(markdown_content)

for chunk in chunks:
    # metadata 中保留了完整的标题路径
    # 例如:{'h2': 'Redis Cluster 集群', 'h3': '3.4.1 Redis Cluster 是什么'}
    print(f"标题路径: {chunk.metadata}")
    print(f"内容长度: {len(chunk.page_content)} 字符")
    print(f"内容预览: {chunk.page_content[:100]}...")
    print("---")

但这只是第一步。MarkdownHeaderTextSplitter 按标题切完后,如果某个 H3 下的内容有 3000 多字怎么办?单个 chunk 直接塞进向量库,embedding 质量会严重下降。所以需要加一层多级回退:

代码语言:javascript
复制
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 多级回退拆分器:优先按 \n\n(段落)切,不行再按 \n(句子),最后按字符
fallback_splitter = RecursiveCharacterTextSplitter(
    chunk_size=800,
    chunk_overlap=80,
    separators=["\n\n", "\n", "。", ".", " ", ""],
    length_function=len,
)

# 对每个 header split 的结果再做尺寸检查
final_chunks = []
for chunk in header_chunks:
    if len(chunk.page_content) <= 800:
        # 内容在阈值内,直接保留
        final_chunks.append(chunk)
    else:
        # 超过阈值,用回退拆分器继续切,但保留原始 metadata
        sub_chunks = fallback_splitter.split_text(chunk.page_content)
        for i, sub in enumerate(sub_chunks):
            sub_chunk = type(chunk)(
                page_content=sub,
                metadata={**chunk.metadata, "sub_chunk_index": i}
            )
            final_chunks.append(sub_chunk)

print(f"最终生成 {len(final_chunks)} 个 chunk")

这套策略在我的 240 篇文章上实测下来,命中率稳定在 78% 左右。而且有个额外的好处:每个 chunk 自带标题路径,LLM 在生成回答时能明确知道信息来自哪篇文章的哪个章节,幻觉明显减少。

适用内容:技术教程、专栏文章、架构文档——任何有清晰标题层级的长文。

不适用:自然语言为主的随笔、没有标题结构的纯文本流水账。这种情况建议直接用策略 B。

策略 B:模板/段落分块

原理:优先按 ## 标题切,如果单个 ## 下内容超过阈值,则按段落边界切分。本质上是一个"先尊重结构,再尊重语义边界"的思路。

策略 B 比策略 A 简单,但比固定字符硬切聪明得多。它的 max_size 我设成了 600 字符——比策略 A 的 800 更小,因为适用内容本身信息密度就高,不需要那么大的窗口。

实现代码:

代码语言:javascript
复制
from langchain.text_splitter import RecursiveCharacterTextSplitter

# 针对已精炼的结构化内容
splitter = RecursiveCharacterTextSplitter(
    chunk_size=600,
    chunk_overlap=60,
    separators=["## ", "\n\n", "\n", "。", " "],
    length_function=len,
)

chunks = splitter.create_documents(
    texts=[article_content],
    metadatas=[{"source": "best-practices-microservices-2025.md"}]
)

对比策略 A 和策略 B 的差异,最核心的一点是:策略 B 不会主动构建标题层级路径。它把 ## 当作一个分隔标记,但如果内容本身没有 ## 标题(或者标题层级很浅),它会直接退化到按段落切分。

实测命中率在 72% 左右,比策略 A 低约 6 个百分点。这 6 个点的差距主要来自"缺乏标题上下文"——当用户搜索一个特定的技术概念时,策略 B 的 chunk 里可能没有足够的层次信息帮助 embedding 模型精准定位。

但策略 B 的优势在于快且省。不需要解析 Markdown 标题树,不需要构建 metadata 路径,对内容格式的要求也低得多。如果你的知识库内容本身就是短小精悍的结构化总结(比如设计模式清单、API 最佳实践卡片),策略 B 完全够用。

适用内容:日常摘要、设计模式文档、最佳实践列表——内容本身已经很精炼,不需要额外的结构解析。

策略 C:Q&A 对分块(规划中)

这部分是我踩过的一个坑,还在填。

我的知识库里有一部分内容来自社区问答——读者问"Redis 哨兵模式和 Cluster 模式怎么选",我写了一篇详细的回复。这篇回复里既有问题又有答案,如果按标题切分或段落切分,问题和答案大概率会被拆到不同的 chunk 里。

后果就是:用户搜"哨兵模式和 Cluster 怎么选"时,向量库可能只召回答案的那一半 chunk——语义上匹配,但缺少了问题上下文,LLM 不知道这段话在回答什么。

策略 C 的思路很简单:一个问答对 = 一个 chunk。不按字数切,不按标题切,就按 Q&A 的边界切。

代码语言:javascript
复制
# 策略 C 示意代码(规划中,非生产级)
import re

def split_qa_pairs(text: str) -> list[dict]:
    """按 Q&A 模式切分文本"""
    # 匹配 Q: 或 问: 开头的段落
    pattern = r'(?:Q[::]|问[::]|#### Q\d+)(.*?)(?=Q[::]|问[::]|#### Q\d+|$)'
    matches = re.findall(pattern, text, re.DOTALL)
    
    qa_pairs = []
    for match in matches:
        parts = re.split(r'(?:A[::]|答[::])', match, maxsplit=1)
        if len(parts) == 2:
            qa_pairs.append({
                "question": parts[0].strip(),
                "answer": parts[1].strip(),
            })
    
    return qa_pairs

这个策略在 v3.0 里才规划落地,目前只覆盖了我知识库中约 5% 的内容(那批 FAQ 类文章)。但效果已经能看出来——Q&A 类内容的检索命中率提升非常明显,因为问题本身就是一个天然的检索 query,chunk 里同时包含问题和答案,embedding 向量能更好地捕捉这种"提问-回答"语义模式。

适用内容:FAQ 页面、社区问答、踩坑笔记——所有以"问题-回答"形式组织的内容。

我的迭代过程:三个版本的教训

说实话,我在分块策略上踩的坑,比在 embedding 模型选型上踩的还多。

v1.0:固定 500 字符硬切

这是绝大多数教程推荐的"最佳实践"——设个 chunk_size=500,overlap 给个 50,跑起来就行。

结果惨不忍睹。一段讲 Go 并发模型的中文段落,500 字符可能刚好卡在"goroutine 的调度基于 GMP 模型,G 代表"——后面就没了。G 代表什么?读者想知道,向量库也不知道。

更致命的是代码块被拦腰截断。一个完整的设计模式示例,前半段是问题描述,后半段是实现代码,被切到两个 chunk 里之后,两个 chunk 都变成了语义残片。

命中率 60% 出头——跟随机抽差不多。

v2.0:按 H2 标题切

意识到问题之后,我改成了按 H2 标题切分——每个大章节一个 chunk。

效果好了一些,但马上遇到新问题:有些 H2 下面的内容多达 3000+ 字。比如我有一篇讲 Redis 持久化的文章,H2"RDB 快照原理"下面光代码注释就有 1500 字。这么大一个 chunk 塞进向量库,embedding 向量的语义焦点完全散了。

v3.0:结构感知 + 多级回退

v3.0 就是现在这套方案——H2 → H3 → H4 → 段落 → 句子,逐级回退。每个 chunk 自带标题上下文,尺寸控制在 800 字符左右。

这套方案上线后,命中率稳定在 75%+,少数优化过的专题能达到 80% 以上。

结构感知分块的多级回退流程
结构感知分块的多级回退流程

结构感知分块的多级回退流程

四个核心原则(比参数更重要)

扒完三种策略,我得出的结论是:参数(chunk_size、overlap)只是表象,核心原则决定了你的分块策略能不能用

原则一:保持语义完整性

宁可 chunk 稍大,不要打断一个完整的技术概念。切在段落中间、切在代码块中间、切在"首先……其次……"的并列结构中间——这些都是致命伤。一段被腰斩的技术解释,embedding 质量远不如稍长但完整的段落。

原则二:保留上下文锚点

每个 chunk 要知道自己属于哪篇文章、哪个章节。这不是为了好看——当 LLM 同时拿到 5 个相关 chunk 时需要回答"这 5 段来自 3 篇不同的文章,它们之间是什么关系"时,上下文锚点是 LLM 拼接信息的唯一线索。

原则三:内容类型决定分块策略

不要一刀切。技术教程和 FAQ 问答天然需要不同的分块方式。我现在的做法是:在内容入库时就打上类型标签,分块器根据标签自动选择策略。

原则四:max_size 不是越短越好,也不是越长越好

太短丢失上下文(500 字以下的 chunk,embedding 质量下降明显),太长检索精度下降(超过 1200 字的 chunk,容易出现"什么都沾边但什么都不准"的问题)。800 字符是我反复实验后找到的甜点——当然,这个数字对英文内容可能不同,英文的分块甜点通常在 256-512 tokens 之间。

综合评分

评估维度

策略 A:结构感知

策略 B:模板段落

策略 C:Q&A 对

检索命中率

★★★★★ (78%)

★★★★ (72%)

规划中

实现复杂度

★★★ (需解析标题树)

★ (仅需配置拆分器)

★★

内容格式要求

必须有 Markdown 标题

有段落结构即可

必须有 Q&A 格式

长文处理能力

★★★★★ (多级回退)

★★★ (超大段落会退化)

★★ (不适用长文)

LLM 回答质量

★★★★★ (自带上下文)

★★★ (上下文依赖检索)

★★★★ (Q&A 语义完整)

适用范围

80% 的常见内容

15%(精炼内容)

5%(FAQ/问答)

我的实际数据:240 篇文章产生了 5877 个 chunk,平均每篇约 24 个 chunk。策略 A 覆盖了其中 80% 的内容,策略 B 覆盖 15%,策略 C 只占了 5%(还在扩展中)。

有两点值得单独拉出来说——

第一,语义分块为什么不在我的策略列表里? 语义分块(按相邻句子 embedding 相似度的骤降点切分)听起来很高级,但社区真实 benchmark 给出了一个扎心的数据:它在某些测试集上以 50 倍的计算成本,只换来了和递归切分几乎持平的 recall(0.72 vs 0.74)。花 50 倍成本换不到 2 个点的提升,这性价比不适合大多数生产环境。除非你的文档是纯自然语言长文(法律合同、科研论文),语义分块才有明显优势——因为这类文档没有标题结构可依赖。

第二,overlap 可能被高估了。 几乎所有教程都在推荐设 10-20% 的 overlap,但我最近读到了一篇 2025 年的系统分析论文(arXiv:2601.14123),结论是:overlap 在 Natural Questions 这类数据集上没有显著收益,反而让索引体积膨胀了约 25%。我的经验是 10% 的 overlap 有个保底作用——防止关键句恰好落在边界上被截断——但不要无脑往上加。overlap 不是越大越好。

常见问题

Q:chunk_overlap 设多少合适?

先说一个反常识的结论:有学术分析(arXiv 2025)表明 10-20% 的 overlap 在某些 benchmark 上没有显著收益,反而让索引体积膨胀了 25%。我的折中做法是 chunk_size 的 10%——chunk_size=800 时 overlap 设 80。这更多是一个"保底"策略,防止关键句恰好落在边界上被截断,但不用无脑往上加。如果你的文档本身就是按标题/段落组织的,overlap 甚至可以降到 5%。

Q:什么叫 Context Cliff?它和分块策略有什么关系?

Context Cliff 是一个 2025 年被反复验证的现象:LLM 在上下文超过约 2500-3000 tokens 后,对中间位置信息的提取能力会显著下降。这和分块策略直接相关——如果你为了"多给上下文"把 chunk 设得过大(>1500 tokens),或者一次召回太多 chunk 全塞给 LLM,反而会触发这个退化效应。正确的思路是:小块精准召回 + 只把最相关的 3-5 个 chunk 喂给 LLM。这也是为什么 Small-to-Big(父子文档)方案值得关注——它在检索阶段用小 chunk 保证精度,生成阶段才扩展到大 chunk,避开了 Context Cliff 的风险。

Q:中文和英文的分块策略有区别吗?

有。中文字符本身的信息密度比英文 token 低——一个中文字符通常对应 1.5-2 个 token,一个英文单词也是 1-2 个 token。所以中文的 chunk_size 可以设大一些(我用的 800 字符),英文通常在 256-512 tokens 之间。另外,中文分词对句子边界识别更敏感,分块器中的 separators 建议加入中文标点()。

Q:我的文章很短,每篇只有几百字,还需要分块吗?

如果单篇文章本身不超过 chunk_size 上限,可以整篇作为一个 chunk 入库,不需要额外拆分。但建议在 metadata 中保留文章标题,方便后续检索时做来源标注。

Q:三种策略能混用吗?

能,而且推荐混用。我的做法是在内容入库时,先判断文章类型标签,然后路由到对应的分块策略。比如一篇 Markdown 技术教程走策略 A,一篇设计模式清单走策略 B,一篇 FAQ 走策略 C。不要试图用一套参数覆盖所有内容——这是最常见的错误。

Q:怎么客观评估分块策略的效果?

我用的方法是用一组标准测试问题(覆盖不同内容类型),对每种策略分别评估 Top-3 检索命中率。注意要用真实用户会问的问题,而不是你自己写的问题——后者往往和你的 chunk 语义分布高度匹配,测出来的命中率不准。

“💬 你现在用的什么分块策略?

  • A. 固定 size 硬切,设个 chunk_size 就完了
  • B. 按标题/章节切分,保留结构信息
  • C. 上了 LangChain/LlamaIndex 的默认配置,没细调
  • D. 还没在生产环境真正用过 RAG

评论区说说,码哥猜选 A 和 C 的最多 😂

我的建议

分块策略是 RAG 系统里最容易被低估、同时也是性价比最高的优化点。换 embedding 模型可能需要重新入库、重新调参,改向量数据库可能需要数据迁移,但优化分块策略只需要改几行 Python 代码——效果立竿见影。

如果你现在用的是固定字符硬切,建议优先迁移到策略 A(结构感知分块)。如果你的内容本身没有 Markdown 标题结构,策略 B(段落切分)是更好的起点。如果你是做 FAQ/客服知识库的,策略 C 的 Q&A 对思路值得一试。

说到底,分块策略的本质是让每一段文字在脱离原文后仍然能被理解。技术文章的标题结构是为人类阅读设计的,而分块策略的职责就是把这套结构翻译成向量库能理解的语言。

这篇文章里提到的方法和代码,我自己在生产环境的知识库上跑了几个月。实际效果比我预想的要好——在不需要换 embedding 模型、不需要加 reranker 的情况下,只靠优化分块策略就把命中率从 60% 提到了 78%。如果你的知识库还在为检索精度头疼,不妨先检查一下分块策略——大概率问题就出在这。

写完这篇,码哥自己又过了一遍自己的分块逻辑,发现还有几篇老文章的 chunk 是用 v1.0 策略切的——该重跑了。

下一篇打算拆 embedding 模型选型,bge-large、text2vec、m3e 这些到底哪个更适合中文技术内容,实测数据说话。

感兴趣的关注一下,不然算法不一定推给你。如果不星标,算法就不会主动推给你——错过几篇没关系,但有些内容正好是你当时需要的,那就不划算了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码哥跳动 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先给答案:三种策略怎么选
  • 为什么分块策略被低估了
  • 策略 A:结构感知分块(我的主力策略)
  • 策略 B:模板/段落分块
  • 策略 C:Q&A 对分块(规划中)
  • 我的迭代过程:三个版本的教训
  • 四个核心原则(比参数更重要)
  • 综合评分
  • 常见问题
  • 我的建议
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档