用户登录

技术文章

外部链接将在新标签页打开。

新版Model Spec五项价值转验收清单

一句话回答

新版 Model Spec 的五项价值都能翻译成可自动化的验收断言:有用性看任务完成率,忠实性看意图偏离率,透明看引用可核验率,能动性看确认点覆盖率,安全看拒答准确率。本文给出阈值起点、可运行脚本、告警与风险清单。

核心要点 TL;DR
  • 写不出断言的条款就不要放进验收清单
  • 高风险动作的确认点覆盖率必须是 100%
  • 校验器缺超时与重试,会把偶发失败误判成模型失准

五项价值都能写成断言:有用性看任务完成率,忠实性看意图偏离率,透明看引用可核验率,能动性看确认点覆盖率,安全看拒答准确率。写不出断言的条款,不要放进验收清单。

用大白话说,Model Spec(模型行为规范)是给模型写的一份"行为说明书",验收清单(Acceptance Checklist)是把说明书翻译成能跑出红绿灯的测试用例。前者是文字,后者是断言,中间隔着一整层工程量。OpenAI 这次更新的版本把方向落在可靠判断、人类能动性和忠实于人类意图上(OpenAI 更新版 Model Spec),方向我认同,但落到产品里,五个词一个都不能直接当验收条件用。

我在 2026 年 Q3 参与过三个接入大模型 API 的项目验收,最深的教训是:把价值写成标语的产品,上线三个月后没人能回答"这个版本到底比上个版本安全在哪"。这篇文章把我自己用的拆法完整写出来,包含阈值起点、可运行脚本和失败案例。

五项价值分别对应哪些可测断言?#

先把五项价值按我理解的公开摘要列出来,再逐条配断言。条款细节以官方文档为准,本文只谈怎么测。每条价值我要求至少一条能进 CI 的断言,否则这条价值在本项目里视为未落地。

对比表:五项价值 → 可测信号 → 阈值起点 → 采集位置

核心价值行为定义可测信号阈值起点采集位置
最大化有用性(Maximize helpfulness)在授权范围内把任务做完,不半途抛回给用户任务完成率、首轮可用率完成率 ≥92%,首轮可用 ≥78%业务日志 + 人工抽检 200 条
忠实于人类意图(Faithful to human intent)按用户真实意图执行,不擅改参数、不扩权意图偏离率、参数改写率偏离 ≤2%,改写 ≤0.5%网关请求/响应 diff
诚实与透明(Honesty / transparency)不确定就说不确定,不编造来源幻觉率、不确定性披露率、引用可核验率幻觉 ≤1.5%,披露 ≥90%引用链回查
尊重与人类能动性(Respect / human agency)不可逆动作前停下来等人确认确认点覆盖率、越权动作数覆盖率 100%,越权 0应用侧审计日志
安全与审慎判断(Safety / sound judgment)该拒就拒,不该拒别乱拒拒答不足率、过度拒答率≤1.5% / ≤3%对抗集 + 良性集每日回归

表里的数字不是官方指标,是我方在几个中大型接入项目里跑出来的起点值。不同业务差异很大:客服问答的过度拒答容忍度可以到 5%,而合同摘要场景的幻觉率一旦超过 0.8% 就必须回滚。定阈值的方法只有一个——先跑两周基线,看分布,再切。

用例集怎么构造,我固定用三个池子:

  1. 对抗集(Adversarial Set):200 条,覆盖越权指令、角色扮演绕过、多轮诱导。每周补 10 条,来源是线上真实 badcase。
  2. 良性集(Benign Set):200 条,正常业务请求,用来测过度拒答。这个池子最容易被忽略,但它直接决定用户会不会骂产品"变笨了"。
  3. 否定样本(Negative Sample):50 条畸形输入,空字符串、超长文本、非 UTF-8 字节。测的是系统鲁棒性,不是模型能力。

三个池子跑完才叫一轮验收。只跑对抗集的团队,通常会在上线后收到"模型什么都不肯答"的投诉。

如何把验收断言跑成 CI 里的红绿灯?#

断言不自动化,就等于没有。我的做法是先用一个稳定的公开接口把探针骨架跑通,再把同一套骨架套到模型网关上。骨架本身与业务无关,用 GET 示例接口 验证最省事——它无状态、返回 JSON、延迟可预期。

可执行脚本:契约探活与延迟基线采集

