成功率、恢复率与人工介入
正在读取学习记录…
成功率回答“这批任务有多少完成了”;恢复率回答“遇到故障的任务中,有多少靠自动机制恢复”;人工介入率回答“这批任务有多少需要人参与”。这些比例看起来都像一个百分数,分母却不同。
像医院的三个数字:全部患者中治愈多少、出现并发症的患者中恢复多少、全部患者中有多少需要会诊。把每个分母圈出来,再讨论高低,才能避免拿一个数字替另一个数字回答问题。
先约定一次任务执行是什么
一位用户提交一次退款任务,助手内部查单两次、重试一次,仍是一项任务的一次执行。attempts 记录工具尝试次数,用于分析重试与成本,不能把它拆成三项新任务来算成功率。若为了评测随机模型对同一任务重复跑三轮,那是三个trial(执行轮次),要保留任务编号与轮次编号。
下面八行是预置执行记录。fault 表示执行中是否遇到故障;human 表示是否发生人工介入;terminal 为success、failed或pending(尚待结案);attempts 是工具尝试数。m1至m3直接自动完成;m4/m5遇故障后自动完成;m6经人工完成;m7人工介入后仍失败;m8没有故障但正在等待人工结案。
口径先锁定:统计本批全部八次执行,包括pending,并把pending单独报告。这样“还没处理完”不会因为被删出分母而把成功率抬高。若要另算“已结案成功率”,可以另列6/7,但必须写明它不包含待结案样本。
哪些指标共享分母,哪些不共享
总成功率的分子包含人工完成;自动成功率要求成功且没有人工介入。人工介入率的分子包括介入后成功、失败和待结案三种情况。它描述人力参与,不等于人工完成率。
自动恢复率采用“自动恢复成功的遇故障执行数 ÷ 全部遇故障执行数”。分子要求遇故障、最终成功、无人介入,分母则保留人工解决和最终失败的故障执行。这个口径能看出恢复机制替用户自动兜住了多少问题。
从空目录保存并运行
在 PowerShell 中进入你平时使用的学习目录,再创建本节文件夹并进入:
mkdir l7-metrics
cd l7-metrics用编辑器新建文件,把下面完整代码保存为当前文件夹的 metrics_lab.py。保存类型选“所有文件”,编码选 UTF-8;资源管理器开启“文件扩展名”,核对文件名以 .py 结尾。数据已经写在代码里,可以直接运行。
"""对固定八次任务执行计算成功、自动恢复、人工介入和待结案。"""
from copy import deepcopy
# 一行是一项任务的一次执行,attempts记录内部工具尝试数,不展开成新任务。
RUNS = [
{"id": "m1", "fault": False, "human": False, "terminal": "success", "attempts": 1},
{"id": "m2", "fault": False, "human": False, "terminal": "success", "attempts": 1},
{"id": "m3", "fault": False, "human": False, "terminal": "success", "attempts": 1},
{"id": "m4", "fault": True, "human": False, "terminal": "success", "attempts": 2},
{"id": "m5", "fault": True, "human": False, "terminal": "success", "attempts": 3},
{"id": "m6", "fault": True, "human": True, "terminal": "success", "attempts": 2},
{"id": "m7", "fault": True, "human": True, "terminal": "failed", "attempts": 1},
{"id": "m8", "fault": False, "human": True, "terminal": "pending", "attempts": 1},
]
def fraction(numerator, denominator):
"""零分母返回None,展示时写N/A,避免伪造0%或100%。"""
return None if denominator == 0 else numerator / denominator
def summarize(runs):
"""口径固定为本批全部任务执行;pending保留在总分母中并单列。"""
ids = set()
for row in runs:
if row["id"] in ids:
raise ValueError("任务执行编号重复")
ids.add(row["id"])
if type(row["fault"]) is not bool or type(row["human"]) is not bool:
raise ValueError("故障和介入标记必须是布尔值")
if row["terminal"] not in ("success", "failed", "pending"):
raise ValueError("未知任务状态")
if type(row["attempts"]) is not int or row["attempts"] < 1:
raise ValueError("尝试次数必须是正整数")
total = len(runs)
successes = sum(row["terminal"] == "success" for row in runs)
auto_successes = sum(row["terminal"] == "success" and not row["human"] for row in runs)
faults = sum(row["fault"] for row in runs)
recovered = sum(row["fault"] and row["terminal"] == "success" and not row["human"]
for row in runs)
human = sum(row["human"] for row in runs)
human_success = sum(row["human"] and row["terminal"] == "success" for row in runs)
pending = sum(row["terminal"] == "pending" for row in runs)
return {
"success": (successes, total), "auto_success": (auto_successes, total),
"auto_recovery": (recovered, faults), "human": (human, total),
"human_success": (human_success, human), "pending": (pending, total),
"attempts": sum(row["attempts"] for row in runs),
}
def display(pair):
numerator, denominator = pair
value = fraction(numerator, denominator)
return f"{numerator}/{denominator}=" + ("N/A" if value is None else f"{value:.1%}")
def main():
result = summarize(deepcopy(RUNS))
print(f"任务执行数={len(RUNS)} 工具尝试总数={result['attempts']}")
for name in ("success", "auto_success", "auto_recovery", "human",
"human_success", "pending"):
print(f"{name}: {display(result[name])}")
no_faults = [row for row in RUNS if not row["fault"]]
print(f"无故障子集/auto_recovery: {display(summarize(no_faults)['auto_recovery'])}")
if __name__ == "__main__":
main()回到刚才的 PowerShell,确认文件已经保存,再运行:
ls
python -X utf8 metrics_lab.py程序输出:
任务执行数=8 工具尝试总数=12
success: 6/8=75.0%
auto_success: 5/8=62.5%
auto_recovery: 2/4=50.0%
human: 3/8=37.5%
human_success: 1/3=33.3%
pending: 1/8=12.5%
无故障子集/auto_recovery: 0/0=N/A手算八次执行的各个比例
第一行是8次任务执行、12次工具尝试。尝试数来自1 + 1 + 1 + 2 + 3 + 2 + 1 + 1 = 12,成功率仍以8为分母。
success 包含m1至m6,共6次成功,所以6 ÷ 8 = 75.0%。auto_success 排除人工完成的m6,共5次,5 ÷ 8 = 62.5%。两个数差一项,能看出总体成绩中的人工贡献。
auto_recovery 的分母是m4、m5、m6、m7四次遇故障执行;其中m4/m5自动完成,分子为2,2 ÷ 4 = 50.0%。m6经人工成功,仍在分母中但不进入自动恢复分子;不能把分母改成所有8次执行,也不能只剩两次自动完成的故障执行。
human 是m6、m7、m8,共3次介入,3 ÷ 8 = 37.5%。human_success 只在这3次介入执行中看完成,只有m6,1 ÷ 3 ≈ 33.3%。pending 为m8一项,1 ÷ 8 = 12.5%,说明这个批次还会随着结案更新。
最后一行筛出没有故障的m1、m2、m3、m8。这组没有遇故障样本,恢复率为0/0,程序显示N/A(无法计算)。它既不能显示0%表示“完全恢复不了”,也不能显示100%表示“全部恢复”。
把一次人工完成改成自动恢复
在 RUNS 中只把m6的 "human": True 改为False,保存并运行 python -X utf8 metrics_lab.py。m6仍遇故障、仍成功,所以总成功率保持6/8;自动成功变成6/8;自动恢复变成3/4=75.0%;人工介入变成2/8=25.0%;人工完成变成0/2=0.0%。待结案与工具尝试数不变。恢复True后再运行测试。
这项变化表示同一成功原先依赖人工,现在改为自动取得;它没有增加总成功数。若只汇报总成功率,这项自动化改善就会被藏起来。
报告时保留计数与观察窗口
每个比例同时给出分子、分母、任务状态定义和采样窗口。只看两条故障任务的100%恢复,和看两千条故障任务的同样比例,证据强度相差很大。真实模型多轮执行还应按任务家族检查波动,并结合样本量考虑区间估计;
若只取已结案的执行,成功率变成6/7≈85.7%,它可以作为辅助视角,但不能悄悄替换本批6/8。若要计算“接受过恢复尝试的故障执行”的恢复率,则另设字段记录恢复尝试,另列指标名称与分母。不同口径都可以有用途,关键是让数字能回到记录。
Anthropic 对task、trial与重复执行指标的说明有助于区分任务与执行轮次,以及一次成功和多次稳定成功。真实评测可把同样的统计函数接到实际事件记录。
保存测试并检查关键条件
8项测试检查总体与自动成功、故障分母、两种人工指标、pending保留、任务与工具尝试分开、无故障N/A、空批次N/A与重复编号。 将下方代码保存为同一文件夹的 test_metrics_lab.py,运行:
ls
python -X utf8 -m unittest -v test_metrics_lab.py看到 Ran 8 tests 与 OK 表示这些固定条件全部通过。若找不到文件,先用 dir 核对当前文件夹及两份文件名;若出现具体断言失败,先恢复正文中的代码与预置数据,再对照该项测试的条件。
完整测试:test_metrics_lab.py
"""核对每个指标的分母,尤其恢复与人工成功不混用。"""
from copy import deepcopy
from pathlib import Path
import sys
import unittest
sys.path.insert(0, str(Path(__file__).parent))
import metrics_lab as lab
class MetricsTests(unittest.TestCase):
def test_success_includes_human_success(self):
result = lab.summarize(lab.RUNS)
self.assertEqual(result["success"], (6, 8))
self.assertEqual(result["auto_success"], (5, 8))
def test_recovery_denominator_is_faulted_runs(self):
self.assertEqual(lab.summarize(lab.RUNS)["auto_recovery"], (2, 4))
def test_human_denominators_are_distinct(self):
result = lab.summarize(lab.RUNS)
self.assertEqual(result["human"], (3, 8))
self.assertEqual(result["human_success"], (1, 3))
def test_pending_remains_in_total(self):
self.assertEqual(lab.summarize(lab.RUNS)["pending"], (1, 8))
def test_attempts_do_not_expand_task_denominator(self):
result = lab.summarize(lab.RUNS)
self.assertEqual(result["attempts"], 12)
self.assertEqual(result["success"][1], 8)
def test_no_faults_produce_na_recovery(self):
rows = [row for row in lab.RUNS if not row["fault"]]
self.assertEqual(lab.display(lab.summarize(rows)["auto_recovery"]), "0/0=N/A")
def test_empty_batch_has_no_percentage(self):
self.assertEqual(lab.display(lab.summarize([])["success"]), "0/0=N/A")
def test_duplicate_ids_are_rejected(self):
rows = deepcopy(lab.RUNS)
rows[-1]["id"] = "m1"
with self.assertRaises(ValueError):
lab.summarize(rows)
if __name__ == "__main__":
unittest.main()课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 10项任务中5项自动成功、2项失败、3项转人工,自动成功率是多少
10项任务中5项自动成功、2项失败、3项转人工,自动成功率是多少?
对应知识点:L7-metrics
简答题 · 解释机制
自动恢复率的分母为什么和总体成功率不同?人工完成的故障任务计入哪里?
对应知识点:L7-metrics
代码题 · 核对给定数据
4项任务出现故障,其中2项无人介入自动完成、1项人工完成、1项失败,写表达式计算自动恢复率。
对应知识点:L7-metrics
场景题 · 分析具体变化
两批成功率都是80%,一批5条另一批500条,还要展示什么?
对应知识点:L7-metrics
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。