Skip to content

Agent 自我纠错:Reflexion、Self-Refine 与 CRITIC 怎么选

本文是 AI 应用系统学习系列的 L2 核心篇。前置:Agent 系统三件套:ReAct、工具与记忆。 学完可以配合面试题食用:05 Agent 架构设计25 Agent Benchmark:SWE-bench 与 GAIA

为什么单次生成不够用

让 Agent 一遍写对一段有业务逻辑的代码,相当于让你闭卷一次写对一份合同:不是能力不行,是没有反馈。人对自己的输出也一样——一口气写完就交稿,错别字和逻辑漏洞全在;放半天再回来审稿,问题自己就跳出来了。

模型这边有个稳定的不对称:一遍做对难,挑错相对容易。让它凭空写一个括号匹配函数,可能漏掉嵌套的边界情况;把它的答案和两个测试用例摆在一起让它找 bug,准确率明显更高。自我纠错(self-correction)就是把这个不对称工程化:生成、检查、修改拆成循环来跑,而不是指望一把过。

先泼冷水:自我纠错不免费。每多一轮,token 成本至少翻倍,方向错了还会越改越差。三种主流做法——Self-Refine、Reflexion、CRITIC——区别就在三个问题上:谁来检查、检查什么、检查结果怎么用

Self-Refine:自己写,自己评,自己改

最直接的循环:生成 → 自评 → 修改,回到自评,直到分数不再涨或到达轮数上限。原论文(Madaan et al., 2023)在 GPT-4 上测了 7 类任务,平均绝对提升约 20%。这些任务有个共同点:质量标准能写进 prompt,比如「摘要不超过 100 字、必须包含结论」。

python
# Self-Refine 最小循环:生成 → 自评 → 修改,分数不涨就停
draft = llm(f"{task}\n请直接给出答案")
best_score = -1
for _ in range(3):                                  # 硬上限 3 轮
    review = llm(f"按标准打分(0-10)并列出问题:{rubric}\n\n{draft}")
    score = int(review.splitlines()[0])             # 约定第一行是分数
    if score <= best_score:                         # 不再提升就停,别来回改
        break
    best_score = score
    draft = llm(f"按问题清单修改:{review}\n\n{draft}")

什么时候越改越差:没有外部标准的任务。写营销文案,「更有感染力」没法打分,模型每一轮的口味还不一样,第三轮常常把第一轮的好句子改没了。Huang et al.(ICLR 2024)的结论更直接:没有外部反馈时,让模型原地修改推理题答案,GSM8K 上的准确率不升反降。模型对自己的输出有偏好,看自己的答案总顺眼,改着改着就把对的改成错的。

判断标准一句话:rubric 能写清楚就上 Self-Refine,写不清楚就别上。

Reflexion:失败之后,先记教训再重试

普通重试和 Reflexion 的区别一句话说清:重试不带教训,只是把骰子再扔一次;Reflexion 失败后先回答三个问题——错在哪、为什么错、下次怎么改——把这段话写进记忆,下一轮生成时连同任务一起塞进 prompt。

mermaid
sequenceDiagram
    participant A as Agent
    participant T as 测试/解释器
    participant M as 经验记忆
    A->>T: 第 1 轮:生成代码并运行测试
    T-->>A: FAIL + 报错栈
    A->>M: 写入教训(错在哪 / 为什么 / 下次怎么改)
    A->>T: 第 2 轮:带教训重新生成
    T-->>A: FAIL + 新报错
    A->>M: 追加教训(与旧教训合并去重)
    A->>T: 第 3 轮:换思路再试
    T-->>A: PASS
    Note over A,M: 3 轮上限,仍失败则人工介入

这相当于把强化学习的奖励信号换成自然语言:模型权重不动,改的只是上下文里的经验,原论文管这叫 verbal reinforcement learning。效果有数字:HumanEval 上 ReAct 基线 pass@1 是 80.1%,加 Reflexion 到 91.0%(Shinn et al., 2023)。

前提也明确:任务必须能自动判定失败。代码跑不过测试、工具调用返回异常,这些是清晰的失败信号,教训才有得写。如果「失败」本身要靠模型感觉,Reflexion 就退化成 Self-Refine 的老问题。

CRITIC:裁判必须是外部工具

模型自查的可靠性天花板不高,那就别让它批改自己。CRITIC(Gou et al., 2023)的思路:生成之后接一个工具验证环节——搜索引擎查事实、Python 解释器算数、单元测试验代码,验证不过的反馈再喂回去修改。

代码 Agent 是这套打法收益最大的场景,原因很朴素:编译器和测试是最诚实的评委,报错信息就是现成的反思材料TypeError: unsupported operand type(s) 直接告诉你第几行什么类型错了,比模型「感觉这里不太对」精确一个量级。SWE-bench 榜上排名靠前的 agent,无一例外都靠「跑测试 → 读报错 → 修改」这个循环刷分。

事实和计算同理。让模型心算 3847 × 2931,算错了它自己看不出来;让它写代码算再执行,对错一目了然。

怎么选,以及怎么停

场景用什么理由
文案、摘要,rubric 能写清楚Self-Refine,2-3 轮封顶自评够用,标准明确
代码、工具调用,成败可自动判定Reflexion,常配 CRITIC失败信号清晰,教训可积累
事实、计算,能调外部工具CRITIC工具验证比自查可靠

三个停止条件,命中任意一个就停:

  • 硬上限:最多 3 轮。经验值,3 轮还过不了的任务,多半是任务描述或测试本身有问题
  • 分数平台:连续两轮自评分差小于 1,停
  • 已通过验证:测试全绿立刻停,别为了「再优化一下」继续烧 token

