工单自动打标实战:用微调突破提示词准确率天花板

发布时间:2026/10/20 21:05:42
工单自动打标实战:用微调突破提示词准确率天花板
1. 工单打标为什么要用微调而不是继续调大模型提示词1.1 业务场景与现有方案的痛点前一阵我负责某内部系统的工单管理模块业务方提了个需求每天上千条用户反馈工单需要按“产品故障”“账号安全”“退款诉求”“使用咨询”“其他”这五类自动打标方便后续流转到对应处理组。一开始我图省事直接用通用大模型API写了一段很长的系统提示词把分类规则、边界场景、返回格式全部塞进去实测下来准确率大约在82%到86%之间波动。听起来还行但业务方反馈说问题不在“它分对了没有”而在“它不敢确定的时候会乱给结果”。比如一条工单说“我在下单页面卡住了页面一直转圈”模型有时候给“产品故障”有时候给“使用咨询”同样的内容换个说法结果就变了。另一个更大的痛点是响应格式不稳定我要求模型返回JSON它偶尔夹杂一句解释导致下游解析模块直接抛异常还要额外挂一层正则清洗。这其实就是提示词方案的天花板通用模型的知识面广但它不理解你这个业务系统里“产品故障”和“使用咨询”的实际划分口径是什么。你说得再详细它也只是在“猜”而不是在“按规则判断”。1.2 微调与提示词工程的边界再说直白一点提示词工程是在“约束”模型的行为微调是在“改写”模型对特定输入的理解方式。工单打标这个场景有个特点类别是固定的判断依据是明确的样本特征是高度重复的——用户来来回回就是那些说法。这类任务恰恰是大模型微调最容易见效的类型因为它是典型的“低熵任务”答案空间很小输入模式很固定。我当时的判断是与其继续和提示词较劲不如直接用百炼API做一次轻量微调把已有的历史工单和人工标注结果喂给模型让它“见过”这个系统里的真实表达方式。结果也确实验证了这个判断微调后同一测试集上的准确率从82%冲到了94%左右最关键的是——格式稳定性明显好了再也没有出现过返回结果里夹带解释文字的情况。关于微调和提示词工程的选择我个人的经验是这样对比维度提示词工程微调冷启动速度快当天能跑通慢要准备数据、训练、评估准确率上限依赖模型本身能力一般在85%上下可以贴近人工标注水平95%可期格式可控性不稳定需要额外清洗稳定训练时就绑定了输出格式维护成本改提示词即可改数据重新训练适用场景类别少、边界模糊、样本不足类别固定、样本充足、表达模式集中如果你的工单量每天不到几十条直接调API加提示词够用了。一旦过了某个量级比如每天几百上千条微调投入的这点时间成本是绝对值回来的——因为它的收益不只是准确率还有下游流程的稳定性。2. 数据准备决定微调效果上限的关键步骤2.1 工单打标训练数据的格式要求准备数据这一步我的体会是你花多少时间在数据上模型就回报你多少能力。微调模型本质上是在“模仿”你给的数据中的输入输出映射关系数据质量直接决定了效果上限训练过程本身只是把这个映射关系学出来而已。百炼微调API支持的数据格式中我用的是一行一条JSON的JSONL格式每条包含两部分prompt是输入工单内容response是期望的标签输出。我当时的格式是这样定的{prompt: 用户反馈下单页面一直转圈无法完成支付请问怎么处理, response: 产品故障}这里有一个很关键的细节prompt里我没有让模型“分析”或者“分类”而是直接用真实工单原文风格。因为微调的本质是让模型记住“看到这种输入就输出这种结果”你不需要给它布置任务你只需要给它看例子。任务指令式的写法反而会引入干扰。关于工单文本的预处理我踩过一次坑直接把客服系统里导出的原始工单文本丢进去结果里面带着“【用户您好】”“——来自APP客户端”这类模板后缀。模型确实会把这些东西学到表现为上线后遇到干净的工单文本反而偶尔犹豫因为它训练时见过的样本都带尾巴没见过不带尾巴的长什么样。所以数据清洗时一定要把系统自动拼接的模板字符、无意义的符号串、重复的标点都处理掉让样本尽量“干净且接近真实使用场景”。2.2 数据清洗、标注规范与正负样本平衡数据量上我最初只准备了300条训出来的模型在验证集上只有89%的准确率后来扩充到1200条准确率才上到94%。不是说300条不行而是在工单打标这种“类别间表达高度相似”的任务上样本量直接决定了模型能不能学到区分边界。比如“退款诉求”和“产品故障”的分界很多时候就是一句话的问题样本少了模型就学不到这些微妙差异。这里我把数据准备的步骤拆一下方便你照做从历史工单里导出最近3到6个月的数据按时间倒序抽取避免样本分布与当下业务脱节。将用户原始描述规范化去HTML标签、去客服回复部分、去系统拼接模板只保留用户第一次提交的问题描述。对照人工标注结果把每个类别下的样本各抽一部分检查确认标签与内容一致。对类别做一次分布统计防止某类样本占比过高比如“使用咨询”占了一半如果分布不均优先补充小类样本。样本平衡这件事值得多说一句。我当时统计后发现“使用咨询”占了43%“账号安全”只有8%训练结果里模型对“账号安全”的判断明显偏保守大量误判成“使用咨询”。后来我针对性补充了账号安全类的样本把它拉到总样本的20%以上“账号安全”的召回率才从71%涨到90%。不要追求绝对均衡但最小类占比最好不要低于15%到20%。2.3 数据集上传与检验数据准备好之后下一步就是传到百炼平台。我用的方式是调用OpenAI兼容的数据上传接口把文件传上去并获取文件ID这个文件ID在创建微调任务时要用。数据校验这一步很容易被忽略。百炼平台支持上传时自动校验格式但只会提示“第X行格式错误”不会告诉你哪一行。我传了一次全错的样本错误提示在第312行肉眼根本找不到。后来我写了一个小脚本先做本地校验速度就快了脚本逻辑很简单import json def validate_finetune_data(file_path, required_keys(prompt, response)): with open(file_path, r, encodingutf-8) as f: lines f.readlines() for idx, line in enumerate(lines, 1): line line.strip() if not line: continue try: obj json.loads(line) except json.JSONDecodeError as e: print(f第{idx}行JSON解析失败: {e}) continue for key in required_keys: if key not in obj: print(f第{idx}行缺少字段: {key}) if isinstance(obj.get(response), str) and len(obj[response]) 100: print(f第{idx}行response字段长度异常请检查是否混入了长文本) print(校验完成) validate_finetune_data(work_order_v1.jsonl)有个细节值得注意response字段建议直接写标签字符串不要写“这是一个产品故障工单”这种解释了理由的完整句。理由是输出越短越稳定训练目标越明确。如果你将来想同时让模型输出标签和置信度或简短依据那再单独设计格式。初期就只做纯标签输出先把管线跑通再考虑扩展。3. 百炼API微调核心流程从创建任务到获取专属模型3.1 环境准备与API密钥开始调API之前需要准备几样东西一个已开通百炼服务的账号、API密钥我这里统一叫API_KEY具体获取入口控制台里找、以及Python环境中的openai SDK。百炼的API兼容OpenAI接口所以直接装openai包就能用不需要额外SDK这一点很省事。我当时的安装和初始化是这样pip install openaifrom openai import OpenAI client OpenAI( api_key你的API_KEY, base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1 )base_url指向百炼的兼容模式地址这个不能写错写错会直接报连接错误我一开始就犯过这个低级错误查了半天网络配置才发现是URL的问题。关于API密钥的权限我有个建议如果你是在公司环境创建一个专用的子账号或者RAM用户来调API只授予需要的模型权限和文件上传权限不要用主账号的密钥。原因很简单主账号密钥权限太大万一落到代码仓库里泄漏了影响面很大。这个成本是很低的却能避免很多不必要的麻烦。3.2 微调任务创建与参数选择创建微调任务的API调用方式如下client.fine_tuning.jobs.create( modelqwen-turbo-latest, training_filefile-xxxxxx, hyperparameters{ n_epochs: 3, batch_size: 8, learning_rate_multiplier: 0.3 } )这里每个参数我都解释一下方便你按需调整model基础模型选择。我试过在qwen-turbo、qwen-plus、qwen-max三个模型上微调同样数据结论是如果工单内容里面存在大量前后文较强的对话比如多轮求助qwen-max的收益更明显如果是单条短文本打标qwen-turbo足够效果差不多但训练成本和推理成本都低一截。工单打标这种短文本分类场景qwen-turbo基本是性价比最优解。n_epochs训练轮数。我之前默认设了3轮但数据量在800条以下时3轮容易过拟合后来600条样本用2轮1200条以上用3轮效果比较稳定。不要一次性设很高后面我会专门讲怎么判断该加轮还是该减轮。batch_size百炼支持根据数据量自动选择batch size我一般不动它让它自动决定省事。learning_rate_multiplier学习率缩放系数。默认值是1但微调时建议设低一点比如0.3到0.5防止模型在少量数据上“冲过头”忘了原有知识。我做工单打标时固定用的0.3效果没有明显差别如果你后续做多轮对话微调这个参数要更谨慎一些。3.3 训练监控与结果获取创建任务之后训练是在服务端异步执行的需要轮询任务状态。用下面的代码获取状态和结果# 获取任务列表 jobs client.fine_tuning.jobs.list() # 获取指定任务详情 job client.fine_tuning.jobs.retrieve(ft-job-xxxxxx) print(job.status) print(job.fine_tuned_model)训练一般需要几十分钟到几个小时不等取决于数据量和基础模型规格。我当时的1200条数据在qwen-turbo上跑了大约40分钟状态从pending到running再到succeeded。训练完成后返回的fine_tuned_model字段就是你的专属模型名称后续调用就和调普通模型一样只需在model参数里填这个专属模型名。有一个我觉得很值得做的操作训练过程中把任务状态变化打成日志这样你可以大概估算一下时间同时判断任务是不是卡住了。正常情况任务状态会正常流转如果长时间停留在pending多半是平台资源排队等一会就好如果直接变成failed通常去查一下是不是数据文件格式有问题。4. 微调效果评估与模型迭代Loss不是唯一标准4.1 评估指标与验证集设计模型训练完成后不能直接上线你需要先做一次“考试”。考试的核心是准备一份独立的验证集——所谓“独立”指的是这些样本绝对不能出现在训练集里。我当时从全部数据中按8:2拆分训练集1280条验证集320条验证集里的工单都是模型从未见过的。评估环节除了看准确率还要做一张“混淆矩阵”看清楚模型到底在哪些类别之间摇摆。5类工单的混淆矩阵长这样我当时的测试结果简化版预测\实际产品故障账号安全退款诉求使用咨询其他产品故障611230账号安全154020退款诉求405810使用咨询231601其他00018从这组数据能看出来容易混淆的主要是“产品故障”和“退款诉求”尤其是“退款诉求”出现了5次被误判成“产品故障”。原因也简单——用户描述退款问题时常常会提到“页面出错”“系统提示失败”这类故障类词汇模型容易被误导。发现这个规律后我去翻了那几条误判样本发现它们的表达模式高度相似都是用户说“支付页面报错申请退款”两种表述各占一半。这种就不应该死磕模型而应该回到数据层面去补充“同时包含报错和退款关键词”的样本。4.2 过拟合、欠拟合与bad case分析很多人在微调的时候只盯着训练集上的Loss看但我建议更多关注训练集和验证集之间的差距。我当时第一版模型用3轮训练在训练集上准确率99%在验证集上却只有90%这是一个典型的过拟合信号——模型把训练样本“背下来”了但没有真正学到分类规则。判断方法很简单训练集准确率远高于验证集就是过拟合。这时候应该减少训练轮数或者考虑增加数据量。我后来把轮数降到2数据量增加后训练集和验证集准确率的差距缩小到3%以内这样就比较健康了。如果训练集和验证集准确率都很低则是欠拟合优先检查数据量是否太少、标签是否错误、类别是否失衡。bad case分析是我每次微调之后必做的一步。我会把验证集里所有预测错误的样本单独打印出来一条条看。不要只统计一个准确率就完事那些错误样本会直接告诉你模型到底哪里没学会以及下一步数据应该往哪个方向补。如果你不做这步再调十次参数也不会有本质提升。4.3 迭代策略微调的迭代不是一个“一次定终身”的过程我的习惯是每次迭代只改一个变量避免多个因素同时变化导致搞不清哪个起了作用。比如第一次增加样本量第二次调整epoch第三次再修改数据预处理方式。每次改动后记录下验证集准确率、混淆矩阵的变化跑个两三轮之后你基本就能找到一个相对稳定的配置组合。迭代过程中还要特别留意一个边界你的训练数据本身可能带着业务规则的“时效性”。比如某个月平台刚好推了一波促销活动导致“退款诉求”的工单表达风格与平时明显不同这种情况下训练数据里这部分样本占比过高模型上线一个月后准确率会自然下滑。所以数据准备的时间范围最好拉长一些不要只取最近一两周的数据不然你把“临时热点”当成“长期规律”教给模型了。5. 上线打标服务的完整链路与踩坑记录5.1 服务封装与降级兜底模型微调好、验证集跑完、准确率达标不等于事情结束。工单打标是一个会被其他系统高频调用的功能上线前必须做服务封装否则微调效果再好也白搭。我当时的封装思路很简单用一个Python服务包一层接口对外提供HTTP接口内部再挂几个关键点——关键词预判规则、模型调用、结果解析、异常兜底。大概流程是这样的请求进来先经过一个极轻量的关键词规则层。比如工单里包含“退款”和“钱”两个词先临时标记为“疑似退款诉求”但这个标记不会直接作为结果返回只做记录。调用微调模型获取标签。如果模型调用超时或失败直接走降级逻辑返回关键词规则层的结果并标记为“规则兜底”。如果模型成功返回但内容不在预定义的五类标签内也走规则兜底防止脏数据打穿下游。这一步极其重要。我在内测阶段做过一个测试故意传空字符串、只传标点符号、传超长文本1万字结果模型对空字符串给出了一个莫名其妙的标签对超长文本则直接超时报错。要不是做了兜底这两类情况上线后就是两个线上事故。5.2 我踩过的三个真实坑第一个坑是召回率与分类分布的矛盾。我最初直接用全部工单数据训练模型在占比最高的“使用咨询”上表现很好准确率很高但整体准确率并不理想。后来才发现是数据不均衡导致模型倾向于输出高频类别。解决办法就是前面说的补样本把小类别样本数量拉上来效果一下就对了。第二个坑是温度参数。微调模型调用时我一开始没有设置temperature默认值是1结果模型的输出偶尔不稳定同一个工单重复调用两次会得到不同标签。后来我把temperature调到0输出完全稳定了。工单打标是确定性任务推理时需要保持低温甚至零温度这个细节一定要记得。第三个坑是超长工单。用户的工单有时候不是一条消息而是包含多轮追问的完整会话长度可能超过模型的处理上限。我一开始直接把整段对话截断后丢给模型结果经常把“中间某条消息”里的关键信息截掉了。后来我改成只取用户的第一条完整描述辅助取最后一条消息的片段测试下来准确率反而比硬截断高。你要做的是根据自己工单的实际形态来决定截断策略而不是无脑截前N个字符。5.3 后续优化方向如果你已经跑通了上面的流程有几个方向可以继续深入。首先是把打标结果沉淀回训练数据每周把新增的、模型预测正确且用户后来按这个标签成功流转的工单整理出来人工抽检后追加进训练集让模型持续演进。其次是增加一个“置信度”信号虽然微调模型本身不直接输出置信度但你可以借助一些技巧——比如对同一个工单用略高的temperature采样多次看标签的稳定性如果多次结果不一致就把这条工单标记为“需人工复核”。这个做法比单纯依赖一次调用的输出要可靠许多。还有一点是我的个人建议在跑微调之前先花时间把你的数据管线、评估流程、服务封装都搭好再回头去做模型微调。因为这些步骤虽然看起来和“模型效果”无关但真正影响你能否把一件事做成而不是只做出来。一个能在半小时内完成“新数据进来-训练-评估-上线”的流程和一次性的微调脚本两者在长期价值上完全不是一个量级。就说到这里最后分享一个我个人的小习惯每次微调完我都会把这次用的数据、参数、验证集结果存在一个备注里下次再调的时候先看一眼上次做了什么。坚持几轮之后你会发现模型效果提升的主要来源往往不是参数调优而是你对数据和问题本身的理解程度。