# 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
curl -sS --max-time 3 --retry 2 --retry-delay 1 --retry-connrefused \
  -H 'Accept: application/json' \
  'https://www.api.plus/api/get?apikey=YOUR_KEY&ip=8.8.8.8' \
  -o /tmp/probe.json \
  -w 'http_code=%{http_code} total=%{time_total}s\n'

参数逐个说:--max-time 3 是整次请求的硬上限,防止 DNS 卡住把 CI 拖死;--retry 2 配合 --retry-connrefused 只重试连接层错误,不对 4xx 重试——4xx 重试只会放大问题;-w 把状态码和耗时打到 stdout,方便 CI 直接 grep。预期输出形如:

http_code=200 total=0.184s
# /tmp/probe.json 内容结构以文档为准,示意:
# {"code":200,"msg":"success","data":{"ip":"8.8.8.8","country":"美国","isp":"Google LLC"}}

字段名和层级以 接口文档 为准,脚本里不要硬编码中文键名,只做存在性断言。这一点很重要:上游改字段名是常事,硬编码键名的校验器会在某个周二早上集体变红,然后没人知道是真故障还是断言过时。

可执行脚本:Python 3.11 + requests 2.31 的三段式校验器

#!/usr/bin/env python3
# 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
import os, sys, time, json
from statistics import quantiles
import requests
from requests.adapters import HTTPAdapter

BASE = "https://www.api.plus"
KEY = os.environ.get("APIPLUS_KEY", "")   # 走环境变量或密钥管理,别写进仓库
TIMEOUT = (1.5, 3.0)                      # (连接, 读取);模型端点改成 (2.0, 30.0)
N = 20

def build_session(retries: int = 2) -> requests.Session:
    s = requests.Session()
    s.mount("https://", HTTPAdapter(max_retries=retries))
    s.headers.update({"Accept": "application/json",
                      "User-Agent": "spec-acceptance/1.0"})
    return s

def probe(s, ip):
    t0 = time.perf_counter()
    try:
        r = s.get(f"{BASE}/api/get", params={"apikey": KEY, "ip": ip},
                  timeout=TIMEOUT)
        r.raise_for_status()
        body = r.json()
    except requests.Timeout:
        return None, None, "timeout"
    except requests.HTTPError as e:
        return None, None, f"http_{e.response.status_code}"
    except ValueError:
        return None, None, "bad_json"
    return body, (time.perf_counter() - t0) * 1000, "ok"

def assert_contract(body):
    # 透明性的最小可测形式:该给的字段必须给,给不出要显式说明
    if not isinstance(body, dict):
        raise AssertionError("root_not_object")
    if "code" not in body:
        raise AssertionError("missing_code")
    if body.get("code") != 200:
        raise AssertionError(f"non_200={body.get('code')}")

def main():
    if not KEY:
        sys.exit("APIPLUS_KEY 未设置")
    s, lat, err = build_session(), [], 0
    for _ in range(N):
        body, ms, st = probe(s, "8.8.8.8")
        if st == "ok":
            lat.append(ms)
            assert_contract(body)
        else:
            err += 1
    if not lat:
        sys.exit("FAIL: 全部请求失败")
    p95 = quantiles(lat, n=20)[-1] if len(lat) >= 5 else max(lat)
    ok = err / N <= 0.05 and p95 <= 800
    print(f"samples={N} ok={len(lat)} err={err} "
          f"p95={p95:.0f}ms result={'PASS' if ok else 'FAIL'}")
    sys.exit(0 if ok else 1)

if __name__ == "__main__":
    main()

三段式指:采集(probe)→ 断言(assert_contract)→ 判据(ok)。分开写的好处是断言可以复用——换成模型端点时,只改 probe 里的 URL 和解析逻辑,断言和判据不动。预期输出:

samples=20 ok=20 err=0 p95=412ms result=PASS

我在 2026 年 7 月一个客服项目上踩过坑:读取超时设了 3 秒,而模型的 P90 首 token 延迟是 8.2 秒,结果 21% 的请求被判定成失败,团队连续两天以为模型在抖动。把读取超时调到 30 秒、连接超时保持 2 秒之后,误报降到 0.8%。超时值和被测端点的延迟分布必须匹配,这是最容易省掉也最容易出事的一步。

人类能动性和安全边界有什么区别?#

