终身学习智能体+Loop工程五步法 | 附调参示例

你有没有见过那种越玩越聪明的游戏角色,它自己给自己出题,自己写技能代码,扔进环境里跑,跑崩了就根据报错信息自己改,改到通过验证就把这招存进技能库,下次遇到类似关卡直接掏出来用。

终身学习智能体根本不需要每次学新东西都重新训练整个大脑,只需要在已有的技能库里不断叠加新代码,像搭积木一样越搭越高,而且永远不会出现学了新招就忘老招的灾难性遗忘。

整个系统就靠三层循环堆在黑盒GPT-4上面,模型权重一丁点都不动,纯靠代码技能库当长期记忆,每次遇到新任务就从库里翻找合适的旧技能组合一下,或者写个新技能加进去,就像一个玩了几千个小时游戏的老玩家,面对新关卡的时候翻自己的招式本子,看一眼就知道该用哪几招连起来打。

五步法

这篇论文的运行逻辑就五步:自己给自己定个任务,写一段代码技能,扔进环境里跑,跑崩了就根据报错自己改,改到通过就把这招存进技能库。

五步蓝图是这样的:

  1. 自动课程表,模型根据自己现在的状态、手头有啥工具、之前失败过哪些任务,自己给自己出下一道题,逼着自己去探索,从最基础的挖矿一点点往上垒。
  2. 技能就是代码,行为直接写成可执行代码,不是那些低级动作指令,所以每一招你都能看懂它在干啥,还能跟其他技能拼在一起用。
  3. 迭代式调代码,代码扔进环境里跑,报错信息和环境反馈全塞回给提示词,智能体就一圈一圈地重写代码直到跑通。
  4. 自己验证,另设一道检查关卡确认任务真的完成了,才把技能放进记忆里。
  5. 技能库,所有成功的技能存进一个不断变大的库,每条技能都配好描述和索引,以后遇到新任务直接翻出来用,能力就这么一层层叠上去,完全不用重新训练。

关键洞察是,这个智能体完全不需要动模型权重,一个不断增长的代码技能库就是它的长期记忆,也是它对抗学了新招就忘老招的秘密武器。整套系统就靠在黑盒GPT-4上面跑三层循环,模型权重一丁点都不动。

Voyager:GPT-4驱动,终身学习智能体代码技能库

原文期刊/发表日期: arXiv / 2023年5月25日
原文标题: Voyager: An Open-Ended Embodied Agent with Large Language Models
作者单位背景: NVIDIA、加州理工学院、德克萨斯大学奥斯汀分校、斯坦福大学、威斯康星大学麦迪逊分校

公开页面和代码仓库地址如下:

  • 论文原文页面 (arXiv):https://arxiv.org/abs/2305.16291 
  • 项目官网与开源代码库:https://voyager.minedojo.org/ 


Loop Engineering(循环工程)是2026年由OpenClaw创始人Peter Steinberger正式提出的AI工程新范式。其核心主张是:你不应该再给编程Agent写提示词,而应该设计一套让Agent自主迭代的循环系统。


调参案例

打游戏卡关的时候你会一遍遍试同一个boss,试到第十次你连自己刚才用了什么技能都忘了。
调参也是这副德行,对着文本块大小发呆,换向量嵌入模型像换装备,加个重排序器跟换宠物似的,跑完一轮评估看一眼召回率就忘了刚才那个配置是啥。

根据“终身学习智能体”写个自动调优脚本,让代码自己在配置空间里跑搜索来调检索增强生成系统,预算控制帮你守住钱包,留出验证帮你揪出过拟合,整个过程就叫循环Loop工程。把调参交给自动脚本,就像打游戏把重复刷怪交给挂机,省下的时间够你刷三集番了。


调参为什么适合交给循环

循环工程的核心规则是:只有当存在一个能自动给出判决的检查时,循环才有意义。RAG调参完美符合这个条件。检查点就是评估集上的recall@k,这是一个赤裸裸的数字,高于阈值就是过了,低于就是没过,没有任何扯皮的空间,跟单元测试变绿一样直白。循环永远知道自己是否达成了目标,这种度量指标天生就适合自动化。

调参本质上是在配置空间里做搜索,而搜索正是循环碾压人类的地方。循环不会疲劳,不会丢失记录,会按部就班地迭代并记住已经试过什么。人类跑到第十轮就开始犯迷糊,满脑子都是“我好像试过800加100加这个embedding?不对好像是600加200”。循环可不会这样,它老老实实把每次尝试都记在日志里,比你的备忘录靠谱一万倍。

起跑线:你得先有个东西能调

