这道题其实问的是,怎么让大模型输出严格限定在我们指定的几个标签里,比如客服场景里,用户问退款问题,模型只能输出“退款中、已退款、退款失败”这几个状态,不能自由发挥。这本质上是可控生成问题,我有几层手段,从轻到重,但真正落地的时候,我不会只依赖一层。
先说提示工程,这是最基础的。我会在[[系统提示]]里明确给出标签列表,加上类似“只能从以下选择,不要解释”的指令。再给几个例子,就是 [[Few-shot]],让模型模仿。但说实话,这层只能管住大部分情况,模型还是可能跑偏,尤其面对复杂表述或者对抗性输入时,它可能会自己发明一个标签。所以提示工程只是基线,不能当唯一保障。
再一个,约束解码,这是核心。原理很简单:在模型生成每一步,只保留合法标签对应的 token 概率,其他 token 的概率直接置成负无穷。标签往往由多个token组成,token也可能跨多个字符;约束器需要按实际tokenizer和已生成前缀保留所有仍能组成合法标签的token,不能把一个汉字等同一个token。正常完成时可约束标签集合,但截断、拒绝与语义误判仍要处理。实现上可以用 有限状态机 或者上下文无关文法来定义合法输出空间,有现成的库比如 Outlines、Guidance。生产环境我一般会用这层做主力。
然后微调,也就是 [[SFT]],可以提升模型对指令的遵循能力。比如我收集一批指令-标签对,让模型学会在特定格式下输出。但注意,单独微调也不能保证百分之百,尤其模型没见过的新变体。所以我会把微调和约束解码结合起来,微调让模型更听话,约束解码兜住最后的底线。
另外还有 [[Function Calling]],应核对具体模型和API,并启用支持的strict schema模式;不能认为只传工具schema就一定严格匹配,也不能用JSON mode替代枚举约束。如果标签需要附带理由,可以放进schema;模型自报置信度还需另做校准,不能直接当真实正确概率。
最后是后处理校验,先完整解析并验证标签在集合内,失败后进入明确的失败/待复核状态或有界重试,不把解析失败静默变成某个有效业务标签。这层是兜底,不能依赖它,但加上去能提高鲁棒性。
选型上,我的经验是分场景。快速验证,用提示工程加后处理就够了。生产部署,我会优先用约束解码库或者 Function Calling,因为可靠性高。如果领域很特殊,比如医疗诊断分类,我还会加一层微调,让模型更理解领域语义。每次请求需要确定允许的有限标签集合;它可以在请求之间动态变化,更新schema/文法即可,不一定要重训模型。
常见集成错误是文法与tokenizer不兼容,或允许集合无法完成到终止状态。正确实现会在采样前屏蔽非法token;是否允许前导空白取决于消费方协议,不应为绕过错误随意放宽标签语法。还有,如果标签本身有歧义,比如“退款中”和“退款处理中”语义相近,模型可能混淆,那就需要靠微调或者更好的标签设计来区分。
另外,如果标签集特别大,比如上千个,约束解码的性能会下降,因为每一步要过滤的 token 太多。这时候我会考虑用分层标签,先输出大类再输出小类,或者用向量检索先缩小候选集,再让模型从候选中选。这其实变成了检索加精排的思路。
所以总的来说,我的做法是多层防护:提示工程打底,约束解码做核心保障,微调提升能力,后处理兜底。具体用哪几层,取决于你对可靠性的要求、标签的规模和动态性。我更倾向于在解码层下功夫,因为它最可控,不依赖模型“自觉”。