根据检查结果循环改进
用生成、检查和反馈组成有限循环,明确通过、重复失败与预算耗尽三个结果。
根据检查结果循环改进
一次模型生成不一定满足要求。如果结果可以检查,就可以把不合格原因反馈给模型,再生成一份。但循环必须有停止条件,不能把“再试一次”当作无限可靠的修复办法。
本篇沿用工作流项目,使用同一份函数解释任务。先检查报告是否提到函数名与空数组结果,避免一开始引入复杂评估器。
明确什么算通过
本例要求报告包含 first 和 undefined。这是一个可重复的教学检查,不代表整份报告的语义正确,也不说明源码已经执行。真实任务可以换成 Schema、测试命令结果或更严格的业务检查。
模型负责生成,检查器负责给出可观察结果,程序负责决定是否继续。不要只让模型自报“已修复”就退出。
实现一个有限反馈循环
创建 src/feedback.ts:
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:
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
}运行:
node --env-file=.env --import tsx src/feedback-main.ts教学模拟会一次给出符合关键词要求的固定文本。要观察失败路径,可以临时改变检查要求;自动检查另外使用确定性的生成序列验证通过、重复失败和预算耗尽,不依赖真实模型恰好失败。
把反馈写成可执行的信息
反馈需要说明哪项检查没过,而不是只写“再好一点”。代码中的提示词同时保留原始源码和上一轮意见,避免模型只看到错误描述却失去任务输入。
报告越来越长、轮数越来越多,不代表质量提升。达到次数或时间预算时应该报告未通过,而不是返回最后一份结果并标成成功。
这里的“检查没通过再生成”属于业务反馈循环,与网络超时后的请求重试不同。后者没有得到可用生成结果,处理方式放到失败、超时与重试。
扩展到真实修复任务
如果后续要修改代码并运行测试,应保留每轮修改、测试退出状态和失败输出,避免重复执行危险动作。检查失败时还要区分模型建议错误、工具执行失败与环境问题。
本篇只生成报告,没有修改源文件,也没有执行测试命令。这样可以先验证循环机制,再把更复杂的修复执行器接进来。