预算先算再开跑。一轮 Self-Refine ≈ 生成 + 自评 + 修改 = 3 倍单次成本;Reflexion 最坏情况(3 轮全失败)≈ 4 次生成 + 3 次反思,7 倍起步。反例:不设轮数上限的反思循环遇上一个死循环 bug,agent 一晚上能烧掉几百块 token 费,最后输出的还是错的。

反思是叠加层,不是独立范式

纠正一个常见误解:Reflexion 不是和 ReAct 并列的新范式,它是个外挂循环——ReAct 或 Plan-and-Execute 照常跑,跑挂了带教训重跑。ReAct 单步的「观察」是即时反馈,Reflexion 的记忆是跨轮次的经验,两层反馈不冲突。实操里的常见组合:主体用 ReAct,外层套 Reflexion,每步的工具结果当 CRITIC 的验证用。

动手实操:手写 Reflexion 循环

完整流程:生成代码 → 落盘跑 pytest → 失败则把报错和教训写入记忆 → 换思路重试,最多 3 轮。

python
# reflexion_loop.py — 手写 Reflexion 循环
import subprocess, tempfile, pathlib
from openai import OpenAI

client = OpenAI()          # 任意 OpenAI 兼容端点均可
MODEL = "your-model"

def llm(prompt: str) -> str:
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=0.7,
    )
    return resp.choices[0].message.content

def extract_code(text: str) -> str:
    # 取第一个 ```python 块;模型偶尔忘记加围栏,兜底取全文
    if "```python" in text:
        return text.split("```python")[1].split("```")[0].strip()
    return text

TESTS = """\
from solution import is_balanced

def test_basic():
    assert is_balanced("()[]{}")
    assert not is_balanced("(]")

def test_nested():
    assert is_balanced("{[()]}")
    assert not is_balanced("([)]")

def test_empty():
    assert is_balanced("")
"""

def run_tests(solution: str) -> tuple[bool, str]:
    """生成代码落盘,pytest 跑一遍,返回 (是否通过, 报错输出)"""
    with tempfile.TemporaryDirectory() as d:
        p = pathlib.Path(d)
        (p / "solution.py").write_text(solution)
        (p / "test_solution.py").write_text(TESTS)
        r = subprocess.run(
            ["python", "-m", "pytest", "-x", "-q", str(p)],
            capture_output=True, text=True, timeout=60,
        )
        return r.returncode == 0, (r.stdout + r.stderr)[-2000:]

def reflect(task: str, code: str, error: str, lessons: list[str]) -> str:
    """失败后让模型总结:错在哪、为什么、下次怎么改(不重写代码)"""
    history = chr(10).join(lessons) or "(无)"
    prompt = f"""任务:{task}

上一版代码(测试未通过):
{chr(96)*3}python
{code}
{chr(96)*3}

测试报错:
{error}

历史教训:
{history}

用三句话总结:1) 错在哪 2) 根本原因 3) 下一版要换什么思路。不要重写代码。"""
    return llm(prompt)

TASK = "实现 is_balanced(s):判断括号串是否配对,只含 ()[]{} 三种括号"

def solve(task: str, max_rounds: int = 3) -> str:
    lessons, prev_code = [], None
    for i in range(1, max_rounds + 1):
        prompt = f"{task}\n\n只给一个代码块,不要解释。\n"
        if lessons:                                   # 教训进 prompt,下一轮生效
            prompt += "\n之前失败的教训,这次必须避开:\n" + \
                "\n".join(f"- {l}" for l in lessons) + "\n"
        if prev_code:                                 # 给上一版,要求换思路
            prompt += f"\n上一版代码,换思路,不要原样重交:\n{prev_code}\n"
        code = extract_code(llm(prompt))
        ok, err = run_tests(code)
        print(f"[round {i}] {'PASS' if ok else 'FAIL'}")
        if ok:
            return code
        prev_code = code
        lessons.append(reflect(task, code, err, lessons))
    raise RuntimeError("3 轮未通过,人工介入")

if __name__ == "__main__":
    print(solve(TASK))

典型输出:

text
[round 1] FAIL
[round 2] FAIL
[round 3] PASS

两个工程细节:

  • 教训要合并去重。3 轮以后 memory 里常有重复教训,合并成一条再进 prompt,上下文不白烧
  • 失败代码别丢。下一轮把上一版给模型并注明「换思路」,比让它从零再来快

常见误区

  • 把自评分当质量保证:自评分和真实正确率相关性弱,7 分和 8 分没区别,只当停止信号用
  • 没有标准还硬上 Self-Refine:主观任务越改越摇摆,两轮就该停
  • 轮数无上限:反思最坏 7 倍 token,预算和轮数必须写死在代码里
  • 教训只加不减:失败十几轮的 agent 塞满过期教训,老教训要合并归档,别全量进 prompt
  • 把反思当新架构:没必要推翻 ReAct 重写,反思是外层循环,主体不动

小结

这篇在路径里的位置:ReAct(35 篇)解决怎么行动,本篇解决行动错了怎么补救——这是 Agent 可靠性环节的核心。选型记三句话:能外部验证就 CRITIC,能自动判定成败就 Reflexion,rubric 写得清楚才 Self-Refine。反思是叠加层,挂在你现有的 agent 外面,不是替代它。

下一篇:50 篇讲 Agent Skills——把领域知识和操作流程打包成可插拔的技能模块。

参考

  • Reflexion: Shinn et al., 2023 — arXiv:2303.11366
  • Self-Refine: Madaan et al., 2023 — arXiv:2303.17651
  • CRITIC: Gou et al., 2023 — arXiv:2305.11738;无外部反馈时自我纠错失效的反例:Huang et al., ICLR 2024

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1