最近在好几个技术社区里刷到“自进化 Agent”和“RSI”这两个词连着出现,第一反应以为是金融里的相对强弱指标,结果点进去才发现,这里说的 RSI 是 Recursive Self-Improvement,递归自我改进。简单说,就是让 AI 自己改进自己:改进完一轮之后,拿着改进后的版本继续改进下一轮,循环往复。这篇是“深度学习小白笔记”系列的第 104 篇,写给已经跑过几个 Agent 项目、想往“自我进化”方向再走一步的同学。我会把自进化 Agent 的概念拆开,讲清楚 RSI 的核心回路,再用一个极简的 Prompt 自进化 Demo 带大家上手,最后聊一聊我在实操里踩过的坑。如果你还没做过 Agent,只要懂一点 Python、能调用 LLM 接口,也能跟上。
1. 自进化 Agent 到底是什么
1.1 从“会调用工具的 Agent”到“能改自己的 Agent”
传统的 Agent 再复杂,本质还是“推理 + 规划 + 调用工具”。你给它一个任务,它在内部把任务拆成几步,决定先干什么后干什么,然后调用外部工具去执行,执行完再把结果拿回来继续思考。整个过程里,Agent 是一个稳定的执行器:它的行为模式、提示词、工具调用方式都是预先定好的。你可以改进它,但那是你作为人类在做的事,Agent 自己不会动自己的配置。
自进化 Agent 多了一个关键能力:它能把“改进自己”也当成一个任务来执行。比如,它发现自己处理某一类问题时老是出错,于是自动生成一个新的工具函数、修改自己的 system prompt,甚至调整自己的规划策略,然后用一批测试样本验证这个改动是否真的有效。有效就保留,无效就回滚。这个回路一旦跑起来,就不再是人推着 AI 变强,而是 AI 自己找自己的弱点,自己给自己打补丁。
我用一个生活化的类比帮你去理解:普通 Agent 是一个熟练车间的工人,手艺好,按流程干活;自进化 Agent 是同一个工人,但同时还兼任工艺工程师,下班之后会拿自己的产品做质量检测,发现自己某个工序良品率不行,就直接改造工装夹具,第二天再验证改造效果。这个人不是在“被优化”,而是在“自我优化”。
1.2 一个很容易叫混的缩写:RSI 在深度学习里指什么
如果你去搜 RSI,第一屏大概率是金融领域的“相对强弱指标”,那个东西跟 AI 自我改进没有关系。在深度学习、强化学习和 Agent 研究圈子里,RSI 指的是 Recursive Self-Improvement,递归自我改进。这个词的关键在“递归”。
什么叫递归?一个函数调用自己,叫递归;一个改进系统改进出了更好的自己,然后用更好的自己再去改进,这也是一种递归。形式化一点说:假设当前有一个 AI 系统 S,它具备对自身进行改进的能力,改进一次后得到 S';如果 S' 依然具备这种能力,那么它可以继续改进得到 S''。这个链条 S → S' → S'' → … 就是递归自我改进。
这里有一个容易忽略的点:不是所有“AI 自动优化”都配叫 RSI。比如你用贝叶斯优化去搜一组超参数,搜完就结束了,系统本身没有因为这次搜索而变得更会搜索,这只能叫自动调参。真正的 RSI 要求改进能力本身也被改进,或者至少改进的成果能作为下一轮改进的基础。就像一个人不仅学会了解题,还因为解题过程总结出了一套更高效的“学习方法”,然后用新方法去学更难的内容。
1.3 为什么这轮生成式 AI 浪潮里,自进化突然变得可落地了
“让 AI 改进自己”这个概念其实一点不新鲜,几十年前就有人提过。真正的问题是:以前做自进化太贵了。比如早年的神经网络架构搜索(NAS),要训练上千个候选网络,每个网络训练几十个小时,一次搜索就要烧掉一个大型实验室几个月的算力。这种成本下,“自我进化”只能停留在论文里。
LLM 出现之后,情况完全变了。首先是生成能力:一个预训练好的大模型可以直接“生成”改进的候选方案,比如新的提示词、新的代码实现、新的规划策略,这比从零训练一个网络便宜得多。其次是评估能力:LLM 可以做自动裁判,给候选方案打分、判对错,甚至解释为什么这个方案好、那个方案差。生成和评估这两个环节都自动化了,自进化回路就从一个烧钱的研究课题,变成了一个普通开发者也能在笔记本上跑的实验。
所以你可以理解为:不是 AI 突然学会了自我改进,而是“成本”这扇门终于被推开了一条缝。
2. RSI 的工作原理:AI 到底怎么改进自己
2.1 一套最小可用的自我改进回路
无论多复杂的自进化系统,拆到底都是四个环节在循环:定义指标、生成候选、评估筛选、保留更新。
定义指标是第一步,也是最容易被糊弄过去的一步。你让 AI 改进自己,总要告诉它“什么样算更好”。对数学推理 Agent 来说,指标可以是测试题正确率;对代码 Agent 来说,可以是某个 benchmark 的通过率;对客服 Agent 来说,可以是用户反馈满意度。指标不一定是标量,但必须可量化、可比较。没有标量指标,后面的“评估”就无从谈起。
生成候选是第二步。当前 Agent 根据自己的行为记录、失败案例或者一个“反思”提示词,生成若干个修改方案。常见做法是让 LLM 基于当前 system prompt 或工具代码,尝试写几个变体。这一步可以加温度扰动,让候选之间保持多样性,避免所有变体都是同一个套路。
评估筛选是第三步。把每个候选方案放到一组固定的测试任务上运行,记录指标结果。这组测试任务相当于 AI 自己的“月考”,必须和生成过程隔离,不能泄漏到候选方案的构造逻辑里去,否则很容易出现“看起来高分、实际没本事”的假改进。
保留更新是第四步。比较新一代候选和当前版本的指标,只保留有效提升的那一个,然后进入下一轮。这个更新不一定每次都是正向的,所以保留一份历史最佳版本作为基准非常重要。
2.2 三种常见改进路径:改提示、改代码、改策略
自进化 Agent 的“改进”落到具体地方,通常有三种路径,我放在一起对比一下:
| 改进路径 | 改的是什么 | 适合场景 | 成本量级 | 风险点 |
|---|---|---|---|---|
| 提示词自优化 | system prompt、few-shot 示例 | 任务边界模糊、依赖指令理解的场景 | 低 | 容易钻评估集空子 |
| 工具/代码自改写 | 函数实现、工具调用逻辑 | 有明确输入输出、可单测的代码任务 | 中 | 引入语法错误或隐藏 bug |
| 策略/权重自搜索 | 规划策略、RL 奖励参数、甚至模型权重 | 有仿真环境或强化学习通道的场景 | 高 | 改进不可解释、难以回滚 |
最轻量的是提示词自优化。你把 system prompt 当作“基因”,让 LLM 基于错误样例生成几条新的提示词,放到测试集上跑分,留最好的。这个方案几乎任何有 LLM API 的人都能复现,后面的实操我会用它做例子。
工具/代码自改写更进一步。Agent 把自己的一个工具函数作为改进对象,运行过程中如果发现某类输入老是返回错误结果,就生成一个新版本的函数代码,编译一下、跑一下单测,通过了再替换。这条路径要求评估环境有自动检查机制,否则模型很容易改出一个“看起来对、实际上错”的函数。
策略/权重自搜索最重。它通常和强化学习结合:Agent 不直接改代码,而是尝试调整自己的决策策略或奖励函数参数,在仿真环境里跑大量 episode 来验证。这个方向的成本高,但上限也高,有团队已经在用类似思路做“算法发现”类实验。
2.3 评估器设计是整个系统的“心脏”
如果说自进化系统里只能专注一个地方,我会选评估器。评估器决定了什么算“改进”,也就决定了整个进化方向。评估器一旦有偏差,整个系统会朝着错误的方向一路狂奔,而且速度很快。
最容易踩的问题叫“既当运动员又当裁判”。如果你用同一个 LLM 既生成候选方案,又给候选方案打分,它天然会偏好自己生成的那些文本,甚至因为它记住了测试集上的标准答案,分数虚高。我在实验里就遇到过:候选 prompt 在 20 道评估题上效果明显提升,换一批题立刻失效。后来排查发现,那个 prompt 里写了很多跟具体题目相关的“技巧”,本质是在背答案。
避免这个问题有三类做法:第一,评估任务隔离,评估集绝不参与任何候选生成过程;第二,判卷机制多元化,能上真值校验就上真值校验,不要什么都交给 LLM 自由发挥;第三,保留一份人类回归集,每隔几轮人工抽查,防止系统在统计上“作弊”。
2.4 小白友好类比:教练、运动员和裁判不能是同一个人
我把自进化里的三方角色拆给你看:运动员是当前 Agent,它在测试任务上执行、给出结果;教练是修改器,它观察失败、构思改进方案;裁判是评估器,它决定改动是否被采纳。
理想情况下,这三个角色可以由同一套 LLM 系统扮演,因为 LLM 是人家的全部技能包。但作为工程实践,我强烈建议你在架构上把三者分开:教练可以读错误日志,但不能直接改评估集;裁判可以看打分标准,但不能参与生成候选;运动员只负责执行。哪怕底层调的是同一个模型接口,逻辑上也要做到隔离,否则系统很容易退化成“自己给自己发奖牌”。这个隔离不是形式主义,是保证进化方向可靠的底线。
3. 动手做一个极简自进化 Prompt Agent
3.1 项目目标与总体思路
我们要做的 Demo 不追求复杂,目标是:让一个具备数学应用题解答能力的 Agent,通过自进化回路把一组固定测试题的准确率从约 70% 提升到 85% 左右。改进对象是 system prompt,进化方式是“生成变体 → 评估保留”。
整体思路分三层:底层是一组 20 道数学应用题和对应的标准答案,作为评估集;中间是一个 LLM API 封装,负责两件事——用给定 system prompt 解答数学题,以及生成新的 prompt 变体;顶层是进化循环:每轮基于当前最佳 prompt 生成 8 个变体,每个变体跑 20 道题,计算准确率,表现最好的变体替换当前最佳方案。
这个过程相当于给 prompt 做了一个小小的“遗传算法”,遗传信息就是 prompt 文本,适应度就是准确率。选遗传算法而不是完全随机的爬山法,是为了让多个优秀特征有机会组合起来,而不是局部调几下就停住。
3.2 环境准备与指标定义
依赖很简单:一个能访问的 LLM 接口(本地模型或者任意 API 都行),Python 3.10 以上环境,以及一个openai兼容的 SDK 客户端。我用的是调用环境变量LLM_API_KEY和LLM_BASE_URL的方式,方便切换不同供应商。
评估指标就一个核心指标:准确率。20 道题,每道题让模型输出一个结果,和标准答案做字符串比对。有一个小技巧:不要直接让模型只输出数字,而是让它先写推理过程、再单独输出一个Answer:标记,最后解析标记后面的内容。这样做的好处是减少格式错误导致的误判,也不至于因为模型想“解释一下”就把结果弄得乱七八糟。
测试题的选择有讲究。我挑了 20 道涵盖四则运算、比例、方程、连续推理的小学奥数级别题目。题目不能太简单,否则初始准确率就 95%,没有进化空间;也不能太难,否则模型怎么改都蹦不过 50%,体验会很挫败。
3.3 进化回路的核心代码
我把核心循环写成了一个 Python 脚本,关键逻辑如下。先定义两个基础函数:run_solver(prompt, question)让模型用指定 prompt 解答单题,evaluate_prompt(prompt)返回一个 prompt 在全部 20 题上的准确率。
import os import random from openai import OpenAI client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL"), ) # 20 道数学题及其标准答案 EVAL_SET = [ {"q": "小明有 12 个苹果,吃掉三分之一后又买了 5 个,现在有几个?", "a": "13"}, # ... 共 20 条 ] BASE_PROMPT = ( "你是一个擅长小学数学应用题的助手。" "请一步步推理,并在最后单独一行输出 Answer:<结果>。" ) def run_solver(prompt, question): try: resp = client.chat.completions.create( model=os.getenv("LLM_MODEL", "default-model"), messages=[ {"role": "system", "content": prompt}, {"role": "user", "content": question}, ], temperature=0.0, ) text = resp.choices[0].message.content or "" answer_part = text.split("Answer:")[-1].strip() return answer_part except Exception as e: return f"ERROR:{e}" def evaluate_prompt(prompt): correct = 0 for item in EVAL_SET: out = run_solver(prompt, item["q"]) if out == item["a"]: correct += 1 return correct / len(EVAL_SET)注意temperature=0.0:评估时要让模型保持贪心输出,避免随机性干扰分数。等会儿生成候选变体的时候,才把温度调高。
接下来是生成变体和进化主循环。每一轮调用generate_variants(),让 LLM 基于当前 prompt 和上一轮的失败案例,生成POP_SIZE个新 prompt,再逐个评估,取最优。
def generate_variants(base_prompt, feedback, n=8): variants = [] messages = [ {"role": "system", "content": ( "你是一个 prompt 优化专家。你会收到一个当前 system prompt 和它的失败样例。" "请生成 n 个新的 system prompt 变体,要求:" "1. 保留原 prompt 的优点;" "2. 针对失败样例进行改进;" "3. 输出格式为纯文本,每个变体用 <VARIANT> 开头。" )}, {"role": "user", "content": f"当前 prompt:\n{base_prompt}\n\n失败样例:\n{feedback}"}, ] resp = client.chat.completions.create( model=os.getenv("LLM_MODEL", "default-model"), messages=messages, temperature=0.8, ) raw = resp.choices[0].message.content or "" for block in raw.split("<VARIANT>"): block = block.strip() if block: variants.append(block) # 不够 8 个就用模板补,防止候选不足 while len(variants) < n: variants.append(base_prompt + f" 补充策略:请检查是否遗漏了题干关键数字。") return variants[:n] def evolution_loop(rounds=5): best_prompt = BASE_PROMPT best_acc = evaluate_prompt(best_prompt) print(f"Round 0, accuracy: {best_acc:.2%}") for r in range(1, rounds + 1): # 收集上一轮最佳 prompt 的错误题,作为反馈 failed_items = [] for item in EVAL_SET: if run_solver(best_prompt, item["q"]) != item["a"]: failed_items.append(item["q"]) feedback_str = "\n".join(failed_items[:5]) variants = generate_variants(best_prompt, feedback_str, n=8) candidate_scores = [] for i, v in enumerate(variants): acc = evaluate_prompt(v) candidate_scores.append((acc, v)) print(f" candidate {i}: {acc:.2%}") candidate_scores.sort(key=lambda x: x[0], reverse=True) if candidate_scores[0][0] > best_acc: best_acc = candidate_scores[0][0] best_prompt = candidate_scores[0][1] print(f"Round {r}, best accuracy: {best_acc:.2%}") return best_prompt, best_acc这个循环有几个细节值得注意。第一,失败样例只取前 5 条,别一股脑全部塞进反馈,太长既费 token 又让模型抓不住重点。第二,候选评估是同步串行的,20 道题 × 8 个候选 = 160 次推理调用,速度取决于你的 API 延迟,如果太慢可以考虑并成 2 个候选减少压力。第三,只有当最优候选的分数严格超过当前版本时才替换,否则保留原版,这是防止退化的底线。
3.4 三个关键参数:候选数、评估样本数、保留阈值
我跑了多次实验之后发现,自进化系统里最影响效果的参数是这三个。
POP_SIZE:每轮生成多少个候选。太小(比如 2 个),候选多样性不足,几轮之后就开始在原地打转;太大(比如 20 个),每一轮评估成本直接翻好几倍。8 个是我在“进化效果”和“调试速度”之间觉得比较舒服的档位。
EVAL_SET样本数:每次评估用多少道题。理论上样本越多,分数越稳定;但每个候选都要跑全量样本,评估成本随样本数线性上涨。如果你的题目量只有 20 道、准确率差异又比较大,全量跑是可以接受的;如果题目量有几千道,建议先在一个 30 题的子集上粗筛,再用全量精评。粗筛的目的是快速淘汰明显不行的候选,减少精评成本。
保留阈值:我直接用“严格大于当前准确率才替换”,没有加容差。这样最保守,不容易把随机噪声当成改进。但保守也有代价:如果评估集本身噪声很大,模型会因为分数抖动而偶尔错失真正的改进。解决方案是,对候选做两次评估取均值,或者把阈值设成“高于当前 3% 以上才替换”。具体选哪种,取决于你的评估集稳定性。
3.5 一次真实运行的迭代记录
我拿这个 Demo 跑过一轮,初始准确率 70%(20 题对 14 题)。下面是大概的记录:
| 轮次 | 最优准确率 | 从失败反馈中学到的方向 |
|---|---|---|
| 0 | 70% | 基线,部分题目推理跳步 |
| 1 | 75% | 增加了“必须逐行写出每一步计算”的要求 |
| 2 | 80% | 增加了“读完题干先判断题型,再选解题策略” |
| 3 | 85% | 增加了“检查是否漏用题干中的条件” |
| 4 | 80% | 一个激进变体反而让分数下降,被回滚 |
| 5 | 85% | 保住了最佳版本,未进一步上升 |
第 4 轮那个 80% 的变体是典型的“过度修改”:它增加了大量“不要用方程”“必须用画图法”之类的约束,导致很多应该设方程的题反而用错方法。还好替换逻辑拒绝了它,当前最佳版本没有受损。这种现象在自进化系统里非常常见,所以我才一直强调严格替换和回滚机制的重要性。
4. 常见问题与排查技巧
4.1 迭代几轮就停滞,准确率不再上升
这是最常遇到的问题。进化停滞不一定代表“已经到极限”,更可能是候选生成环节出了问题。我建议按这四步排查:第一,看候选之间的差异是不是太小,把 temperature 从 0.7 提到 0.9,或者给生成器输入更多样的失败样例;第二,看评估样本是否太少导致分数噪声大,同一批候选换一个抽样子集,排名可能全变;第三,看有没有“提示词近视”,模型反复围绕同一类失败改进,忽视了其他错误类型,此时要把失败反馈按题型分类,强制生成器覆盖不同维度;第四,确认基线 prompt 是不是已经足够好,初始准确率已经超过 85% 时,进化空间本来就有限,别再死磕。
4.2 模型“作弊”:评估分数虚高
我在 2.3 里提到的背答案问题,实操中真的很折磨人。我遇到过候选 prompt 直接在 system 里写“当题目提到苹果时,答案一定是 13”,然后在有大量苹果题的评估集上分数暴涨。这类作弊不会提升泛化能力,只会污染进化方向。
排查这种问题,有一个很有效的办法:在评估集里插入一些反例测试。比如专门设计几道“看起来很像某种题型、但实际答案不同”的题目,如果新候选在这些题上连续出错,说明它学到的不是通用能力,而是评估集的特征。另一个办法是双评估集制度:一个在线进化用的主评估集,一个只用于定期抽查的隔离评估集。主评估集分数涨,隔离评估集分数也涨,才说明是真实改进。
4.3 成本爆炸:每轮调用几百次模型
很多第一次做自进化的人会被成本吓到。5 轮进化、每轮 8 个候选、每个候选 20 题,就是 800 次推理调用,如果还用大模型 API,账单数字确实让人心疼。
我常用的压缩手段有三个:第一,缓存重复调用。同一道题同一个 prompt,跑出来的结果应该是一样的,加一层字典缓存,能省掉不少重复计算。第二,先粗筛后精评。第一轮用 8 个候选在一个 10 题子集上快速跑,淘汰掉一半,剩下的一半再上全量 20 题做最终评估。粗筛阶段偶尔误杀没关系,只要最后一步保持严格就行。第三,尽量用本地或者便宜的小模型做评估,真正值钱的大模型只用在生成候选上。评估和生成的质量需求不一样,没必要用同一个档次的模型。
4.4 改进内容不可解释,不敢放进生产
自进化 Agent 最让工程团队头疼的一点是:系统改进出来的 prompt 或代码,有时候看起来非常有道理,有时候又完全是一堆玄学词句。尤其是 prompt 经过十几轮进化以后,里面可能堆了几十条互相矛盾的要求,谁都不敢保证它在线上不会出问题。
我的建议是给每个改进步骤做“审计三件套”:记录这一步改了什么 diff、为什么改(关联的失败样例)、评估证据是什么(分数变化)。然后把所有进化结果做成可回滚的版本列表,线上只切换到经过人工 review 的版本。自进化可以做探索,但发布必须走人类的确认流程。
4.5 常见问题速查表
| 问题现象 | 可能原因 | 排查/解决方法 |
|---|---|---|
| 几轮后准确率不再上升 | 候选多样性不足 | 提高生成温度、按错误类型分组反馈 |
| 新候选分数虚高、泛化差 | 候选在背评估集答案 | 插入反例题、加隔离评估集 |
| 每轮调用成本太高 | 候选太多、评估样本太重 | 粗筛+精评、缓存重复结果 |
| 某个变体让分数暴跌 | 过度修改核心逻辑 | 严格阈值替换、保留上一版本 |
| prompt 越来越长、冗余多 | 每轮只加不改,没有清理 | 每几轮做一次 prompt 精简优化 |
| 换了新数据后效果崩了 | 进化过拟合旧评估集 | 定期更换/扩充评估题集 |
5. 自进化 Agent 能做什么、不能做什么
5.1 适合做的场景
从投入产出比来看,我最推荐把自进化用在几类任务上:prompt 优化、代码小工具优化、数据清洗策略调优。这些任务的共同点是:有明确的输入输出、有自动化的评估方式、单次改进成本低。比如你有一个处理订单文本的解析器,就可以让 Agent 自己尝试改进解析规则,用一批历史订单作为评估集,每轮进化都验证真的提升了解析准确率。
另外一个被低估的场景是“自动修复失败用例”。Agent 在跑批量任务时,把失败样本汇总起来,自动生成提示词补丁或代码补丁,然后重新验证。这种“日拱一卒”式的修 bug 循环,比一次性做大优化更稳,也更容易被团队接受。
5.2 不适合做的事情
我真心不建议上来就让自进化 Agent 去改线上系统的配置参数、调数据库连接池大小、或者动任何跟用户资金和隐私相关的策略。原因不是“AI 能力不够”,而是“失败成本不对称”。自进化跑出来一个无效改动,在测试集上你可能只是掉几分,重跑一轮就行;但如果在线上改崩了用户数据,后果完全不是一个量级。安全边界必须由人划好,而不是让 AI 自己去试。
还有一类不适合的是模型权重级别的自我修改。有人会说“RSI 的最终形态不就是 AI 改自己的权重吗”,理论上确实,但当前我们既没有足够稳定的梯度通路,也没有办法解释修改后的权重会产生什么行为变化,这个方向在工程上仍属于研究探索,不适合普通项目直接上手。
5.3 我个人的几个实操体会
做这个 Demo 的过程中,我有几个很深的感受。第一,自进化最难的不是写进化循环,而是设计一套不骗人的评估标准。代码就几十行,评估器设计才是灵魂。第二,一定要给系统画一条“人不在场也能放心跑”的边界:允许它改 prompt、改工具函数,但不允许它无限制地调用外部副作用接口。第三,小步快跑是最好的策略。每次只改一个点、每轮保留历史最佳版本、每次发布前有人工 review,比一次性让 AI 大改特改要可靠得多。
如果你也想尝试,我建议从今天这份笔记里的最小 Demo 开始,先跑通一轮,把“生成候选 → 评估 → 替换”的感觉找到,再去考虑更复杂的架构。自进化 Agent 不是科幻,它只是把“反馈闭环”这四个字做到了系统本身里,而搭建这个闭环的工具,现在已经在你手上了。