9.3 何时该训、何时调提示:成本收益决策清单
前两节讲了「怎么训」——轨迹怎么形式化、奖励怎么设计、SFT 怎么接到 RL。但一个更根本的问题还没回答:你该不该训? 这行里有个反复出现的误区:一遇到效果不好就想「去训个模型」。可训练是这五种手段里最贵、最慢、最不可逆的一种,而效果不好往往用更便宜的手段就能解决。本节把「调提示 / 换模型 / 加工具 / 微调 / RL」五种干预摆在同一个成本-收益坐标里,给出一张可照着走的决策清单。
本节是本章的收口:把前面讲的 RL 能力放回工程决策的语境。先按成本与收益给五种干预排序,再给出一张「什么信号出现时该上哪一档」的清单,最后用一个真跑的决策打分脚本,对两种典型场景算出排序,帮你在动手训之前先把这笔账算清楚。
9.3.1 五种干预手段:从最便宜到最贵
先把候选手段列清楚。它们的成本量级差了几个数量级,生效速度也完全不同:
| 干预手段 | 一次性成本 | 迭代速度 | 前置条件 | 主要收益 | 主要风险 |
|---|---|---|---|---|---|
| 调提示 | 极低 | 分钟级 | 无 | 格式、风格、少样本示例 | 天花板低,改不动模型能力 |
| 换模型 | 低 | 小时级 | 有更强的模型可选 | 整体能力抬升 | 成本/延迟上升,可能锁供应商 |
| 加工具/检索 | 中 | 天级 | 有可封装的工具或知识源 | 补上模型「不知道」的部分 | 工具本身有 bug、延迟与成本增加 |
| SFT 微调 | 高 | 周级 | 有成百上千条高质量示范 | 稳定格式、领域适配、降本 | 需要标注数据;过拟合;更新滞后 |
| RL 训练 | 极高 | 周~月级 | 有可靠奖励信号 + 大量采样 | 突破示范上限、优化多步策略 | 奖励被钻空子;环境不可复现;方差大 |
看这张表,有一条从上往下走的默认路径:先穷尽上面的便宜手段,再动下面的贵手段。原因很直白——上面每一档的边际成本都远低于下面一档,而它们能解决的问题有大量重叠。很多「模型能力不行」的抱怨,实际是提示里没给示例、没给工具、没给上下文。
但也要看清每一档的边界:
- 调提示改的是「怎么问」,改不动「模型本身会不会」。如果任务需要的是模型不具备的能力(比如某种特定的多步推理模式),提示写得再好也到不了。
- 换模型能抬整体能力,但它是钝器——你为整个任务付更贵的推理成本,只为解决其中一个子问题,往往不划算。
- 加工具/检索是 Agent 场景里性价比最高的一档:模型不需要「记住」知识,只需要「会调用」工具。本卷前面几章讲的工具化、MCP、浏览器 Agent,本质都在这一档。
- SFT / RL 是重器:只有当上面三档都撞到天花板、且你有对应的数据和信号时,它们才划算。
9.3.2 决策清单:出现什么信号时该上哪一档
把上面的判断做成一张可照着走的清单。从左往右逐列检查,第一个满足「该上」条件的,就是你应该先试的手段:
| 检查项 | 如果答案是… | 该上的手段 |
|---|---|---|
| 失败案例里,是否同一条提示在不同输入上表现不稳(时好时坏)? | 是 | 调提示(补示例、加约束、明确格式) |
| 换个更强的模型跑同一套提示,是否明显更好? | 是 | 换模型(或做模型路由,见本卷 10.2 ) |
| 失败是否集中在模型缺某个知识/能力,而这个能力能封装成工具或检索? | 是 | 加工具 / 检索 |
| 失败是否格式稳定但语义不对,且你有大量高质量示范? | 是 | SFT 微调 |
| 失败是否过程走法不对(绕远、乱调工具、引用错),且有可自动判分的奖励信号? | 是 | RL 训练(或先跑 ReST 的采样-筛选) |
| 以上都不是,或者你拿不出对应数据/信号 | — | 先别训,回到调提示或加工具 |
这张清单的核心逻辑是按「失败模式」而不是「想要的效果」来选手段:
- 「时好时坏」是提示不够明确的信号,不是能力不足;
- 「换个模型就好」说明当前模型的基座能力不够,训练也救不了(你没法用小数据把一个小模型训成大模型);
- 「缺知识」优先加检索而不是微调——知识会更新,而微调进去的知识会过时;
- 只有「格式对、语义错」且示范数据充足,才轮到 SFT;
- 只有「过程不对」且有可验证奖励,才轮到 RL。
9.3.3 决策打分脚本:把「该不该训」算出来
清单给的是定性判断。当几个条件同时满足、你拿不准时,可以用下面这个可运行的启发式打分脚本,把收益、成本、前置条件一起算进去。它不是精确预测,而是一个把「拍脑袋」变成「显式比较」的工具——真跑(Python 3.14.6):
"""「何时该训、何时调提示」决策打分:按成本/收益把五种干预排个序(启发式,非实验)。"""
from dataclasses import dataclass
@dataclass
class Scenario:
name: str
error_rate: float # 当前任务失败率 0~1
has_verifiable_reward: bool # 是否有可自动判定的奖励信号
labeled_data: int # 有多少条人工标注/可用样本
budget: str # "低" | "中" | "高"
latency_budget_ms: int
@dataclass
class Intervention:
name: str
cost: float # 一次性/持续成本 0~1,越大越贵
needs_data: int # 需要多少样本才划算
needs_reward: bool # 是否必须有可验证奖励
latency_add_ms: int
INTERVENTIONS = [
Intervention("调提示", 0.05, 0, False, 0),
Intervention("换模型", 0.30, 0, False, 0),
Intervention("加工具/检索", 0.40, 0, False, 30),
Intervention("SFT 微调", 0.70, 200, False, -20),
Intervention("RL 训练", 0.95, 1000, True, -20),
]
def expected_gain(iv: Intervention, s: Scenario) -> float:
"""收益随失败率上升,但要满足前置条件(数据量 / 奖励信号)。"""
if s.labeled_data < iv.needs_data:
return 0.0
if iv.needs_reward and not s.has_verifiable_reward:
return 0.0
base = s.error_rate * 1.0 # 可修复的错误比例上限
if iv.name in ("SFT 微调", "RL 训练"):
base *= 0.8 # 训练类收益上限更高但方差大,先打 8 折
return round(base, 3)
def score(iv: Intervention, s: Scenario) -> float:
gain = expected_gain(iv, s)
over = max(0, iv.latency_add_ms - s.latency_budget_ms) / 1000
return round(gain - iv.cost - over, 3)
def rank(s: Scenario):
rows = [(iv.name, expected_gain(iv, s), iv.cost, score(iv, s)) for iv in INTERVENTIONS]
return sorted(rows, key=lambda r: r[3], reverse=True)
scenarios = [
Scenario("冷启动、无标注数据", 0.35, False, 0, "低", 2000),
Scenario("有 5k 可验证轨迹", 0.45, True, 5000, "高", 2000),
]
for s in scenarios:
print(f"--- 场景:{s.name} ---")
for name, gain, cost, sc in rank(s):
print(f" {name:12s} 收益={gain:<5} 成本={cost:<4} 净分={sc}")
本机实测输出:
--- 场景:冷启动、无标注数据 ---
调提示 收益=0.35 成本=0.05 净分=0.3
换模型 收益=0.35 成本=0.3 净分=0.05
加工具/检索 收益=0.35 成本=0.4 净分=-0.05
SFT 微调 收益=0.0 成本=0.7 净分=-0.7
RL 训练 收益=0.0 成本=0.95 净分=-0.95
--- 场景:有 5k 可验证轨迹 ---
调提示 收益=0.45 成本=0.05 净分=0.4
换模型 收益=0.45 成本=0.3 净分=0.15
加工具/检索 收益=0.45 成本=0.4 净分=0.05
SFT 微调 收益=0.36 成本=0.7 净分=-0.34
RL 训练 收益=0.36 成本=0.95 净分=-0.59
这份输出给了两个反直觉但重要的结论:
- 即使有 5k 条可验证轨迹,调提示的净分依然最高。 因为在脚本的假设里,调提示的成本是 0.05、能吃到和训练同样的「可修复错误比例」。这说明只要调提示还有空间,就该先调提示——它是最便宜的一档。只有当调提示撞到天花板(脚本里体现为「失败模式不是提示能解决的」),训练的收益才显现。
- 训练类(SFT/RL)在冷启动场景直接归零。 因为
labeled_data=0 < needs_data,前置条件不满足,expected_gain返回 0,再高的失败率也换不来收益。这精确地表达了那句话:没有数据,就没有训练。
脚本里的数字是启发式假设,不是实验结论——0.35、0.8 这些系数是我为演示「怎么算」而设的,你应当按自己团队的真实成本与实测收益替换它们。它的价值不在给出答案,而在逼你把「数据够不够」「奖励信不信得过」「延迟预算多少」这些前置条件显式写下来。很多「该不该训」的争论,一旦被拆成这几行,答案就清楚了。
9.3.4 敏感性:最优手段会不会翻转
上一节的脚本有个隐含假设:调提示能吃到和训练一样的「可修复错误比例」。这显然不对——调提示有天花板。真实决策里最关键的一个变量,是「当前失败里,有多大比例是调提示能修的」。把这个变量抽出来跑一遍,看最优手段会不会翻转(真跑):
"""决策的敏感性:当「失败里有多少是调提示能修的」下降时,最优手段会不会变?"""
COST = {"调提示": 0.05, "换模型": 0.30, "加工具/检索": 0.40, "SFT 微调": 0.70, "RL 训练": 0.95}
COVER = {"调提示": None, "换模型": 0.70, "加工具/检索": 0.80, "SFT 微调": 0.68, "RL 训练": 0.72}
def best(error_rate, prompt_fixable, has_data, has_reward):
rows = []
for name, cost in COST.items():
if name == "SFT 微调" and not has_data:
rows.append((name, round(-cost, 3))); continue
if name == "RL 训练" and not (has_data and has_reward):
rows.append((name, round(-cost, 3))); continue
cover = prompt_fixable if name == "调提示" else COVER[name]
rows.append((name, round(error_rate * cover - cost, 3)))
return max(rows, key=lambda r: r[1])[0]
print("失败率固定 0.5,数据与奖励都齐备:")
for pf in (0.9, 0.6, 0.3, 0.2, 0.1):
print(f" 调提示可修比例={pf:<4} -> 最优手段={best(0.5, pf, True, True)}")
print("失败率固定 0.5,冷启动(无数据无奖励):")
for pf in (0.9, 0.3, 0.1):
print(f" 调提示可修比例={pf:<4} -> 最优手段={best(0.5, pf, False, False)}")
本机实测输出:
失败率固定 0.5,数据与奖励都齐备:
调提示可修比例=0.9 -> 最优手段=调提示
调提示可修比例=0.6 -> 最优手段=调提示
调提示可修比例=0.3 -> 最优手段=调提示
调提示可修比例=0.2 -> 最优手段=调提示
调提示可修比例=0.1 -> 最优手段=换模型
失败率固定 0.5,冷启动(无数据无奖励):
调提示可修比例=0.9 -> 最优手段=调提示
调提示可修比例=0.3 -> 最优手段=调提示
调提示可修比例=0.1 -> 最优手段=换模型
结论有两层:
- 交叉点出现在「调提示可修比例」约 0.2 附近:只要失败里还有两成以上是提示能修的,调提示就仍然是净收益最高的手段——因为它太便宜了。只有当绝大多数失败根本不是提示能碰的(比如缺知识、缺能力),换模型/加工具才反超。
- 注意翻转后的赢家是「换模型」而不是训练:因为脚本里换模型(成本 0.30)比加工具(0.40)便宜、比训练(0.70/0.95)便宜得多。这恰好印证了 9.3.1 那条默认路径——即使调提示失效,也先轮到「换模型/加工具」,训练仍排在最后。要让训练胜出,得同时满足:调提示失效 + 换模型和加工具也失效 + 有数据 + 有奖励信号。
这两个数字同样是启发式假设,不是实测。但交叉点存在这件事本身是稳健的:任何「便宜手段有天花板」的模型都会产生一个类似的翻转点,你的任务是估算自己场景里的这个点在哪,而不是照抄 0.2。
9.3.5 三个典型决策
把清单和脚本合起来,看三种真实场景:
场景一:客服问答,失败率 20%,错误五花八门。 逐列检查:失败是否「时好时坏」?是——同一条提示不同问法表现差很多。→ 先调提示:补 few-shot 示例、固定输出格式。这一步往往能吃掉一半以上的错误,成本几乎为零。
场景二:代码 Agent,失败率 30%,集中在「找不到正确的 API」。 失败不是提示问题(格式都对),也不是能力问题(换个模型也好不到哪去)——是缺知识。→ 加检索工具:把内部 API 文档做成可检索的知识源,让模型「查」而不是「猜」。这比把文档微调进模型更好,因为文档会更新。
场景三:多步研究 Agent,失败率 40%,过程绕远、引用错,但你能自动判定「引用是否可回溯」。 这正好命中 RL 的适用条件:过程不对 + 有可验证奖励 + 有大量采样能力。→ 但仍建议先跑 ReST 的采样-筛选(9.2.6):用现成模型采一批轨迹、按引用可回溯打分、筛出好的再 SFT。这一步不需要策略梯度的基础设施,就能验证「奖励信号到底有没有用」;只有验证有效,再上更重的 RL。
9.3.6 常见误区
- 「效果不好就去训」:训是最后手段。先问「提示穷尽了吗、工具给够了吗」。
- 「训了就能用更小的模型省钱」:确实可以,但前提是你有足够数据把大模型的能力蒸馏到小模型里;数据不够时,小模型 + 微调往往还不如大模型 + 提示。
- 「RL 一定比 SFT 强」:RL 需要可靠奖励信号,没有它,RL 会放大奖励函数的缺陷;有可靠信号时,ReST 的「采样-筛选」往往先于完整 RL 生效。
- 「微调能灌进新知识」:微调擅长格式与风格,不擅长注入会过时的知识——知识类需求优先用检索。
- 「跳过评测直接上线训练结果」:训练改动的是模型行为,必须用固定评测集回归(本卷 10.2 讲评测兜底),否则你无法判断到底是变好还是变坏。
- 「一次训到位」:Agent RL 的环境不可复现、分布非平稳(9.2.7),指望「训一次、永久生效」不现实;正确姿势是小步迭代——训一轮、评测、看行为漂移、调奖励、再训。
- 「用训练替代工程」:很多所谓「模型能力问题」,其实是工具设计、上下文管理、错误处理没做好。把这些工程问题用训练去盖,等于用最贵的手段去修最便宜的 bug。
9.3.7 这套判断替你做了什么、你又让渡了什么
这张清单与脚本替你做的:它把「该不该训」从感觉之争变成显式比较——把成本、前置条件、失败模式都摆到台面上;它给出了一条「先便宜后昂贵」的默认路径,避免团队一上来就投入最贵的方案;它逼你在动手前先回答「数据够不够、奖励信不信得过」。
你让渡的:任何清单都是简化,真实决策里「调提示」和「加工具」的边界常常模糊(加个 few-shot 示例算调提示,加个检索工具算加工具),失败模式也可能同时命中多项;脚本里的成本系数是你填的,填错了排序就错了——它放大你的判断,不替代你的判断。最终,「该不该训」仍是一个要拿真实评测数据说话的经验问题,而不是一个能被脚本算死的公式题。
本章到此收尾。我们从「把任务形式化成轨迹」讲到「算清训练的成本收益」,核心是一句话:RL 是能力上限最高的手段,但也是成本最高、最需要前置条件的手段——先穷尽便宜的手段,再用评测数据决定要不要动它。下一章 10.1 统一可观测:三框架 trace 归一 OTel 会把视角从「单个 Agent 怎么训」拉回「整个系统怎么看」——无论你训不训模型,生产化的第一件事都是先能看见它在干什么。
阅读导航:上一节:9.2 从 SFT 到 RL:工具使用与多步推理训练流程 · 下一节:10.1 统一可观测:三框架 trace 归一 OTel 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。