首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >#WorkBuddy# 万字长文档别整篇丢给 AI:目录 → 分块 → 逐块摘要 → 覆盖校验,一条零依赖脚本跑完(macOS 实测)

#WorkBuddy# 万字长文档别整篇丢给 AI:目录 → 分块 → 逐块摘要 → 覆盖校验,一条零依赖脚本跑完(macOS 实测)

原创
作者头像
用户12406184
发布于 2026-11-01 00:18:10
发布于 2026-11-01 00:18:10
1080
举报

一、你会遇到的场景

手上有一份一万五千字的产品规范,想让 AI 干三件事:出一版目录、每章写一段摘要、把散在正文里的参数表汇总成一张对比表。

最常见做法是整篇复制进去,然后祈祷。结果通常是三种:要么超出上下文被截断,后半篇直接消失;要么模型"读了但没读全",摘要里漏掉两章你事后才发现;要么它很努力地把表格重新排版了一遍,但漏了三张。

问题不在模型,在于你把"喂进去"这件事当成了一次性动作。长文档要的是流水线:先看清结构,再切成能消化的块,逐块处理,最后用机器校验有没有漏——最后这一步最容易被跳过,也最要命。

二、结论先行

长文档处理的正确顺序是:目录 → 分块 → 逐块摘要 → 覆盖校验。 其中"覆盖校验"是唯一能保证"AI 没漏章节"的手段,靠人眼读摘要发现不了漏。

适用边界:本文全部实测于 macOS + Python 3.14.3,脚本零第三方依赖、Python 3.9+ 可跑。只处理 Markdown;docx / PDF 需先转成 md。它做的是结构层面的切分与校验,不做语义相似度检索,也不替代你判断摘要写得对不对。

三、完整操作步骤

步骤 1|准备环境与示例文档

代码语言:bash
复制
# 环境:macOS,Python 3.9+,零第三方依赖
mkdir -p longdoc-demo && cd longdoc-demo
curl -O https://example.com/longdoc_kit.py   # 换成你自己的存放路径
python3 make_longdoc_demo.py                 # 生成一份虚构示例文档

