Skip to content
Published on

框架指纹与版本管理 — 让没有记录的变更可以追踪

分享
Authors

再说一次:没有提交的 6 个百分点

本系列的起点文章以一个构造的案例开头:成功率比上周涨了 6 个百分点,既没有提示词提交也没有换模型,一查才发现某个工具描述的一行、重试上限、读文件的行数上限,分别被不同的人改过。下周成功率要是掉了,该回滚哪一个?没有记录,就没有答案。

本文讲的是让那起事件变得可回答的最小装置:框架指纹。先说明一点——指纹这个装置不是 Lilian Weng 的框架文章提出的,而是本博客为了把那篇文章划出的边界搬进运维而构造的方法。

框架是带版本号的交付物

指纹的前提是视角转换:把框架看成一个交付物,而不是一堆散落的配置。是交付物就有版本,有版本就能问两个时间点之间差了什么。Weng 的定义在这里派上用场:有了"协调模型的思考与行动、上下文、产物、评估的那一整层"这条边界,"这个改动算不算框架改动"这个问题才能得到一致的回答。打磨一行工具描述也好,调高重试上限也好,都在边界之内,所以都是会移动版本的变更。

制作指纹:归一化占一半

指纹本身很短:把构成框架的决定序列化后取哈希即可。难的那一半是归一化。含义相同的框架必须得到相同的指纹,但如果傻乎乎地直接序列化,仅仅工具注册顺序变了指纹就会变。所以工具列表按名字排序,JSON 键固定为排序后的顺序,不影响语义的空白与字段顺序在序列化前统一。反过来,每次运行都会变的值——时间戳、运行 ID——绝不能进指纹。一进去,每次运行都成了彼此无法比较的框架。

什么放进去,什么留在外面

放进去的是所有会改变智能体行为的决定:模型标识、系统提示词、完整的工具 schema、重试上限与停止条件、上下文策略、权限范围,直到评估者的版本。放评估者的理由正是第 5、6 篇讲过的:评分标准一变,同一套框架也会得到不同的分数,所以评分标准的变更同样要被追踪。

留在外面的有两类。一类是运行属性:任务输入、执行时间、随机种子是某次运行的元数据,不是框架。另一类更重要:日志与观测设置不进指纹,因为你能看见什么,不会改变智能体做什么。把观测设置塞进指纹,行为相同的结果就会碎成无法比较的碎片。这条原则在本博客的框架 RPG 里原样实现:观测装备随进度解锁,而指纹不动。

结果盖上指纹,比较才成立

指纹的用途是给评估结果盖章。

{
  "run_id": "2026-08-12T03:14:07Z-a41",
  "harness_fingerprint": "3f9c1d2ab714",
  "suite": "issues-50",
  "success_rate": 0.62,
  "counter_metrics": { "deleted_tests": 0, "avg_tool_calls": 11.4 }
}

有了这一行,就跟来两条规则。第一,比较只在相同指纹之间成立。把两个指纹不同的成功率摆在一起不是比较而是实验,是实验就必须说得出 diff 是什么。第二,所有移动指纹的变更,都是需要重跑评估的变更。改一行工具描述开始被当作与提示词大改同一级别对待——这正是想要的效果。

回滚与回归二分

成功率曲线上出现台阶时,指纹历史会替你列出嫌疑名单。

def regression_boundary(runs):
    """找出成功率折断处的指纹边界(构造的示例)。"""
    runs = sorted(runs, key=lambda r: r["ts"])
    for prev, cur in zip(runs, runs[1:]):
        if cur["success_rate"] < prev["success_rate"] - 0.03:  # 噪声余量
            return prev["harness_fingerprint"], cur["harness_fingerprint"]
    return None

# 边界两个指纹对应 spec 的 diff 就是嫌疑名单。
# 嫌疑不止一个时,像 git bisect 一样对半回退、二分排查。

两个指纹之间的 spec diff 就是候选变更集。候选只有一个就回退它再评估;有多个就按 git bisect 的要领对半收窄。回滚也定义在同一份历史之上:重新部署早先指纹对应的 spec,确认部署产物的指纹与目标指纹一致,回滚即告完成。没有指纹的年代那句"大概是这套配置吧",变成了"哈希一致"。

亲手练习

框架工程 RPG 第 1 层"观测与指纹"的第一个场景,正是本文开头的那起事件。为了复现上周的 6 个百分点而亲手组装框架的过程中,你会用手记住指纹对哪些变更有反应、对哪些没有。

参考资料