同一段提示词两次跑出结构完整度差很多,通常不是提示词本身在变,而是生成和评分链路上几个会漂移的量没有被固定:随机种子与采样参数、任务抽样顺序、页面渲染的等待条件与视口尺寸、以及超时设置。把这些量在运行脚本里钉住并把本次输入落盘,两次运行的差异才有资格归因到模型或提示词。
判断方向是先把环境变量钉死,再谈模型差异。适用场景:同一提示词、同一 WebCraftBench 任务集,两次结果不可比。操作动作:固定 seed 与采样参数、复用同一份任务 id 列表、统一渲染等待与视口、先跑两轮空对照。验证方式:空对照两轮分数接近且失败任务重合度高,再动提示词。风险边界:接口若不暴露 seed 或不支持确定性采样,只能缩小波动,不能指望完全复现。
在运行脚本里固定随机种子与采样参数
先让两次生成在源码层面可比。做法是把本轮所有影响输出的参数收进一个配置对象,在调用模型前打印并写入运行目录,而不是散落在脚本各处。
run_cfg = {
'benchmark': 'WebCraftBench',
'task_ids': [], # 由任务清单文件填充,见下一节
'seed': None, # 占位:接口支持 seed 时填固定整数
'temperature': None, # 占位:以实际接口文档为准
'top_p': None, # 占位
'max_tokens': None, # 占位:值不确定就不要写死
'timeout_s': None, # 占位
}
# 调用前落盘
write_json('runs/<run_id>/run_cfg.json', run_cfg)
resp = model.generate(prompt, **run_cfg)参数名一律以实际接口文档为准,这里给的是字段位而非官方命名。凡是接口文档里没确认的字段,留成 None 或 TODO,并在日志里标注未确认,不要凭印象写死一个默认值——写死猜测值比留空更糟,因为它会伪装成已固定。如果所用接口根本不支持固定 seed,就在 run_cfg 里显式标注 seed_unsupported,后续看波动时把这部分排除。
把任务抽样顺序固定下来并落盘
WebCraftBench 这类任务集如果在每轮启动时重新随机抽样,即使模型完全没变,两轮跑的任务也不是同一批,分数差异就无从归因。要求每轮把实际执行的任务 id 按顺序写成文件,下一轮直接复用这份文件。
# 第一轮:抽一次并落盘
python run_bench.py `--out` runs/0001 `--sample` 40 `--dump-tasks` runs/0001/tasks_run.txt
# 后续所有轮次:复用同一份列表,不再重新抽样
python run_bench.py `--out` runs/0002 `--tasks` runs/0001/tasks_run.txttasks_run.txt 每行一个任务 id,顺序也保留,便于按位对比。需要换任务集时新建一份列表文件并改运行编号,不要覆盖旧文件,否则之前的对照记录就失去参照。
统一网页渲染的等待条件与视口尺寸
页面还没渲染完就开始截图和评分,会制造出一批假失败,表现为同一任务时好时坏。等待条件和视口尺寸都要写进配置,不要用工具默认值。
render:
viewport:
width: 1440
height: 900
device_scale_factor: 1
wait:
until: networkidle # 或 load,取值以所用工具支持为准
extra_delay_ms: 800 # 固定延时,补字体与动画
timeout_ms: 30000
screenshot:
full_page: true验证方式是同一页面连拍两张截图做对比:逐像素比对或感知哈希都行,两张基本一致再开始评分。若两次截图仍有明显差异,说明页面里还有异步内容在变,可以加大 extra_delay_ms、屏蔽动画,或把动态时间戳一类元素在评分前剔除。视口尺寸一旦改变,布局和分数都会变,所以尺寸必须和截图一起进 run_cfg。
跑两轮空对照,确认基线是否稳定
在改任何提示词之前,先用完全相同的配置和任务列表跑两轮,量出同一输入下的天然波动。
python run_bench.py `--config` cfg/base.yaml `--tasks` runs/0001/tasks_run.txt `--out` runs/base_a
python run_bench.py `--config` cfg/base.yaml `--tasks` runs/0001/tasks_run.txt `--out` runs/base_b把两轮结果记成一张表,留作后续对照的底稿:
- 轮次:base_a / base_b
- 总分:各自记录
- 失败条数:各自记录
- 失败任务 id:写成列表文件
- 备注:是否中途报错、是否超时
如果两轮总分差异已经很大、失败任务几乎不重合,说明环境还没稳定,此时比较提示词改动没有意义,先回到前三节把种子、任务列表、渲染等待逐项确认。
把改动后的差异逐条对到任务 id 上
改动生效后,不要只看总分涨跌,要把失败任务集合做 diff,看变化集中在哪些任务。
python report.py `--run` runs/base_a `--list-failed` > runs/base_a/failed.txt
python report.py `--run` runs/after_a `--list-failed` > runs/after_a/failed.txt
diff runs/base_a/failed.txt runs/after_a/failed.txt解读规则可以先用这套:两轮都失败的任务属于重合部分,通常与该改动无关,优先怀疑任务本身的固有难度或环境问题;改动后新出现的失败是最该查的回归,说明这条改动在特定任务上有副作用;从失败列表里消失的任务,代表该改动在这类任务上有效,但最好再跑一轮同配置确认它不是随机波动。如果失败任务几乎整体平移、重合度很低,往往是基线不稳而不是改动有效,回到空对照重新判断。