# 反思环的受控修订

> “草稿→自我修订×N”的反思环常常越改越差：整篇重写式修订平均回退 16–27% 已覆盖内容，无引导自反思几乎无效且附带损伤更大。本文记录一次三段式改造——结构化编辑计划、scoped 逐节拼回、零 token 回归门——含实现坑、边界 case，以及研究过程自身的一次失败复盘。

- Canonical (HTML): https://kaguc.com/blog/reflection-gate-zh/
- Date: 2026-07-29


*English version: [Controlled revision in reflection loops](/blog/reflection-gate/)。“LLM 应用工程”系列第 10 篇。*

## 问题：“草稿→自我修订×N”为什么常常越改越差

自主生成流水线的标准收尾是反思环：初稿完成后，让模型自我审视、修订若干轮。直觉上多改几轮至少不会更差。我们的第一版实现就是这个直觉的直接翻译——critique 产一段散文点评，revise 据此整篇重写，新稿无条件替换上一版：

```python
msgs.append({"role": "assistant", "content": draft})
yield {"type": "draft", "text": draft}      # 无条件保留这一版
```

实证测量表明这个直觉是错的。arXiv:2601.13217（*Beyond Single-shot Writing: Deep Research Agents are Unreliable at Multi-turn Report Revision*，我们读过 arXiv 原文核验）对 deep research agent 的多轮修订做了测量：处理反馈时，整篇重写式修订**平均回退 16–27% 已覆盖内容**，并伴随引用质量下降；更值得警惕的是，**无引导的“请自己反思并修订”对几乎所有被测 agent 无效**——5 个里仅 1 个有提升（+3.6%），且比定向反馈造成**更大的附带损伤**。机理侧，Self-Refine（arXiv:2303.17651，检索摘录、未对抗核验）的结论一致：反馈必须具体可执行，泛化反馈显著掉分，收益集中在前 1–2 轮，错误难自检的任务上几乎无效。正面对照是 PaperOrchestra（arXiv:2604.05018）：其精修环带显式“改好才收、改差就撤并停”闸门，报告 79–81% 胜率、0% 变差（该细节取自论文正文，未独立核验）。

对照下来，第一版实现三个雷全踩：

| 失效模式 | 实证 | 对应改造 |
|---|---|---|
| 整篇全量重写 | 平均回退 16–27% 已覆盖内容、引用降质（2601.13217，已手工核验） | 改造一 + 改造二 |
| 开放式散文反馈 | 泛化反馈显著掉分；无引导自反思几乎无效且附带损伤更大 | 改造一：定位到节 + 可执行动作 |
| 无接受/回退闸门 | 带闸门的精修环才有 79–81% / 0% 变差（PaperOrchestra，未独立核验） | 改造三：零 token 回归门 |

下文按侵入性从小到大拆解三段改造。语境是一个 LaTeX 报告生成 agent（scan→探索→出图→撰写→反思×N→bib→编译自修复），但机制与格式无关。

## 改造一：反思输出结构化编辑计划

第一刀砍在反馈的形态上。critique 不再是散文，而是强制输出 JSON 编辑计划：

```python
_REFLECT_SYS = (
    "你是实验报告 / 课程笔记的审稿人。对照【原始素材】审视【当前草稿】，只找出**确实需要修正**的问题，"
    "并把每个问题**定位到具体某一节**（给该节的 anchor，如 n2）；跨章节 / 整体结构 / 摘要关键词类问题用 \"whole\"。"
    "每条给 issue（问题）与 action（怎么改，可执行）。已达标的不要提、不要泛泛润色。"
    "严格只输出 JSON：{\"changes\":[{\"section\":\"n2 或 whole\",\"issue\":\"\",\"action\":\"\"}]}；"
    "整体已合格则输出 {\"changes\":[]}，不要任何额外文字。"
)
```

四个设计点：

1. **每条问题必须定位到具体节**（分节器给的 anchor，如 `n2`），跨章节、整体结构、摘要关键词类问题显式标 `"whole"`。定位不是装饰——它决定下一步走哪条应用路径。
2. **issue 与 action 成对。**光说“误差分析太浅”不够，必须给可执行动作，直接对应 Self-Refine 的“反馈须具体可执行”。
3. **允许空数组。**`{"changes":[]}` 即“整体已合格”，反思环提前终止。修订从“默认要改”反转为“默认不改、有据才改”。
4. **解析失败不炸流程。**`_parse_reflect_plan` 遇到非 JSON 或无 `changes` 一律返回空表，调用方视为“无需修改”；计划封顶 8 条；anchor 命不中时的标题兜底是“先精确等值、再取最长被包含标题”——“结果”不会抢先匹配到“结果与讨论”头上。