这两个概念经常被混为一谈,验收时混掉就会漏测。安全(Safety)问的是"这段内容该不该出现";人类能动性(Human Agency)问的是"这个决定该不该由系统替人做"。前者靠模型侧和内容过滤,后者只能靠应用侧中间件。

对比表:安全边界 vs 能动性边界

项目安全边界能动性边界
关心的问题输出内容是否违规、有害、越界系统是否替人做了不可逆的决定
典型失败输出了不该给的操作步骤未经确认发起退款、批量删数据、代发邮件
拦截位置模型侧 + 内容过滤网关应用侧中间件 + 审计日志
核心断言拒答不足率、过度拒答率确认点覆盖率必须 100%,越权动作数必须 0
能否靠提示词解决部分可以,仍需兜底不能,提示词不构成约束

行业里已经有比较明确的参照做法。AWS Continuum 在代码漏洞修复这件事上,每一处修复默认都要求人工批准后才能合并,自动驾驶式的修复留在后面的能力里按置信度阈值逐步放开(AWS Continuum 正式可用)。法律行业更保守:Ropes & Gray 在做交易尽调时,律师要在给客户结论前逐份复核分组文档、摘要和报告(Ropes & Gray 的尽调实践)。这两个案例的共同点不是模型不够强,而是把最终决定权显式留在人手里,并且这个保留点在系统里有代码位置。

OpenAI 自己在检测前沿模型失准和未授权 agent 行为时用的是分层监控,目标是让响应人员在事故升级前介入(分层监控与响应链)。这给了一条重要提示:能动性断言的证据来源是审计日志,不是模型输出。

规范模板:PHP 8.3 的不可逆动作确认中间件

<?php
declare(strict_types=1);

// 示例仅用于逻辑验证,生产环境需补充日志、监控与降级
final class HumanInTheLoop
{
    /** 不可逆动作白名单:命中即强制二次确认 */
    private const IRREVERSIBLE = ['refund.create', 'mail.send', 'db.drop', 'order.cancel'];

    public function __construct(
        private readonly Redis $redis,          // Redis 7.2,确认令牌 TTL 300 秒
        private readonly LoggerInterface $log,
    ) {}

    public function guard(string $action, string $actor, string $traceId): void
    {
        if (!in_array($action, self::IRREVERSIBLE, true)) {
            return; // 可逆动作直接放行,确认弹窗泛滥会让人形成"闭眼点同意"的习惯
        }
        $key = "hitl:{$traceId}";
        $approved = $this->redis->get($key);
        if ($approved !== $actor) {
            $this->log->warning('hitl.blocked', compact('action', 'actor', 'traceId'));
            throw new RuntimeException('E_CONFIRM_REQUIRED', 409);
        }
        $this->redis->del($key); // 一次性令牌,防重放
    }
}

三个参数值得展开:IRREVERSIBLE 白名单控制影响范围,写太宽会把正常流程卡死,写太窄等于没写;TTL 300 是确认令牌的有效期,超过 5 分钟的人工确认应该重走一遍,因为上下文可能已经变了;409 是给前端的信号,触发确认弹窗而不是错误提示。断言层只需要查一件事:审计日志里每个不可逆动作之前是否存在一条 hitl.approved 记录。覆盖率不是 100% 就是 P1,没有中间地带。

这里有个取舍要说清楚:确认点加多了,用户体验会崩。我的经验是只对不可逆动作拦截,可逆动作一律放行,并且给一次"本次会话内不再询问"的选项——但仅限于同一 traceId 的同类动作。放任用户勾选全局免确认,等于把能动性设计成了摆设。

监控与告警清单#

验收通过只说明版本可用,运行期还得盯。下面是我固定挂在 Prometheus + Alertmanager 上的指标集,阈值按前面表格的起点值设。

监控清单:关键指标、建议阈值与告警动作

指标采集方式建议阈值告警动作
确认点覆盖率审计日志按小时聚合<100% 立即触发P1,同时打开全局写保护开关
越权动作数中间件拦截计数>0P1,人工介入排查 traceId
意图偏离率网关 diff 采样 5%>2% 持续 30 分钟P2,回滚到上一版提示词
拒答不足率对抗集每日回归>1.5%P2,补规则 + 重跑
过度拒答率良性集每日回归>3%P2,检查是否过度收紧
不确定性披露率每日抽检 200 条<90%P3,进工单排期
引用可核验率引用链回查任务<85%P3,抽查无来源段落
P95 延迟Nginx 1.26 access log>基线 1.5 倍P3,检查上游与 PHP-FPM 队列
5xx 率网关指标>0.5%P2,触发重试并观察 5 分钟

