JitRL 是我最近反复读的一篇 Oral 论文,它做的事情可以用一句话概括:让 LLM Agent 在不做任何梯度更新的前提下,完成持续强化学习。更抓眼球的是成本——整套流程跑下来大约 98 美元,而同等任务规模下走传统全参数强化学习路线,GPU 账单通常落在 3500 美元这个量级。
这个数字对比乍一看像是营销话术,但把机制拆开看,它其实是必然结果:既然不训练模型权重,就不用买 GPU 小时,你付的只是推理 API 的 token 钱。JitRL 要解决的问题也很具体——真实业务里的 Agent 每天都在产生新轨迹、遇到新任务分布,你不可能每周重训一次模型,但又确实希望它"越用越聪明"。这篇论文给出的答案是:把"策略"从权重里搬到文本里,用自然语言规则库承载持续学习。
这篇解读适合三类人:一是正在做 Agent 产品、被"上线后无法迭代"困住的工程师;二是想搞懂无梯度强化学习到底怎么落地算法同学;三是预算有限、只有 API 额度没有 GPU 集群的小团队。我会把机制、公式骨架、可复现的代码流程、成本核算和踩坑记录都摊开讲,能抄的部分直接抄。
1. 先把结论摆在前面:这个方案到底在改什么
1.1 一句话拆解:把"梯度"换成"文本编辑"
传统强化学习微调 LLM,更新的是权重矩阵里的浮点数。JitRL 更新的是一个文本文件——准确说是一个结构化的策略条目库。Agent 每跑完一批任务,系统把轨迹按好坏分组,让模型自己对比反思,产出一批候选规则,比如"当工具返回空结果时,不要立即重试同一参数,应先换关键词再查一次"。这些规则经过打分、去重、合并之后进入策略库,下一轮推理时被检索出来拼进系统提示。
所以整个"学习"过程是:执行 → 打分 → 反思 → 改库 → 再执行。参数空间从几百亿个浮点数,变成了几百条自然语言条目。这是一个典型的黑箱优化问题,和进化策略、贝叶斯优化属于同一个家族——你把 prompt 当基因型,把任务奖励当适应度,用选择和变异代替梯度下降。
我特别欣赏这个设计的一点是:它把"持续学习"的工程门槛从"有没有集群"降到了"有没有 API key"。对绝大多数做应用层 Agent 的团队来说,这才是能真正跑起来的东西。你在本地跑一个脚本,接上模型 API,睡一觉起来策略库就更新好了,中间不需要碰任何训练框架。
1.2 98 美元和 3500 美元,差的是哪几笔账
先把两边的成本结构摆清楚,不然这个对比容易被误读。
全参数 RL 路线的钱花在两处:rollout(采样)和训练(前向+反向)。以 8 卡高端 GPU 为例,机器租用价约 2.5 美元/卡/小时,一小时就是 20 美元;跑满 7 天约 168 小时,账单约 3360 美元,加上存储和调试重跑,3500 美元是个很实在的估计。这里面最贵的是反向传播——每一条轨迹都要过一遍完整的梯度计算。
JitRL 路线只有 rollout 和反思,没有反向传播。它把"训练"这一步整个删掉了,剩下的全是推理调用。我在第 4 节会给出详细的成本表,这里先给结论:约 1200 个任务、3 轮外层迭代、平均 6 轮交互的规模下,rollout 花了 58 美元上下,反思不到 2 美元,验证评分约 6.5 美元,检索装配约 3.5 美元,再乘上 40% 的重试与失败重跑系数,总计 98 美元左右。
这里有个关键点必须说清楚:98 美元替代的不是预训练,也不是基座模型的能力上限。它替代的是"任务级策略适配"这一段。换句话说,基座模型本身的多步推理能力不会因为 JitRL 变强,但它在你的具体业务场景里的表现——工具调用顺序、错误恢复策略、输出格式遵循——确实会肉眼可见地变好。
1.3 什么场景该用它,什么场景别浪费时间
适合的场景有个共同特征:任务分布会漂移,但单任务的判断标准相对稳定,而且你能拿到可自动化的奖励信号。典型比如工具调用型 Agent(查库、下单、填表)、代码修复 Agent(跑测试就知道对错)、客服对话 Agent(有满意度或转人工率作为代理指标)。这类场景奖励信号干净,轨迹可回放,规则可复用,JitRL 的性价比极高。
不适合的场景也很明确。第一,你需要模型掌握全新的知识或语言能力,那是继续预训练和 SFT 的活,写多少条规则都没用。第二,你的任务奖励极度稀疏且延迟,比如一个销售线索三个月后才转化,反思阶段拿不到有效对比样本,规则提炼会变成瞎猜。第三,任务之间几乎没有共性,每条轨迹都是独立特例,那策略库只会膨胀成一堆互相冲突的补丁。
还有一个容易忽略的限制:上下文预算。策略库最终要塞进系统提示,而系统提示是有硬上限的。我在实测里给策略库留了 1500 token,每条规则平均 40 token,也就意味着同时生效的规则最多三十几条。这个物理约束直接决定了你必须做淘汰和压缩,不是"规则越多越好"。
2. 为什么"不训模型"还能叫强化学习
2.1 把 RL 三要素重新落位到文本空间
很多人第一反应是:不更新权重,凭什么叫强化学习?这个问题值得掰开说,因为它决定了你理解这套方法的深度。
标准 RL 里有三样东西:策略(policy)、奖励(reward)、更新算子(update rule)。JitRL 一个都没少,只是换了个载体。策略 π 不再是 LLM 的权重 θ,而是"权重固定 + 上下文可变"的复合体,形式化写成 π(a|s) = LLM(a | s, Retrieve(M, s)),其中 M 是策略库,Retrieve 是按状态检索相关条目的函数。奖励 R(τ) 完全不变,还是环境给的标量。更新算子则从梯度下降换成了"反思 + 选择"的文本编辑算子,输入是带奖励标注的轨迹集,输出是新的库 M'。
优化目标也一字不改:max_M E_{τ~π_M}[R(τ)]。区别只在于,你在一个离散的、非欧几里得的文本空间里做搜索,梯度不存在,所以必须用无导数优化。这个视角一旦建立起来,很多工程细节就顺理成章了——为什么需要探索(多样性 rollout)、为什么需要利用(保留高命中率规则)、为什么会有过拟合(在训练任务上把库调得太窄)。
我在给团队做内部分享时用过一个类比:这就像调音师调音,传统微调是直接把钢琴拆了重新做音板,JitRL 是听一遍演奏,在小本子上记下"第三段左手偏重",下次演奏前提醒自己。前者彻底但昂贵且不可逆,后者轻量、可回滚、可解释,但受限于演奏者本身的水平。
2.2 无梯度更新的形式骨架与伪代码
把上面的话写成可执行的形式,核心就四个式子:
- 采样:D_t = {τ_i} ~ π_{M_t},每条轨迹带奖励 r_i
- 反思:C_t = Reflect(D_t),产出一组候选规则
- 选择:M_{t+1} = Select(M_t ∪ C_t),按效用排序并裁剪到预算内
- 迭代:t ← t+1,直到验证集指标不再提升
关键在第三步的 Select。它不是简单拼接,而是要做信用分配——判断某条规则到底有没有用。我倾向于给每条规则维护一个三元组 (n_used, n_success, avg_delta),n_used 是被检索进提示的次数,n_success 是这些回合里奖励达标的次数,avg_delta 是相对基线的平均增益。然后用 Beta 后验或 UCB1 打分,决定下一轮谁上谁下。
# 策略条目的效用打分(示意) import math def ucb_score(item, total_pulls, c=1.2): if item.n_used == 0: return float("inf") # 未探索的条目优先试一次 p = item.n_success / item.n_used bonus = c * math.sqrt(math.log(total_pulls + 1) / item.n_used) return p + 0.5 * item.avg_delta + bonus这段代码很短,但解决了一个真问题:新规则没有历史数据,如果纯按成功率排,它永远上不了场,策略库就固化了。UCB 里的探索项保证了每条候选规则都有被验证的机会,这跟多臂老虎机的思路完全一致。
2.3 和 PPO、GRPO 路线摆在一起对比
把三条路线放一张表里看,取舍关系会非常清楚。下表是我根据自己的理解和实测经验整理的,具体数字是量级估计而非精确基准。
| 维度 | 全参 PPO/GRPO | LoRA 轻量微调 | JitRL 无梯度 |
|---|---|---|---|
| 更新对象 | 全部权重 | 低秩适配器 | 文本策略库 |
| 是否需要反向传播 | 需要 | 需要 | 不需要 |
| 典型硬件 | 8 卡以上集群 | 1 至 2 卡 | 无,仅 API |
| 单轮成本量级 | 数千美元 | 数百美元 | 数十美元 |
| 迭代周期 | 天级 | 小时级 | 分钟到小时级 |
| 能力上限提升 | 有 | 有(有限) | 无 |
| 可解释性 | 差 | 差 | 极好,规则可读 |
| 可回滚性 | 需存 checkpoint | 需存 checkpoint | 删几条记录即可 |
| 灾难性遗忘风险 | 有 | 有 | 低(库可审计) |
这张表最有价值的一行是"可解释性"。规则库是纯文本,出问题时你能直接读到"Agent 这轮为什么选择先查 A 再查 B",而不是对着一堆 logits 猜。对线上业务来说,这一点的价值经常超过几个点的分数提升。反过来,"能力上限提升"那一行是 JitRL 的硬伤,你得接受它只能在基座模型的能力范围内做调度优化,不能凭空长出不会的技能。
3. JitRL 的核心机制拆解
3.1 经验池:轨迹怎样被压缩成可复用条目
原始轨迹是又长又脏的,一次任务动辄几千 token,里面八成内容是工具返回的无关字段。直接拿去做反思,成本高且噪声大。JitRL 在中间加了一层压缩:每条轨迹先被摘要成结构化的四元组(任务描述、关键决策点、结果状态、奖励)。这一步我认为是整个流水线里最容易被低估的环节,因为它决定了后面反思的质量上限。
摘要的粒度要拿捏。太粗,反思阶段拿不到足够的因果信息,产出的规则全是"要更仔细地检查输入"这种废话;太细,token 成本翻倍,而且模型会抓不住重点。我的经验是保留三类信息:一是失败点前后各两步的完整动作,二是工具返回的关键字段(不要全文),三是最终结果与期望的差异。这个粒度下,单条摘要大约 200 到 300 token,一批 20 条也就 6000 token 左右,成本可控。
还有一个细节值得单独提:奖励的离散化。连续奖励在反思阶段很难用,模型不知道 0.73 和 0.78 的区别意味着什么。我的做法是先分档,达标/边缘/失败三档,反思时只做组间对比,不做组内排序。这样反思提示里就是清晰的"这些成功了,那些失败了,找出区别",模型的表现会稳定很多。
3.2 及时反思:批量对比式反思怎么触发、提示怎么写
"Jit" 这个词我理解是 Just-in-Time 的意思,也就是策略更新发生在需要它的时刻,而不是攒够了再离线批处理。落到实现上,就是反思的触发条件设计:每积累 B 条新轨迹触发一次,或者当滑动窗口内的成功率跌破阈值时立即触发。前者保证吞吐,后者保证响应速度。
反思提示的结构我踩过不少坑,最后稳定下来的模板是这样的:先给任务背景和评分标准,再给正例组(3 到 5 条达标轨迹摘要),再给负例组(同样数量),最后明确要求"只输出可执行的动作级规则,每条不超过 40 字,不要输出'要小心'这类无法执行的建议,最多 8 条"。
约束条数这个细节很关键。不加限制的话,模型一次能吐 30 条,其中 25 条是同义反复,你还得花时间清洗。加了限制它反而会挑最重要的说。另外"动作级"这个限定也得写死,否则产出全是"提升理解能力""加强验证"这种听着对但没法落地的空话。
我还试过让模型直接输出对比表而不是规则,效果反而更好——先让它列出"成功组和失败组在这个决策点上的差异",再基于差异写规则。等于给它加了一步显式的中间推理,规则质量肉眼可见地提升。
3.3 策略库的增删改:信用分配与冲突消解
库的维护是最考验工程功力的地方。新增相对简单,难的是改和删。
改指的是合并近义条目。两条规则如果语义重合度超过阈值,就合并成一条更通用的表述,并继承两者的统计量。我一般用向量相似度做初筛,再让模型判断是否真的等价,两级过滤能省掉大量误合并。
删指的是淘汰。三种情况要删:连续多轮 n_used 为 0 的(说明检索器根本不会选它,占着预算没意义)、成功率显著低于基线的(负向规则,留着有害)、以及和更高优先级规则冲突的。冲突检测这块我建议别完全交给模型,做一个简单的规则引擎扫关键词组合会更可靠,比如同时存在"先验证再执行"和"不要重复验证"就是硬冲突,直接进人工复核队列。
条目生命周期我习惯分四态:candidate(刚生成,待验证)、active(已验证,可进提示)、probation(指标下滑,降权观察)、archived(归档,不再检索但保留记录)。这个状态机让整个库的演进过程可追溯,出问题时能回放"哪一轮引入了哪条规则导致指标变化"。这件事在事故复盘时的价值,比任何监控指标都高。
3.4 提示装配:检索、排序与预算控制的取舍
最后一步是把库里的条目塞回系统提示。这里有三个决策点:选哪些、怎么排、留多少预算。
选择用检索,按当前任务描述做语义匹配,取 top-k。但纯语义匹配有个陷阱,它偏向选和任务字面相似的规则,容易漏掉跨任务的通用规则。我的折中方案是混合排序:语义分占七成,效用分(UCB)占三成,保证既相关又有效。
排序上,我把最关键的 3 条规则放在提示开头和结尾各一份,中间放次要的。这不是玄学,位置对模型注意力的影响在长上下文里非常明显,实测同一批规则换个位置,遵循率能差十几个点。
预算控制是硬约束。我给自己定的红线是策略库不超过系统提示总长度的 40%,剩下留给任务说明和工具定义。超了就按效用分从低到高裁,裁下来的进 probation 而不是直接删,避免误杀。这个机制的副作用是库的规模会稳定在一个平台期,而不是无限膨胀,长期看反而是好事。
4. 实操:从零搭一条能跑通的流水线
4.1 环境与依赖:最小可用栈
这套东西对环境的依赖极低,这是它最讨喜的地方。你不需要 CUDA,不需要深度学习框架,一台普通开发机加一个模型 API 就够了。我用的是 Python 3.11,依赖只有四样:模型 SDK、向量检索库、一个轻量数据库存策略条目、以及任务环境本身的执行器。
pip install openai chromadb pydantic tenacity数据库我用的 SQLite,够用且零运维。表结构很简单,策略条目表加轨迹表,两张表就能撑起整个流程。如果你要做多 Agent 并行,换成 Postgres 加一个任务队列就行,接口不用改。
配置方面有几个参数必须先定下来,它们是后面所有调优的基准:
| 参数 | 含义 | 我的取值 | 取值理由 |
|---|---|---|---|
| BATCH_SIZE | 触发反思的轨迹数 | 20 | 太少反思没对比样本,太多单批成本高且噪声大 |
| MAX_RULES | 策略库容量上限 | 120 | 对应 1500 token 预算,留出淘汰空间 |
| TOP_K | 单次装配检索条数 | 12 | 再多遵循率反而下降,注意力被稀释 |
| ROLLOUT_PER_TASK | 单任务采样次数 | 3 | 至少 2 次才能形成组内对比 |
| MAX_STEPS | 单轨迹最大交互轮数 | 10 | 防止死循环,也控制单次成本 |
| TEMP | 采样温度 | 0.8 | 需要探索多样性,不能太贪心 |
温度这一项特别说一下。很多人习惯把 Agent 的温度设成 0 求稳定,但在这套方法里,rollout 阶段必须保留随机性,否则同一任务三次采样结果完全一样,你拿不到对比样本,反思就退化成复述。我的做法是采样用 0.8,评估用 0,两套配置分开。
4.2 一轮完整迭代的代码骨架
下面这段是我压到最简的架子,去掉业务细节之后大概长这样。它不追求性能,追求的是把数据流走通,你可以直接在上面加自己的环境和评测。
import json, uuid from collections import defaultdict def run_one_iteration(env, llm, policy_store, batch_size=20): # 1. 采样 trajectories = [] for task in env.sample_tasks(batch_size): rules = policy_store.retrieve(task.query, top_k=12) traj = env.rollout(task, system_prompt=assemble(rules), temperature=0.8, max_steps=10) trajectories.append(traj) # 2. 按奖励分档 buckets = defaultdict(list) for t in trajectories: buckets[t.grade].append(summarize(t)) # 压缩成 200~300 token # 3. 批量对比反思 candidates = llm.reflect( good=buckets["pass"][:5], bad=buckets["fail"][:5], max_rules=8, max_chars=40, ) # 4. 入库 + 去重 + 信用分配 for rule in candidates: policy_store.upsert(rule, state="candidate") # 5. 更新统计量并按效用裁剪 for t in trajectories: for rid in t.used_rule_ids: policy_store.record(rid, success=(t.grade == "pass"), delta=t.reward - baseline_reward) policy_store.prune(max_rules=120, budget_tokens=1500) return evaluate(policy_store)几个实现细节值得补充。assemble 函数要处理 token 预算,超了就截断,别指望模型自己忽略超长提示。summarize 我建议单独用一次低成本调用做,比在反思提示里塞原始轨迹便宜得多也干净得多。prune 的裁剪顺序是先按状态(archived 优先出),再按效用分,最后按最近使用时间,三级排序能避免误删刚上线的新规则。
还有一点:整个循环必须有 held-out 验证集。每轮迭代结束在一个完全不参与反思的固定任务集上评一次,只有分数提升才提交策略库变更,否则回滚。这个 gate 听起来啰嗦,但它挡住的是最常见的一种翻车——训练集分数一路涨,实际业务表现原地踏步。
4.3 参数怎么调:三个真正影响结果的旋钮
调参这件事上,真正影响结果的其实只有三个,其他都是噪声。
第一个是反思频率。BATCH_SIZE 设太小(比如 5),反思过于频繁,规则碎且重复,库迅速膨胀;设太大(比如 100),反馈延迟高,早期的错误策略会污染后面几十条轨迹。20 到 30 是我试下来比较稳的区间,任务同质性高的话可以放大到 50。
第二个是规则长度上限。40 字是我反复试出来的甜点。短于 20 字,规则会退化成口号("先检查再执行"),不带任何决策信息;长于 80 字,规则变成一段叙述,模型在提示里读它的成本上升,而且多条长规则会互相稀释注意力。
第三个是探索率。表现在温度、采样次数和 top_k 的组合上。我的经验公式是:任务分布越稳定,探索越少;新任务占比越高,越要保证每条候选规则都被真实试过一次。这里可以用 UCB 的探索系数 c 来做单一旋钮,c 从 1.2 起调,涨得快就降到 0.8,长期不动就升到 1.5。
反过来说,有几个参数我建议你别在上面花时间:反思提示里的措辞微调、摘要的具体字段顺序、检索的相似度阈值。这几个我做过对照实验,差异在噪声范围内,有那功夫不如多跑两条轨迹。
4.4 把 98 美元算给你看:一份可核对的成本表
这是全文最实的一段。下表按 1200 个任务、3 轮外层迭代、单任务平均 6 轮交互、批量反思 20 条一轮来算,单价取某中端模型的公开价位(输入 0.55 美元/百万 token,输出 2.2 美元/百万 token)。
| 费用项 | 调用次数 | 输入 token | 输出 token | 小计(美元) |
|---|---|---|---|---|
| rollout 交互 | 7200×3 | 25.2M×3 | 2.52M×3 | 58.2 |
| 批量反思 | 60×3 | 0.72M×3 | 0.09M×3 | 1.8 |
| 结果验证评分 | 3600 | 9.0M | 0.72M | 6.5 |
| 规则检索装配 | 21600 | 6.5M | 0 | 3.6 |
| 小计 | — | — | — | 70.1 |
| 重试与失败重跑(40%) | — | — | — | 28.0 |
| 合计 | — | — | — | 98.1 |
几个计算口径说明一下。rollout 的输入 token 按每轮 3500 算,其中系统提示 1200(含 12 条规则约 500 token)、历史轨迹 1800、工具返回 500。输出按每轮 350 token 算,Agent 的动作通常不长。反思按每批输入 12000 token(20 条摘要加指令)、输出 1500 token(8 条规则加解释)算。验证评分是每轮跑完对全部任务重评一次,输入 2500、输出 200。
40% 的重试系数不是拍脑袋,实测里超时、限流、解析失败、格式不合规加起来差不多就是这个量级。想把这个系数压下去,最有效的办法是给所有结构化输出加上强校验和自动修复,一行重试逻辑能省下几百次无效调用。
对边一下 GPU 那边:8 卡 2.5 美元/卡/小时,跑 168 小时是 3360 美元,加存储和调试重跑约 3500。差距 35 倍。这里面最反直觉的一点是——JitRL 的成本几乎全部由"任务数量"决定,和模型参数量无关。你换更大的基座模型,token 单价涨一点,但 GP U 那边的成本会涨一个数量级。规模越大,这个方案的相对优势越明显。
5. 踩坑记录:那些文档里不会写的问题
5.1 常见故障速查表
下面这张表是我和几个同行交流后攒出来的,覆盖了八成以上的翻车场景。建议直接存下来,出问题时按现象查根因。
| 现象 | 大概率根因 | 快速排查 | 处理方式 |
|---|---|---|---|
| 分数先涨后跌 | 策略库过拟合训练任务 | 画 held-out 曲线对照 | 加去重、提高淘汰阈值、扩充任务多样性 |
| 提示长度持续超限 | 条目无硬上限 | 统计每轮 token 分布 | 设硬预算裁剪,低效条目进 probation |
| 模型不遵守规则 | 规则埋在提示中段 | 做位置对照实验 | 关键规则前置与末尾重复 |
| 反思产出全是空话 | 轨迹摘要信息不足 | 抽 5 条看反思输入 | 补充失败点前后动作、加对比表中间步 |
| 成本远超预算 | 上下文重复注入 | 按调用拆分 token 账单 | 开前缀缓存、轨迹压缩、减少无关工具定义 |
| 同类任务反复失败 | 检索没召回到相关规则 | 打印 top_k 命中列表 | 混合语义分与效用分、补充同义表述 |
| 规则互相打架 | 缺冲突检测 | 关键词组合扫描 | 合并或加优先级字段,冲突进人工队列 |
我重点讲三个表里最不好处理的。
第一个是"分数先涨后跌",这个最折磨人。它通常在第二轮或第三轮迭代出现,训练集分数稳步上升,验证集在某个点之后掉头向下。根因几乎永远是库变窄了——反思只在当前批次里找差异,产出的规则越来越贴合那批任务的表面特征。解法有两个:一是强制任务采样分层,保证每批里都有一定比例的历史任务和边缘任务;二是给规则加"适用范围"字段,反思时要求模型显式写出这条规则在什么条件下不适用,检索时按适用范围过滤。第二个办法效果更好,但会让反思成本涨三成左右。
第二个是"模型不遵守规则"。这个坑我踩了整整两周。一开始以为是模型能力问题,换了两家服务商都没用,后来做位置对照才发现,把同一条规则从提示中段移到末尾,遵循率从六成跳到九成。长上下文里的注意力衰减是真实存在的,规则写得好不好是一回事,放对位置是另一回事。顺便说,规则数量也不是越多越好,我测过 12 条和 30 条两组,前者遵循率反而更高,因为模型面对太多约束时会开始"挑着遵守"。
第三个是"成本偷偷超标"。这事最隐蔽,因为单次调用看起来都很便宜。根因通常是重复注入——每轮交互都把完整的工具定义和全部规则重新发一遍,实际有效信息只占三成。解法是前缀缓存加上动态裁剪,把稳定不变的部分(工具定义、任务说明)放在提示最前面以保证缓存命中,把每轮变化的部分放在后面。这一项调整在我的实测里省了四成左右的输入成本。
5.2 策略库退化的三种形态与对症下药
策略库退化是个缓慢过程,等你从分数上发现时,通常已经积累了几十轮坏数据。我总结了三种典型形态。
第一种是膨胀。表现是条目数持续增长,但有效条目占比越来越低。根因是新增没有配套淘汰,而反思又倾向于生成近义表述。药方是提高合并阈值,并且强制每轮淘汰固定比例的末位条目——哪怕它们的统计量看着还行。这个"强制淘汰"听着粗暴,但它逼着系统只保留最有价值的规则,长期效果比温和淘汰好。
第二种是冲突。两条规则在特定场景下给出相反指令,模型随机选一个,表现为同一类任务时好时坏,方差极大。这类问题靠分数很难定位,得靠人工抽检提示内容。药方是入库前做一次语义冲突检测,把潜在冲突的条目挂上互斥标签,检索时同组只取一条。
第三种是僵化。表现是所有新规则都上不了场,老规则永远霸占提示位置。根因是纯利用的选择策略,新规则没有历史数据,效用分自然低。药方就是前面写的 UCB 探索项,另外可以加一条硬规则:每轮装配必须包含至少一条 candidate 状态的条目,强制给它曝光机会。这一条我在三个不同项目里都验证过,加上之后新规则的存活率明显提升。
5.3 评估失真:为什么离线涨了线上没动
这个问题值得单开一节,因为它决定了你这套系统到底有没有用。
失真的第一个来源是评估集污染。如果你的验证任务和训练任务来自同一个池子,只是随机切分,那策略库实际上见过它们的"近亲",分数会虚高。正确做法是按任务类型或时间切分,验证集必须是完全不参与反思的那部分,而且最好在时间上晚于训练集,模拟真实的分布漂移。
第二个来源是单指标。只看成功率会漏掉很多问题,比如成功率涨了但平均交互轮数从 4 涨到 9,成本翻倍;或者成功率涨了但工具调用错误率也涨了,只是恰好被重试掩盖了。我建议至少盯四个指标:任务成功率、平均交互轮数、工具调用错误率、规则遵循率。前两个看效果,后两个看健康度。
第三个来源是评估器本身不稳定。用 LLM 做裁判时,同一个输出评两次可能给出不同结论。解法是固定评估提示、温度设 0、并且对关键指标做多次采样取多数。这件事成本不高但收益很大,我见过因为评估器抖动导致整个迭代方向跑偏的案例。
最后分享一个我觉得很实用的小技巧:给策略库做"消融评估"。每轮迭代结束后,除了正常评估,再跑一次"清空策略库"的基线评估,两个分数的差值才是这套方法真正贡献的增益。如果某轮迭代后这个差值在缩小,说明规则库的边际价值在下降,该考虑换基座模型或者扩充任务类型了,而不是继续在这条路上硬堆规则。