配套改动：反思全程改用 fresh 最小上下文（system + 素材 + 当前稿），不再背着探索阶段的全程消息；素材作为稳定前缀，跨轮命中缓存。

## 改造二：纯 section 级问题走 scoped 逐节 splice

计划里的每一条按定位分流。纯 section 级问题不再触发整篇重写，而是复用交互编辑侧的成熟机件（分节器、作用域提示词、片段体检、确定性 splice——即[第 07 篇](/blog/versioning-scoped-edit-zh/)那套）逐节应用：`_scoped_reflect_apply` 对每条计划项，在**当前 working 文本**上重新解析定位（前一次 splice 之后 offset 已移动），只把目标节加该条 issue/action 加相关素材发给模型；拿回的片段过体检（非空、花括号配平、环境配平、不带 `\documentclass`），不过体检就跳过该节——**绝不拼回坏片段**；过了就 splice 拼回，其余节字节级不动。跨章节、摘要关键词、无法分节的稿子，才回退整篇最小必要重写。

这一步有个实现坑，由内部对抗审查发现（定级[中]）：

```python
# 片段必须保持被替换跨度内的顶层 \section 数（通常 =1；子节 =0）——否则 splice 改变全文节数，
# 令同轮后续 scoped 项的位置型 anchor 漂移、改错节；且“增节”方向不被 regression_check 兜住
# （它只报“减节”）。数不符 → 跳过该节，绝不拼回会移位的片段。
if len(re.findall(r"\\section\{", new_text)) != len(re.findall(r"\\section\{", section_src)):
    continue
```

模型偶尔会“热心”地把一节拆成两节：片段本身健康、体检全过，但 splice 之后全文节数 +1，同一轮里后续计划项的位置型 anchor 整体漂移、改错节；而回归门只报“减节”不报“增节”，这条路径上原本没有兜底。守恒检查把它收口在拼回之前。

## 改造三：零 token 回归门

无论走 scoped 还是整篇回退，每轮产出落库前都过 `regression_check`——六项全部是确定性判据、零 API 调用：

- 丢失可编译骨架（`\documentclass` / `\end{document}`）；
- 篇幅骤缩到上一版 60% 以下（截断或“其余不变”式省略）；
- 结构分下降；
- `\cite` key 集合变小（报出丢了哪些 key）；
- 章节数减少；
- 插图或数据表减少。

任一触发即判 regressed：回退保留上一版、终止反思。两版去空白后逐字相同则判 `converged`，同样提前停——收敛不浪费轮次。

为什么不用 LLM 评委当闸门？理由写在模块注释里：这个失败模式（丢内容）**本身可确定性检测**，而配对评委在中文域的可靠性未经验证（这是评委模块自己的告诫），且每轮要多付一次真实调用——更对，且更省。评委的正确位置在离线评测，不在在线闸门；效度问题见[第 09 篇](/blog/judge-validity-zh/)。

闸门自身也有边界 case：流水线里 bib 生成排在反思**之后**，反思阶段 bib 恒为空，此时“引用闭合”检查对任何含 `\cite` 的稿子恒为 False——新增一处引用会被误判成结构分下降、误回退好改动。修复：bib 为空时把该项移出闸门比分，引用是否丢失改由“key 集合非减”单独判定，并加一条专门的回归测试（bib 空不误判）钉死。

闸门决策以 step 事件呈现并持久化（日志页、断线重连都能看到），附结构化 `reflect_gate` 字段进 trace，供实验台观测“反思是否真在提质、哪一轮回退”。

## 改造后的闭环全景

```mermaid
flowchart TD
    A[当前稿 vN] --> B[审稿人 fresh 最小上下文：输出 JSON 编辑计划]
    B -->|changes 为空| S[判定已合格，提前停]
    B -->|纯 section 级| C[逐节 scoped 编辑 + splice，其余节字节级不动]
    B -->|含 whole 项| D[整篇最小必要重写]
    C --> G{零 token regression_check}
    D --> G
    G -->|劣化| X[回退 vN，终止反思]
    G -->|converged| S
    G -->|通过| V[落为 vN+1，进入下一轮]
```

