跳转至

max_content_length 与 min_content_length 参数评估方法

这两个参数需要基于实际文档结构和下游模型约束来评估。下面以万用表文档为例,演示完整的评估过程。

第一步:分析文档的 section 长度分布

对万用表文档按标题切分后,得到 22 个 section,body 长度分布如下:

Text Only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
序号  标题                      body长度   title+body
─────────────────────────────────────────────────────
 1   (文档开头)                    0          12       <-- 极短
 2   # 使用说明书                 190        199       <-- 较短
 3   # 安全手册                   610        618
 4   # 安全标识                   381        389
 5   # 控制与端口                 151        160       <-- 较短
 6   # 功能符号指示                123        133       <-- 较短
 7   # 规格                     1351       1357       <-- 较长
 8   # 技术指标说明              1016       1026       <-- 较长
 9   # 电池安装                   182        190       <-- 较短
10   # 操作指导                     0          8       <-- 极短
11   # 数值保持按键HOLD             79         93       <-- 极短
12   # 背光灯键                   202        221
13   # 测量非接触交流电压           198        211       <-- 较短
14   # 直流电压测量                172        182       <-- 较短
15   # 交流电压测量                374        384
16   # 直流电流测量                460        470
17   # 电阻测量                   281        289
18   # 短路蜂鸣测试                140        150       <-- 较短
19   # 二极管测试                  128        137       <-- 较短
20   # 电池测试                   330        338
21   # 更换电池                   132        140       <-- 较短
22   # 更换保险丝                  215        224

统计摘要

Text Only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
总 section 数: 22
body 长度:
  最小值:   0
  最大值: 1351
  平均值:  305
  中位数:  198

长度区间分布:
  [0,    100)    3 个  
  [100,  200)    9 个       <-- 大量集中在这里
  [200,  500)    7 个 
  [500, 1000)    1 个  
  [1000, 2000)   2 个  
  [2000, +∞)     0 个

关键发现:大部分 section(12/22 = 55%)的 body 不到 200 字,只有 3 个 section 超过 500 字,最长的是"规格"表格(1351 字)。

第二步:确定 max_content_length(上限)

需要考虑的约束条件

Text Only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
约束 1: Embedding 模型的 Token 上限
    |
    |  BGE-M3: 8192 Token
    |  1 个中文字符 ≈ 1.5 Token
    |  理论上限 ≈ 8192 / 1.5 ≈ 5400 字符
    |  但不建议用满(太长的向量语义模糊)
    |
约束 2: LLM 上下文窗口的利用效率
    |
    |  通常注入 3~5 个 chunk 作为上下文
    |  如果每个 chunk 2000 字,5 个就是 10000 字
    |  会挤占用户对话和指令的空间
    |
约束 3: 检索精度与 chunk 大小的关系
    |
    |  chunk 越小 → 向量越聚焦 → 检索越精确
    |  chunk 越大 → 向量越模糊 → 检索越粗糙
    |
    v
综合权衡: max_content_length 通常设在 500 ~ 2000 之间

用实际数据验证不同 max 值的切分效果

Text Only
1
2
3
4
5
max=500    需要二次切分: 3/22 个 (安全手册、规格、技术指标)
max=800    需要二次切分: 2/22 个 (规格、技术指标)
max=1000   需要二次切分: 2/22 个 (规格、技术指标)
max=1500   需要二次切分: 0/22 个 (全部保持原样)
max=2000   需要二次切分: 0/22 个 (全部保持原样)

分析

  • max=500:会把 3 个 section 切碎,但大部分 section 本身就不到 500,切分比例适中
  • max=1000:只有"规格"和"技术指标"两个含表格的大 section 被切分,其余全部保留原始结构,是比较理想的平衡点
  • max=2000:没有任何 section 需要切分。看起来省事,但"规格"(1351 字)和"技术指标"(1016 字)的 chunk 体积偏大,向量语义会比较模糊

推荐值:对于这类技术文档,max_content_length = 800 ~ 1000 是比较理想的范围。

第三步:确定 min_content_length(下限)

需要考虑的因素

Text Only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
min 太小 (如 50):
    几乎不合并,保留大量碎片
    --> 很多 chunk 只有一两句话
    --> 向量语义稀薄,检索区分度差

min 太大 (如 800):
    疯狂合并,几乎所有 section 都被吞掉
    --> 丢失原有的标题边界
    --> 合并后的 chunk 包含多个不相关主题

min 合适:
    只合并确实太短的碎片
    --> 保留有意义的标题边界
    --> 每个 chunk 有足够的信息密度

用实际数据验证不同 min 值的合并效果

Text Only
1
2
3
4
5
6
min=50     可能被合并:  2/22 个  (只有空的 section)
min=100    可能被合并:  3/22 个  (极短的 3 个)
min=200    可能被合并: 12/22 个  (超过一半!)     <-- 临界点
min=300    可能被合并: 15/22 个  (合并过度)
min=500    可能被合并: 19/22 个  (几乎全部)
min=800    可能被合并: 20/22 个  (只剩 2 个)