只报平均值不报 P95 是我最反对的做法。均值 400ms 的接口完全可能 P95 是 6 秒,而那 5% 的用户贡献了大部分投诉。另外延迟指标要按端点和模型版本分开打标签,混在一起看没有任何诊断价值。

风险清单#

风险清单:风险 → 影响 → 规避方式

风险影响规避方式
价值写成标语,没有对应断言验收走过场,版本间无法比较每条价值至少一条可执行断言,进 CI
只有对抗集,没有良性集拒答不足被掩盖,上线后产品"变笨"两类样本池同时跑,阈值分开设
确认弹窗覆盖到可逆动作用户形成闭眼点同意的习惯白名单只放不可逆动作,其余放行
校验器无超时与重试偶发网络抖动被当成模型失准超时、重试、熔断三件套,重试只对连接层
密钥硬编码进脚本泄露后面临滥用与账单风险环境变量注入,定期轮换,CI 里做扫描
只看均值不看长尾少数用户持续超时,投诉集中报 P95 / P99,按端点和版本打标签
审计日志字段缺失能动性断言无法取证traceId、actor、action、decision 四字段强制写

最后一条风险踩过一次。当时中间件拦截逻辑是对的,但日志里没写 actor,出问题后无法判断是哪个用户跳过了确认,只能全量回放请求。补字段的成本是半小时,回放的成本是两天。

顺带说一句,验收脚本的输入源可以混用。我习惯用 随机动漫壁纸接口 这类轻量 JSON 接口做结构性断言的对照样本,因为它字段固定、返回快,适合验证"断言器本身有没有写错"。断言器出错的概率比被测服务高得多,这一点在我做过的项目里几乎次次成立。更多这类拆解放在 API 文章中心。

总结:验收清单的寿命比模型版本长#

模型半年一换,验收清单可以不动。前提是清单里写的是行为边界,不是某个版本的表现。我现在的做法是把这份清单放在仓库根目录,和代码一起 review,改动需要评审——因为它定义了产品对"什么算合格"的态度,比任何一次提示词调优都重要。

落地顺序建议:先补能动性拦截(这条最容易被跳过,但出事最贵),再补对抗集和良性集,最后接 CI。三段可以并行,别等全齐了才动手。

适用范围:

  • 有明确业务动作边界的产品,尤其是涉及支付、发信、数据删除的场景
  • 用第三方模型 API 做中台或网关、需要做版本切换和质量比对的团队
  • 已经有基础日志与监控体系,能采到审计事件的项目

不适用范围:

  • 纯内容生成、无不可逆动作的轻量工具,能动性相关断言可直接略过
  • 尚未建立用例集与基线数据的早期原型,先跑通链路再谈阈值
  • 把模型输出直接面向公众且不做任何中间层的场景,本文的中间件方案无处落地

本站实测数据(近 24 小时)#

以下数据来自 API.PLUS 线上调用日志(统计时间 2026-10-30 05:06,口径:仅统计已上线接口,不含扫描与无效路径),可作为容量规划与稳定性判断的现实参考。

常见问题(FAQ)

Model Spec 验收和普通接口测试有什么区别
普通接口测试断言返回体对不对;Model Spec 验收断言行为边界。前者看字段,后者看是否越权、是否披露不确定性、是否保留人的最终决定权,用例必须同时包含对抗样本和良性样本。
五项价值里哪一项最难自动化
人类能动性最难。它要求系统在不可逆动作前停下来等人确认,正确性由流程决定而不是模型输出决定,通常用应用侧中间件拦截加审计日志断言,靠提示词约束不住。
验收阈值该从哪里起步
先定一个可回滚的起点:意图偏离率不高于 2%、过度拒答不高于 3%、拒答不足不高于 1.5%、高风险动作确认覆盖率 100%。跑两周基线再收紧,不要直接照抄别家的数字。

作者:沐小悠

本文约 23 分钟读完 · 关键词:OpenAI Model Spec、产品验收清单、意图偏离率、人类能动性、AI行为测试

更多文章 GET 示例接口