结构化输出是便宜模型最先崩的地方。所以真正值得看的工程能力,不在第一次回答,而在它出错之后的恢复路径。一道只给"顺利路径"打分的笔试题,奖励的是候选人挑了个配合的提示词、抽到了一次好运的样本。
反过来,一道给"修复循环"打分的笔试题,奖励的是候选人假设输出会漂移、能测量漂移、并在修复失效时果断失败关闭。这套题包定义了任务书、加权评分表、参考实现,以及把两类候选人区分开的失败模式。
交给候选人的任务书
做一个端点,把自由文本的客服工单转成一条通过校验的记录。记录必须匹配给定的 JSON schema。模型可能返回非法输出,你的任务是在没有人工介入的情况下恢复。
硬性约束保证不同候选人之间可比:
- 只注入一个生成函数,候选人不得中途更换供应商。
- 一次校验失败最多触发两次修复尝试,之后调用返回类型化失败。
- 端点必须为每个请求记录尝试次数和确切的校验错误。
- 测试套件必须离线跑在录制的固定样本上,不访问网络。
- Markdown 代码围栏、正文前言、尾部评论必须被显式处理,不能用一揽子正则糊过去。
schema 刻意做得很小但不平凡:ticket_id(字符串)、severity(low 或 high 的枚举)、components(字符串数组)、customer_impact(布尔值)。手写校验器和引入库的候选人都可以接受,只要失败信息足够具体,能回灌进修复提示词。
这道题不要求什么
任务书从不要求包一层单一厂商的 SDK,也从不要求把错误藏起来的重试逻辑。评审应把精心设计的重试退避视为干扰项——被打分的问题是提示词修复质量。一个加了指数退避却没有尝试遥测的候选人,优化错了变量。
评分表按评审速度加权,两名评审大约十五分钟能过一遍。其中占 40% 的那一行是唯一需要细读代码的,也正是能预测值班表现的那一行。
参考实现里,校验器手写,让练习保持零依赖,并把解析和校验当作两类独立的失败。修复循环保持任务文本稳定,只替换错误块,这样各次尝试的 token 用量大致持平。
循环最多修复两次。解析失败时,提示词追加"只返回一个 JSON 对象,不要正文、不要代码围栏、不要尾部文本";校验失败时,提示词追加具体的错误清单,要求只返回修正后的 JSON 对象。预算耗尽则返回 validation_budget_exhausted,并带上完整的尝试轨迹。
测试套件必须包含的固定样本
- clean.json——第一次就合法的对象,循环必须返回 attempts: 1。
- fenced.md——同一个对象被 JSON 代码围栏包住,循环不得消耗一次修复尝试。
- bad-enum.json——severity 取值超出枚举,随后给出修正后的对象。
- truncated.json——每次尝试都是未闭合的对象,循环必须返回 validation_budget_exhausted。
截断这个用例最该被评审看重,因为它证明候选人知道什么时候停。一个永不放弃的循环能通过每一次手动演示,却会在供应商降级时于生产环境翻车。
换成免费端点后,练习变了什么
离线固定样本能证明控制流,却说明不了模型面对一个它没见过的错误块会怎么反应。把同一个循环跑在实时端点上,能暴露出"点名失败路径的修复提示词"和"把整个 schema 重述一遍的修复提示词"之间的差别。这个阶段用免费模型访问就够了,因为量只有几百次短调用,目标是看漂移,不是看吞吐。
免费服务器选项的意义在另一处:它让评审通过 HTTP 打端点,而不是跑在候选人的笔记本上,于是尝试日志在评审过程中就是可观察的。
常见失败模式
- 只给第一次回答打分。候选人拿一个干净样本自评,会报出固定样本已经证伪的成功。
- 修复提示词把一切都重发一遍。把 schema、任务和候选人的完整历史全附上,只会让每次尝试变贵,不会让修复变好。
- 静默剥围栏。一个宽松到剥掉任何反引号内容的正则,是在掩盖解析失败,而不是给失败分类。
- 无界循环。在生成外面套 while (true) 看起来很有韧性,却是投稿被拒最常见的原因。
- 把校验通过当成事实正确。符合 schema 不等于语义正确,一个看起来合理的 ticket_id 仍然可能是错的。
- 没有尝试遥测。没有逐次错误,没人分得清是修复失败了,还是压根没试过。
谁不该用这套题
招 UI 偏重岗位的团队从这里拿不到什么信号;schema 还不稳定的团队应该先把 schema 定下来。硬件受限的候选人不应被要求本地托管模型,因为被打分的产物是循环而不是运行时。需要逐字节确定性输出的场景应该用语法约束解码器,而不是修复循环,因为修复是缓解手段,不是保证。
如果目的是看一个人在模型以可信方式出错时会怎么做,这套题包跑起来便宜,也很难造假。保留评分表和失败模式,把 schema 换成你自己领域的即可。
热门跟贴