分析

  • min=500(当前值):22 个 section 中有 19 个 body < 500,意味着绝大部分 section 都会尝试合并。最终产出的 chunk 数量很少,每个 chunk 包含了多个原始章节,主题可能混杂
  • min=200:12 个 section 会尝试合并,但这正好是那些确实太短的(100~200 字的操作步骤、警告提示)。像"交流电压测量"(374 字)、"电池测试"(330 字)这些内容完整的 section 会被保留
  • min=100:只合并 3 个极短的 section(body < 100 字),最大程度保留原始结构

推荐值min_content_length = 100 ~ 200。具体取决于你的文档特征:如果文档中有大量简短的操作步骤适合合并,取 200;如果每个标题下的内容都相对独立,取 100。

第四步:参数评估方法论总结

在实际项目中,可以用以下脚本快速评估参数:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import re

def analyze_sections(md_path: str):
    """分析文档的 section 长度分布,辅助参数设定"""
    with open(md_path, "r", encoding="utf-8") as f:
        content = f.read()

    heading_re = re.compile(r"^\s*(#{1,6})\s+(.+)")
    sections = []
    current_title = ""
    body_lines = []
    in_fence = False

    def flush():
        body = "\n".join(body_lines).strip()
        if current_title or body:
            sections.append({"title": current_title, "body_len": len(body)})

    for line in content.split("\n"):
        if line.strip().startswith("```") or line.strip().startswith("~~~"):
            in_fence = not in_fence
        match = heading_re.match(line) if not in_fence else None
        if match:
            flush()
            current_title = line.strip()
            body_lines = []
        else:
            body_lines.append(line)
    flush()

    lengths = [s['body_len'] for s in sections]
    print(f"section 数: {len(sections)}")
    print(f"body 长度: 最小={min(lengths)}, 最大={max(lengths)}, "
          f"平均={sum(lengths)//len(lengths)}, 中位数={sorted(lengths)[len(lengths)//2]}")

    # 建议
    p75 = sorted(lengths)[int(len(lengths) * 0.75)]
    p25 = sorted(lengths)[int(len(lengths) * 0.25)]
    print(f"\n建议 max_content_length: {max(p75 * 2, 800)} ~ {max(p75 * 3, 1500)}")
    print(f"建议 min_content_length: {max(p25, 100)} ~ {max(p25 * 2, 200)}")

评估流程

Text Only
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Step 1: 对样本文档运行分析脚本
    --> 得到 section 长度分布
    |
Step 2: 确定 max
    --> 参考 P75 的 2~3 倍
    --> 同时不超过 Embedding 模型 Token 上限的 1/3
    --> 确保 90% 以上的 section 无需切分
    |
Step 3: 确定 min
    --> 参考 P25
    --> 确保只合并确实太短的碎片(< 20% 的 section)
    --> 合并后不应丢失重要的标题边界
    |
Step 4: 用测试文档验证
    --> 查看 chunks.json 的实际效果
    --> 人工抽查 3~5 个 chunk 的语义完整性
    --> 微调参数

如何理解"确保 90% 以上的 section 无需切分"?

按标题切出来的 section 本身就是作者组织好的语义完整单元。二次切分是不得已而为之,会破坏原始语义结构。所以 max 的设定目标是:只对少数异常长的 section 动刀,绝大部分保持原样

Text Only
1
2
3
max=500    需要二次切分: 3/22 个  --> 86% 无需切分  (切得太多)
max=1000   需要二次切分: 2/22 个  --> 91% 无需切分  ✓ (只切异常长的)
max=1500   需要二次切分: 0/22 个  --> 100% 无需切分 (但大 chunk 语义模糊)

如果把 max 设得太小(比如 300),90% 的 section 都要被切碎,等于标题切分白做了,退化成了暴力按字数切。

如何理解"确保只合并确实太短的碎片(< 20% 的 section)"?

和 max 的 90% 是同一个思路,只不过方向相反。合并意味着把两个不同标题的内容混在一起,title 也会回退为 parent_title,丢失了原始标题信息。所以 min 的设定目标是:只对那些确实太短、单独存在没有意义的碎片做合并,其余保持原样

Text Only
1
2
3
min=100    可能被合并:  3/22 个  --> 14% 需要合并  ✓ (只合并极短碎片)
min=200    可能被合并: 12/22 个  --> 55% 需要合并    (临界,需要人工判断)
min=500    可能被合并: 19/22 个  --> 86% 需要合并    (过度!)

min=500 为例,86% 的 section 都会尝试合并,最终一个 chunk 可能包含了"电池安装"+"操作指导"+"数值保持按键"+"背光灯键"四个完全不同的主题。用户问"怎么开背光灯"时,检索到的 chunk 里掺杂了大量无关内容,影响 LLM 的回答质量。而 min=100 只会合并 body 为空的"# 操作指导"和只有一句话的"# 数值保持按键HOLD"这类真正的碎片。

万用表文档的最终推荐参数

参数 当前值 推荐值 理由
max_content_length 2000 1000 文档最长 section 为 1351 字,1000 可以切开它;P75=330,1000 ≈ P75 × 3
min_content_length 500 200 P25=137,200 ≈ P25 × 1.5;min=500 会导致 86% 的 section 被合并,过于激进