循环不负责从零搭RAG,它负责调优一个已经存在的系统。所以出发前你手里必须捏着三样东西。第一个是一个能跑的RAG流水线,参数得能改:chunk size、overlap、embedding模型、候选数k、reranker开不开。第二个是一套评估集,三十到五十个问题,每个问题都得配好一个正确答案所在的源chunk。没有这个评估集,循环就是个瞎子。第三个是一个能跑评估并返回recall@k的函数,这是循环的裁判。

python
# eval.py — 循环的裁判,对某个配置返回recall@k
def evaluate(config, eval_set):
    pipeline = build_rag(config)        # 用这些参数搭建RAG
    hits = 0
    for case in eval_set:
        retrieved = pipeline.retrieve(case["question"], k=config["k"])
        retrieved_ids = {c["id"] for c in retrieved}
        if case["gold_chunk_id"] in retrieved_ids:
            hits += 1
    return hits / len(eval_set)

第一步:划定搜索地盘

循环搜索配置,你得先明确它具体能拧哪些旋钮,以及拧到什么范围。不是无限空间,而是一套确实会影响recall的参数组合。这个搜索空间已经不小了,全量组合有648种,一个个试过去既费钱又犯傻。循环不会做全量扫描,它会更聪明地走,这是后面的事。

python
# search_space.py — 循环要搜索的范围
SEARCH_SPACE = {
    "chunk_size":   [400, 600, 800, 1200],
    "chunk_overlap":[0, 100, 200],
    "embedding":    ["text-embedding-3-small", "bge-large", "e5-large"],
    "k":            [5, 10, 20],
    "reranker":     [None, "bge-reranker", "cohere-rerank"],
    "hybrid":       [False, True],   # 纯向量 或 向量加BM25
}

第二步:一个骗不了的检查

这是循环的心脏。评估集上的recall是个好检查,但发射前得堵上两个坑。第一个坑是对评估集过拟合。循环在评估集上搜配置搜到recall涨了,可能只是恰好在你这四十个问题上表现好,上了生产环境就露馅。这跟代码里的reward hacking是一个道理,只不过这里的度量是个数字。防御手段是把评估集拆成两份,循环只优化其中一份训练集,最终配置在另一份循环没见过的留出集上做验证。

python
import random

def split_eval(eval_set, holdout_frac=0.3, seed=42):
    random.Random(seed).shuffle(eval_set)
    n = int(len(eval_set) * (1 - holdout_frac))
    return eval_set[:n], eval_set[n:]   # 前一份给循环调优,后一份留给你做最终验证

train_set, holdout_set = split_eval(eval_set)

第二个坑是测量噪声。只有三十个问题时,recall 0.87和0.90的差异可能纯属随机波动,不是真进步。循环如果对改善阈值设得太小,就会去追这些噪声。防御手段是只把超过合理阈值比如0.02的改善算作有效,否则循环就会在随机波动上瞎折腾,今天调高明天调低,永远不消停。

第三步:带脑子的搜索循环

全量扫648种组合又贵又没必要。循环采用贪心策略,一次只动一个参数:固定其他参数,把当前参数的所有取值都试一遍,挑最好的锁死,然后换下一个参数。这叫坐标下降法,几十次运行就能找到好配置,而不是几百次。


import json

def tune_rag(search_space, train_set, target_recall=0.9, max_evals=40):
    config = {
        "chunk_size": 600, "chunk_overlap": 100,
       
"embedding": "text-embedding-3-small", "k": 10,
       
"reranker": None, "hybrid": False,
    }
    best_recall = evaluate(config, train_set)
    evals_used = 1
    log =

    for param, values in search_space.items():
        if evals_used >= max_evals:
            break
        best_value = config[param]
        for value in values:
            if value == config[param]:
                continue
            if evals_used >= max_evals:
                break
            trial = dict(config, <strong>{param: value})
            recall = evaluate(trial, train_set)
            evals_used += 1
            log.append({
"config": trial, "recall": recall, "eval": evals_used})

            if recall > best_recall + 0.02:
                best_recall = recall
                best_value = value

            if best_recall >= target_recall:
                config[param] = best_value
                save_log(log)
                return config, best_recall, evals_used

        config[param] = best_value

    save_log(log)
    return config, best_recall, evals_used

def save_log(log):
    with open(
"tune_log.jsonl", "w") as f:
        for row in log:
            f.write(json.dumps(row) +
"\n")


注意这里的目标检查是每次运行后都做:一旦达到target_recall,立刻退出,不浪费剩余的运行次数。max_evals是保险丝,没有它循环会在不幸运的空间里扫完所有组合。0.02的阈值是防追噪声的护盾。日志写入jsonl,你事后能清楚看到每个参数带来了什么变化。

第四步:刹车片,因为跑一次要花钱

