首页
/ Ponytail 鲁棒性审计:让模型写最懒的代码,会不会在边缘用例上写错?

Ponytail 鲁棒性审计:让模型写最懒的代码,会不会在边缘用例上写错?

2026-09-05 10:23:25作者:明树来

本文以 Ponytail 仓库中 2026-06-16 的鲁棒性审计报告为骨架,完整还原这次"敌意测试"的设计与全部结论:Ponytail 技能(一种强制模型产出最简最短解法的 SKILL.md 规则集)是否会诱导弱模型在 n=0、世纪闰年、减法罗马数字等经典边缘用例上产出错误代码。读完你可以掌握一套自校验基准(self-verified benchmark)的完整构造方法——如何用"已知正确"与"已知偷懒且错误"的参照实现先验证测量仪器本身,再评估模型输出——并理解"parse ≠ validate"这类跨供应商的隐性失效模式为何无法靠修改提示词修复。

背景:从 issue #65 到一次敌意审计

Ponytail 的核心主张是"最好的代码是你从未写下的代码":通过 skills/ponytail/SKILL.md 把模型约束成"房间里最懒的资深工程师",优先复用、优先标准库、优先原生特性,直到找到第一个能用的最短解。这一主张自然会引来质疑:把模型往最短解上推,会不会让弱模型在边缘用例上产出错误的代码?

这正是 issue #65 提出的问题。同一天的另一份报告 benchmarks/results/2026-06-16-correctness-gate-fix.md 已经证明:此前社区报告的正确率暴跌其实是正确性闸门(correctness gate)自身的两个 bug(读不了无代码围栏的输出、debounce 任务测错了交付物)造成的假象。修好闸门之后,真正悬而未决的问题就落在了本次审计头上——用一个故意敌意的测试集、在足够高的样本量下,直接回答"降级"是否存在。

审计方法:先验证仪器,再打分

审计的两个对比臂(arm)是:

  • baseline:不加载任何技能,裸模型作答;
  • ponytail:把完整的 SKILL.md 作为 system prompt 注入。

单轮(single-shot)生成、默认参数,模型为 gpt-4.1-minigpt-5.4-mini 两个弱模型,每格 n=20。

整套方法的核心防御是自校验(self-verification)。测试集共 16 个任务,每个任务都内置两份参照实现:good(已知正确)和 bad(已知偷懒且错误)。任何模型输出被打分之前,先运行 node robustness-audit.js --selftest 验证这 16 对参照:good 必须全部通过断言、bad 必须全部失败,16/16 全部成立才允许开始消耗 API 额度。从 benchmarks/robustness-audit.js 的源码可以直接看到这套机制:

  • 每个任务是一个结构体:{ name, prompt, names, arity, cases, good, bad },其中 cases[参数, 期望值] 断言数组,names 是模型可能使用的多个候选函数名(如 email 任务接受 validate_email/is_valid_email/validate 等别名);
  • checkPy() 会把生成的代码拼进一个 Python 测试夹具(harness)里,在系统临时目录写成脚本、用 python3 执行,任何一条断言不匹配(MISMATCH)、抛异常(EXC)或找不到函数(NOFN)都判失败,且带 10 秒超时;
  • pyBlock() 负责从模型回复的 fenced 代码块中抽取 Python 代码(优先取语言标记含 py 的块,否则取第一个块)。
// benchmarks/robustness-audit.js --selftest 分支:16 项仪器全绿才允许跑真测试
for (const t of TASKS) {
  const g = checkPy(t.good, t), b = checkPy(t.bad, t);
  const pass = g === true && b === false;   // good 必须过、bad 必须挂
  ...
}

