基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-05 23:46

为模型 JSON 输出建立上线验收门禁

dev.to · AI#结构化输出#模型评测#数据校验
Editor brief · 编辑速览

文章建议先验证模型输出的结构契约,再衡量延迟,统计直接合规率、模式违规率及一次修复后的恢复率。检查同时覆盖字段间约束,并明确少量试验不能证明负载能力或长期可用性。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

模型接口返回 200 OK,业务就能放心往下走吗?作者提醒:如果返回内容无法解析,或字段之间互相矛盾,一次看似成功的请求可能把错误数据送进数据库。相比之下,503 至少会明确进入错误处理流程。

这篇文章给出的办法,是在上线前增加一道「输出契约门禁」:先定义什么结果能用,再测原始合规率、结构违规率,以及恰好一次修复后的恢复率。它适合检查依赖结构化输出的 AI 原型,但不能代替性能测试,也不能证明接口长期可靠。

先定义什么能用,再讨论模型快不快

作者所说的契约(contract),就是程序对模型输出的明确要求。JSON 能解析只是第一关,还需要检查四层约束:

  • 结构:必填字段是否存在,类型是否正确。
  • 枚举:字符串只能取约定值,不能接受模型自行生成的近义词。
  • 跨字段规则:例如 severity == "high" 时,needs_human 必须为 true。
  • 输出形式:只能返回 JSON,不能夹带解释文字或 Markdown 代码围栏。

最后一层很容易被忽略。如果程序先剥掉代码围栏再解析,系统已经替模型修复了一次。修复可以保留,但必须单独计数,否则提示词稍作调整,原来被兼容逻辑遮住的问题就可能暴露。

能解析的 JSON,不一定是业务能接受的数据。

原文以工单分级为例,用 Node 20+、零依赖脚本调用兼容 OpenAI 的 /chat/completions。下面这段定义了请求条件和校验规则,解决的是「到底怎样才算合格」,尚未包含完整测试循环:

// contract-gate.mjs — Node 20+, no dependencies
import { createHash } from "node:crypto";
import { writeFileSync } from "node:fs";

const BASE   = process.env.BASE_URL;   // e.g. https://host/v1
const KEY    = process.env.API_KEY;
const MODEL  = process.env.MODEL;
const TRIALS = Number(process.env.TRIALS ?? 50);
const THRESH = Number(process.env.MIN_CONFORMANCE ?? 0.98);

const SYSTEM = [
  "Return exactly one JSON object. No prose. No markdown fences.",
  'Shape: {"ticket_id": string, "severity": "low"|"high", "needs_human": boolean}',
].join("\n");
const USER = 'Triage: "checkout button does nothing on Safari 17".';

const promptHash = createHash("sha256")
  .update(SYSTEM + "\n" + USER).digest("hex").slice(0, 12);

const violation = (o) => {
  if (o === null || typeof o !== "object" || Array.isArray(o)) return "not-an-object";
  if (typeof o.ticket_id !== "string" || o.ticket_id.trim() === "") return "ticket_id";
  if (o.severity !== "low" && o.severity !== "high") return "severity";
  if (typeof o.needs_human !== "boolean") return "needs_human";
  if (o.severity === "high" && o.needs_human !== true) return "invariant:high";
  return null;
};

async function callOnce(extra = []) {
  const res = await fetch(`${BASE}/chat/completions`, {
    method: "POST",
    headers: { "content-type": "application/json", authorization: `Bearer ${KEY}` },
    body: JSON.stringify({
      model: MODEL,
      temperature: 0,
      max_tokens: 200,
      messages: [
        { role: "system", content: SYSTEM },
        { role: "user", content: USER },
        ...extra,
      ],
    }),
    signal: AbortSignal.timeout(30_000),
  });
  return { status: res.status, body: await res.text() };
}

重点看 invariant:high 那一行:字段类型全部正确,仍可能违反业务规则。另外,temperature: 0 必须与实际部署一致。作者举例,如果测试用 0.7、上线用 0,测试证据就没有描述真正运行的配置。

失败要分层,修复不能混进首次通过率

作者建议每次测试只记一个主要失败阶段,按检查顺序归类:

分类 具体含义
http HTTP 状态码不是 200
envelope 无法解析接口响应外壳,或取不到 choices[0].message.content
parse 模型正文无法解析成 JSON
schema 字段、枚举或跨字段规则校验失败
pass-after-fence-strip 去掉代码围栏后才通过
clean 无需修复,直接通过

envelope 特别适合排查部署环境问题:接口虽然返回 200,正文却可能是 HTML 错误页、网络认证页或代理生成的页面。只统计「模型生成失败」,会把网络路径上的问题算到模型头上。

