BLOG · #Engineering

反思环的受控修订

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

English version: Controlled revision in reflection loops。“LLM 应用工程”系列第 10 篇。

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

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

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 编辑计划:

_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 篇那套)逐节应用:_scoped_reflect_apply 对每条计划项,在当前 working 文本上重新解析定位(前一次 splice 之后 offset 已移动),只把目标节加该条 issue/action 加相关素材发给模型;拿回的片段过体检(非空、花括号配平、环境配平、不带 \documentclass),不过体检就跳过该节——绝不拼回坏片段;过了就 splice 拼回,其余节字节级不动。跨章节、摘要关键词、无法分节的稿子,才回退整篇最小必要重写。

这一步有个实现坑,由内部对抗审查发现(定级[中]):

# 片段必须保持被替换跨度内的顶层 \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 篇

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

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

改造后的闭环全景

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 篇《确定性边界》呼应:那篇把闸门与 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 反超”的具体分数摘要中无,未核实。