(对应源码 benchmarks/robustness-audit.js#L168-L178。)

另外两个工程细节值得注意:运行是串行的,目的是避免 API 配额 429 错误把分母缩掉(失败请求被单独计数为 err,从分母中扣除,见 benchmarks/robustness-audit.js#L186-L193);API key 从仓库根目录的 .env 读取,模板见 .env.example

12 个经典边缘用例陷阱:两臂全平

16 个任务中的前 12 个是算法题,每格 n=20。结果:两个弱模型上,全部 12 题 baseline 20/20 == ponytail 20/20。这些题目的共同设计思想是"偷懒版实现能过常见用例、必挂边缘用例",原始报告给出的陷阱表如下:

任务 懒实现会漏掉的陷阱
is_prime n = 0、1、负数
factorial / fibonacci n = 0
binary_search 空列表、target 在最后一个下标(off-by-one)
is_leap_year / days_in_month 1900 不是闰年、2000 是闰年(世纪规则)
int_to_roman 减法形式(4=IV、9=IX、40=XL)
flatten 嵌套深度超过一层
clamp 值本身已在区间内
chunk 末尾的余数

以闰年题为例,robustness-audit.js 中的 bad 参照就是最典型的"懒错":return y % 4 == 0——常见年份全对,但断言里埋了 [1900, false][2400, true] 两条世纪规则检查(见 benchmarks/robustness-audit.js#L61-L65)。罗马数字题的 bad 参照则故意省掉 (900,'CM')(400,'CD') 等减法档位,专打 int_to_roman(4) 这类输入。

首次运行中唯一低于 20 的格子是 gpt-5.4-miniflatten,19/20——一次没有复现的随机失误:加试到 n=50 时拿到 50/50。(clamp 显示 19/19 则是 1 次 API 报错,不是答错。)

结论的第一半就此落定:在经典的"最短解诱导错误"怀疑场景里,Ponytail 没有比裸模型多错一题,保持基线平权(baseline parity)。

验证器任务:email 滑点是跨供应商、且与模型大小无关

16 个任务的后 4 个是验证器:emailurlcreditcardipv4。真正暴露差异的是 email,其机理是一个 "parse ≠ validate"陷阱:在"优先标准库"(stdlib-first)的压力下,OpenAI 模型有时会伸手去拿 email.utils.parseaddr——那是一个解析器,不是校验器,它会接受 @missing-local.com 这种没有 local part 的畸形输入——而不是写一条显式检查。robustness-audit.js 里的 bad 参照精确复刻了这个反模式(见 benchmarks/robustness-audit.js#L98-L102)。

滑点的分裂维度是供应商(provider),不是模型大小

OpenAI 系(email,baseline vs ponytail,n=50–100):

模型 baseline ponytail
gpt-4.1-mini 100% 98%
gpt-4.1 100% 79%
gpt-5.4-mini ~100% ~92%
gpt-5.4 100% 98%
gpt-5.5 98% 94%

Claude 系(email,baseline vs ponytail,n=40):

模型 baseline ponytail
claude-haiku-4-5 35/40 40/40
claude-sonnet-4-6 0/40 * 40/40
claude-opus-4-8 39/40 40/40

每一个 OpenAI 模型无论大小都会滑(gpt-4.1 完整版最差,79%),而 Ponytail 的目标平台 Claude 全系在 ponytail 下都是 100%

* Sonnet baseline 的 0/40 是一个返回类型伪影(artifact),不是逻辑失败,不能读作"Sonnet 不会校验邮箱"。不受约束的 Sonnet 把校验器过度工程化成返回 dict{is_valid, message})而非 bool;测试按 bool 调用该函数,非空 dict 恒为真值,于是它"接受"所有地址、得 0 分。若按 dict 感知(读 is_valid 字段)来判读,其逻辑约 75% 正确(9/12)。诚实的结论很窄:ponytail 写出了任务隐含的朴素正确 bool,而不受约束的模型把接口过度构建,绊倒了天真的 if validate(x) 调用方。这与同日 gate 修复报告中观察到的 Sonnet 失分是同一现象。

其余三个验证器在两个供应商下都稳定在约 100%:它们的"懒的标准库选择"(ipaddress、Luhn 校验、scheme 检查)本身就是严格的,唯独 email 最显眼的标准库工具是个解析器。

那个"没有发生的修复":8 次 SKILL.md 编辑全部无效

SKILL.md 本身已经有两条直接针对这个滑点的规则:"Never simplify away: input validation at trust boundaries..."(绝不简化掉信任边界的输入校验)以及"Two stdlib options, same size? Take the one that's correct on edge cases."(两个标准库选项一样长?选那个在边缘用例上正确的)。团队仍然认真尝试过用编辑技能文本来把 OpenAI 侧的通过率推到 100%——8 次不同编辑,覆盖反向施压措辞(counter-pressure wording)、强制检查条款、显式检查优于委托、few-shot 示例、各种组合与三处放置位置。结果:

  • 每一次编辑的得分都 ≤ 当前技能,其中几个差得多(有一次崩到 78%);
  • 每一次都让中位 LOC 膨胀;
  • 最有希望的一版做了 n=100 的决定性 A/B:
OLD skill: 96/100 (96.0%)
NEW skill: 95/100 (95.0%)   -> within noise, no reliable effect

反向指令会起反作用:往技能文本上堆校验规则,只会让小模型过度思考,产出更多坏校验器而不是更少。伸手去拿 parseaddr 的这个反射住在 OpenAI 模型的训练里,没有任何技能措辞能可靠覆盖它——所以什么都没发版。报告的原话点题:往技能里加一段不起作用的文字,正是 Ponytail 这个存在本身要阻止的 cargo-cult(货舱崇拜)行为。

结论:LOC 减半,Claude 上零正确性代价

"Ponytail 会让模型性能降级"这一说法不被数据支持。跨 12 个边缘用例陷阱,ponytail 与 baseline 完全平权;在验证器上它对每一个 Claude 模型都是 100%——那是它的目标平台。唯一的瑕疵是 OpenAI 模型上的 email 校验器滑点(一个跨供应商的 parseaddr 反射,每个尺寸都存在),已如实记录在此、且无法靠技能文本修复。而 LOC 收益(大约一半的代码量)在 Claude 上没有付出任何正确性代价。

复现指南

以下命令继承自原文档的 Reproduce 一节,可在本仓库中直接运行(.env.env.example 复制而来,位于仓库根目录;--selftest 不需要任何 API key):

cd benchmarks
node robustness-audit.js --selftest        # 验证全部 16 项仪器(无需 API)
node robustness-audit.js                    # 16 任务审计,gpt-5.4-mini,n=20
AUDIT_MODEL=gpt-4.1-mini node robustness-audit.js

# email 跨供应商测试(即那个滑点)
ME_MODELS="gpt-4.1,gpt-5.4,gpt-5.5" ME_N=50 node model-email.js   # OpenAI  (OPENAI_API_KEY)
node claude-email.js                                              # Claude  (ANTHROPIC_API_KEY)

OPENAI_API_KEY / ANTHROPIC_API_KEY 均从 ../.env 读取。相关脚本与可调参数:

脚本 用途 关键环境变量(默认值)
benchmarks/robustness-audit.js 16 任务、两臂(baseline/ponytail)审计 AUDIT_MODEL(默认 gpt-5.4-mini)、AUDIT_N(默认 20)、--selftest
benchmarks/model-email.js email 任务在多 OpenAI 模型上高 n 复测 ME_MODELS(默认 gpt-4.1-mini,gpt-5.4-mini)、ME_N(默认 100)
benchmarks/claude-email.js email 任务在 Claude 三档模型上复测 CE_MODELS(默认 haiku/sonnet/opus 三档)、CE_N(默认 40)

这三个脚本共享同一套 checkPy / pyBlock / TASKS 导出(model-email.jsclaude-email.js 都是 require('./robustness-audit.js') 后取 email 任务),因此任何对断言集的修改都会同时作用于所有测量,仪器的自校验性质不会被脚本分叉破坏。审计结果所在的 benchmarks/results/ 目录还收录了其他各次专项报告(成本复核、agentic 安全等),而更宏观的三臂基准(LOC/成本/延迟)与复现流程见 benchmarks/README.md

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384