跳到主内容
创建时间:最后更新:作者:白川

根据检查结果循环改进

用生成、检查和反馈组成有限循环,明确通过、重复失败与预算耗尽三个结果。

根据检查结果循环改进

一次模型生成不一定满足要求。如果结果可以检查,就可以把不合格原因反馈给模型,再生成一份。但循环必须有停止条件,不能把“再试一次”当作无限可靠的修复办法。

本篇沿用工作流项目,使用同一份函数解释任务。先检查报告是否提到函数名与空数组结果,避免一开始引入复杂评估器。

明确什么算通过

本例要求报告包含 first 和 undefined。这是一个可重复的教学检查,不代表整份报告的语义正确,也不说明源码已经执行。真实任务可以换成 Schema、测试命令结果或更严格的业务检查。

模型负责生成,检查器负责给出可观察结果,程序负责决定是否继续。不要只让模型自报“已修复”就退出。

实现一个有限反馈循环

创建 src/feedback.ts:

typescript
export type Review = { ok: true } | { ok: false; reason: string }
export type LoopResult =
  | { status: "passed"; text: string; attempts: number }
  | { status: "failed"; text: string; reason: string; attempts: number }
  | { status: "exhausted"; text: string; reason: string; attempts: number }

export async function improveReport(
  generate: (feedback: string, signal: AbortSignal) => Promise<string>,
  check: (text: string) => Review,
  signal: AbortSignal,
  maxAttempts = 3,
): Promise<LoopResult> {
  if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
    throw new Error("最大生成次数必须是正整数")
  }
  const seen = new Set<string>()
  let feedback = ""
  let text = ""
  let reason = ""
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    signal.throwIfAborted()
    text = await generate(feedback, signal)
    signal.throwIfAborted()
    const review = check(text)
    if (review.ok) {
      return { status: "passed", text, attempts: attempt }
    }
    reason = review.reason
    if (seen.has(text)) {
      return { status: "failed", text, reason: "重复产生同一份不合格结果", attempts: attempt }
    }
    seen.add(text)
    feedback = reason
  }
  return { status: "exhausted", text, reason, attempts: maxAttempts }
}

成功返回 passed;重复产生同一份不合格文本返回 failed;不同结果仍不能通过且次数用尽时返回 exhausted。取消和生成异常向外抛出,不被强行包装成其中一种业务结果。

重复检测按完整字符串比较,只能捕获相同输出。两份措辞不同但原因相同的失败,需要更细的失败指纹或业务规则,本例没有宣称已经解决。

把模型生成接进去

创建 src/feedback-main.ts:

typescript
import { improveReport } from "./feedback.js"
import { readLesson } from "./lesson.js"
import { askModel } from "./model.js"

const signal = AbortSignal.timeout(30_000)
const input = await readLesson(signal)
const result = await improveReport(
  (feedback, itemSignal) =>
    askModel(
      `解释函数与空数组行为。上一轮检查意见:${feedback || "无"}。源码:\n${input.source}`,
      itemSignal,
    ),
  (text) =>
    text.includes("first") && text.includes("undefined")
      ? { ok: true }
      : { ok: false, reason: "报告必须提及函数 first 和空数组结果 undefined" },
  signal,
)
console.log(result)
if (result.status !== "passed") {
  process.exitCode = 1
}

运行:

bash
node --env-file=.env --import tsx src/feedback-main.ts

教学模拟会一次给出符合关键词要求的固定文本。要观察失败路径,可以临时改变检查要求;自动检查另外使用确定性的生成序列验证通过、重复失败和预算耗尽,不依赖真实模型恰好失败。

把反馈写成可执行的信息

反馈需要说明哪项检查没过,而不是只写“再好一点”。代码中的提示词同时保留原始源码和上一轮意见,避免模型只看到错误描述却失去任务输入。

报告越来越长、轮数越来越多,不代表质量提升。达到次数或时间预算时应该报告未通过,而不是返回最后一份结果并标成成功。

这里的“检查没通过再生成”属于业务反馈循环,与网络超时后的请求重试不同。后者没有得到可用生成结果,处理方式放到失败、超时与重试。

扩展到真实修复任务

如果后续要修改代码并运行测试,应保留每轮修改、测试退出状态和失败输出,避免重复执行危险动作。检查失败时还要区分模型建议错误、工具执行失败与环境问题。

本篇只生成报告,没有修改源文件,也没有执行测试命令。这样可以先验证循环机制,再把更复杂的修复执行器接进来。

参考资料