在第 01 篇里,我们搭好了 Harness 基线:
- 样本可执行
- 结果可对比
- 回归可追溯
但团队很快会遇到第二个瓶颈:
- 样本集越来越“过拟合旧问题”,新问题测不出来
- 每次补样本都靠人肉,维护成本持续上升
- 通过率看起来在涨,但线上事故并没有减少
这篇只回答一个问题:如何把“样本”从一次性资产,变成可持续演进的数据系统。
一、为什么评测会失真:不是模型退化,而是样本老化
很多团队把回归率波动归因给模型,但根因常在样本工程:
- 覆盖结构失衡
- 黄金样本占比过高,长尾失败场景占比过低。
- 输入分布漂移
- 线上真实请求变化了,离线样本还停留在两个月前。
- 断言粒度过粗
- 只判断“看起来像对的”,没有验证关键约束是否满足。
- 样本生命周期缺失
- 旧样本没人清理,失效样本持续污染结论。
所以第 02 步的重点不是“多加样本”,而是建立样本生命周期治理。
二、样本工程目标:四个可持续能力
你需要的不是大样本库,而是四项稳定能力:
- 可分层
- 明确知道每条样本属于哪类风险与业务路径。
- 可演进
- 样本可新增、可降级、可归档,不会越堆越乱。
- 可追因
- 一次失败能快速定位到场景、规则、版本。
- 可闭环
- 线上失败可低成本回流为离线可测样本。
三、样本分层模型:从“按来源”改为“按风险”
建议把样本集切成四层,而不是只分黄金/失败回放:
L1-Contract(契约层)
- 验证固定接口约束与输出格式,防止基础破坏。
L2-Task(任务层)
- 验证核心业务任务是否达标,是日常优化主战场。
L3-Risk(风险层)
- 聚焦高风险失败:越权、幻觉、错误执行建议、敏感操作。
L4-Drift(漂移层)
- 采样近期线上流量,监控输入分布与性能漂移。
这四层对应不同发布门禁强度:
L1/L3失败可直接阻断L2失败按阈值告警或阻断L4主要用于趋势观察与提前预警
四、样本元数据:没有元数据,就没有治理
每条样本至少要带以下字段:
{
"id": "risk_authz_20260416_001",
"layer": "L3-Risk",
"domain": "repo_write",
"source": "production_incident",
"created_at": "2026-04-16T09:30:00Z",
"owner": "ai-platform",
"severity": "high",
"freshness_days": 14,
"ttl_days": 90,
"version": "v1",
"expected": {
"must_include": ["需要审批", "只读替代方案"],
"must_not_include": ["直接执行删除", "绕过权限"]
}
}
核心点只有两个:
freshness_days:多久必须复核一次ttl_days:超过多久必须重写或归档
这样可以直接解决“样本永生”导致的失真问题。
五、自动补样本流水线:把事故变资产
建议建立最小闭环:
线上失败事件 -> 事件清洗 -> 脱敏 -> 归因打标 -> 生成候选样本
-> 人审(轻量) -> 入库(L2/L3/L4) -> 下次回归执行
关键原则:
- 自动提取上下文,不手工复制粘贴
- 默认脱敏,敏感信息不入库
- 失败先进入候选池,不直接成为门禁样本
- 人审只做关键决策(风险等级/断言有效性)
六、C# 样本治理最小实现
下面给一个可落地的仓库治理骨架:
using System.Text.Json;
public enum CaseLayer
{
L1Contract,
L2Task,
L3Risk,
L4Drift
}
public sealed class DatasetCase
{
public string Id { get; set; } = string.Empty;
public CaseLayer Layer { get; set; }
public string Domain { get; set; } = string.Empty;
public string Source { get; set; } = string.Empty;
public string Owner { get; set; } = string.Empty;
public string Severity { get; set; } = "medium";
public int FreshnessDays { get; set; } = 14;
public int TtlDays { get; set; } = 90;
public DateTime CreatedAtUtc { get; set; }
public ExpectedSpec Expected { get; set; } = new();
}
public sealed class DatasetHealth
{
public int Total { get; set; }
public int Expired { get; set; }
public int NeedsRefresh { get; set; }
public Dictionary<CaseLayer, int> LayerCounts { get; set; } = new();
}
public static class DatasetRegistry
{
public static async Task<List<DatasetCase>> LoadAsync(string caseDir)
{
var result = new List<DatasetCase>();
var files = Directory.GetFiles(caseDir, "*.json", SearchOption.AllDirectories);
foreach (var file in files)
{
var json = await File.ReadAllTextAsync(file);
var item = JsonSerializer.Deserialize<DatasetCase>(json);
if (item is not null)
{
result.Add(item);
}
}
return result;
}
public static DatasetHealth CheckHealth(List<DatasetCase> cases, DateTime nowUtc)
{
var health = new DatasetHealth { Total = cases.Count };
foreach (var c in cases)
{
if (!health.LayerCounts.ContainsKey(c.Layer))
{
health.LayerCounts[c.Layer] = 0;
}
health.LayerCounts[c.Layer]++;
var ageDays = (nowUtc - c.CreatedAtUtc).TotalDays;
if (ageDays > c.TtlDays) health.Expired++;
else if (ageDays > c.FreshnessDays) health.NeedsRefresh++;
}
return health;
}
}
这段代码先解决三件事:
- 可统计:知道样本结构是否健康
- 可预警:及时发现过期与待刷新样本
- 可治理:为 CI 门禁提供可执行信号
七、门禁策略:按层定义,不要一刀切
建议采用分层阈值:
L1-Contract:PassRate 必须 100%L3-Risk:PolicyViolationRate 必须 0L2-Task:PassRate >= 基线 - 1%L4-Drift:仅告警,不阻断
再加两条全局约束:
ExpiredCases == 0才允许发布NeedsRefresh超阈值时触发样本维护工单
这样可以避免“模型指标达标但样本库已经腐化”的假乐观。
八、一周落地模板(从手工到飞轮)
Day 1
- 给现有样本补齐元数据(layer/source/severity/freshness/ttl)
Day 2
- 上线
DatasetHealth报告并接入 CI
Day 3-4
- 接入线上失败自动提取,进入候选池
Day 5
- 建立轻量人审流程,形成入库规范
Day 6
- 发布首版分层门禁阈值
Day 7
- 复盘一次漂移告警与失效样本清理效率
九、第 02 篇结论
Harness 的长期价值,不在 Runner,而在样本工程。
当你把样本当作“可治理、可演进、可闭环”的系统资产,评测结果才会持续代表真实质量,而不是历史幻觉。
下一篇进入《Harness 实战 03:评分器与裁判体系(规则判定 + LLM-as-Judge 的混合策略)》,解决“能跑回归但判不准”的问题。