make_longdoc_demo.py 的作用很朴素:造一份 15233 字符 / 429 行 / 59 个标题的虚构规范文档,里面故意埋了 8 类坑(代码块里的 # 注释、Setext 标题、跳级编号、超长单行段落、frontmatter 等)。用固定随机种子,任何人跑出来的文件字节完全一致,方便复现我的实测数字。

所有示例内容均为虚构,与任何真实系统、真实业务数据无关。

步骤 2|先提目录,别急着切

代码语言:bash
复制
python3 longdoc_kit.py outline longdoc-demo/spec_v23.md --stats
echo $?    # 有跳级返回 1

真实输出(本机,节选):

代码语言:plaintext
复制
文件: longdoc-demo/spec_v23.md
总行数: 429  字符数: 15233
识别标题: 59 个(朴素 ^# 扫描: 77 个,含围栏内误判)
层级分布: H1=1  H2=13  H3=30  H4=15
------------------------------------------------------------
H1  L   7  智能办公助手部署与运维规范(示例文档)
  H2  L  12  1. 部署前检查
    H3  L  16  1.1 环境依赖
    ...
------------------------------------------------------------
[WARN] 检测到 1 处层级跳级,目录可能编号有误:
       H2 → H4  8.0 跳级小节(示例:H2 下直接 H4)

这一步的价值在于"先发现文档自己的问题"。跳级、编号断裂、章节重名,这些在原文里看不出来,一旦切成目录就一目了然。目录同时也是后面分块的锚点。

步骤 3|按标题分块,而不是按字数硬切

代码语言:bash
复制
python3 longdoc_kit.py chunk longdoc-demo/spec_v23.md --max-chars 3000 --overlap 200
python3 longdoc_kit.py hardcut longdoc-demo/spec_v23.md --max-chars 3000  # 对照组

按标题切(14 块)与纯按字符硬切(6 块)的差别,不在块数,在切坏了什么:

代码语言:plaintext
复制
--- 硬切 max_chars=3000 ---
块数: 6
被切断的代码块: 2 处(该块内围栏数为奇数,AI 会看到半个代码块)
被切断的表格:   1 处(表头与数据行分家)

代码块被切成一半、表格表头和数据行分家,AI 拿到的就是残缺信息,它会非常自信地基于残缺内容作答。按标题切永远不会切在围栏中间,因为标题本身就是语义边界。

步骤 4|生成逐块的"摘要输入包"

代码语言:bash
复制
python3 longdoc_kit.py pack longdoc-demo/spec_v23.md --max-chars 3000 --overlap 150 --out packs/
# 输出:packs/chunk-01.md … chunk-14.md

每个块文件自带三样东西:标题路径(这个块属于哪一章)、字符数与行区间、以及可选的"上文衔接"片段。逐块喂给 AI 时,指令要统一,别让模型自己发挥:

代码语言:plaintext
复制
只摘要下面这一个块,不要臆测块外内容。
输出三行:① 本块讲了什么(≤60 字)② 关键参数或结论(有就列,没有写"无")
③ 本块是否包含表格(是/否,是则原样保留表头)
块内容:
<粘贴 chunk-XX.md>

最后把所有"①"按顺序拼起来就是全文摘要,把"③=是"的块单独拎出来就是对比表的素材。摘要的合并顺序由脚本保证,不靠模型记忆。

步骤 5|覆盖校验:唯一能证明"没漏"的一步

代码语言:bash
复制
python3 longdoc_kit.py verify longdoc-demo/spec_v23.md --max-chars 3000
echo $?    # 0=通过,1=有遗漏或重复,可直接挂流水线做门禁
代码语言:plaintext
复制
覆盖校验: 块数=14  应覆盖正文行=264  实际覆盖=264  耗时=0.1 ms
覆盖率: 100.0%
[OK] 无遗漏
[OK] 无重复覆盖

这里校验的不是"摘要质量",而是原文的每一行正文是否恰好落在一个块里。漏行 = AI 根本没看到那部分,摘要必然缺失;重复行 = 同一段内容被算了两次。这两个问题用眼睛看摘要都发现不了。

步骤 6|对比表素材先清点再汇总

代码语言:bash
复制
python3 longdoc_kit.py tables longdoc-demo/spec_v23.md
代码语言:plaintext
复制
共 11 张表
  #            行区间   列   数据行  表头
  1         L42-48   3     5  | # | 操作 | 示例阈值 |
  ...
 11       L423-427   3     3  | # | 版本 | 说明 |

先知道有 11 张表、每张几列,再让 AI 汇总成一张对比表,你就能用"表数对不对"来验货。让 AI 从长文里"找表再合并",等于把计数和改写两件事同时交出去,出错率翻倍。

四、实测数据

全部为本机真实执行,示例文档 15233 字符 / 429 行,三次取 min/avg/max:

max_chars

块数

最大块字符

耗时 min/avg/max (ms)

覆盖率

500

44

497

0.21 / 0.23 / 0.24

100.0%

800

30

800

0.21 / 0.21 / 0.21

100.0%

1500

17

1475

0.05 / 0.05 / 0.05

100.0%

3000

14

2500

0.05 / 0.05 / 0.05

100.0%

6000

14

2500

0.05 / 0.05 / 0.05

100.0%

对照组(同一份文档,纯按字符硬切):

max_chars

块数

切断的代码块

切断的表格

800

20

0

2

1500

13

0

1

3000

6

2

1

目录识别对照:朴素 ^#{1,6}\s 扫描命中 77 条,真标题只有 59 条——20 条是代码块围栏里的 # 注释被误判,另有 2 条 Setext 标题(=== / --- 下划线式)被漏掉。

分块耗时在 0.05–0.24 ms 量级,相对于一次模型调用可以忽略,所以分块策略应该按"模型能消化多少"来定,不用考虑脚本性能。

  • 「整篇一次喂」与「分块 14 次喂」的积分消耗差:【待本人实测补充】
  • 不同模型上下文窗口下 max_chars 的最佳取值:【待本人实测补充】
  • 10 万字以上中文文档的真实耗时:【待本人实测补充】

五、踩坑与边界

踩坑(全是真跑出来的,不是想象):

  1. 换行符忘了算。按 len(line) 累加,块实际比上限大(行数−1)个字节——实测 800 的上限切出 830 字符的块。改成 len(line) + 1。
  2. 句子切分的片段必须各自成块。第一版把一行超长段落切成的 3 个片段全塞进同一个块,结果那个块仍是 2913 字符,"假分块"。
  3. 纯空白块会白占一次调用。章节末尾的孤立空行会形成 0 字符块,必须在出块前过滤。
  4. 代码围栏里的 # 是最强噪音源,一份文档贡献 20 条假标题。先标记围栏状态再找标题,顺序不能反。
  5. Setext 标题(文字 + ===)朴素扫描必漏。同时要注意 frontmatter 结尾的 --- 不能被当成 Setext 下划线——必须先圈出 frontmatter 行再找标题。
  6. 覆盖校验必须按行号做,不能按文本比对。句子级切分后文本拼不回原文,按文本比对会全是假失败。
  7. overlap 只进"上下文展示",不进覆盖率统计,否则每块都重复计费且校验必报重复。
  8. GBK 编码的老文档用 errors="replace" 会变成一堆问号,先 file -I 确认编码再读。

边界(这些情况别用):

  • 只支持 Markdown。docx / PDF 请先转 md,扫描件 PDF 不适用。
  • 它做的是结构切分,不做语义检索。跨章节的横向对比类任务("把所有章节提到的阈值汇总")需要额外策略,不是分块能解决的。
  • --depth 决定切多细:结构清晰的文档用 H2(14 块),单章特别长的改用 H3 边界(实测同文档、同样 3000 上限会切出 44 块)。
  • 脚本不判断摘要对错。它只能保证"喂全了、没重复",不能保证"理解对了"。

六、小结

长文档的问题从来不是"模型读不完",而是你没法证明它读全了。目录、分块、摘要这三步大家都会做,真正拉开差距的是第四步——用脚本按行校验覆盖率,把"没漏"从感觉变成可验证的事实。整套流程零依赖、0.05 ms 级别,成本远低于一次重试。

附录 A|核心三段代码

完整脚本见 assets/longdoc_kit.py(含 outline / chunk / pack / verify / hardcut / tables / selftest 七个子命令、内置自检 3/3 通过)。下面是三个最关键的函数:

代码语言:python
复制
# ① 先标围栏与 frontmatter,再找标题(顺序反了就全错)
ATX_RE = re.compile(r"^(#{1,6})[ \t]+(\S.*?)[ \t]*#*\s*$")
FENCE_RE = re.compile(r"^[ \t]{0,3}(```+|~~~+)")

def find_headings(doc):
    out = []
    for i, ln in enumerate(doc.lines):
        if doc.in_fence[i] or doc.is_frontmatter[i] or doc.is_table[i]:
            continue                      # 围栏内、frontmatter、表格行一律不看
        m = ATX_RE.match(ln)
        if m:
            out.append((len(m.group(1)), i, m.group(2).strip())); continue
        if i + 1 < doc.n and doc.lines[i].strip():
            if re.match(r"^=+[ \t]*$", doc.lines[i + 1]):
                out.append((1, i, doc.lines[i].strip()))     # Setext H1
            elif re.match(r"^-{3,}[ \t]*$", doc.lines[i + 1]):
                out.append((2, i, doc.lines[i].strip()))     # Setext H2
    return out

# ② 分块主循环:+1 是换行符;超长行切成片段且各自成块
for i in seg:
    ln = doc.lines[i]
    if cur and cur_len + len(ln) + 1 > max_chars:
        flush()
    if split_long and len(ln) > max_chars:
        flush()
        for piece in split_sentences(ln, max_chars):
            raw.append((path, [(i, piece)], {i}))   # 片段单独成块
        continue
    cur.append((i, ln)); cur_len += len(ln) + 1

# ③ 覆盖校验:按行号,不按文本
expect = set(doc.content_line_idxs())      # 非空、非 frontmatter 的行
covered = {}
for c in chunks:
    for i in set(c.lines):
        covered[i] = covered.get(i, 0) + 1
missing = sorted(expect - set(covered))    # 漏
dup = {i: n for i, n in covered.items()
       if n > 1 and i not in partial_lines}   # 重复(排除句子切分的行)

快速自检:

代码语言:bash
复制
python3 longdoc_kit.py selftest
# [PASS] 目录提取 / 对照组 / 覆盖校验 —— 全部通过(3 项)

附录 B|发布前必须补的真实截图

发布信息

  • 标签:WorkBuddy、AI 办公、效率工具
  • 来源:原创
  • 专栏:AI 与大数据
  • 板块:实用文章
  • 字数检查:≥800 ✅(正文 2083 中文字)
  • 红线检查:示例数据全部虚构,无真实业务数据、无真实账户与资产数据、无内部信息 ✅

关联阅读:本文处理的是单篇长文档;如果你要管理的是整个笔记库(几百篇 md 的索引与断链体检),思路不同,那是另一篇的内容。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

目录
  • 一、你会遇到的场景
  • 二、结论先行
  • 三、完整操作步骤
    • 步骤 1|准备环境与示例文档
    • 步骤 2|先提目录,别急着切
    • 步骤 3|按标题分块,而不是按字数硬切
    • 步骤 4|生成逐块的"摘要输入包"
    • 步骤 5|覆盖校验:唯一能证明"没漏"的一步
    • 步骤 6|对比表素材先清点再汇总
  • 四、实测数据
  • 五、踩坑与边界
  • 六、小结
  • 附录 A|核心三段代码
  • 附录 B|发布前必须补的真实截图
  • 发布信息
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档