测试随改造分批进入：闸门落地时 `test_eval_report` +7（接受/回退/收敛/丢章节/丢引用/篇幅骤缩/bib 空不误判）、`test_run_agent_mock` +1（修订劣化→回退保留上一版并停），后端 303 个测试全绿；scoped 反思落地后 306；本轮全部改造收尾时 322（另有实验台 19 个）。生成质量本身离线测不了，但闭环的每个确定性构件——计划解析、分流、splice、闸门——都可以。

## 研究过程自身的教训

这次改造的证据链本身也值得复盘。对标前沿时我们跑了一个 105-agent 的联网研究工作流；对抗核验阶段因订阅会话额度打满而**整批失败**，25 条 claim 全部停在 unverified。于是手工补核最吃重、也最可能被检索 agent 编造的 3 篇论文（读 arXiv 原文）：2601.13217 真实、核心数字坐实；PaperOrchestra 真实，但有两处更正——它不是 Google 论文，且摘要自称 flexible 而非“刚性固定”，只能拿来证“结构化多 agent 分工胜过自主循环”（胜率 +50–68% / 整体质量 +14–38%），不能证“越固定越好”；RP-ReAct（arXiv:2512.03560）架构真实，但检索 claim 里“简单任务被 vanilla ReAct 反超”的具体分数在摘要中不存在，疑为检索 agent 从正文抽取或补全，标为未核实。还有一处口径更正：检索 claim 写“break rate 21–31%”，那是正文口径，摘要口径为 16–27%，本文以摘要为准。

教训与正文同构：**检索 agent 的产出同样需要一道接受/回退闸门**——手工核验就是研究流程里的 regression_check。

## 什么时候不要这么做

1. **闸门判据是代理指标，防“变差”不证“变好”。**章节数、引用集合、篇幅测的都是结构保全；内容写错了但结构完好，闸门照样放行。偏好层的质量仍要靠离线评测与评委，这套闸门不能替代它们。
2. **scoped 反思的前提是可分节。**解析不出结构的稿子直接走整篇回退路径——这条回退不是缺陷，是对边界的承认。
3. **结构本身错了，scoped 无能为力。**需要全局重组（挪节、并节、换叙事线）的问题必须标 whole 走整篇重写；硬塞进逐节编辑只会产出局部通顺、全局失调的稿子。
4. **别把这套闸门套到已有外部信号的环上。**编译自修复环有编译器当裁判、报错指纹无进展即停，是另一种闭环、已是正解；给它再加结构闸门是重复建设。
5. **N 要小。**Self-Refine 报告收益集中在前 1–2 轮（未对抗核验），2601.13217 显示多轮修订的破坏率是瓶颈——预算花在第 5 轮反思上，大概率在为回退买单。

本文与[第 11 篇《确定性边界》](/blog/deterministic-boundary-zh/)呼应：那篇把闸门与 scoped 机制升格为统一的架构原则，本文是该原则在“反思环”这一个闭环上的完整改造记录。

## 参考

- *Beyond Single-shot Writing: Deep Research Agents are Unreliable at Multi-turn Report Revision*（arXiv:2601.13217）——整篇重写式修订平均回退 16–27% 已覆盖内容、引用降质；无引导自反思几乎无效且附带损伤更大；点名解法：结构化编辑计划、专职 Reviser。已读原文核验。
- *PaperOrchestra: A Multi-Agent Framework for Automated AI Research Paper Writing*（arXiv:2604.05018）——结构化多 agent 流水线胜自主基线 +50–68% / +14–38%（已核验）；“改好才收、改差就撤”精修闸门 79–81% 胜率、0% 变差（正文细节，未独立核验）。
- *Self-Refine: Iterative Refinement with Self-Feedback*（arXiv:2303.17651）——反馈须具体可执行、泛化反馈显著掉分；收益集中前 1–2 轮；错误难自检的任务需外部信号。检索摘录，未对抗核验。
- *Reason-Plan-ReAct*（arXiv:2512.03560）——plan/execute 解耦架构（已核验）；“简单任务被 vanilla ReAct 反超”的具体分数摘要中无，未核实。

