做LLM文本标注的朋友应该都有过这种体验:让模型写一段判断理由,结果它洋洋洒洒写了两百字,你真正要的其实只是一个“是/否”、一个1到5的分值,加一句“有多大把握”。更难受的是,这些长答案还得靠正则或二次调用去解析,稍微换个句式解析就崩。我最近把整个标注流水线改成了另一套思路——直接让模型返回分类、评分和判断概率,不再写长答案。以Jev这一类结构化标注工具为样例实现,整体推理成本降了将近一半,下游处理环节却稳了很多。
这篇文章会把这条路的完整思路讲清楚:为什么要扔掉长答案、分类/评分/概率这三个输出分别怎么设计、提示词和参数怎么调、落地时会踩哪些坑。适合正在做数据标注、内容审核、LLM评测或需要让模型输出可直接入库结果的工程师参考。如果你是刚接触LLM应用的新手,也能照着后面的代码模板直接搭一版。
1. 为什么“先写长答案再解析”的标注方式该淘汰了
1.1 长答案标注的三个代价
先算一笔实际账。传统标注流程里,我们通常会这么写提示词:“请判断以下文本是否包含违规内容,并给出判断理由。”模型老老实实输出一段话,里面的事实判断可能没错,但问题是:你拿到这段文字之后怎么办?
第一,解析成本很高。模型输出的句式千奇百怪,有时是“我认为该内容包含...”,有时是“这段文本包含...”,有时干脆先给结论再解释,还有时解释着解释着就开始自我否定,末尾来一句“但也不排除其他可能性”。你要从这段自然语言里抽取出“是/否”结论,就得设计一堆正则规则,或者再调用一次模型帮你提取结论。提取模型本身也会出错,而且每次提取都在烧Token。
第二,Token成本被成倍放大。写理由的本质是让模型生成大量与最终决策无关的文本。比如一个二分类判断,真正有用的信息只有1个比特,模型却为此生成了200个Token。按主流API价格粗算,单条标注成本翻了三到五倍。如果你每天要标注几十万条数据,这个差距就是真金白银。
第三,质量不稳定。长答案给了模型太多“发挥空间”,它容易在解释过程中引入没有依据的细节,甚至编造原文里不存在的词句。我在实际项目中遇到过模型为了自圆其说,把一条明显中性的文本描述成“暗示性内容”的情况。自由文本生成越开放,幻觉概率越高,标注结果就越不可信。
1.2 结构化输出把问题从“生成”变成了“决策”
Jev这一类方案的核心转变,是不再让模型“写”答案,而是让它“选”答案。它输出的三个字段正好对应三种决策:
- 分类:在预设标签集合里选一个,比如“违规/安全/需复审”。
- 评分:在有序标尺上选一个位置,比如1到5分。
- 判断概率:给这次选择附一个置信度数值,比如0.93。
这样设计有一个很直接的好处:结果天然是结构化数据,可以直接写进数据库、喂给下游策略、或者做成人工复审队列。不需要任何解析层,模型输出的JSON就是最终结果。
我习惯用一个类比来解释这个转变:以前你让面试官给候选人写一段评语,然后HR要从评语里猜“到底录不录”;现在你让面试官直接填一张表——“通过/不通过、综合评分、把握程度”。后者看起来少了点“人情味”,但对招聘流程来说,信息效率高得多,也更容易横向比较。
1.3 Jev解决的核心:让标注结果可直接进下游
很多团队做LLM标注卡在最后一公里:模型判断完,结果却不能直接用。要么是字段不统一,要么是结论藏在文本里,要么是缺少置信度导致无法做风险分层。Jev的思路是把“可消费性”放在第一优先级。
实际效果体现在两个地方。一是下游策略可以写得很简单,比如“概率低于0.6且评分为4以上”的样本进人工复审,这就是一条干净的规则。二是在构建训练数据集时,结构化标签可以直接作为弱监督信号,不需要再清洗。这两点叠加起来,标注流水线的维护成本会明显下降。
2. Jev 是怎么做到的:分类、评分与概率的生成逻辑
2.1 分类:把生成任务换成选择任务
Jev比较讲究标签集合的设计。同样是判断一段文本的情感,标签写成“正面/负面/中性”和写成“积极/消极/中立/其他”效果会差很远。核心原因在于,模型在封闭标签集上的行为更像“分类”,在开放标签集上的行为更像“生成”。
设计标签有几个实际技巧。一是每个标签要有明确的操作性定义,比如“违规”不能只写“包含违规内容”,最好给出典型情况:“包含明确色情描写”“包含人身攻击”“包含交易诱导”。二是要留一个“无法判断”的出口,否则模型会在模棱两可时硬选一个标签,把噪声带进数据。三是标签数量不要太多,我一般控制在5到8个以内,超过10个模型的选择稳定性会明显下降。
“无法判断”这个出口值得多说两句。很多标注需求里,样本本身就是有歧义的,强行让模型选一个类,只是在制造表面上的确定性。允许模型输出“无法判断”,等于把真正的低质量样本分流出来,留给人工处理,总体的标注准确率反而更高。
2.2 评分:有序分类的标尺设计
评分和分类不同,分类是离散无序选择,评分是有序标尺上的定位。比如判断“文本与查询的相关性”,1到5分就构成一个有序空间。这里最大的坑是:模型在没有参考锚点时,会倾向于给中间分。
什么叫锚点?就是每个分数对应的行为描述。3分到底是什么意思?“有点相关但不完全相关”?这种说法太模糊。更好的做法是给每个分数配一两个具体例子:
- 1分:完全不相关,答非所问。
- 3分:部分相关,但包含多余信息或关键信息缺失。
- 5分:直接回答查询核心,信息完整且无冗余。
锚点越具体,模型打分的可重复性越高。我实测下来的经验是,只给文字描述不如“描述加例句”稳定。如果预算允许,每个分值配一个真实样本作为few-shot示例,评分的一致性能提升一大截。
另一个需要注意的点是评分粒度。能用3分制解决的问题不要用5分制,能用5分制解决的不要用10分制。模型在细粒度标尺上的表现没那么可靠,5分和6分的区别可能只是噪声。选粒度时先问自己:下游真的需要区分这么细吗?如果不需要,粗粒度反而更稳。
2.3 判断概率:置信度从哪来
Jev输出的“概率”本质上是模型对自身判断的置信度表达。这里有两类实现路径,理解它们的区别对使用很关键。
第一类是让模型直接输出一个置信度数值,比如在提示词里要求“请同时给出你的判断概率,0到1之间”。这种做法实现简单,但有个已知问题:模型自报的置信度往往过校准不足,它说0.9时实际正确率可能只有0.75。所以拿到这个概率后,不能直接当作真实概率用,建议先做一层校准——取一批已标注样本,统计模型在不同置信度区间的实际正确率,再做一次映射。
第二类则是用模型内部的logprob或多次采样频率来估计概率。logprob反映的是模型在token层面的概率分布,对分类任务来说相对可靠,但大部分API不会直接暴露底层logprob,可获取性差。多次采样统计频率是更通用的做法:同一个样本采样10次,看各类别出现次数分布。这个方法的缺点是成本高,10次采样约等于10倍推理开销。
当判断涉及多个维度时,还会遇到概率合并的问题。比如一条文本要同时判断“相关性”和“合规性”,模型分别给出了0.9和0.8的概率,那综合概率怎么算?如果两个维度独立且都必须满足,可以按概率乘积计算,也就是0.9乘以0.8等于0.72。但要注意,概率乘积会让结果偏保守,维度越多综合概率越低。如果只是想让两个维度互相参考,取最小值或者加权平均会更合理。选哪种,取决于你对“低概率”的定义——是“任何一个维度不过关”还是“整体信心不足”。
3. 实操落地:把一个标注请求从提示词走到概率输出
3.1 提示词模板:三个输出的完整写法
直接给一份我调过很多版、目前在用的提示词模板。场景是文本相关性标注,输出格式为JSON,包含分类、评分和概率三个字段。
system_prompt = """你是文本相关性标注助手。对于给定的查询和文档,你需要做出三个判断。 判断规则: 1. 分类(class):从以下标签中选择一个,只输出标签名。 - "完全相关":文档直接回答查询的核心问题。 - "部分相关":文档涉及查询主题,但信息不完整或包含大量无关内容。 - "不相关":文档与查询内容无关或仅有表面词面重合。 2. 评分(score):1到5分,5分意味着文档完整、精准地解决了查询问题。 锚点定义: - 1分:完全答非所问。 - 3分:涉及主题但缺少关键信息,或包含较多无关信息。 - 5分:直接命中查询目标,信息完整且无冗余。 3. 判断概率(probability):0到1之间的数值,表示你对上述判断的把握程度。 仅当你对分类和评分都很有把握时,才给出0.9以上。 只输出JSON对象,不要输出任何解释文字。格式如下: {"class": "完全相关", "score": 5, "probability": 0.95} """ user_prompt = """查询:{query} 文档:{document} 请完成标注:"""这里有几个细节值得解释。第一个是“只输出JSON对象,不要输出任何解释文字”,这句话虽然简单,但对约束模型行为很有效。第二个是两个字段的取值空间都提前限定好了,模型不需要自己发挥。第三个是概率字段给了“0.9以上”的使用条件,这相当于一个校准提示,能减少模型盲目自信的情况。
3.2 参数设置与解析兜底
调用阶段有两个关键参数:温度和response_format。
温度建议设成0或者非常接近0的值,比如0.1。分类和评分是决策任务,不需要创造性,温度越高输出越不稳定。我在调参时对比过,温度0.8时同样一条样本五次输出的分类结果能出现三种,而温度0.1时基本能稳定一致。
response_format建议直接启用JSON模式。当前主流API都支持强制JSON输出,比如OpenAI的response_format={"type": "json_object"},Anthropic的JSON结构输出,国内大模型API也大多支持。强制JSON模式能大幅降低解析失败率,但还是建议写一层兜底解析逻辑,因为即使是JSON模式,偶尔也会有字段缺失或类型错误。
兜底逻辑不复杂:解析失败就重试一次,最多重试两次,仍然失败则标记为“解析失败”进入待人工队列。我见过一些团队在这里偷懒,直接让程序抛异常退出,结果跑批任务经常中断。加个重试和队列标记,成本很低,但跑批稳定性会好很多。
3.3 概率的工程化使用:复审队列与置信度分层
拿到概率之后,最忌讳的做法是设一个简单阈值(比如0.8以上通过)然后不闻不问。更稳妥的做法是设计一个分层策略:
| 概率区间 | 处理策略 | 说明 |
|---|---|---|
| 0.95以上 | 自动通过 | 高置信样本,可直接作为训练数据或直接放行 |
| 0.75到0.95 | 自动通过,但定期抽检 | 需要在离线环节抽样复标,监测漂移 |
| 0.6到0.75 | 降级为低优先级 | 可以自动处理,但结果不参与关键决策 |
| 0.6以下 | 进入人工复审 | 低置信度样本,交给人工标注员处理 |
| 格式异常/解析失败 | 强制进入人工队列 | 兜底,防止异常数据漏到下游 |
这个分层策略的精髓在于“让模型处理它有把握的事,把没把握的事交给人”。成本上,人工只处理低置信度样本,量往往只有全量的10%到20%,整体效率依然远高于全部人工标注。
这里还有一个工程细节:概率阈值不能一劳永逸。数据分布是随时间变化的,模型在不同批次上的平均置信度也会波动。我建议在流水线里加一个监控指标——每天统计概率的分布情况,如果发现“0.9以上”的样本占比突然从50%跌到20%,说明模型可能遇到了分布外数据,需要重新评估提示词或标签设计。
4. 我在标注流水线上踩过的坑和排查方案
4.1 模型就是不按格式输出,如何强制与兜底
最常遇到的问题就是模型偶尔不遵守“只输出JSON”的指令,会在JSON外面包一层Markdown代码块,或者在JSON后面追加一句“以上是我的判断”。这种情况下即使启用了JSON模式,偶尔也会有漏网之鱼。
我的排查顺序是这样的。第一步先检查提示词里是否有与格式指令冲突的内容,比如让模型“先思考再回答”之类的描述,这会诱导模型生成额外文本。第二步确认temperature确实设得很低,高温会让模型更“自由散漫”。第三步是兜底逻辑:写一个提取器,先从返回文本里截取第一个“{”到最后一个“}”之间的内容再解析,解析失败就重试。第四步是统计格式失败率,如果超过3%,要回头审视提示词是否写得太绕太复杂。
还有一个容易被忽略的点:如果你传了few-shot示例,示例本身也必须严格符合格式要求。我见过有团队在few-shot里混入了一条带解释文字的示例,模型立刻学会了,开始模仿着输出解释。
4.2 分类概率永远接近均匀分布的排查
另一个高频问题是:模型给出的分类概率总是在0.3到0.4之间晃,看不出它在哪个类上有信心。这说明模型其实分不清类别,只是硬着头皮给了一个低置信度判断。
排查顺序如下:先看标签定义是否模糊。如果“部分相关”和“不相关”的边界没有说清楚,模型当然会犹豫。其次是检查few-shot示例是否过于接近,示例越相似,模型越难区分。再就是看样本本身是否真的可区分,我遇到过一批数据,查询和文档完全脱节,模型基本上只能在“不相关”和“无法判断”之间随机猜。
处理办法通常有三个:一是细化标签定义,把容易混淆的类别拆开或者干脆合并;二是增加典型案例的示例数量;三是调整分流策略,让这部分低置信度样本自动进入人工队列,而不是硬等模型给出高置信度。第三条是最现实的解法——不是所有样本都适合模型标注,硬让模型标只会浪费存储和算力。
4.3 评分与概率打架时的合并规则
有些样本会出现一种矛盾情况:评分给了4分(较高),但概率只有0.55。怎么理解?模型打高分是因为文本和查询确实“像相关”,概率低则代表模型对自己的判断没把握。这种情况下直接相信评分很危险。
我现在的处理规则是:概率不达标时,评分不作为主要依据。具体实现是一个简单的条件判断:“如果probability低于0.6,则无论score多高,该样本都进入低置信度队列等待人工确认”。这个规则看起来简单,但能拦住很多“自信度不足但数值好看”的误判。
另一种打架情况是分类与评分矛盾,比如分类是“不相关”,评分却给了4分。这说明提示词里的分类定义和评分锚点有冲突,模型在两个任务上用了不同的判断标准。遇到这种系统性矛盾,该做的是回到提示词定义,统一两个字段的评价维度,而不是在代码层面打补丁。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查与解法 |
|---|---|---|
| 输出格式混乱 | 温度过高、提示词有冲突指令、few-shot格式不干净 | 降温度到0附近,精简提示词,检查示例格式 |
| 概率集中在0.3-0.5 | 类别边界模糊、样本区分度低 | 细化标签定义,合并易混类,低置信度进人工队 |
| 评分普遍偏高(或偏低) | 锚点描述不清、few-shot示例有偏向 | 补充分数锚点描述,平衡示例分布 |
| 同一个样本多次标注结果不同 | 温度偏高、提示词缺乏确定性引导 | 温度设0,增加“只输出JSON”严格指令 |
| 概率自报0.99但实际错误 | 过校准问题,模型盲目自信 | 做离线校准统计,按实际正确率映射概率 |
| 分类总分到某一个类 | 标签集合不平衡、示例有偏 | 调整few-shot示例比例,考虑类别均衡采样 |
| 字段类型错误(字符串当数字) | 预定义Schema缺失 | 启用JSON Schema校验,类型不合规时重试 |
写在最后的实际体会
整套方案跑了几个月之后,我自己最大的感受是:LLM标注的真正价值不在于“模型有多聪明”,而在于“结果有多容易被系统消费”。结构化输出把模型从“写手”变成了“决策者”,这一个小改变换来的是下游的极简处理和显著的成本下降。
如果你也想改造成结构化标注,我的建议是先小规模试跑。拿几百条真实样本,跑出结构化结果,手动检查一遍“概率-正确率”的对应关系,再决定阈值怎么设。不要一开始就追求完美提示词,先让流水线跑通,再根据统计结果逐步迭代提示词和锚点。Jev这样的工具已经把最麻烦的部分——格式约束和概率建模——封装好了,你需要做的就是把业务规则定义清楚。最后再分享一个小技巧:概率字段并不只是给下游用的,它还能反过来帮你做提示词评估——如果你改了提示词版本后,整体平均概率下降,说明新提示词让模型更犹豫了,这往往意味着定义变得更模糊。用概率来观测提示词质量,是个便宜又实用的方法。