每次评估都是跑一遍完整的RAG,涉及几十个问题的查询嵌入,还有reranker和LLM调用。这既花时间也烧钱。没有刹车的循环能悄无声息地把预算烧光,尤其是搜索空间里混着昂贵的配置组合,比如大k值加上cohere-reranker在每个问题上都跑一遍。

python
def tune_rag_safe(search_space, train_set, target_recall=0.9,
                  max_evals=40, max_budget_usd=15):
    config = default_config()
    best_recall = evaluate(config, train_set)
    spent = estimate_cost(config, len(train_set))
    evals_used = 1
    log =

    for param, values in search_space.items():
        best_value = config[param]
        for value in values:
            if value == config[param]:
                continue
            trial = dict(config, {param: value})
            trial_cost = estimate_cost(trial, len(train_set))
            if spent + trial_cost > max_budget_usd:
                print(f"预算${max_budget_usd}已用完,停止在{evals_used}次运行")
                save_log(log)
                return config, best_recall, evals_used
            if evals_used >= max_evals:
                save_log(log)
                return config, best_recall, evals_used

            recall = evaluate(trial, train_set)
            spent += trial_cost
            evals_used += 1
            log.append({"config": trial, "recall": recall,
                        "spent": round(spent, 2), "eval": evals_used})

            if recall > best_recall + 0.02:
                best_recall = recall
                best_value = value
            if best_recall >= target_recall:
                config[param] = best_value
                save_log(log)
                return config, best_recall, evals_used

        config[param] = best_value

    save_log(log)
    return config, best_recall, evals_used

def estimate_cost(config, n_questions):
    cost = n_questions * 0.00002
    if config["reranker"] == "cohere-rerank":
        cost += n_questions * config["k"] * 0.000001
    return cost

这里两个刹车分别是max_evals运行次数上限和max_budget_usd金钱上限。预算刹车在实际运行前就做检查,如果下一次评估会超限,循环会提前停下,而不是等钱烧完了才反应过来。这是资产和意外账单之间的区别。

第五步:发射,然后用留出集验货

在训练集上跑循环,然后务必用循环没见过的留出集验证找到的配置。

python
if name == "main":
    train_set, holdout_set = split_eval(eval_set)

    config, train_recall, n = tune_rag_safe(
        SEARCH_SPACE, train_set, target_recall=0.9, max_evals=40
    )
    print(f"跑了{n}次找到。训练集recall: {train_recall:.2f}")
    print(f"配置: {config}")

    holdout_recall = evaluate(config, holdout_set)
    print(f"留出集recall: {holdout_recall:.2f}")

    gap = train_recall - holdout_recall
    if gap > 0.1:
        print("警告:训练集和留出集差距过大,循环对评估集过拟合了,配置不可信")
    else:
        print("差距很小,配置能泛化,可以上线")

留出集验证不是走过场,这是区分真进步和对评估集过拟合的分水岭。训练集recall 0.92但留出集只有0.79,说明循环找到的不是一个更好的RAG,而是一套恰好讨好了你那二十八个训练问题的组合。这个差距就是警报器。

循环是怎么死的

任何循环都会死,调参循环也不例外。追噪声是最常见的死法,循环在随机recall波动上反复横跳,永远不收敛。病因是改善阈值太小或者评估集太小,药方是0.02阈值加至少四十个问题。过拟合评估集是另一种死法,训练集recall涨了但生产环境没变好,病因是循环针对特定问题做了优化,药方是留出集验证,差距会报警。预算失控也常见,循环搜了昂贵配置烧光钱,病因是没有预算刹车或者搜索空间里混了高价组合,药方是运行前检查max_budget_usd。

无尽循环则是目标在当前空间里根本达不到,循环把组合都扫了一遍还差得远,病因是target_recall设得比空间天花板还高,药方是max_evals兜底,再不行就扩空间或者降目标。

你最后得到什么

最终你拿到的不仅是一个调好的RAG,而是一套可复现的流程。jsonl日志清楚记录每个参数的效果,留出集验证告诉你该不该信它,数据和模型变了之后一键重新跑调优就行。这就是循环工程在真实任务上的应用。不是什么花哨的agent魔法调参,而是一条工程化的循环:一个骗不了的检查、对过拟合的防御、预算刹车、可审查的日志。拿上你的RAG和评估集,把调参过程包进这样的循环里,做一次,然后这辈子都不用亲手拧chunk size那个旋钮了。

真正会调参的人不是在拧旋钮,是在造拧旋钮的机器人。而那个机器人最擅长的,就是让你忘记自己曾经亲手拧过。

人一辈子都学不会把知识变成工具箱,AI却靠五步做到了。你最骄傲的学习能力,在它面前活像个笨手笨脚的新手。