契约锁定而非模型:构建可换供应商的Agent评分系统

训练营用提示词哈希与JSON cassette实现离线回放评分,锁定接口契约而非模型。
文章针对AI Agent训练营中实时模型评分导致结果不确定的问题,提出「记录一次,永久回放,离线评分」方案。核心是锁定Provider接口契约而非具体模型:提示词做SHA256哈希、响应存为JSON cassette、评分只跑回放、实时运行仅作演示。通过三个一分钟检查点验证基础设施与设计哲学,其中「两个供应商一个分数」是证明评分模型无关的关键。评分表只奖励契约稳定性、响亮失败和追踪,不给模型选择、token消耗、速度任何分数。作者还明确三条禁区:工具调用语义、延迟/质量基准、生产评估框架,并强调「录制好的cassette是持久资产;配额是天气」。
在AI Agent训练营里,一个反复出现的尴尬场景是:学生用某个免费模型完成了作业,但两天后评分者重跑时,模型变了、结果变了,评分标准形同虚设。问题的根源不在学生,而在实验设计——如果评分必须调用实时模型,那么评分本身就是非确定性的。
最近一篇来自训练营实践者的技术笔记提出了一个简洁的解决方案:记录一次,永久回放,离线评分。核心思想是「锁定接口契约,而非锁定模型」。本文基于该素材,拆解这套方法的设计哲学、实现细节与适用边界。

