深入理解 AI Loop:从 Prompt 工程到 Agent 循环控制实战
在大模型应用发展的早期阶段,开发者关注的重点往往是 Prompt Engineering(提示词工程)。为了让模型输出更符合预期的结果,我们会不断修改 Prompt、调整角色设定、补充约束条件,甚至通过 Few-Shot Learning 提供示例来引导模型完成任务。
然而随着 Agent 技术的发展,越来越多开发者开始意识到一个问题:仅靠一次 Prompt 调用,很难稳定完成复杂任务。
前段时间,一条关于 AI Agent 的推文获得了数百万浏览量,其中提到一个非常有意思的观点:
别再给 AI 写 Prompt 了,你应该去设计 Loop。
Claude Code 的开发者也曾表达过类似的看法。原因并不复杂,当任务开始变得复杂时,决定 Agent 能力上限的往往不再是 Prompt 本身,而是 Agent 如何循环执行任务、检查结果并持续改进。
本文将通过一个完整的 AI Loop 实战案例,深入理解 Loop 的工作原理,并带大家从代码层面分析 生成器(Generator)、检查器(Checker) 以及 循环控制器(Loop Controller) 的设计思路。
一、从 Prompt 到 Loop:为什么 Agent 时代开始强调循环
在计算机科学中,Loop(循环) 并不是一个新概念。
无论是 for 循环还是 while 循环,本质上都在解决同一个问题:
- 从哪里开始执行
- 重复执行什么任务
- 在什么条件下停止
例如下面这段最简单的循环:
while(condition){
doSomething();
}
虽然只有短短几行代码,但已经完整包含了循环结构的核心思想。
首先程序会根据某个条件进入循环,然后不断执行指定任务,直到条件不再满足后退出循环。
事实上,Loop 不仅是编程语言中的基础语法,也是现代人工智能系统的重要组成部分。
以大模型训练为例,模型并不是一次训练完成的。
整个训练过程实际上就是一个规模极其庞大的循环:
训练数据
↓
模型预测
↓
计算误差
↓
更新参数
↓
继续训练
然后不断重复这一过程:
训练
↓
计算损失
↓
调整参数
↓
再次训练
经过数十亿甚至数万亿次循环后,大模型逐渐获得理解语言和生成内容的能力。
换句话说,今天我们使用的 DeepSeek、Claude、Qwen 等大模型,其底层训练过程本身就是一个巨大的 Loop。
不仅模型训练如此,我们平时与 AI 的交互过程其实也是一种隐性的循环。
例如下面这个场景:
编写 Prompt
↓
查看结果
↓
不满意
↓
修改 Prompt
↓
重新生成
很多开发者每天都在重复这个过程。
第一次生成的结果不符合预期,于是修改 Prompt;第二次结果仍然存在问题,于是继续优化;经过多次尝试后,最终得到满意的答案。
这个过程本质上属于一种 Human Loop(人工循环)。
在整个流程中,人类负责检查结果、发现问题并决定下一步操作。
虽然这种方式能够获得较好的结果,但也存在明显缺点:
整个过程高度依赖人工参与。
对于简单任务或许问题不大,但当任务需要大量重复执行时,人工检查将成为整个系统的瓶颈。
因此,Agent 的核心目标之一就是将这种人工循环自动化。
将原来的:
Human
↓
Prompt
↓
LLM
↓
人工检查
↓
重新生成
转变为:
LLM
↓
生成
↓
检查
↓
重新生成
↓
再次检查
也就是:
Completion
↓
Check
↓
Retry
不断循环,直到满足目标条件。
这种模式就是现代 Agent 系统 中最常见的 AI Loop。
与传统 Prompt 调用相比,Loop 最大的优势在于能够将开发者从重复检查工作中解放出来。
系统不再依赖人工逐次审核结果,而是由程序自动完成验证和重试过程。
当然,这种能力并非没有代价。
由于每一轮循环都会额外调用模型,因此 Token 消耗 也会快速增长。
例如:
生成一次
检查一次
实际上已经产生两次模型调用。
如果循环执行五轮,那么就会产生十次调用。
因此在 Agent 系统中,如何控制循环次数、限制 Token 消耗以及避免无限循环,同样是非常重要的问题。
而接下来我们要分析的代码,正是一个典型的 AI Loop 实现。
在这个案例中,我们将构建一个能够自动生成小红书文案、自动检查规则并自动重试的 AI Agent,并通过代码分析理解整个循环控制流程的设计思想。
二、构建一个最简单的 AI Loop
在理解了 Loop 的基本思想之后,接下来我们通过一个实际案例来看看如何利用大模型构建一个简单的 AI Loop。
本文案例的目标非常明确:
自动生成符合要求的小红书美妆文案,并自动检查生成结果是否满足规则。如果不满足,则继续生成,直到满足要求或者触发停止条件。
整个系统的运行流程如下:
开始
↓
生成文案(Generator)
↓
检查文案(Checker)
↓
是否通过?
↓ ↓
是 否
↓ ↓
结束 下一轮生成
从架构角度来看,这个案例实际上由三个核心模块组成:
Generator(生成器)
↓
Checker(检查器)
↓
Loop Controller(循环控制器)
其中:
Generator负责调用大模型生成内容Checker负责校验生成结果是否满足规则Loop Controller负责控制循环的执行和退出
整个项目的完整代码如下:
import { OpenAI } from 'openai';
import dotenv from 'dotenv';
dotenv.config();
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_BASE_URL
});
const limit = {
maxRound: 5,
maxToken: 2000,
sameStop: 2
}
const task = {
desc: "小红书美妆文案",
rules: ["标题带数字", "正文<300字", "大爆款", "结尾有行动号召"]
}
let round = 0,
totalToken = 0,
lastText = "",
sameCount = 0;
function needStop() {
return round >= limit.maxRound ||
totalToken >= limit.maxToken ||
sameCount >= limit.sameStop;
}
async function gen() {
const res = await client.chat.completions.create({
model: 'deepseek-v4-flash',
messages: [{
role: 'user',
content: `假如你是一位资深小红书美妆博主,
写一篇${task.desc}, 严格遵守:
${task.rules.join("、")}, 只输出文案`
}]
});
return {
text: res.choices[0].message.content.trim(),
token: res.usage.total_tokens
}
}
async function check(text) {
const res = await client.chat.completions.create({
model: "deepseek-v4-flash",
messages: [
{
role: "user",
content: `校验文案:${text}
规则:${task.rules.join("、")},
仅输出JSON {pass:布尔, fail:数组}`
}
]
});
return JSON.parse(
res.choices[0].message.content.trim()
);
}
async function runLoop() {
console.log('AI Loop 开始');
while(!needStop()) {
round++;
const { text, token } = await gen();
totalToken += token;
sameCount =
text === lastText
? sameCount + 1
: 0;
lastText = text;
const { pass, fail } = await check(text);
if(pass){
console.log("全部规则通过");
console.log(text);
return;
}
console.log(`不满足:${fail}`);
}
console.log(`触发停止条件:${lastText}`);
}
runLoop();
如果从传统开发的角度来看,这段代码并不复杂。
真正值得关注的是它所体现出的 Agent 工作模式。
在传统程序中,逻辑通常是固定的:
输入
↓
处理
↓
输出
例如:
const result = add(1,2);
程序运行一次就结束了。
但是在 AI 应用中,情况并非如此。
由于大模型本身具有概率性,同样的 Prompt 多次调用可能会产生不同结果,因此一次生成并不能保证一定满足需求。
例如:
第一次生成:
标题没有数字
第二次生成:
正文超过300字
第三次生成:
缺少行动号召
如果每次都需要开发者手动检查,再决定是否重新生成,那么整个流程实际上仍然停留在 Human Loop 阶段。
因此我们需要把原来由人完成的工作交给程序自动执行:
生成
↓
检查
↓
不通过
↓
重新生成
这正是 AI Loop 的核心思想。
从某种意义上来说,这段代码实际上是在模拟开发者平时使用 ChatGPT 或 DeepSeek 的过程。
当我们与 AI 对话时,通常会经历这样的流程:
发送 Prompt
↓
获得结果
↓
检查结果
↓
发现问题
↓
重新生成
而 AI Loop 所做的事情,就是把这套流程程序化。
开发者不再亲自参与每一次检查,而是提前定义好规则,让系统自动完成生成、验证和重试。
因此,AI Loop 的本质并不是让模型变得更聪明,而是通过循环机制让模型拥有持续修正结果的能力。
这也是为什么越来越多的 Agent 框架开始强调:
Prompt 决定单次输出质量,而 Loop 决定任务最终完成质量。
三、代码实现:生成器、检查器与循环控制器
理解整体架构之后,接下来正式进入代码分析阶段。
虽然整个项目只有几十行代码,但已经完整包含了一个 AI Loop 所需的核心组件。为了便于理解,我们按照程序执行顺序逐步分析。
首先是依赖导入部分:
import { OpenAI } from 'openai';
import dotenv from 'dotenv';
dotenv.config();
这里使用了两个第三方库。
openai 用于调用大模型 API。
虽然我们实际使用的是 DeepSeek 模型,但由于 DeepSeek 兼容 OpenAI API 规范,因此可以直接使用 OpenAI 官方 SDK 进行访问。
而 dotenv 的作用则是读取环境变量文件。
例如项目根目录下的 .env 文件:
DEEPSEEK_API_KEY=sk-xxxxxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com
执行:
dotenv.config();
之后,程序便可以通过:
process.env.DEEPSEEK_API_KEY
读取配置。
这样做最大的好处是避免将 API Key 直接写在代码中。
如果将密钥硬编码到项目里:
const apiKey = "sk-xxxxxxxx";
一旦代码上传到 GitHub,就有可能造成密钥泄露。
因此在实际开发中,敏感信息通常都会存放在环境变量中统一管理。
完成配置读取后,接下来创建大模型客户端:
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_BASE_URL
});
这里的 client 可以理解为程序与大模型之间建立的连接对象。
后续所有模型调用都会通过它完成。
例如:
client.chat.completions.create(...)
本质上就是向模型发送请求。
接下来是任务配置部分:
const task = {
desc: "小红书美妆文案",
rules: [
"标题带数字",
"正文<300字",
"大爆款",
"结尾有行动号召"
]
}
很多初学者看到这里可能会觉得:
直接把内容写进 Prompt 不就行了吗?
实际上这里体现的是一种非常重要的设计思想——配置驱动(Configuration Driven)。
如果把所有要求直接写死:
content: "写一篇小红书美妆文案..."
那么以后每次更换任务都需要修改代码。
而通过配置对象:
task.desc
task.rules
程序逻辑与业务需求被分离开来。
例如:
const task = {
desc:"旅游攻略",
rules:["100字以内"]
}
甚至不需要修改 Generator 代码,就能完成新的任务。
在很多 Agent 框架中,经常能够看到类似结构:
goal
rules
constraints
memory
本质上都是为了让系统具备更强的可配置能力。
接下来是停止条件配置:
const limit = {
maxRound: 5,
maxToken: 2000,
sameStop: 2
}
这一部分看起来很简单,但实际上非常重要。
因为 AI Loop 最大的风险之一就是:
无限循环。
例如下面的代码:
while(true){
await gen();
}
理论上它永远不会停止。
如果模型始终无法满足要求:
生成
↓
失败
↓
生成
↓
失败
程序就会一直运行下去。
不仅浪费 Token,还可能导致额外费用。
因此 Agent 系统通常都会设计多重退出机制。
maxRound 表示最大执行轮数:
maxRound:5
即使一直失败,也最多执行五轮。
maxToken 表示预算限制:
maxToken:2000
所有请求消耗的 Token 总和超过 2000 后立即停止。
这实际上是一种非常常见的 Token Budget 控制策略。
因为在真实项目中:
Token = 成本
如果不控制预算,Agent 的运行成本可能远超预期。
而最后一个参数:
sameStop:2
则用于检测模型是否陷入重复输出。
例如:
第一次:
文案A
第二次:
文案A
第三次:
文案A
此时继续执行已经没有意义。
说明模型可能进入某种固定模式。
因此系统会主动终止循环。
配置完成后,程序需要维护一些运行状态:
let round = 0,
totalToken = 0,
lastText = "",
sameCount = 0;
这些变量实际上构成了整个 Agent 的状态信息。
其中:
round
记录当前执行轮数。
例如:
第1轮
第2轮
第3轮
totalToken
记录累计消耗:
400
+
500
+
300
=
1200
用于后续预算控制。
lastText
保存上一轮生成结果。
例如:
上一轮:
7天逆袭冷白皮
用于判断是否出现重复输出。
sameCount
用于统计连续重复次数。
这是 Agent 中一种非常简单但有效的状态管理方式。
随着 Agent 规模不断扩大,这部分状态甚至会演化为:
Memory
Context
Knowledge Base
等更加复杂的模块。
完成状态定义后,程序实现了一个统一的停止判断函数:
function needStop() {
return round >= limit.maxRound ||
totalToken >= limit.maxToken ||
sameCount >= limit.sameStop;
}
这里出现了多个逻辑或运算符:
||
表示:
或者
只要其中任意一个条件成立:
true
函数就会返回停止信号。
例如:
执行轮数达到5
或者:
Token 超过2000
或者:
连续两次输出相同内容
都会导致循环退出。
这种设计方式的优点在于将所有停止逻辑集中管理。
后续如果需要增加新的退出条件,只需要修改这一个函数即可。
至此,整个 AI Loop 的基础框架已经搭建完成。
接下来,我们将进入最核心的部分:Generator 与 Checker 的实现原理,看看程序究竟是如何利用大模型完成“生成”和“检查”这两个关键步骤的。
四、Loop 为什么能够替代人工校验
在前面的代码中,我们已经完成了整体框架的搭建,但真正让 AI Loop 运转起来的核心其实是两个模块:
Generator
↓
Checker
对应代码中的:
gen()
和:
check()
这两个函数共同组成了整个 Agent 的执行闭环。
Generator:负责生成内容
首先来看文案生成模块:
async function gen() {
const res = await client.chat.completions.create({
model: 'deepseek-v4-flash',
messages: [{
role: 'user',
content: `假如你是一位资深小红书美妆博主,
写一篇${task.desc}, 严格遵守:
${task.rules.join("、")}, 只输出文案`
}]
});
return {
text: res.choices[0].message.content.trim(),
token: res.usage.total_tokens
}
}
从功能角度来看,这部分代码的职责非常简单:
发送 Prompt
↓
获取结果
↓
返回结果
这里最核心的是:
client.chat.completions.create()
这是 OpenAI SDK 提供的聊天接口。
调用后会向模型发送请求,并返回生成结果。
请求参数主要包含两个部分:
model
messages
其中:
model:'deepseek-v4-flash'
用于指定模型。
而:
messages:[]
则用于构建对话内容。
例如:
messages:[
{
role:"user",
content:"你好"
}
]
表示用户向模型发送了一条消息。
在本案例中:
content: `
假如你是一位资深小红书美妆博主,
写一篇${task.desc},
严格遵守:
${task.rules.join("、")}
`
实际上就是动态拼接 Prompt。
假设当前配置为:
const task = {
desc:"小红书美妆文案",
rules:[
"标题带数字",
"正文<300字",
"大爆款",
"结尾有行动号召"
]
}
那么最终发送给模型的内容大致为:
假如你是一位资深小红书美妆博主,
写一篇小红书美妆文案,
严格遵守:
标题带数字、
正文<300字、
大爆款、
结尾有行动号召
只输出文案
模型收到请求后开始生成结果。
返回数据通常类似于:
{
choices:[
{
message:{
content:"7天逆袭冷白皮..."
}
}
],
usage:{
total_tokens:368
}
}
因此代码中通过:
res.choices[0].message.content
获取生成内容。
通过:
res.usage.total_tokens
获取本次调用消耗的 Token。
最终返回:
return {
text,
token
}
这样后续循环既能获得文案内容,也能统计资源消耗。
Checker:让 AI 检查 AI
接下来来到整个案例最有意思的部分。
async function check(text) {
...
}
很多初学者第一次看到这里都会有一个疑问:
AI 写出来的东西,再让 AI 自己检查,有意义吗?
答案是:
有,而且这是当前 Agent 领域非常常见的做法。
这种模式有一个专门的名称:
LLM as a Judge
即:
让大模型担任评审员。
其核心思想是:
一个模型负责生成
↓
一个模型负责评估
虽然案例中使用的是同一个模型,但逻辑上已经分成了两个角色。
来看具体实现:
async function check(text) {
const res = await client.chat.completions.create({
model: "deepseek-v4-flash",
messages: [
{
role: "user",
content: `
校验文案:${text}
规则:
${task.rules.join("、")}
仅输出JSON
{pass:布尔, fail:数组}
`
}
]
});
return JSON.parse(
res.choices[0].message.content.trim()
);
}
这里并没有要求模型生成新的文案。
而是让模型判断:
这篇文案是否符合规则?
例如:
7天逆袭冷白皮!
姐妹们最近发现一个...
...
快来试试吧!
模型会根据规则进行分析:
标题是否包含数字?
正文是否超过300字?
是否具有爆款风格?
结尾是否有行动号召?
然后返回结果。
理想情况下返回:
{
"pass": true,
"fail": []
}
表示全部通过。
或者:
{
"pass": false,
"fail": [
"标题没有数字",
"缺少行动号召"
]
}
表示存在问题。
此时系统就获得了自动判断能力。
为什么要求只输出 JSON?
这里有一个非常重要的细节:
仅输出JSON
很多初学者容易忽略这一点。
如果没有这个限制:
请检查下面的文案...
模型可能返回:
当然可以!
经过检查后发现:
{
"pass":true
}
看起来没问题。
但程序执行:
JSON.parse(...)
时会直接报错。
因为:
JSON.parse()
只能解析合法 JSON。
下面这种格式:
{
"pass": true
}
可以解析。
而:
当然可以!
{
"pass": true
}
则不是合法 JSON。
因此在实际开发中,我们通常会明确要求:
只输出 JSON
这样程序才能稳定处理结果。
JSON.parse() 的作用
最后来看这行代码:
JSON.parse(
res.choices[0].message.content.trim()
)
模型返回的数据本质上是字符串:
'{"pass":true,"fail":[]}'
而程序真正需要的是对象:
{
pass:true,
fail:[]
}
因此需要通过:
JSON.parse()
进行转换。
转换完成后:
const { pass, fail } = await check(text);
便可以直接使用:
if(pass)
判断是否通过。
至此,一个完整的闭环已经形成:
Generator
↓
生成文案
↓
Checker
↓
规则校验
↓
返回结果
如果通过:
结束
如果失败:
进入下一轮
而真正负责控制这一切的,则是最后的循环控制器 runLoop()。
五、当前实现的局限与 Agent 的进化方向
完成 Generator 和 Checker 的设计之后,整个 AI Loop 最后由 runLoop() 负责调度执行。
async function runLoop() {
console.log('AI Loop 开始');
while(!needStop()) {
round++;
const { text, token } = await gen();
totalToken += token;
sameCount =
text === lastText
? sameCount + 1
: 0;
lastText = text;
const { pass, fail } = await check(text);
if(pass){
console.log("全部规则通过");
console.log(text);
return;
}
console.log(`不满足:${fail}`);
}
console.log(`触发停止条件:${lastText}`);
}
从执行流程来看,这部分代码实际上就是整个 Agent 的大脑。
程序启动后首先进入:
while(!needStop())
循环。
这里的意思是:
只要没有触发停止条件
就继续执行
随后开始执行每一轮任务。
首先记录当前轮数:
round++;
例如:
第1轮
第2轮
第3轮
...
这部分数据会被后续的停止条件使用。
接着调用 Generator:
const { text, token } = await gen();
这里使用了对象解构语法。
Generator 返回的数据格式为:
{
text:"生成内容",
token:368
}
因此可以直接获取:
text
和:
token
两个变量。
其中:
text
保存生成结果。
token
保存本次调用消耗的 Token 数量。
随后更新总预算:
totalToken += token;
例如:
第一轮 300
第二轮 400
第三轮 500
则:
totalToken = 1200
这样系统能够实时监控整体成本。
接下来是一个非常有意思的设计:
sameCount =
text === lastText
? sameCount + 1
: 0;
很多初学者第一次看到这里可能会有些困惑。
实际上它等价于:
if(text === lastText){
sameCount++;
}else{
sameCount = 0;
}
作用非常简单:
判断本次生成结果是否与上次一致。
例如:
第一轮:
文案A
第二轮:
文案A
则:
sameCount = 1
如果第三轮仍然是:
文案A
则:
sameCount = 2
达到阈值后系统会自动停止。
因为这通常意味着模型已经陷入重复输出状态。
继续循环只会消耗更多 Token,而不会带来更好的结果。
这种设计本质上是一种简单的防死循环机制。
随后更新历史记录:
lastText = text;
用于下一轮比较。
然后进入检查阶段:
const { pass, fail } = await check(text);
如果返回:
{
pass:true,
fail:[]
}
说明所有规则全部满足。
程序执行:
if(pass){
...
}
输出最终结果并结束。
return;
执行后会直接退出整个函数。
因此循环也会随之结束。
如果检查失败:
{
pass:false,
fail:[
"标题没有数字"
]
}
则程序进入:
console.log(`不满足:${fail}`);
然后重新回到下一轮循环。
整个过程如下:
第1轮
↓
生成
↓
检查
↓
失败
↓
第2轮
第2轮
↓
生成
↓
检查
↓
失败
↓
第3轮
第3轮
↓
生成
↓
检查
↓
通过
↓
结束
这就是一个最基础的 AI Loop。
不过从 Agent 的角度来看,这套实现仍然存在明显局限。
最关键的问题在于:
模型并没有真正利用失败信息。
例如第一轮返回:
标题没有数字
理论上最合理的做法应该是:
告诉模型:
你的标题没有数字
请保留原内容
重新修改
但当前代码并没有这么做。
当校验失败后:
const { text, token } = await gen();
下一轮仍然会重新生成。
也就是说:
失败
↓
完全重来
而不是:
失败
↓
分析原因
↓
针对性修复
这也是为什么它能够被称为 AI Loop,却还不能算是真正意义上的 Agent。
目前主流 Agent 框架普遍采用的是:
Generate
↓
Evaluate
↓
Reflect
↓
Retry
模式。
即:
生成
↓
评估
↓
反思
↓
重试
例如:
生成文案
↓
发现标题没有数字
↓
记录错误原因
↓
请修复标题问题
↓
再次检查
这种方式被称为:
Reflection(反思机制)
也是现代 Agent 体系中的重要能力之一。
除此之外,真正的 Agent 往往还会具备更多能力。
例如:
Memory(记忆)
记录历史任务信息。
上一次失败原因
用户偏好
历史上下文
都可以被存储和利用。
Tool Calling(工具调用)
让 Agent 不再局限于生成文本。
例如:
搜索网页
查询数据库
读取文件
执行代码
这些能力都可以通过工具调用实现。
Planning(任务规划)
面对复杂目标时:
直接完成任务
往往效果较差。
因此 Agent 会先拆解任务:
分析需求
↓
制定计划
↓
逐步执行
↓
汇总结果
从而提升成功率。
回过头来看,本文实现的案例虽然规模很小,但已经完整展现了 AI Agent 最核心的思想:
生成
↓
检查
↓
循环
↓
退出
它虽然还不具备记忆、反思和工具调用能力,但已经拥有了 Agent 的雏形。
而现代 AI Agent 的各种高级能力,本质上也都是在这个循环框架之上不断扩展出来的。
总结
本文通过一个自动生成小红书文案的案例,实现了一个最基础的 AI Loop。
在整个系统中:
Generator
负责内容生成;
Checker
负责规则校验;
Loop Controller
负责循环调度与退出控制。
通过这种方式,原本需要人工完成的:
生成
↓
检查
↓
修改
↓
重新生成
被程序自动接管,形成完整的自动化闭环。
从更宏观的角度来看,无论是大模型训练、Prompt 调优还是 Agent 执行,本质上都离不开 Loop。
因此对于 Agent 开发而言,真正重要的往往不是如何写出更长的 Prompt,而是如何设计出更合理的 Loop。
因为 Prompt 决定的是模型一次能做多好,而 Loop 决定的是系统最终能走多远。


780

被折叠的 条评论
为什么被折叠?