对 parse 或 schema 失败,作者允许追加一次修复请求:带上原始 assistant 输出和准确的违规信息,让模型重新回答,再单独记录是否恢复。剥离代码围栏则另记,不应把它和追加请求修复混为一谈。

为什么只修一次?作者认为,继续重试既消耗额度,也会掩盖首次遵守契约的能力。这个上限是工程取舍,文章没有给出一次与多次修复的对照实验。

三个数字因此需要分开看:首次直接通过多少,结构校验失败多少,一次修复又救回多少。恢复率还应写清分母,避免把「失败样本中的恢复比例」和「全部测试中的恢复比例」放在一起比较。

门禁必须拦住合并,证据也要有有效期

脚本默认运行 50 次,MIN_CONFORMANCE 默认是 0.98。原文给出的启动命令如下,作用是把端点、密钥、模型和测试次数传入脚本:

BASE_URL="$BASE" API_KEY="$KEY" MODEL="$MODEL" TRIALS=50 node contract-gate.mjs

要注意 TRIALS=50:一次失败就占 2%。这只是有记录的冒烟测试,不是统计保证。作者提出约十分钟可以得到初步证据,但没有提供实际运行耗时;请求本身还设置了 AbortSignal.timeout(30_000)。

原始合规率低于门槛时,脚本应以退出码 1 结束,让 CI 阻止合并。业务不变量违规则必须单独拦截,即使通过率达到 0.99,也不能放过一次「高严重程度却不需要人工处理」。

每次运行还要保存并提交一份 JSON,至少包含:

  • timestamp、endpoint_host、model_string、prompt_hash。
  • trials、clean、pass_after_fence_strip、http、envelope、parse、schema。
  • recovered_after_repair,以及最多五条截断的原始失败片段。

示例使用 sha256 生成提示词摘要,截取前 12 位;分类代码中的原始失败片段截取前 160 个字符。提示词摘要变化、模型字符串变化,或证据超过十四天,都要重跑。

作者还建议在原型实际部署的环境执行,因为出口代理、DNS 和平台错误页会影响结果。文章提到 MonkeyCode 当时提供 10M tokens 免费额度及免费服务器,同时明确披露这是产品推广合作。额度需要以控制台为准,不能作为长期承诺。

我的判断

我认可把业务规则放进代码、把修复从首次通过中拆出来。这比只看 HTTP 成功率更接近实际风险,也比持续重试直到成功更容易定位问题。

但这篇提供的是检查方案,没有公开某个模型跑完 50 次后的结果,也没有模型间对照数据。0.98、一次修复、十四天有效期,都是作者选择的工程参数,不能直接当成普适标准。

示例也有几个边界。它反复测试同一条 Safari 17 工单,覆盖不了真实输入的多样性;ticket_id 只检查非空字符串,并未证明编号对应正确工单;校验器没有拒绝额外字段。结构合格与内容正确,仍是两回事。

交付完整性同样要打个问号:原文没有展示循环聚合、修复执行、证据写盘和退出码实现,分类代码还夹杂模板残片,提到的决策表也未出现。读者拿到的是实现骨架,不能直接视为可运行的完整门禁。

与结构化输出能力或 JSON Schema 校验相比,这套方案更强调端到端行为和业务不变量;具体端点是否支持那些替代能力,原文没有说明。若输出只供人阅读,作者也承认严格 schema 可能增加无谓成本。延迟、吞吐、并发、费用上限和可用性,则需要另测。

能拿来做什么

  • 给 Node 服务接入模型生成的工单分级、字段提取等结果时,在进入业务流程或写库前校验字段和跨字段规则,明确拒绝无效结果。
  • 为已有 JSON 清洗逻辑增加计数,区分原始通过、剥离围栏后通过和一次修复后通过,再决定是否继续依赖这些兼容处理。
  • 把端点验收放进 CI,并在实际部署环境运行;首次通过率不足或出现关键业务规则违规时,用非零退出码阻止合并。
  • 将提示词摘要、模型字符串、测试时间和失败样本随变更保存;沿用 50 次冒烟测试时,在评审说明中明确样本范围和局限。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
迁移模型前,用契约测试替代凭感觉
下一篇
AQuA 从会话中定位智能体隐性失败

资讯速览

dev.to · AI · 2026-11-05 23:46

AI 评分 8.0 / 10

相关标签

#结构化输出#模型评测#数据校验

继续阅读

  • 用大模型为 Kubernetes 日志做初诊
  • 迁移模型前,用契约测试替代凭感觉
  • AQuA 从会话中定位智能体隐性失败
  • Qwen Code 自动审批边界需要检查
  • 用工具参数校验阻止 Agent 静默出错