为什么实时评分是灾难
训练营进行到第二周,几乎每个学员都会撞上同一堵墙:有人基于当天免费的模型搭了一个Agent循环,提交后运行正常。两天后评分系统重新执行提交,却得到不同答案、不同工具调用、超时或限流页面。
那么评分标准到底衡量了什么?不是设计能力,而是重跑时的运气。
这当然有学生的责任,但主要是实验设计的bug。作者指出:如果评分器必须调用实时模型才能决定分数,那么评分器在构造上就是非确定性的,没有任何评分标准能在此之上存活。 这个诊断一针见血,把问题从「学生依赖不稳定资源」转移到「评分架构本身不可靠」。
四条规则:把契约钉死
作者现在要求每份提交满足四条规则:
- 提示词做哈希:同样的提示词字符串(忽略首尾空白)映射到同一个键。
- 响应录制为JSON cassette:纯JSON文件,提交到代码仓库。
- 评分只跑回放:不联网、不需要API Key、不依赖时钟。
- 最后一次实时运行仅作演示:永远不作为真相来源。
这四条规则的巧妙之处在于把「模型行为」和「评分逻辑」彻底解耦。模型成为环境变量,而评分针对的是学生设计的Agent逻辑。
实现:三个文件,十五分钟
整个实验的初始设置非常轻量:创建虚拟环境,安装httpx和pytest,建立cassettes和lab目录。核心是一个Provider协议——注意是Protocol而非抽象基类,任何拥有complete方法的对象都符合契约。
class Provider(Protocol):
name: str
def complete(self, prompt: str) -> str: ...
prompt_key函数对提示词做strip后SHA256哈希取前16位,确保空白差异不会产生新记录。Replay类读取cassette文件,如果找不到对应哈希则抛出CassetteMiss异常,而不是返回空字符串。
Recorder类包装任意实时Provider,先查cassette再调用外部接口,避免重跑半截会话时浪费调用次数。
实时适配器(Live类)被明确标注为草图而非验证代码——不同供应商的端点、认证头、响应结构各不相同,必须参考各自文档。这种诚实的标注值得肯定,避免了读者直接复制导致生产事故。
Provider 协议利用了 Python 的**结构化子类型(structural subtyping)**机制:只要一个类实现了 complete 方法并带有 name 属性,它就自动满足 Provider 类型,无需显式继承。这与 Java 式接口不同,好处是实时适配器、录制器、回放器可以彼此独立演化,测试时也能轻松用桩对象替换。Protocol 在 typing 模块中引入(Python 3.8+),配合静态类型检查器(如 mypy)可以在不运行代码的情况下发现契约违规,进一步强化了「锁定接口」的目标。
Cassette 一词借自 HTTP 录制回放工具(如 VCR.py),原指把一次网络交互序列化为可重放的文件。在本实验中,cassette 是纯 JSON,键为提示词哈希,值为模型响应字符串。与二进制或压缩格式相比,纯文本 JSON 便于代码审查、差异对比和版本控制,也让评分器无需任何反序列化依赖即可读取。
三个检查点:一分钟内判胜负
作者设计了三个快速检查点,每个都能在一分钟内完成:
检查点1:空白不是新提示词。 两次调用只有空白差异,应命中同一条录制。
检查点2:未命中必须响亮。 没有空字符串、没有None、没有吞异常。CLI必须非零退出。
检查点3:两个供应商,一个分数。 同一cassette喂给评分器两次,输出哈希必须一致。这是证明评分标准模型无关的关键检查,也是大多数学生首次失败的地方——因为他们把model_name藏进了评分逻辑。
前两个检查点验证基础设施的正确性,第三个检查点验证设计哲学是否真正落地。层层递进,设计得很扎实。
第三个检查点实际上在检验确定性评分函数:给定同一份 cassette,评分器的输出(包括分数和任何中间哈希)必须逐字节一致。这要求评分逻辑中不能混入时间戳、随机数、环境变量或模型名称等非确定性来源。学生常犯的错误是把 model_name 写入评分逻辑用于「区分供应商」,结果同一 cassette 在不同供应商标签下产生不同分数,直接违反「两个供应商一个分数」的契约。正确的做法是让评分器只依赖 cassette 中的提示词-响应对,供应商信息仅作为元数据记录在追踪日志中,不参与评分决策。
评分标准:只奖励设计,不奖励运气
作者实际使用的评分表值得完整呈现:
| 标准 | 权重 | 满分表现 | 典型失分 |
|---|---|---|---|
| 契约稳定性 | 30 | 两个供应商下分数一致 | 只在碰巧用的模型上通过 |
| 响亮失败 | 25 | CassetteMiss传播,退出码非零 | 返回空串却看似成功 |
| 追踪 | 20 | 每次调用记录键、供应商、耗时 | 只记录最终答案 |
| 书面总结 | 15 | 一段诚实的失败复盘 | 只写「能跑」 |
| 扩展目标 | 10 | 完成一个并记录发现的失败 | 三个都开了头却没完成 |
说个细节缺席的项:模型选择、token消耗、速度都不给分。作者明确说,那些是环境变量,不是技能。这个评分设计把学生的注意力从「调模型」拉回到「设计可靠的系统」。
边界:什么时候这个实验不适用
作者主动列出了回放方法的三个禁区,这种自我限制反而增加了方案的可信度:
- 不要用于工具调用语义:不同供应商对函数参数的序列化方式不同,回放可能掩盖真实的不兼容性。
- 不要用于延迟或质量基准测试:那只是在评测一个JSON文件。
- 不要用作生产评估框架:生产需要随机采样;这是评分装置,不是评估平台。
「边界是交付物的一部分。如果学生说不出自己的实验测不了什么,那他的总结就没写完。」这个观点值得所有技术教学者借鉴。
成本为零的实验室
对于预算为零的训练营,作者推荐了MonkeyCode的开源项目,提供免费模型访问和免费服务器选项——足够第一天录制cassette,整个学期回放。利益披露:本文是作为MonkeyCode产品推广的一部分撰写的。 作者同时给出了一个诚实的警告:免费额度会变,录制当天去控制台确认当前额度,不要照抄博客里的数字——包括这篇。
「录制好的cassette是持久资产;配额是天气。」这个比喻精准地表达了回放方法的核心价值:把不稳定的外部依赖转化为稳定的本地资产。
学生真正学到的东西
这套实验的最终收获与供应商无关。核心教训是:任何你不能一致地重跑的东西,你都无法公平地评分——包括你对模型本身的假设。
作者建议在仓库第一天就建立FAILURES.md,并说「第二条记录总是比第一条更有意思」。这种对失败记录的重视,恰恰是工程教育中最容易被忽略的部分。
对于正在构建Agent评估体系的团队,这套方法提供了一个值得借鉴的中间层:它不解决生产级评估的复杂问题,但能确保教学和作业评分不被模型的随机性绑架。在模型供应商快速更迭的当下,「契约锁定」的思路或许比任何具体工具都更持久。
相关推荐

Cursor AI编程实战:30分钟从零构建学生管理系统
B站UP主实战演示Cursor AI编程:30分钟用自然语言生成学生管理系统。详解Agent模式、Claude模型选择、提示词技巧与使用门槛,帮你判断Cursor是否值得入手。

AI编程工具20强榜单:从夯到拉的评分矩阵
B站UP主发布AI编程工具20强榜单,从夯到拉分为T0-T4五个层级,Claude Code和Codex并列T0。本文详解评分维度(能力、可靠性、耐操性、耐久性、成本、国内可用性)及工具表现,帮助开发者选择适合的AI编程助手。

Vibe Coding系统教程:零基础玩转AI编程实战
B站UP主耗时三个月打造的Vibe Coding系统教程,覆盖Codex、ClaudeCode、Cursor等AI编程工具,从基础逻辑到商业级项目实战三段式讲解,适合零基础学习者系统入门AI编程。