别再跟 AI 死磕 prompt 了,我写了个 Loop 让它自己改到满意为止

0 阅读7分钟

我昨天凌晨两点还在跟 Deepseek 较劲。就为了一篇奶龙按摩椅的小红书广告文案,我改了八版 prompt,从 "写个爆款文案" 到 "标题必须带数字,正文不超过 300 字,结尾要有行动号召,要像小红书博主那样说话",结果它要么标题不带数字,要么写了 400 多字,要么结尾就是 "快来购买吧" 这种干巴巴的话。

我盯着屏幕,手指悬在回车上面,突然觉得特别荒谬。我这是在干嘛?不就是生成、检查、不满意、调整 prompt、再生成吗?这不就是个循环吗?我一个写代码的,为什么要自己当这个循环的人肉执行器?

就在这个时候,我刷到了那条 OpenClaw 开源项目创始人彼得·斯坦伯格发布的 780 万浏览的推文。

QQ_1782025498068.png

原来我一直在做最笨的事

说实话,看到这句话的时候我愣了一下。我一直以为用好 AI 的关键是写好 prompt,是把话说清楚,是用各种技巧让 AI 理解我的需求。结果人家开源大佬说,别写 prompt 了,写 Loop。

我翻了翻评论区,发现 Claude Code 的作者也在下面附和,说他现在也不写 prompt 了,只写 Loop。

我突然反应过来,我每天和 AI 的交互,本质上就是一个手动的循环。我给 AI 一个指令,它输出结果,我检查结果是否符合要求,如果不符合,我就修改指令,再让它输出一次。这个过程,除了 "检查" 这一步需要人的判断,其他都是机械重复的。

那为什么不把这个检查也交给 AI 呢?

Loop 到底是什么?说穿了就是三件事

我之前对 Loop 的理解,就是 for 循环、while 循环,就是重复执行一段代码。直到我看到这张图,才突然把这个概念想透了。

image.png

任何一个有用的 Loop,都必须回答这三个问题:

1. 从哪里开始?

2. 重复做什么?

3. 什么时候停止?

就这么简单。你炒菜的时候,从洗锅开始,重复 "炒一下、尝一口、加点盐" 的动作,直到味道合适了就停。你写报告的时候,从第一页开始,重复 "写一段、读一遍、改一改" 的动作,直到领导满意了就停。

缺了第三个问题,就是死循环。程序会一直跑下去,直到内存溢出或者你的 API 额度烧光。

我之前的手动循环,停止条件就是 "我满意了" 或者 "我累了"。现在我要做的,就是把这个停止条件变成代码能理解的规则。

原来大模型自己就是这么学会的

更有意思的是,我们现在用的所有大模型,本身就是用 Loop 训练出来的。

image.png

你看,AI 训练的逻辑,也是一个完美的 Loop:

  • 给模型看一批数据
  • 计算它的预测和正确答案差了多少
  • 根据这个差值调整模型的参数
  • 再拿一批新的数据,重复上面的步骤

万亿次这样的循环之后,AI 就学会了对话、写作、写代码。

那我们用 AI 的时候,为什么不用同样的逻辑呢?让 AI 自己生成,自己检查,自己调整,直到满足我们的要求。

我写了个能跑的最小 Demo

说干就干,我花了半小时搭了个最简单的项目结构。

image.png

