Agent 2 面试题与答案
每道题分三部分:一句话(先抛结论)→ 详细回答(被追问时展开)→ 可能的追问。
数字来自agent2.md和代码仓库(agents/reconstruct/、benchmarks/reports/agent2_chunking.md)。
先看:口径统一
| 项目 | 口径 |
|---|---|
| 技术栈 | 全套谷歌:教师 Gemini,学生 Gemma 3 27B。修复用关闭思考的轻量教师,切块用旗舰教师;清晰度、切分点由 LLM 裁判打分(被问型号要提前定好) |
| 两个子任务 | 修复(把乱文本修干净)+ 切块(每块 ≤ 4096 token、话题完整) |
| 「不合法率 70% 以上降到 0」 | 统计单位是每篇文档的切块方案,不是单个块。见 Q12 |
| 4096 | 是上限,不是固定长度;没有做过 2048 / 8192 的对比实验,不要说「实验证明 4096 最好」 |
| 重叠 | 块与块之间不设重叠,见 Q2 |
| LLM 打分的误差 | 清晰度、切分点两次评测会差 0.1~0.2,小于 0.2 的差距不下结论 |
术语对照
| 中文 | 英文 |
|---|---|
| 块 | chunk |
| 重叠 | overlap |
| 奖励 | reward |
| 优势 | advantage |
| 组 | group |
| 价值模型(评论家) | critic / value model |
| 采样(生成回答) | rollout |
| 重要性采样比 | importance sampling ratio |
| 截断目标 | clipped objective |
| 奖励投机 | reward hacking |
| 向量 | embedding |
| 目录层级 | TOC |
| 饱和组 | 组内分数完全一样的组 |
关键数字速查(37 篇测试文档)
| 方案 | 完整度 F1 | 清晰度 /5 | 格式缺陷 / 千词 | 切分点 /5 | 块利用率 | 碎块 | 超限块 |
|---|---|---|---|---|---|---|---|
| 处理前(Agent 1 输出) | 0.806 | — | 21.5 | — | — | — | — |
| 按 token 硬切(不修复) | 0.807 | 3.35 | 21.7 | 2.00 | 0.92 | 0 | 0 |
| 规则(规则清洗 + 按标题切) | 0.810 | 3.63 | 0.1 | 3.96 | 0.70 | 6 | 0 |
| 教师 Gemini | 0.850 | 4.17 | 0.8 | 3.83 | 0.83 | 0 | 0 |
| Gemma 27B SFT + GRPO(最终) | 0.853 | 3.87 | 1.7 | 3.98 | 0.81 | 1 | 0 |
| 训练过程(同一批输入) | 完整度 F1 | 清晰度 |
|---|---|---|
| 未训练(先测 19 篇) | 0.822 | 3.04 |
| SFT 后 | 0.850 | 3.35 |
| GRPO 10 轮后 | 0.857 | 3.53 |
| 修上游,模型不重训 | 0.853 | 3.87(格式缺陷 17.4 → 1.7) |
模块一:项目架构与切分策略
Q1:请介绍 Agent 2 的作用、输入输出以及完整工作流程。为什么不直接使用固定长度切分?
一句话
Agent 2 把 Agent 1 解析出的乱文本修干净,再切成每块不超过 4096 token、话题完整的块交给 Agent 3;不用固定长度切,是因为它只顾长度不顾话题,我的对照实验里切分点只有 2.00 分(满分 5),几乎总是切在句子中间。
详细回答
为什么需要 Agent 2:Agent 1 只负责「把字完整取出来」,输出里有断行、断词(Optimiza- 换行 tion)、页眉页脚和页码、网页导航残留、被拆成一行一个字符的公式。下游 Agent 3 要逐块抽问答,一篇论文几万 token 一次放不下,太长也会漏抽中间的内容,所以必须先修干净再切块。
输入:Agent 1 的解析文本 + 公式表(网页公式已换成 ⟦F1⟧ 占位符)。
输出:若干个干净的块,每块 ≤ 4096 token、话题完整。
核心原则:只修格式,不改内容。不能改写、总结、补充、翻译。原文「学习率 5e-6」修完必须还是「5e-6」,否则错误会直接进知识库。
完整流程:
1 | Agent 1 的文本(乱) |
注意:窗口不是块。窗口(约 2500 token)只是修复时的分段,修完拼回;块(≤ 4096)是切出来交给 Agent 3 的。
为什么不用固定长度切分:
| 方法 | 切分点 /5 | 块利用率 | 问题 |
|---|---|---|---|
| 按 token 硬切 | 2.00 | 0.92 | 每块都装满,但几乎总切在句子或推导中间 |
| 规则按标题切 | 3.96 | 0.70 | 切得太碎,碎块 6 个 |
| 最终方案 | 3.98 | 0.81 | 话题完整,块也装得比较满 |
固定长度切把一个推导劈成两半,Agent 3 在两块里都只看到一半,抽出的问答就不完整。
Q2:为什么选择约 4096 Token 作为 Chunk 大小?Chunk Size 和 Overlap 如何确定?
一句话
4096 是由下游 Agent 3 的上下文预算倒推出来的上限,不是实验调出来的;块与块之间不设重叠,因为切点已经落在话题边界,跨块关联交给 Agent 3 的合并步骤处理,重叠反而会产生重复问答。
详细回答
4096 怎么来的:
| 太小(如 2048) | 太大(如 8192) | |
|---|---|---|
| 话题完整性 | 一个完整小节、一个推导经常就有两三千 token,容易被切断 | 好 |
| Agent 3 的上下文 | 宽裕 | 放不下:Agent 3 输出预算 12000 token,部署上下文 20480,输入翻倍会挤占输出 |
| 抽取质量 | 块多,跨块重复和关联多,合并负担重 | 输入太长,容易只关注开头结尾、漏抽中间 |
4096 + 提示词 + 12000 的输出刚好放进 20480 的上下文,是折中。
是上限,不是固定长度:实际平均利用率约 0.81,约 3300 token 一块,每块是一个完整话题。
为什么不设重叠:
- 切点落在话题转换处,相邻块本来就是不同话题,重叠的那部分对理解帮助不大
- Agent 3 是逐块抽问答,重叠部分会被抽两次,产生重复问答,还要额外去重
- 跨块的关联由 Agent 3 的「向量召回 + 关系判断」合并处理(见 Q5)
可能的追问
- 做过 2048 / 8192 的对比吗? 没有专门做。这个值由下游上下文预算决定,可选范围本来不大。如果要做,我会固定话题转换点、只改装箱上限,看 Agent 3 的召回率和跨块合并的错误率。
Q3:为什么 Agent 2 需要采用 Agent 或模型驱动方案,而不是 RecursiveCharacterTextSplitter 等传统切分方法?
一句话
传统切分只认字符和分隔符,判断不了「这里是不是新话题」,也修不了乱文本;但模型也不是万能的,我最终的方案是「语义判断交给模型、长度算术交给程序」,两者结合。
详细回答
传统方法(递归按分隔符切)的问题:
不懂语义:它按「段落 → 换行 → 句子 → 字符」的优先级切,只保证长度,不知道哪里是话题边界。我的对照里,按向量相似度的语义切法切分点 4.25,但利用率只有 0.53,平均 10.5 块,切得太碎。
输入是乱的:Agent 1 的输出里,一句话被拆成好几行、页码混在正文中,按换行切会切在句子中间。必须先修复。
修复本身需要理解:
- 一行单独的
3,是页码还是表格里的数? - 一行很短的文字,是标题还是被断开的半句话?
- 网页底部一串链接,是导航还是参考文献?
这些都要看上下文,规则判断不了。
- 一行单独的
但模型也有短板:长串加法
第一版让模型直接输出切块方案 [{"start": 0, "end": 12}, ...],SFT 后仍然 70%~80% 不合法,最常见是超长。原因:判断一块是否超过 4096,要把几十段的 token 数加起来,而大模型是逐字预测,不是真的在算。给每段加上累计 token 数也只改善一点,37 篇里还有 30 篇不合法。
最终方案:各做擅长的
| 子任务 | 本质 | 交给谁 |
|---|---|---|
| 哪里是新话题? | 语义判断 | 模型:只输出 {"boundaries": [3, 4, 18, 31]} |
| 怎么切才不超 4096? | 算术 | 程序:装箱 |
不合法率降到 0,而且没有重新训练,同一个 SFT 模型改成标转换点就能做。
切块方法对比(同一份修好的正文):
| 方法 | 不合法 | 平均块数 | 块利用率 | 切在句中 | 切分点 /5 |
|---|---|---|---|---|---|
| 规则(按标题) | 0 | 8.9 | 0.62 | 11% | 4.36 |
| 语义(按向量相似度) | 0 | 10.5 | 0.53 | 13% | 4.25 |
| 教师 | 0 | 7.4 | 0.80 | 8% | 3.93 |
| 学生(标转换点 + 装箱) | 0 | 7.6 | 0.76 | 9% | 3.92 |
可能的追问
- 规则和语义切法的切分点分数更高,为什么不用? 它们切得太碎(利用率 0.53~0.62),块越碎,切点越多落在标题处,单个切点的分数自然高;但碎块会加重 Agent 3 的跨块合并负担。要同时看切分点和利用率。
模块二:语义完整性与结构保留
Q4:如何保证每个 Chunk 的语义完整性?遇到段落、表格、公式和标题跨越 Chunk 边界怎么办?
一句话
切点只会落在段落之间,并优先落在模型标出的话题转换点上;公式在修复前就被占位符保护、不经过模型;表格作为一个整段不会被拆开;只有单段超过 4096 时才会按句子、再按字符硬切。
详细回答
切点只在段落边界:正文先切成编号段落,切块问题变成「在哪几段之间切一刀」,所以永远不会切在段落中间。
装箱规则(agents/reconstruct/pipeline.py 的 pack()):
1 | 一段一段往块里装 |
各种跨边界情况:
| 情况 | 怎么处理 |
|---|---|
| 段落 | 切点只在段落之间,段落不会被切开 |
| 标题 | 模型会把新章节、新小节的标题标成转换点,切点优先落在标题前,标题和它的正文在同一块 |
| 公式 | Agent 1 把网页公式取出、换成 ⟦F1⟧;修复时要求模型原样保留,最后由程序换回 LaTeX。公式一个符号错意思就全变了,所以不让模型碰 |
| 表格 | Markdown 表格内部没有空行,会被当成一个整段,不会被切开 |
| 超长单段 | 超过 4096 的段落先按句子拆,再不行按字符硬切(split_long()),这是唯一可能切断语义的情况 |
怎么衡量:
- 切分点(LLM 裁判,1~5):最终 3.98,教师 3.83
- 切在句子中间的比例:约 9%(教师 8%)
修复阶段的完整性:每个窗口修完检查输出长度 ≥ 输入的 30%,不够再检查原文「像正文的句子」是否保留了 70% 以上;不通过就放宽输出上限重试一次,还不行退回规则清洗。宁可修得不好,也不能丢内容。
可能的追问
- 为什么不只看长度判断丢没丢内容? 维基网页删掉导航栏后长度可能只剩一半,但这是正确的删除。所以第二道检查看「正文句子」:以句号结尾、英文至少 8 个词或中文至少 15 个字的行。
- 代码块里有空行会被拆开吗? 有可能,这是已知局限;测试集里代码块很少。改进办法是把代码块整体当一个段落。
Q5:你如何保留文档的 TOC 层级关系?如何解决跨 Chunk 的上下文关联与信息丢失?
一句话
修复时把丢了格式的标题恢复成 Markdown 的 #、##,层级就保留在正文里;切点优先落在标题前,信息丢失靠修复后的完整度检查兜底;跨块的关联不在 Agent 2 里解决,而是交给 Agent 3 用向量召回和关系判断合并成全局知识树。
详细回答
目录层级怎么保留:
- 修复任务的一项就是「恢复标题」:
2.1 Group Relative Advantage混在正文里,修成## 2.1 Group Relative Advantage - 层级通过
#的个数体现,随正文一起进块 - 切块时模型优先把新章节、新小节的标题标成转换点,所以一块通常从一个标题开始
诚实说明的局限:每块没有附带完整的标题路径(比如「第 2 章 > 2.1 节」)。如果一块从小节中间开始,它就不知道自己属于哪一章。
改进:给每块开头加上它所属的标题路径,几乎没有成本,能直接帮助 Agent 3 判断归属。
跨块信息丢失的三个层面:
| 层面 | 怎么处理 |
|---|---|
| 修复时删掉了正文 | 窗口级「正文句子保留率」检查;GRPO 的完整度奖励直接惩罚丢内容 |
| 切块把一个话题劈开 | 切点优先落在话题转换处;切分点奖励和「不切在句中」奖励 |
| 两块之间本来就有关联 | 交给 Agent 3:每块抽成问答后,用向量召回相似问答,再由关系判断模型判断「同一知识点 / 相关 / 无关」,合并成全局知识树 |
为什么不用重叠解决跨块关联:见 Q2,重叠会产生重复问答;而且关联可能跨越很远的两块(比如引言和实验),重叠只能覆盖相邻的块。
模块三:GRPO 核心原理
Q6:什么是 GRPO?它与 SFT、PPO 的核心区别是什么?为什么 Agent 2 选择 GRPO?
一句话
GRPO 是对同一道题采样一组回答,用每个回答相对组内平均分的好坏来强化或削弱它;和 SFT 比,它直接优化我关心的指标、可以超过教师;和 PPO 比,它用组内平均代替价值模型,省掉一个和策略一样大的模型。
详细回答
三者对比:
| SFT | PPO | GRPO | |
|---|---|---|---|
| 学什么 | 照着教师的答案抄 | 按奖励优化 | 按奖励优化 |
| 需要标准答案吗 | 要(教师输出) | 不要,要奖励函数 | 不要,要奖励函数 |
| 上限 | 最多到教师水平 | 可以超过教师 | 可以超过教师 |
| 基线怎么来 | — | 训练一个价值模型预估平均分 | 同组回答的平均分 |
| 额外模型 | 无 | 价值模型(和策略一样大) | 无 |
GRPO 怎么做(同一道题答 8 次):
| 回答 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 总分 | 0.9 | 0.5 | 0.7 | 0.8 | 0.4 | 0.6 | 0.9 | 0.6 |
1 | 组内平均 = 0.675,标准差 ≈ 0.17 |
为什么 Agent 2 选 GRPO:
- SFT 后清晰度离教师差很远(3.35 vs 4.15),抄教师已经到瓶颈,需要直接优化指标
- 有明确可算的奖励:完整度、格式缺陷可以用规则算,清晰度、切分点可以用 LLM 裁判打分
- 教师自己也会悄悄改写:SFT 会把这个毛病也学去;GRPO 的完整度奖励直接惩罚编造
- 省显存:27B 再加一个 27B 的价值模型放不下,GRPO 不需要
- 奖励天然适合组内比较:同一个窗口修 8 次,好坏差别明显
为什么 Agent 1 不做而 Agent 2 做:Agent 1 SFT 后离天花板只差 0.007;Agent 2 的清晰度差距大,而且修复的输出空间大,有足够的组内差异可学。
Q7:GRPO 中 Group、Reward、Advantage 分别是什么?为什么不需要单独训练 Critic?
一句话
组是同一道题的 8 个回答;奖励是给每个回答打的总分;优势是这个回答比组内平均好多少;不需要价值模型,是因为「这道题平均能拿多少分」直接用同组 8 个回答的平均分来估计。
详细回答
| 概念 | 在我的项目里 |
|---|---|
| 组 | 同一道题(一个修复窗口,或一篇文档的段落列表)让模型采样 8 个回答 |
| 奖励 | 5 项奖励按权重加权平均的总分(修复题用 2 项,切块题用 3 项,见 Q9) |
| 优势 | (这个回答的总分 − 组内平均) / 组内标准差 |
为什么不需要价值模型:
- 优势要回答的问题是「这个回答比一般水平好多少」,关键是找一个基线
- PPO 训练一个价值模型来预测基线,它要和策略一样大,还要单独训练,不准时会拖累策略
- GRPO 对同一道题采样多个回答,组内平均分就是这道题基线的无偏估计,不用训练
代价:每道题要采样 8 次,生成成本是 8 倍。我用 vLLM 在一张 A100 上专门采样来抵消。
一个重要现象:饱和组
- 8 个回答分数完全一样 → 标准差为 0 → 优势全是 0 → 对训练没有贡献,直接丢掉
- 10 轮共采样 1280 个回答,去掉饱和组后有效样本 848 条,更新 106 步
可能的追问
- 为什么除以标准差? 让不同难度的题目优势尺度一致。注意它会放大差别很小的组(比如分数只差 0.01),这时信号可能主要是噪声。
Q8:GRPO 的 Importance Sampling Ratio、Clipped Objective 和 KL 惩罚分别起什么作用?
一句话
重要性采样比让我能用旧模型采的回答训练新模型;截断目标把这个比限制在 0.8~1.2,防止一步改太大;KL 惩罚让模型别离 SFT 模型太远,防止奖励投机。
详细回答
损失函数:
1 | 损失 = −min( 比 × 优势, 截断(比, 0.8, 1.2) × 优势 ) + β × KL |
| 组件 | 作用 | 我的设置 |
|---|---|---|
| 重要性采样比 | 回答是旧模型采的,训练的是新模型,用概率比修正分布差异 | — |
| 截断目标 | 比超出 0.8~1.2 就不再给梯度,限制每步更新幅度 | clip 0.2 |
| KL 惩罚 | 衡量当前模型离 SFT 参考模型多远,越远罚越多 | β = 0.04,用 k3 估计 |
为什么需要 KL:防止奖励投机。比如模型发现「删掉内容后清晰度更高」,就开始乱删。KL 把它拉在 SFT 模型附近。
训练稳定性的实际数据:
- KL 每轮平均 0.01~0.11;第 5、7 轮各有一次约 0.4 的尖峰,之后回落
- 截断比例 2%~8%
- 没有出现「奖励上涨、KL 同时暴涨」的投机迹象
踩过的坑:dropout 让概率比对不上
- 现象:每批数据只更新一次时,新旧模型是同一个,比应该恰好是 1,却触发了截断
- 原因:vLLM 采样时是推理模式,dropout 关着;训练时 LoRA 的 dropout 开着,同一个模型算出的概率不一样
- 修复:GRPO 训练时关掉 LoRA dropout
模块四:奖励函数设计与训练
Q9:Agent 2 的奖励函数是如何设计的?各项 Reward 分别衡量什么?权重如何确定?
一句话
5 项奖励,修复题用完整度和清晰度,切块题用长度上限、块间独立和块尽量大,覆盖规则与 LLM 判别、奖励与惩罚、连续 / 离散 / 门槛三种值类型;权重从全 1 起步,第一轮后按每项的组内方差和饱和比例调整。
详细回答
5 项奖励(agents/reconstruct/rewards.py):
修复题(2 项)
① 完整度(规则,连续,有奖有罚)
1 | 完整度 = max(−1, 召回 − 2 × max(0, 新增比例 − 5%)) |
- 召回:输入窗口里「像正文的句子」的词,输出保留了多少
- 新增比例:输出里有多少词是输入没有的,即编造
- 允许 5% 新词(修断词、纠 OCR 错字会带来变化),超出部分加倍扣
- 和输入窗口比,不和标准答案比:GRPO 不需要标准答案
② 清晰度(LLM + 规则,离散 + 连续)
1 | 清晰度 = 0.5 × LLM 裁判打分(1~5 换算到 0~1)+ 0.5 × 规则缺陷分 |
切块题(3 项):模型输出转换点后程序先装箱,再给切出来的块打分
③ 长度上限(规则,门槛):全部 ≤ 4096 得 1 分;输出不是整数列表,整条记 −1,其他项都不算
④ 块间独立(LLM + 规则)
1 | 块间独立 = 0.5 × LLM 切分点打分(换算到 0~1)+ 0.5 × 不切在句子中间的比例 |
⑤ 块尽量大(规则,连续,有惩罚)
1 | 块尽量大 = 平均利用率 − 碎块比例(< 512 token 的块) |
总分:Σ(权重 × 奖励) / Σ(权重),只算这类题自己的那几项;所有奖励先换算到 0~1,量纲一致才能加权。
对照简历那句话:
| 奖励 | 规则 / LLM | 奖励 / 惩罚 | 值类型 |
|---|---|---|---|
| 完整度 | 规则 | 有奖有罚(编造扣分) | 连续 |
| 清晰度 | LLM + 规则 | 奖励 | 离散 + 连续 |
| 长度上限 | 规则 | 惩罚 | 门槛 |
| 块间独立 | LLM + 规则 | 奖励 | 离散 + 连续 |
| 块尽量大 | 规则 | 有奖有罚(碎块扣分) | 连续 |
权重怎么定:不看总分,拆开看每一项
| 统计量 | 说明什么 |
|---|---|
| 组内标准差 | 同一道题 8 个回答在这一项上差多少,越大越能区分好坏 |
| 饱和组比例 | 多少组在这一项上 8 个分数完全一样,饱和就提供不了学习信号 |
第一轮后的 4 个调整:
| 奖励 | 第一轮 | 第二轮 | 依据 |
|---|---|---|---|
| 完整度 | 1,无下限 | 1,下限 −1 | 第 9 轮有一组 8 个回答全是 −1.86,把整轮平均从 0.9 拉到 0.51 |
| 清晰度 | 1,只有 LLM | 1,LLM + 规则各半 | 格式缺陷每千词 19 个(教师 2.3),LLM 对断行、页码不敏感,能读懂就给 4 分 |
| 长度上限 | 1 | 0,保留 −1 门槛 | 96% 的组饱和:装箱后长度由程序保证 |
| 块间独立 | 1 | 1.5 | 切块总分组内标准差只有 0.031,是修复的一半;块间独立是其中差异最大的一项 |
| 块尽量大 | 1 | 1 | — |
可能的追问
- 长度上限饱和了,为什么不直接删? 权重设成 0 了,但保留「输出不合法记 −1」的门槛,处理剩下约 4% 把 JSON 写坏的情况。它从「需要学的目标」变成了「系统保证」,正是「算术交给程序」的结果。
- 为什么要拆开看每一项? 总分的优势为 0,不代表每一项都是 0,可能一项 +0.3、另一项 −0.3 互相抵消了。
Q10:如何避免 Reward Hacking?如果语义完整性与 Chunk 长度限制发生冲突,你如何处理?
一句话
防奖励投机靠四层:奖励之间互相制约、KL 惩罚、规则硬约束、盯着每项奖励和验证集的变化;完整性和长度的冲突在设计上就解决了:长度由程序硬保证,模型只负责在约束内找最好的切点。
详细回答
可能的投机方式和防法:
| 投机方式 | 怎么防 |
|---|---|
| 删内容换清晰度(越短越干净) | 完整度奖励的召回项直接惩罚丢内容 |
| 改写、编造让文字更通顺 | 完整度的新增比例项:超过 5% 加倍扣 |
| 切得很碎让每个切点都落在标题上 | 「块尽量大」奖励:利用率减碎块比例 |
| 讨好 LLM 裁判 | 清晰度一半用规则缺陷分,规则不会被讨好 |
| 整体偏离太远 | KL 惩罚(β 0.04),把模型拉在 SFT 附近 |
| 输出格式乱来 | 不合法整条 −1 |
监控:
- 每轮看 KL、截断比例;奖励上涨同时 KL 暴涨就是投机信号。实际 KL 0.01~0.11,没有出现
- 加了固定验证集:每轮随机抽题难度不一,平均分在 0.68~0.95 之间跳;每轮额外用同一批 16 道题评一次(不参与训练),才能看出模型真的有没有变好
- 拆开看每项奖励的均值和饱和比例
一个真实的「奖励打架」:测试数据的标准答案里公式本身就是碎片(维基纯文本接口导出时拆散的)。完整度奖励「保留碎片得分」,清晰度奖励「保留碎片扣分」,怎么做都被扣。最后通过修上游解决:公式换成占位符、标准答案里的碎片换回 LaTeX,测试集和训练集全部重新生成(详见 Q13 的「修上游、模型不重训」)。
完整性 vs 长度上限的冲突:
- 长度是硬约束,由程序保证:装箱时每放一段都检查,不可能超长
- 完整性是软目标,由模型优化:在不超长的前提下,切点尽量落在话题转换处
- 冲突时的优先级:先保证不超长;如果范围内没有转换点,就在段落边界硬切(不切句子);单段超长才按句子、再按字符切
- 这样模型标得再差,也只是「话题不够完整」,不会超长
Q11:你如何构造 GRPO 训练样本、生成多个候选切分方案、计算奖励并更新模型?
一句话
题目来自训练集的修复窗口和段落列表,只用输入、不用教师输出;每轮 16 道题 × 8 个回答,一张 A100 用 vLLM 采样,本机打分算组内优势,另一张 A100 训练一步,再把新的 LoRA 热换回 vLLM,循环 10 轮。
详细回答
训练样本两类:
| 类型 | 题目 | 模型输出 |
|---|---|---|
| 修复 | 一个待修复的窗口(约 2500 token) | 修好的正文 |
| 切块 | 一篇修好的文档的段落列表(编号 + 每段开头 60 字) | {"boundaries": [...]} |
题目来自训练集 63 篇,和测试集 37 篇不重叠。GRPO 不需要标准答案,好坏全靠奖励函数。
一轮的流程(scripts/grpo_loop_agent2.py):
1 | GPU0:vLLM 加载当前模型,16 道题 × 8 个回答 = 128 个回答 |
切块的「多个候选方案」怎么来:同一份段落列表,模型用一定温度采样 8 次,得到 8 组不同的转换点;每组都由程序装箱成块,再用块间独立和块尽量大打分。
训练设置:
| 设置 | 值 |
|---|---|
| 每轮 | 16 道题 × 8 个回答 |
| 轮数 | 10 轮 |
| 有效样本 | 848 条,106 步更新 |
| 学习率 | 5e-6(比 SFT 小很多) |
| 截断 / KL | 0.2 / β 0.04(k3) |
| LoRA dropout | 关掉(见 Q8) |
| 硬件 | 2 张 A100,每轮约 26 分钟 |
GRPO 之前的 SFT(冷启动):
- 819 条:教师修复 436 + 合成「弄乱 / 还原」236 + 教师切块 147
- 教师输出要过判卷才能进训练数据:召回下降 ≤ 0.02、清晰度 ≥ 3.5、没触发兜底、输出没变长超过 1.3 倍
- 合成数据:拿干净文本用脚本弄乱(硬换行、断词、插页眉页码、去掉标题
#、OCR 式错字等),让模型还原。好处是输入和目标严格对齐,模型学到「只修格式,一个字都不改」,弥补教师偶尔改写的毛病 - 标准答案只当裁判、不当训练目标:它和「修好后的样子」不是同一个版本,拿它当目标模型会学成「改写」
模块五:数据集与实验评估
Q12:你简历中无效 Chunk 比例从 70% 以上降至 0,这里的无效 Chunk 如何定义和统计?
一句话
「不合法」指的是一篇文档的整个切块方案违反硬约束(有块超过 4096、漏段、重叠乱序、没覆盖到结尾、或输出不是合法 JSON),按文档统计;第一版让模型直接输出切块方案时 70%~80% 的文档不合法,改成「模型标转换点 + 程序装箱」后降到 0。
详细回答
先纠正题目的说法:统计单位不是「单个块」,是「每篇文档的切块方案」。简历写的是「输出不合法率」。
不合法的定义(任一条成立就算):
| 情况 | 例子 |
|---|---|
| 超长 | 某一块超过 4096 token(最常见) |
| 漏段 / 没覆盖完 | 最后几段没被任何块包含 |
| 重叠或乱序 | 两块的段落范围有交叉,或顺序颠倒 |
| 格式错误 | 输出不是合法 JSON |
统计方式:37 篇测试文档,每篇的方案判一次合法 / 不合法:
1 | 不合法率 = 不合法的文档数 / 37 |
各阶段的数据:
| 阶段 | 不合法率 |
|---|---|
| 未训练 27B,直接输出切块方案(先测 19 篇) | 62% |
| SFT 后,直接输出切块方案 | 70%~80% |
| 补训切块 + 每段给累计 token 数 | 30/37 篇(约 81%) |
| 模型标转换点 + 程序装箱 | 0 |
为什么降到 0 是「保证」而不是「碰巧」:程序每放一段都检查长度,不可能超长;从头到尾连续装,不可能漏段或重复。
唯一的剩余风险:模型输出的转换点不是一串整数。这时用标题位置代替转换点,仍然走装箱,结果仍然合法;GRPO 时这种情况记 −1,训练中约 4%。
可能的追问
- 为什么 SFT 后反而比未训练更高? 两次测的样本数不同(19 篇 vs 37 篇),不能直接比;而且 SFT 数据本身就是第一版切块方式,模型学会了格式,但学不会加法。
- 改成标转换点后重新训练了吗? 没有。同一个 SFT 模型直接就能标,切分点 3.92(教师 3.93);后面 GRPO 才专门优化转换点。
Q13:如何对比固定长度切分、语义切分、SFT 与 GRPO?你会设计哪些消融实验?
一句话
切块方法在同一份修好的正文上对比(固定长度、按标题、按向量相似度、教师、学生),训练阶段按「未训练 → SFT → GRPO → 修上游不重训」逐步对比;已有的关键消融是「修上游不重训」,证明清晰度瓶颈在输入而不在模型。
详细回答
评测指标(37 篇测试文档,标准答案来自 LaTeX 源码和维基正文):
| 子任务 | 指标 | 怎么算 |
|---|---|---|
| 修复 | 完整度 F1 | 和 Agent 1 同一个词袋公式,对照标准答案 |
| 修复 | 清晰度 /5 | LLM 裁判给每块打分 |
| 修复 | 格式缺陷 / 千词 | 规则统计断行、断词、页码行、arXiv 页眉、连续空格 |
| 切块 | 切分点 /5 | LLM 裁判看「当前块结尾 + 下一块开头」 |
| 切块 | 块利用率 | 除最后一块外,min(块 token 数 / 4096, 1) 的平均 |
| 切块 | 碎块 / 超限块 | 小于 512 / 大于 4096 token 的块数 |
为什么既要 LLM 打分又要规则:LLM 能判断整体好不好读,但有 0.1~0.2 的波动;规则客观稳定,但只能抓已知的几种问题。
① 切块方法对比(同一份修好的正文,只换切法):
| 方法 | 切分点 /5 | 块利用率 | 平均块数 | 切在句中 |
|---|---|---|---|---|
| 固定长度硬切 | 2.00 | 0.92 | — | 大量 |
| 规则按标题 | 4.36 | 0.62 | 8.9 | 11% |
| 语义(向量相似度) | 4.25 | 0.53 | 10.5 | 13% |
| 教师 | 3.93 | 0.80 | 7.4 | 8% |
| 学生 | 3.92 | 0.76 | 7.6 | 9% |
结论:固定长度不顾话题;规则和语义切得太碎;学生在利用率和话题完整之间取得和教师接近的平衡。
② 训练阶段对比(同一批输入):
| 阶段 | 完整度 F1 | 清晰度 |
|---|---|---|
| 未训练 | 0.822 | 3.04 |
| SFT | 0.850 | 3.35 |
| GRPO 10 轮 | 0.857 | 3.53 |
| 修上游,不重训 | 0.853 | 3.87 |
③ 已经做过的关键消融:修上游、模型不重训
- 清晰度卡在 3.53,让 LLM 对 116 个低分块(251 块中 ≤ 3 分的)逐块归因:网页残留 40%、公式被拆碎 37%、表格乱 18%、断行和 OCR 5%
- 公式和表格(共 55%)在 Agent 2 拿到文本前就坏了
- 修上游:PDF 表格改用 pymupdf4llm 输出 Markdown 表格;网页公式换占位符;标准答案里的公式碎片换回 LaTeX;排除缺陷统计误报(占 16%)
- 模型一个参数没改,重新评测:清晰度 3.53 → 3.87,格式缺陷 17.4 → 1.7
- 这证明瓶颈在输入:如果问题在模型身上,换了输入不会涨
- 同时确认 Agent 1 没退步:F1 0.873 → 0.874
④ 还应该补做的消融
| 消融 | 想回答的问题 |
|---|---|
| 去掉合成「弄乱 / 还原」数据 | 合成数据对「不改写」的贡献 |
| GRPO 只用第一轮权重 vs 调整后权重,各跑完整 10 轮 | 权重调整的单独贡献(第二轮中途就停了,没有完整对比) |
| 清晰度只用 LLM vs LLM + 规则 | 规则缺陷分的贡献 |
| 去掉 KL / 改 β | KL 对防投机的作用 |
| 装箱上限 2048 / 4096 / 8192 | 块大小对 Agent 3 召回的影响 |
| 多次评测取平均 | LLM 打分的波动有多大 |
诚实说明:GRPO 带来的完整度提升只有 0.007(0.850 → 0.857),在误差范围内;GRPO 的主要贡献在清晰度(3.35 → 3.53)。
模块六:工程优化与异常处理
Q14:如果遇到超长文档、复杂表格、公式、代码块或跨页段落,Agent 2 如何处理?
一句话
超长文档靠分窗口并行修复和装箱切块,长度不受限;公式用占位符完全绕开模型;表格保持成一个整段;跨页段落正是修复要做的「合并断行、删页眉页码」;代码块和 PDF 公式是目前的局限。
详细回答
| 场景 | 怎么处理 | 局限 |
|---|---|---|
| 超长文档 | 修复按约 2500 token 分窗口,6 个并行;切块时模型只看每段开头 60 字,输入很短;装箱对长度没有上限 | 超长文档的转换点列表也会很长,可以分段标 |
| 超长单段 | 先按句子拆,再不行按字符硬切 | 唯一可能切断语义的情况 |
| 复杂表格 | 上游改用 pymupdf4llm 把 PDF 表格输出成 Markdown 表格;表格内部没有空行,作为一个整段不会被切开 | 模型是在旧输入(纯文本表格)上训练的,没见过 Markdown 表格,PDF 清晰度 3.76(教师 4.25) |
| 网页公式 | Agent 1 取出 LaTeX,正文换成 ⟦F1⟧,修复时原样保留,最后程序换回 |
公式不经过模型,不会被改坏 |
| PDF 公式 | PDF 里没有公式源码 | 只能保留文字形态,是已知局限 |
| 代码块 | 没有专门处理 | 有空行的代码块可能被拆开;改进:把代码块整体当一个段落 |
| 跨页段落 | 修复的本职工作:合并断行、接回断词、删掉夹在中间的页眉页脚和页码 | 扫描件最难(清晰度 2.67,教师 3.69),OCR 错字和断行叠加,而且只有 4 篇,波动大 |
防丢内容的兜底(所有场景通用):窗口修完检查长度和正文句子保留率,不通过就重试一次,再不行退回规则清洗。
踩过的坑:一开始用思考型教师修复,它的思考把输出长度用光了,正文一个字没输出,返回空结果,这个窗口的内容被悄悄丢掉,没有任何报错。修复:修复改用关闭思考的轻量教师,并加上正文句子保留率检查。
Q15:如果 Agent 3 发现知识图谱缺失某些关系,如何判断是 Agent 2 切分导致的?如何定位并修复?
一句话
沿着数据流往回查:先看缺失关系的两个知识点是不是来自不同的块、切点是否把同一话题劈开,再看修复时有没有删掉相关内容;这个归因由 Agent 4 按块读取上游输入输出自动完成,确认是切块问题后,就把这个位置的切分修正加进训练数据。
详细回答
排查顺序(从下游往上游):
1 | Agent 3 缺失了 A 和 B 之间的关系 |
用到的证据:
- 完整度召回:修复前后和标准答案比,召回有没有掉(看内容丢没丢)
- 切分点打分:这个切点的 LLM 分数是不是低
- 切在句中:切点是不是落在句子中间
- 每一步的输入输出都存了:修复窗口、转换点、装箱结果都有记录,能逐层回放
谁来做:Agent 4(全局检查与错误归因)
- 按块读取上游三个 Agent 的输入和输出,先写推理,再列出问题、归因到出错的 Agent、给修改意见
- 训练时沿标准答案逐级追查,自动生成归因标签
怎么修复:
| 归因结果 | 修复方式 |
|---|---|
| 修复时删了内容 | 加强完整度检查;把这个窗口加入训练数据 |
| 切点劈开了话题 | 运行时:在该处去掉切点,或把相邻两块合并重切;训练时:把正确的转换点作为样本加入 SFT 或 GRPO 题目 |
| 本来就是跨块关联 | 不改 Agent 2,交给 Agent 3 的跨块合并 |
| 系统性问题(某类文档总切错) | 统计归因分布,提高这类文档在训练数据里的比例 |
注意前车之鉴:Agent 2 清晰度卡住时,归因发现 55% 的问题在上游。所以缺失关系时也不要先假设是 Agent 2 的问题,要逐层验证。
附:30 秒版本(面试开场用)
Agent 2 把 Agent 1 解析出的乱文本修干净,再切成不超过 4096 token、话题完整的块交给 Agent 3。我把切块拆成「模型标话题转换点、程序按长度装箱」,因为模型不擅长长串加法,第一版直接输出切块方案时 70% 以上不合法,拆开后长度由构造保证,降到 0。训练上先用 Gemini 教师数据加合成的「弄乱 / 还原」数据做 SFT,再用 5 项奖励做 GRPO,并按每项奖励的组内方差和饱和比例调权重,比如长度项 96% 的组饱和,权重改为 0、只保留门槛。清晰度卡住后,我让 LLM 对低分块逐块归因,发现一半以上的问题来自上游解析和测试数据本身,修完上游、模型不重训,清晰度从 3.53 升到 3.87,格式缺陷从每千词 17.4 降到 1.7。最终完整度 F1 0.853、切分点 3.98,追平或超过教师。