gpt-6.1-sol贵多少?30道编程题实测单价与实付成本 1. 先把“贵多少”拆成两个数名义单价和每道题的实付跑完最后一道题的那天晚上我对着两张账单看了很久。桌面上是两个版本的后端编程题跑批记录一共 30 道同一套 prompt、同一套判分脚本、同一台机器、同一个时间段。让我在意的不是通过率而是标题里那个问题——gpt-6.1-sol 到底比 gpt-6-sol 贵了多少。这个问题的答案比想象中复杂因为“贵”至少有两个层面价目表上的数字和每道题真正花出去的钱。前者是固定的后者会随着输出长度、重试次数、任务复杂度一起浮动浮动幅度能到 30% 以上。先说结论方向**名义单价上gpt-6.1-sol 比 gpt-6-sol 贵了整整 50%但按 30 道后端编程题的实付账单算只贵了 16.5%。**如果再往下拆一层按“每通过一道题”的成本算新版反而比旧版便宜 13%。这三个数字放在一起才是完整的故事。只盯着第一个数字做决策很容易把一次值得的升级砍掉只盯着第三个数字又容易忽略掉新版在简单任务上是纯亏的。我写这篇的目的不是给谁站台而是把这套算法摊开让你能拿自己项目的题去跑一遍得出属于你的那三个数字。不同团队的任务分布差别很大别人跑出来的比例对你未必成立。1.1 两个版本的价目表摆在一起我拿到的报价口径是这样输入输出分别计价按百万 token 折算计费项gpt-6-solgpt-6.1-sol涨幅输入每百万 token$2.00$3.0050%输出每百万 token$8.00$12.0050%输出单价比输入贵 4 倍这个比例在两个版本里都没变。这一点很关键它意味着控制输出长度比压缩输入更省钱而在后端编程题这个场景里输出长度的波动又恰恰是最大的变量——同一道题有的模型生成 1200 token 就收尾了有的能写到 3500 token 还在补注释。很多人算成本的时候习惯按“输入 token 数 × 单价”拍个脑袋这在小任务上误差不大但放到代码生成场景里会低估一半以上。一道中等难度的后端题题目描述加约束条件大概 2000 到 3000 token但模型吐出来的完整实现加解释往往 2000 token 起步。输出那一侧才是账单的大头占比通常在 60% 以上。我在做预算的时候会直接按“输入占四成、输出占六成”来估比逐项精算省事误差也能接受。1.2 单价涨 50%不等于账单涨 50%跑完 30 道题我拉了一张 token 消耗的对照表。这里用的都是接口返回里的 usage 字段不是我估的指标gpt-6-solgpt-6.1-sol平均输入 token68006600平均输出 token24002050平均重试次数每题0.370.18单题平均成本$0.0449$0.052430 题总成本$1.35$1.57输入 token 基本持平新版还略低一点因为它的上下文处理更紧凑同样的题目描述它不会重复复述。真正拉开差距的是输出新版平均少写 350 token降幅 14.6%。这 350 token 不是什么玄学主要来自三个地方——少写冗余的边界检查、少写重复的类型定义、少在代码块外面加一大段解释性文字。说到底就是话少了但该有的实现没缺。把这两个因素叠起来单价涨 50%输出量降 14.6%重试率从 0.37 降到 0.18。最后一次重试的成本是按整题重算的等于把单题成本翻了一倍所以重试率这个变量对总账的影响比想象中大。0.37 次重试意味着三分之一的题要重跑一遍摊到每题上是 37% 的额外开销新版只有 18%这一项就省下将近两成。算下来的结果是单题成本从 $0.0449 涨到 $0.0524涨幅 16.5%。**名义 50%实付 16.5%中间那 33.5 个百分点是被 token 效率和重试率吃掉的。**这个差额不算小如果你的日调用量在几千次量级一年下来差出来的钱够买一台还不错的开发机。1.3 重试率是藏在水面下的第三只手重试率这个东西很少有评测文章认真统计但它对成本的扰动比单价还大。我在跑批的时候把重试分成两类一类是硬失败返回格式不对、代码块没闭合、被截断这种必须重跑另一类是软失败代码能跑但没通过我预设的用例这种我会重跑一次给它“第二次机会”模拟真实使用中“不满意就再问一遍”的行为。旧版的问题集中在硬失败上30 道题里有 6 道第一次返回的代码块没有正确闭合或者把多个文件的内容塞进一个代码块导致解析失败。这类失败跟模型能力无关纯粹是格式稳定性问题但每次都要多花一整题的钱。新版这边只出现了 2 次而且都是超长输出被截断导致的。软失败的重跑更有意思。旧版在并发状态机那组题上第一次跑挂了之后重跑有 2 道题第二次就过了——这说明它不是不会而是采样波动大第一次没抽到好答案。新版这边重跑通过的有 1 道而且那道题第一次只差一个边界条件。采样稳定性本身也是一种能力它不体现在单次通过率上但实实在在地体现在账单上。所以我在做任何模型对比的时候都会把重试率单独列一栏。如果你只看单次调用的价格很容易得出“新版贵 50%”的结论然后放弃把重试摊进去实际差距立刻缩到六分之一。这个坑我踩过一次后来所有跑批脚本都强制记录 usage 和重试次数缺一项就不算跑完。2. 30 道题的分布为什么并发状态机被我单列成一条赛道题库的设计比跑批本身更费时间。我最开始想的是按语言分——TypeScript、Python、Go 各十道后来发现这个分法毫无信息量因为同一个语言里写一个 CRUD 接口和写一个有竞态的状态机难度差着数量级。模型在简单题上都能满分跑出来的数据全挤在天花板上区分度为零。改成按“能力维度”分之后才清晰起来。最终定下来四个题组每组题量不一样因为我想让每个组都有足够的样本支撑结论同时又不能让总成本失控。30 道题是成本和置信度之间的平衡点再少的话单组的样本量掉到个位数结论就不可靠了再多的话跑一轮的时间和钱都翻倍迭代不动。题组题数考察的核心能力典型任务A. API 设计与校验8接口契约、输入校验、错误码设计设计一个分页查询接口并处理非法参数B. 事务与并发控制7事务边界、锁粒度、幂等实现转账接口保证并发下余额不出负C. 并发状态机TypeScript8状态迁移、事件串行化、取消与超时带并发上限和取消能力的任务调度器D. 重构与缺陷定位7读代码、找根因、小步改造给定一段有竞态的旧代码定位并修复2.1 四个题组的划分依据A 组考的是“表达能力”。这类任务的正确答案不唯一但好坏差别很大错误码是随便返回还是分了业务码和系统码分页参数是裸传还是做了上下界收敛返回值是直接抛异常还是统一包装。模型在这组题上的表现差异很小因为训练数据里这类代码太多了闭着眼都能写。我把它放进来的目的是做基线用来验证“新版是不是在所有维度上都更好”这个假设。B 组考的是“边界感”。事务和并发这类题目难点不在写代码而在想清楚哪些操作必须在同一个事务里、锁应该加在哪一层、失败之后怎么补偿。模型经常写出来能跑的代码但事务边界画错比如把远程调用塞进事务里或者用行锁去解决本该用乐观锁解决的问题。这组题旧版通过 5 道新版通过 6 道差距不大但失败的题错得很有共性。C 组是重点单独一节说。D 组考的是“读”的能力给一段有问题的旧代码要求定位根因并给出最小改动方案。这组题最能暴露模型是否会“过度重构”——很多模型一看到旧代码就忍不住整段重写结果引入了新问题。新版在克制程度上明显好一些7 道里过了 5 道旧版 4 道。2.2 并发状态机题组的入选标准C 组这 8 道题是我花时间最多的。入选标准有四条每条都是踩过坑之后加上的第一条必须有 await 打断的地方。没有 await 的状态机是纯计算模型的正确率接近 100%测不出任何东西。只有把await插在“读状态”和“写状态”之间才能制造出真实的 check-then-act 窗口。第二条必须有不变量横跨多个 await。比如“同一时刻运行中的任务数不超过 3”这条约束要在入队、出队、完成回调三个位置都成立。模型很容易在单个函数里守住但跨函数就忘了。第三条必须有取消或超时路径。这是最能拉开差距的一条。取消意味着状态可以从任何一个中间态跳到终止态模型需要处理所有可能的跳转组合而不是只写 happy path。第四条不加库。不允许引入状态机框架、不允许用队列中间件全部手写。加了库之后模型只要会调 API 就行测的就不是推理能力了。这四条一卡8 道题里有一半是我从真实项目的缺陷记录里改出来的把业务逻辑剥掉、只留并发骨架。这么做的好处是场景真实坑都是真实存在的坑不是人为编造出来的刁钻题。2.3 出题时踩过的两个坑第一个坑是题目描述写得太细。我一开始把状态机的所有状态和迁移关系用表格列清楚结果两个版本都拿满分。原因很简单状态迁移图给了我剩下的就是照着写 if-else没有任何推理空间。后来我把迁移图删掉只描述业务语义——“任务可以被取消取消后不能再被执行正在执行时取消要等当前步骤结束”——让模型自己去推导状态集合和合法迁移区分度立刻就出来了。第二个坑是测试用例泄漏。我早期把单元测试一起塞进 prompt 里本意是让模型知道验收标准结果模型直接对着测试用例反推实现写出来的代码只能过那几条用例换个输入就崩。后来改成只给接口签名和三条示例输入输出剩下的用例全部藏在判分脚本里。这两个坑的本质是一样的**prompt 里给的信息越多测的就越偏向“照做”而不是“设计”。**想测推理能力就得主动把信息抽掉一部分让模型自己补全。这个度很难拿捏给少了模型会理解偏给多了又没有区分度我的经验是留 70% 左右关键的约束给具体的实现路径不给。3. 那道并发状态机题长什么样模型又是在哪一步翻车的拿 C 组里最有代表性的一道题展开说。题目要求实现一个任务调度器支持提交任务、限制并发数、支持取消、支持超时。任务本身是个异步函数调度器负责管理它的生命周期。状态包括 pending、queued、running、succeeded、failed、canceled 六种迁移规则需要自己推导。接口签名长这样type TaskState | { kind: pending } | { kind: queued; enqueuedAt: number } | { kind: running; startedAt: number; attempt: number } | { kind: succeeded; finishedAt: number; result: string } | { kind: failed; finishedAt: number; reason: string; retryable: boolean } | { kind: canceled; canceledAt: number }; interface Scheduler { submit(id: string, fn: (signal: AbortSignal) Promisestring): void; cancel(id: string): void; getState(id: string): TaskState | undefined; }并发上限设为 3超过上限的任务进队列。取消一个 running 的任务要触发 abort并且状态最终要落到 canceled不能落到 failed。这两条约束加起来就制造出了大量的边界情况。3.1 翻车点一await 前后状态被偷偷改掉旧版在这个点上翻得最彻底。它写出来的代码大概是这样async function runTask(id: string) { const state states.get(id); if (state?.kind pending) { states.set(id, { kind: queued, enqueuedAt: Date.now() }); await enqueue(id); } }问题出在await enqueue(id)这一行。在等待入队的这段时间里外部完全可能调用cancel(id)把状态改成 canceled而enqueue返回之后没有任何二次校验任务照样会被推去执行。这是一个教科书级别的 check-then-act 竞态单线程 JS 里因为 await 让出执行权照样会发生。新版在这个点上做对了它用的是“先占位再校验”的思路async function transition( id: string, expect: TaskState[kind], next: TaskState ): Promiseboolean { const current states.get(id); if (!current || current.kind ! expect) return false; states.set(id, next); return true; }把“读状态”和“写状态”压进同一个同步函数里中间不留 await这样就不会被打断。这个写法本身不复杂难的是模型要意识到“这两步必须原子”。旧版不是不会写这个函数而是压根没想到需要这个函数。3.2 翻车点二定时器与取消信号的处理超时和取消这两条路径两个版本都翻过车但翻的方式不一样。旧版的做法是给每个任务起一个setTimeout超时了就改状态。问题在于任务正常结束之后它没有clearTimeout。这在单次测试里看不出来因为进程很快就退出了但我的判分脚本会连着跑 8 道题定时器越积越多跑到第 6 道的时候进程挂着不退出测试框架直接报 open handles。这个错误很隐蔽因为代码逻辑是对的只是资源没释放。新版把clearTimeout补上了但它在取消路径上又出了新问题abort触发之后任务函数抛出的异常被它当成普通失败处理状态落到了 failed 而不是 canceled。要修这个问题得在 catch 块里判断一下 signal 的状态try { const result await fn(signal); // ... } catch (err) { if (signal.aborted) { states.set(id, { kind: canceled, canceledAt: Date.now() }); } else { states.set(id, { kind: failed, finishedAt: Date.now(), reason: String(err), retryable: true }); } }这段逻辑不算难但它要求模型同时记住两件事取消是一种“预期的失败”以及 abort 信号是判断依据。旧版压根没走到这一步因为它的定时器问题先把测试搞挂了。3.3 翻车点三类型收窄带来的虚假安全感TypeScript 的 discriminated union 是个双刃剑。好处是switch (state.kind)之后每个分支里 state 的类型会自动收窄写起来很舒服坏处是模型很容易把“编译期类型安全”当成“运行时状态安全”觉得类型对了逻辑就对了。旧版有一道题的实现是这样的switch (state.kind) { case running: // 这里 state 被收窄成 running可以直接读 startedAt clearTimeout(timers.get(id)); break; default: // 什么都不做 }看起来没问题但state是循环开始前读出来的快照。如果在这段代码执行的间隙里状态被别人改成了 succeededclearTimeout清掉的还是一个已经失效的定时器而真正的定时器句柄可能已经被覆盖。运行时没人替你收窄类型state.kind说是 running实际存储里可能早就不是了。新版在这道题上的处理是把“读”和“用”贴在一起每次用之前重新从 Map 里取一次取完立刻判断、立刻操作不留空窗。这个习惯很难从类型系统里推出来更像是写过多线程代码的人才会有的肌肉记忆。我觉得这是新版在这组题上真正的优势所在——不是它更会写 TypeScript而是它对“状态会变”这件事更敏感。4. 判分口径脚本卡硬指标人工只分辨“能跑”和“能维护”判分这块我改过三版。第一版纯人工30 道题看了整整一个下午看完第二天就忘了当时为什么给某道题打 4 分而不是 3 分一致性极差。第二版纯脚本只看测试用例过没过结果被一道“把所有状态塞进一个 any 变量”的代码骗过去了——用例全过代码完全没法维护。第三版才是现在这套脚本负责硬指标人工负责软指标两边分开打分再合并。4.1 三层硬指标脚本这一层跑三件事任何一件挂了直接判不通过不给人工评分的机会检查项具体做法挂掉的原因占比类型检查tsc --noEmit零错误旧版 12%新版 5%用例通过预置 12 条用例全过旧版 41%新版 22%静态扫描无as any、ts-ignore、eslint-disable旧版 28%新版 15%静态扫描这一条是我后来加的起因是发现有一道题两个版本都通过了用例但新版用了 3 处as any把类型问题绕过去了。这种代码在 review 里是要打回去的不能算“做对了”。加上这条之后新版的通过数从 7 掉到 6旧版从 4 掉到 3差距反而拉大了——说明新版更倾向于用类型题外话糊弄过去这本身是个值得注意的信号。用例的设计也有讲究。12 条用例里6 条是正常路径4 条是并发路径同时提交多个任务、执行中取消、超时后重试2 条是资源检查跑完之后没有残留定时器、没有未处理的 Promise rejection。后面这 2 条最容易被忽略但恰恰是区分“能跑”和“能用”的分水岭。4.2 人工评分表过完脚本的题进入人工评分5 分制四个维度各占一部分权重维度权重看什么正确性40%边界情况是否覆盖异常路径是否处理可维护性30%函数拆分是否合理命名是否表意有没有魔法数字资源管理20%定时器、监听器、连接是否成对释放克制程度10%有没有过度设计有没有引入不必要的抽象“克制程度”这一项是我特意加的。模型有个通病一看到状态机就想上设计模式写一堆 Factory、Strategy、Observer代码量翻三倍实际逻辑就那几行。这种代码在评审里是要被砍的所以我在评分里也压了一档。新版在这项上比旧版好但好得不多8 道题里有 3 道还是过度设计了。人工评分我建议至少隔一天再做当天看代码容易被“哇这段写得好”的第一印象带跑。隔一天再看很多当时觉得巧妙的写法会发现其实是绕远路。4.3 盲评怎么做的为了让评分不受模型名影响我把两个版本的输出混在一起随机编号代码块里的注释和变量名统一做了一次替换把可能暴露模型的痕迹抹掉然后自己一份一份看。看完打分最后才对号入座。这么做之后有个意外发现有 4 道题我在盲评时给了高分对号之后发现是旧版写的。这 4 道题的共同点是代码短、直接、没有花活。新版在这些题上反而写得更啰嗦因为它“想得更多”把一些不会发生的边界情况也处理了。想得多不总是好事在简单场景里就是过度设计。盲评这一步费时间但如果跳过评分的客观性基本没法保证。我建议哪怕只做半天也把模型标识抹掉再评心理暗示的力量比想象中大得多。5. 成绩单和账本赢在哪、赢多少、赢的边际有多薄数据摆出来之前先说清楚口径每题跑 2 次取较好的一次计入通过数两次都挂才算不通过。这样处理是为了排除采样波动代价是成本翻倍但相对公平。人工评分取两次里分数高的那份。5.1 总成绩题组题数gpt-6-sol 通过gpt-6.1-sol 通过差距A. API 设计与校验8671B. 事务与并发控制7561C. 并发状态机8363D. 重构与缺陷定位7451合计3018246总通过率从 60% 提到 80%看起来是个不错的提升。但把这个数字拆开看就没什么可兴奋的了**6 道题的增量里3 道来自 C 组另外三组各只多了 1 道。**换句话说如果你手上的任务不涉及并发和状态机这次升级带来的实际收益大概是“每 8 道题多对 1 道”。还有个更扎心的数据30 道题里有 19 道两个版本打平其中 11 道两者都是满分。这 11 道全是 A 组和 D 组里的中等难度题模型闭着眼都能写对。这就是天花板效应它把总通过率的差距稀释了。如果把这 11 道题剔掉只看有区分度的 19 道通过率对比是 7/19 对 13/19差距立刻变得明显。5.2 每通过一题的成本按前面算的账单两个版本跑完 30 道的总花费是 $1.35 和 $1.57。除一下通过数口径gpt-6-solgpt-6.1-sol差异全量每通过一题$0.075$0.065新版便宜 13%C 组每通过一题$0.184$0.104新版便宜 43%A 组每通过一题$0.042$0.052新版贵 24%这张表是整个评测里最有价值的部分。**全量口径下新版更便宜但这个便宜完全是被 C 组拉出来的一旦把 C 组拿掉新版在所有其他题组上都是纯亏。**A 组那 24% 的溢价买到的只是 1 道题的增量而且那道题旧版重跑一次也能过属于采样运气。我在做技术选型的时候习惯看这个指标而不是看总价因为它把“能力”和“成本”压成了一个数。单价高低是采购关心的每通过一题的成本才是工程团队该关心的。5.3 临界点什么任务值得为 50% 的单价买单再往下推一步。设旧版单题成本 $0.0449新版 $0.0524那么新版的通过率必须比旧版高出多少才能让成本打平$$\frac{p_1}{p_0} \frac{0.0524}{0.0449} \approx 1.167$$也就是新版的通过率要高 16.7% 以上才划算。拿这个尺子去量四组题题组旧版通过率新版通过率比值是否划算A75.0%87.5%1.167刚好卡线约等于白干B71.4%85.7%1.200轻微划算C37.5%75.0%2.000明显划算D57.1%71.4%1.250轻微划算这个 1.167 的临界值很有意思。A 组恰好卡在线上说明在这类“表达能力”任务上新版的溢价被完全吃掉了产出和投入对等没有超额回报。C 组的比值是 2.0远超临界值这就是值得为它多付钱的地方。不过要提醒一句样本量只有 7 到 8 道通过率的置信区间很宽。C 组 3/8 对 6/8换个批次可能变成 4/8 对 6/8比值掉到 1.5依然划算但如果变成 4/8 对 5/8比值就掉到 1.25勉强卡线。**所以我的做法是只看方向不看小数C 类明显划算、A 类明显不划算中间两组随缘。**纠结小数点后两位没有意义样本不够。6. 把这套评测搬回自己项目题目自造、成本自记、请求分流别人的评测数据只能做参考真正有用的是你自己跑一遍。原因很简单每个团队的任务分布都不一样做数据管道的团队可能 80% 的活儿是 ETL 转换做交易系统的团队天天跟并发和事务打交道同一份评测结果对这两拨人的意义完全不同。我把自己用的那套东西整理成了三步都不复杂一个下午能搭起来剩下的就是攒题目。6.1 用自己项目的缺陷记录出题最好的题目来源不是网上找的算法题是你自己代码库里的缺陷记录。挑那些“修复提交不超过 50 行、但定位花了两小时”的 bug把修复提交 revert 掉把当时的错误现象描述成 prompt让模型来修。这类题目的好处是贴近真实、有标准答案、而且能直接反映模型能不能解决你团队的实际问题。我一般按这个流程处理从提交历史里筛出带缺陷编号的修复提交只看改动在 100 行以内的把修复部分 revert保留当时的上下文代码写一段 prompt只描述现象和复现步骤不给根因判分用原来的修复提交做参照看模型改的地方对不对记录模型改动行数行数远超原修复的扣分第 5 条是我加的用来压制过度重构。真实项目里改 20 行能解决的问题模型给你重写 200 行就算功能对了也是负担——评审成本、回归风险、后续维护全都上去了。6.2 成本记录脚本成本这块一定要用接口返回的 usage 字段不要自己按字符数估误差能到 20%。每次调用把这几项落到一张表里const record { taskId, variant: 6.1 | 6.0, attempt: n, // 第几次尝试 inputTokens: usage.input_tokens, outputTokens: usage.output_tokens, cost: usage.input_tokens / 1e6 * INPUT_PRICE usage.output_tokens / 1e6 * OUTPUT_PRICE, durationMs, passed: boolean, manualScore: null, // 人工评分后回填 };攒够几十条之后按variant分组求和再除以通过数前面那张“每通过一题的成本”表就出来了。这个脚本不到 30 行但它决定了你后面所有决策有没有数据支撑。有个细节要注意**重试的那次也要单独记一行attempt字段区分开统计成本的时候全部计入统计通过率的时候只取最后一次。**我见过有人把重试的成本丢掉不算算出来的单价漂亮得离谱实际账单对不上。6.3 分流规则数据跑出来之后落地方式不是“全量切换”或“全量不切”而是按任务类型分流。我现在的规则大概是这样任务信号走哪个版本理由提到并发、竞态、事务、幂等、状态迁移新版这类任务推理链条长新版的优势集中在这儿CRUD、DTO 转换、参数校验、单测补全旧版天花板效应明显新版纯溢价单次输入超过 8000 token 的长上下文重构旧版 人工复核新版单价高长输入下成本放大明显线上事故的根因定位新版一次找对比省下的时间值钱这套规则不完美判断“是不是并发任务”本身也需要人来做但比一刀切强。真要做自动化分流可以在 prompt 里做关键词加权出现race、transaction、lock、idempotent、state machine这类词的加权到新版其余走旧版。加权阈值我设在 2 个词以上低于这个数就默认旧版因为误判到新版的成本比误判到旧版高。分流上线之后我观察了两周实际走新版的请求占比大概 18%整体账单涨了 3% 左右但 C 类任务的返工率降了一半。这个交换我认。最后分享一个我踩过的坑。我一开始把gpt-6.1-sol的单价和自己的任务分布一起算得出“整体贵 50%、不值得”的结论差点就放弃升级了。后来把重试率补进账本发现旧版在并发题上的重试率高得惊人摊进去之后实际差距只剩 16.5%。**单价是采购数据重试率是工程数据两个一起看才是真实的成本。**如果你只打算做一件事就把 usage 和重试次数老老实实记下来记满 50 条再回头看很多凭直觉做出的判断会被推翻。另外提醒一句我这次跑出来的 1.167 这个临界值是建立在我这套题目分布上的。你的题目库里简单题多临界值会更低意味着新版更容易划算反过来你的活儿全是硬骨头通过率基数低那新版的相对优势会更大但也意味着两个版本可能都不太够用得预留人工兜底。这个数只能自己跑别人给的都不作数。