因为 Deepseek 的 API 兼容 OpenAI 的格式,所以我直接用了 openai 这个包,省得自己写请求了。

    import { OpenAI } from 'openai';
    import dotenv from 'dotenv';

    dotenv.config();

    const client = new OpenAI({
      apiKey: process.env.DEEPSEEK_API_KEY,
      baseURL: process.env.DEEPSEEK_API_BASE_URL,
    });

    // 注意这三个参数,坑了我半小时
    const limit = {
        maxRound: 5,    // 最多循环5轮,防止死循环
        maxToken: 2000, // 最多消耗2000token,防止烧钱
        sameStop: 2     // 连续2次输出相同内容就停止,防止AI摆烂
    }

    const task = {
        desc: "奶龙按摩椅广告文案",
        rules: ["标题带数字", "正文 < 300 字", "大爆款", "结局有行动号召"]
    }

    let round = 0, totalToken = 0, sameCount = 0, lastText = "";

    // 循环的停止条件,三个满足任意一个就停
    function needStop(){ 
        return round >= limit.maxRound || totalToken >= limit.maxToken || sameCount >= limit.sameStop;
    }

    // 生成文案
    async function gen() {
        const res = await client.chat.completions.create({
            model: process.env.DEEPSEEK_API_MODEL,
            messages: [
                {
                    role: "user",
                    content: `假如你是一位小红书资深广告文案博主,写一篇${task.desc},严格遵守:${task.rules.join('、')},只输出文案`
                }
            ]
        });
        console.log(`消耗token: ${res.usage.total_tokens}` , `\n生成内容:\n${res.choices[0].message.content}`);
        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: process.env.DEEPSEEK_API_MODEL,
            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++;
            console.log(`\n===== 第 ${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(`\n✅ 文案符合要求,通过检查!`);
                console.log(`最终文案:\n${text}`);
                return;
            }
            console.log(`❌ 文案不符合要求,问题:${fail.join('、')}`);
        }
        console.log(`\n⚠️ 触发刹车机制强制停止,最后一次生成的文案:\n${lastText}`);
    }

    runLoop();

然后在.env 文件里填上你的 Deepseek API 密钥和模型就可以跑了。

我踩过的那些坑,你别再踩了

说实话,这个 demo 我写了三遍才跑顺。

第一版我只加了 maxRound,结果有一次 AI 生成的内容一直不合格,循环到第五次就停了,输出的还是一堆垃圾。我当时就想,不行,万一 AI 前五次都没做好,第六次突然开窍了呢?但如果不加 maxRound,万一死循环了怎么办?

后来我想到了加 maxToken,根据你的预算来设置,比如我设置的 2000token,大概也就几毛钱,就算死循环了也损失不大。

最坑的是 sameStop 这个参数。我之前没加这个,结果有一次 AI 连续三次输出一模一样的内容,它自己还觉得没问题,一直在那循环。我看着控制台刷刷刷地跳 token,赶紧把进程杀了。后来才明白,AI 有时候会摆烂,当它觉得自己怎么都满足不了你的要求的时候,就会输出同样的内容敷衍你。这时候就必须有个机制来检测这种情况,及时停止。

一定要设置这三个停止条件!一定要设置这三个停止条件!一定要设置这三个停止条件!不然你早上起来可能会收到一张几百块的 API 账单。

这个方法到底好在哪?又有什么问题?

我跑了几次这个脚本,效果超出我的预期。以前我要花十几分钟反复改 prompt,现在我只需要写好任务描述和检查规则,然后去喝杯咖啡,回来就能拿到符合要求的文案。

它最大的优势就是把你从重复的劳动中解放出来。你不用再盯着屏幕等 AI 输出,不用再一字一句地检查,不用再绞尽脑汁想怎么把 prompt 写得更清楚。你只需要告诉 AI"什么是合格的",剩下的交给循环。

但它也不是没有缺点。最明显的就是 token 消耗高。因为每一轮都要调用两次 API,一次生成,一次检查。我这个 demo 跑一轮大概要消耗 300-500token,跑 5 轮就是 1500-2500token。虽然 Deepseek 的价格很便宜,但如果是更复杂的任务,token 消耗会非常可观。

所以这个方法更适合那些有明确规则、可以量化检查的任务。比如写广告文案、生成测试用例、格式化数据、检查代码规范等等。如果是需要非常有创意、没有明确标准的任务,比如写小说、画插画,这个方法就不太适用了。

最后说几句我真正的收获

昨天晚上写完这个脚本,我躺在床上想了很久。

以前我总觉得,AI 是一个工具,我给它一个指令,它给我一个结果。如果结果不好,那就是我的指令写得不好。所以我一直在研究怎么写更好的 prompt,怎么用更精准的语言描述我的需求。

但现在我明白了,AI 不是一个一次性的工具,它是一个可以迭代的系统。我们不应该追求一次就得到完美的结果,而应该设计一个循环,让 AI 在这个循环里不断地自我修正,直到满足我们的要求。

这大概就是那条推文真正想告诉我们的道理。

如果你也写过这种 AI Loop,或者有更好的停止条件设计,欢迎在评论区告诉我,我也想学习一下。