做智能客服一线这么多年,最头疼的从来不是模型准确率不够,而是用户情绪识别不准。之前我们上线过一套FAQ机器人,意图识别准确率都做到85%了,可满意度一直上不去。后来翻录音记录才发现,用户早就冒火了,机器人还在不紧不慢地背菜单。那次之后,团队就重点研究AI Agent的情感计算,核心就一句话:让机器先读懂情绪,再组织语言。这篇文章把我们从立项到上线的完整路径写出来,包括技术选型、数据标注、模型训练、系统对接,还有后面踩的一堆坑,给做智能客服、对话式AI、体验优化的同行们一些参考。
1. 项目概述与整体设计思路
1.1 情感计算解决的“冷冰冰”问题
传统客服机器人的主线是意图识别加FAQ检索,用户说“我手机死机了”,系统识别出“手机故障咨询”,然后返回一篇标准解决方案。这套逻辑在用户情绪平稳时没问题,可一旦用户处于愤怒、焦虑、失望的状态,单纯按意图走就会显得特别冷血。用户说“你们这破路由器我又重启了还是不行,垃圾产品”,传统系统很可能直接自动回复“请您尝试重启路由器”,这等于火上浇油。
情感计算要解决的就是这个“冷冰冰”问题。它不是在现有人话术后面硬加一句“我理解您的心情”,而是把情绪感知作为整个对话决策的输入信号。系统先判断题目的语义内容,同时判断题目的情绪色彩和强度,两者共同决定机器怎么回应。比如是优先道歉、优先解决问题,还是直接转人工。这样用户感受到的不再是“答非所问”,而是“这个机器人好像真的知道我着急”。
我们当时做了一个很直观的对比实验:同一批客服工单,用传统意图机器人和加入了情感计算的新系统分别回复,请用户打分。在情绪较为激烈的会话里,新系统的满意度比旧系统高出将近两成。这说明在客户服务该场景下,情感计算不是什么锦上添花,而是直接决定服务体验的关键能力。
1.2 从意图识别到“意图+情绪”双通道建模
整体设计上,我们没有把情感计算做成一个游离的插件,而是把它和意图识别放在同一个决策框架下。核心是双通道结构:一条通道做意图识别,一条通道做情绪识别,最后把两个结果拼在一起,交给下游的策略模块。比如用户说“你们这破路由器我又重启了,还是不行”,意图通道给出“网络故障咨询”,情绪通道给出“愤怒:0.85”,策略模块就会决定:不优先推说明书,而是先道歉、共情,再用最快的路径给排查步骤。语义如果对齐还不够,情绪也要对齐。
这个双通道结构的好处是解耦。意图模块和情绪模块可以独立迭代,比如情绪模块有新数据需要更新,不会影响意图识别。我们实践下来,如果直接在一个模型里同时输出意图和情绪,会出现梯度互相干扰的情况,尤其是情绪标签样本不均衡的时候,意图准确率会被拉低。分开做,或者用多任务学习但控制好loss权重,相对更稳。
用户画像也会参与进来。同样一句“怎么又出问题了”,老用户和新用户的容忍度不同,历史投诉多的用户需要更谨慎地对待。所以我们在策略层会叠加一个用户维度:高频投诉用户、高价值用户、首次咨询用户,分别有不同的情绪升级阈值。这个不是技术模型的事,而是业务规则的事,但放在情感计算框架里一起考虑,效果才完整。
1.3 总体架构分层
整个系统我们分成四层:接入层、感知层、认知层、策略层。
接入层负责和客服平台对接。不管是网页在线客服、微信小程序还是电话客服,统一走一个消息网关,转成内部标准格式。感知层做的是原始信号处理,比如文本进来先做清洗和归一化,语音进来先转文本、提取声学特征。认知层是核心,包括意图识别、情绪识别、多轮上下文管理、用户画像查询。策略层则负责决策和生成,比如从话术库挑选模板、判断是否转人工、生成最终回复。
四层之间用消息队列解耦,平时是异步处理,但面向用户的消息走实时通道,必须保证端到端延迟在可接受范围内。我们最初设计时犯过一个错,把所有逻辑放在一个服务里,结果一次模型推理超时就把整个会话阻塞了。后面改成独立部署,情绪识别单独一个微服务,意图识别单独一个微服务,策略引擎再通过RPC调用它们,反而更灵活。
2. 关键技术选型与实现原理
2.1 文本情感识别的模型选型
文本情感识别是整个系统里最先要定的模块。我们最早试过情感词典打分,搞了一套正向词、负向词、程度副词,规则拼了半天,覆盖率低,而且完全处理不了反讽和口语变化。后来直接上预训练语言模型,这里有个经验可以分享:不要一上来就追最新的大模型,先在通用中文预训练模型上加客服语料微调,成本和效果都更可控。
具体做法是用一个12层的中文预训练模型做基底,在积累的客服对话数据上继续训练,做六分类:愤怒、焦虑、失望、满意、中性、其他。序列长度截断到128,学习率设在2e-5,batch size 32,epoch跑3到5轮。拿验证集看F1值,选最好的checkpoint。这里有个关键点:分类标签不是越多越好,我们一开始定了十几个情绪标签,结果很多标签边界模糊,标注一致性极差,后来精简到六个,模型才真正稳住。
分类模型输出的不只是一个标签,我们还额外加了一个情绪强度打分,范围0到1。这个强度分是单独一个全连接头从隐藏层特征回归出来的,和分类头共享同一个BERT。之所以要强度分,是因为“有点烦”和“暴怒”虽然都可能被分到愤怒类,但它们应该触发完全不同的服务策略。只有分类标签的话,策略层很难精细调控。
2.2 情绪分值阈值与标签体系
阈值设计是个非常实际的问题。我们给每个情绪类别设置了两档阈值:比如“愤怒”置信度大于等于0.7,触发安抚话术;“愤怒”置信度大于等于0.9,触发优先转人工。这个阈值不是拍脑袋定的,而是拿历史已完结的工单做样本,请业务团队把需要人工介入的会话标出来,然后画ROC曲线取约登指数最大值对应的阈值。
最初我们的阈值取的是0.5,结果误报率高得吓人,大量其实只是语气不太好的中性对话被标成了愤怒,导致安抚话术泛滥,反而让用户觉得奇怪。后来我们把训练数据和线上分布对齐,重新标了8000条会话,经过两轮评估后,不同情绪的阈值分别落在0.65到0.85之间。这个数字只能作为参考,因为不同业务线、不同用户群体分布差异很大,最好的办法还是自己积累数据做校准。
情绪强度分也做了一层平滑处理。单轮的强度波动太剧烈,比如用户打错字、网络卡顿导致对话暂停,强度会突然跳变。我们会对最近三轮的情绪强度做指数移动平均,权重按照时间衰减,当前轮次占0.6,上一轮占0.3,再往前一轮占0.1,这样既能反映趋势变化,又不会因为一句突发的抱怨就立刻触发升级。
2.3 多轮对话中的情绪累积与衰减
单轮情绪识别只能反映“此刻”的状态,但客户服务里的愤怒往往是累积出来的。用户可能一开始很平静,连续两次没得到有效帮助后逐渐暴躁,到最后破口大骂。如果只看每句话的瞬时情绪,系统会漏掉这种趋势。所以我们做了一个多轮情绪轨迹模块。
设计思路是维护一个会话级的情感状态,每进来一轮新的消息,就更新这个状态。公式上可以简化为当前轮情感得分和上轮历史状态按比例混合。我们用了滑动窗口加权:保留最近五轮的识别结果,当前轮权重最高,越早的轮次权重越低。同时引入一个“触发事件检测”逻辑,比如客服回复超时、错误传递信息、重复询问同一件事,会直接给情绪状态加一个惩罚增量,因为这类事件本身就会激怒用户。
这里有个容易被忽略的细节:用户情绪峰值往往不是第一句,而是中间某句话。比如用户突然说“算了不问了,我去投诉”,这时即便是平静语气,也一定要当最高优先级处理。所以我们额外保存会话内历史情绪最高分,一旦超过阈值,后续策略就保持高度谨慎,而不是等新情绪识别出来再反应。
2.4 语音情绪特征与多模态融合
电话客服场景下,情感计算还需要处理语音。语音里的情绪信息非常丰富,语速变快、音量增大、停顿变多,这些都是典型信号。我们参考了一些公开声学特征方案,把基频、能量、语速、停顿占比和梅尔频谱等特征拼起来,用一个小型CNN或者全连接网络做回归。这里不追求特别复杂的模型,因为实际线上环境噪声很多,声学特征本身不稳定。
多模态融合上,我们采用的是“文本为主、语音为辅”的加权策略。语音先转成文本,同时抽取声学特征,两个模态分别预测情绪标签和强度,最后加权融合。文本权重我们设定为0.6,语音权重0.4。原因是客服场景里如果语音转文本准确率高时,语义信息的可解释性和稳定性比声学特征好得多。实测下来,纯语音识别情绪在怒音、哭腔等场景有一定优势,但误报也多,加权融合后整体AUC最高。
不过多模态融合也带来一个额外成本:每次对话都需要并行调用两个模型,时延压力会变大。我们优化时用了两个措施,一是把语音特征抽取放到接入层缓存,避免重复算;二是把两个模型的推理放到同一个GPU服务上做batch推理,整体时延能控制在200毫秒以内。
3. 数据准备与模型训练实操
3.1 数据标注规范与质量控制
情感计算的数据标注比意图标注难很多。意图标注看的是用户“想做什么”,相对客观;情绪标注看的是用户“是什么状态”,非常主观。我们一开始请了三个人同时标同一批数据,三个人给的标签经常不一致,尤其是“焦虑”和“失望”这两个类别,边界很模糊。
后面我们定了一套标注规范,先把情绪分为正向、负向、中性三大类,负向再细分为愤怒、焦虑、失望。标注时不仅看这条消息本身,还要看前两轮上下文。比如用户说“哦”,单独看是中性,但如果前文客服一直没解决问题,这个“哦”其实是失望。另外,我们要求标注员额外标注一个“触发原因”,比如“客服延迟回复”“重复提问”“推诿踢皮球”,这些信息后面用于事件检测模块。
质量把控上,我们用Kappa一致性系数做指标,要求标注一致性达到0.7以上。达不到的样本会组织复标,并由一位有经验的人仲裁。这个过程非常耗人力,但必须做,因为情绪模型的性能上限是由标注质量决定的。我们总共清洗出来约5万条高质量会话,其中负面情绪占比约30%,覆盖了十余个客服业务场景。
3.2 数据增强与类别不均衡处理
客服数据里中性样本占了绝大多数,真正愤怒、焦虑的样本很稀缺。类别不均衡会导致模型把几乎所有句子都预测成中性,特别是情绪强度低的时候。我们没有简单做欠采样,因为欠采样会丢掉大量上下文信息,特别是多轮对话里前面几轮可能对判断后面的情绪很有用。
我们主要做了三件事。第一是困难样本挖掘:先拿初步模型跑一遍未标注数据,把预测概率在0.4到0.6之间、置信度低的样本挑出来人工标注,这批样本对模型最有价值。第二是回译:将中文客服句子翻译成英文再翻译回来,生成语义一致但表达不同的训练数据,尤其适合扩充愤怒和焦虑类。第三是同音词和口语化表达替换,比如“无语了”“麻了”“裂开”这类网络热词,我们建了自己的客服表情词典,定期更新进训练语料。
损失函数方面,我们把愤怒和焦虑两个类别的样本权重调高到1.5,让模型在训练时更关注这些少数但关键的类别。实验对比发现,调权重比简单改变阈值更有效,因为直接调阈值只影响推理阶段,而损失权重能让模型本身学习到更细的区分特征。
3.3 模型训练与调参细节
训练阶段我们采用五折交叉验证,每一折都单独调学习率和batch size。由于团队有GPU资源限制,我们先把序列长度限制在128,超出部分截断。但注意,有些用户的“前缀铺垫”其实很长,影响情绪判断,所以会在接入层做预处理,把重复的客套话和无意义语气词删掉,再送入模型。
多任务学习我们试过,但有一次踩了坑:意图和情绪同时训练时,因为情绪分类的样本数量远小于意图分类,意图loss把情绪loss压得死死的。后来我们把两个任务放在同一个预训练模型上,共享前几层参数,但最后两个分类头各自接一个梯度缩放系数,情绪头的梯度乘以1.2,才算平衡下来。
训练完成后,我们导出了ONNX格式做加速推理。这里有一个小细节:在导出前要关掉dropout和layernorm的训练模式,否则一致性会有问题。上线前我们还会做模拟对话压力测试,保证每次调用稳定在50毫秒到150毫秒之间。下面给一段基于Transformers库的训练核心代码,不是完整工程,但思路可以直接用:
from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer = AutoTokenizer.from_pretrained("chinese-roberta-wwm-ext") model = AutoModelForSequenceClassification.from_pretrained( "chinese-roberta-wwm-ext", num_labels=6 ) inputs = tokenizer( ["你们这破路由器我又重启了还是不行"], max_length=128, truncation=True, padding=True, return_tensors="pt" ) outputs = model(**inputs) emotion_logits = outputs.logits emotion_probs = torch.nn.functional.softmax(emotion_logits, dim=-1) print(emotion_probs)实际项目中,我们会在情绪头后面再接一个回归头用于输出强度分,训练时总loss是分类交叉熵和强度回归MSE的加权和。这个细节很重要,不要漏掉。
4. 系统集成与客户服务流程落地
4.1 与现有客服平台对接
模型训完只是第一步,真正难的是把情感计算流畅地嵌入现有客服系统。我们当时的客服平台用标准Webhook收发消息,所以定制了一层消息网关做格式转换。网关在收到用户消息后,立刻转发到情绪识别服务,并把识别结果存到会话缓存里。策略引擎在生成回复前,需要先从缓存取情绪状态,再决定话术。
接口我们设计成返回一个标准结构,方便下游使用:
{ "session_id": "S123456", "message": "你们这破路由器我又重启了还是不行", "intent": "network_troubleshoot", "emotion": { "type": "anger", "confidence": 0.82, "intensity": 0.9, "trend": "rising" }, "suggested_strategy": "empathy_and_apology", "priority": "urgent" }这里有个值得注意的点:结构化输出里的confidence和intensity是两个不同概念。confidence是模型对该标签判断的置信度,intensity是情绪本身的强弱。一个模型可能非常确信用户“有点烦躁”(高置信度、低强度),也可能不太确定用户“是不是已经暴怒”(低置信度、高强度)。策略层对这两个维度的处理方式完全不同,一定不能混。
时延方面,我们要求端到端响应不超过500毫秒,否则用户可以明显感觉到卡顿。为了做到这一点,所有模型推理都在一个独立的推理集群上执行,用GPU批量处理,平时稳定在100毫秒左右。情绪识别结果还会缓存10分钟,这样如果用户连发几条消息,不需要重复推理,直接复用上一轮状态。
4.2 动态话术生成与策略路由
有了情绪识别结果,接下来最关键的是怎么决定“说什么”。我们维护了一个策略路由表,根据情绪类型和强度,路由到不同话术策略。比如“愤怒+高强度”,策略是“共情+快速道歉+转人工预备”;“焦虑+中强度”,策略是“安抚+明确给出下一步处理时间”;“失望+低强度”,策略是“解释原因+提供补偿方案”。
共情话术不能做得太死板。我们最早的做法是模板拼接,比如“很抱歉给您带来不好的体验,我们会尽快处理。”这句话单独看没问题,但如果上一轮已经说过一遍,这轮再说,用户会明显觉得机器在敷衍。所以我们为每个策略准备了至少十套不同表达,并且根据对话轮次、用户历史偏好做替换。比如老用户就少一点客套话,直接上报处理;新用户需要更详细的解释。
我们还加了一个“反机械共情”机制:模型会统计最近三轮里相似共情话术出现的次数,如果超过两次,就强制切换到行动导向的表达,比如“我现在立刻帮您查一下,请稍等”。实测这个机制对抑制用户二次情绪升级非常有效。
4.3 人工升级与安全兜底
不是所有情形都适合让机器人继续聊。我们的升级规则是:当情绪强度达到0.9且置信度大于0.7,直接转人工;当用户明确表达“找人工”“转人工”“投诉”,不管情绪分多少,立即转人工;当多轮情绪趋势持续上升,即使当前分还没到阈值,也触发人工预备队列,让人工客服能看到这个会话的实时情绪曲线。
转人工并不是直接把用户丢过去,而是需要在转接信息里携带完整的情绪轨迹。人工客服在接起会话前,已经能看到用户从第一轮到现在情绪是怎么演变的、已经做过哪些安抚、问题有没有解决。我们内部叫“情感接力”,实测接起后人工客服的平均处理时长缩短了15%,因为不用再从头问一遍情况,也更容易知道用户为什么生气。
这里有一条重要的安全兜底经验:情绪识别模块要设置降级开关。如果模型服务异常或者返回结果超时,系统必须自动降级为标准意图模式,绝不阻塞用户对话。宁可让机器人显得笨一点,也不能让用户因为技术故障连话都说不了。
4.4 A/B测试与效果评估
情感计算上线前,我们做了三周A/B测试。实验组接入情感计算,对照组保留原来的意图机器人,所有会话流量随机分配。主要看四个指标:用户满意度CSAT、首解率、情绪转变率、转人工率。情绪转变率是指会话结束时用户的情绪类别相对第一轮是否从负向变为中性或正向。
测下来实验组CSAT提升了8个百分点,情绪转变率从40%提升到55%,转人工率下降了12%。比较有意思的是首解率没有显著变化,说明情感计算没有直接提升业务解决率,但它显著改善了服务过程的体验,用户在同等解决率下更愿意给好评,也更少二次投诉。
当然,A/B测试里也出现过对照组和实验组相互干扰的情况,比如一个用户同时开了两个会话,分别进了不同组,情绪会被另一个会话影响。所以我们在测试时做了用户粒度分流,确保同一个用户始终在一个组里,数据才干净。
5. 踩坑实录与问题排查技巧
5.1 情绪误判的典型案例
我们先看一个非常经典的误判:用户说“你家产品真行!”这明显是反讽,我们第一版模型直接识别成了“满意”,然后给用户发了一个“感谢您的支持”的答复,简直灾难。后来排查发现训练数据里反讽样本太少,模型根本没有见过这种表达。解决方法是专门构造了一批反讽语料,包括“真行”“真棒”“太厉害了”“你们最棒了”等常见说法,并标注为“负向情绪”。还引入了一个小的讽刺检测模块,如果文本里出现明确负向事件但搭配了正向形容词,就提高负向情绪概率。
类似的坑还有用户表达“呵呵”“您说的都对”这类敷衍话。它们从字面看是中性偏正的,但实际是强烈不满。我们也是靠扩充训练数据才最终把这些表达接住。
5.2 方言、口语和网络热词干扰
客服场景里用户不会一本正经说书面语,方言、缩略语、网络热词到处都是。我们遇到过用户说“我裂开了”,模型识别成中性,因为没有见过这个词。还有“栓Q”“YYDS”“无语子”等等,随着网络语境变化层出不穷。单一静态模型根本跟不上。
解决方案分成两步。第一步,在接入层维护一个动态同义词和热词映射表,比如“裂开”映射到“崩溃”,“栓Q”映射到“无语”,把新词先翻译成模型更容易理解的旧词。第二步,每个月用最新的线上语料做一次增量训练,不用全部重训,只往情感分类头里加新样本,大概每轮训练半小时,成本不高。热词表不能盲目替换,比如“牛”在不同语境下可以是夸赞也可以是反讽,要结合上下文判断。
5.3 多轮上下文丢失导致情绪漂移误会
最开始我们只对单轮消息做情绪判断,结果出现了一个典型问题:用户前面抱怨了很久,最后回了一个“嗯”,系统判断情绪为中性,于是自动开始推产品广告,用户彻底爆发。这个“嗯”如果只看字面,确实是中性,但在多轮场景里,它是用户压抑情绪下的敷衍回应,本质上还是负向。
我们后来把所有会话都保留最近五轮输入,并且计算情绪趋势。如果用户当前情绪分虽然不高,但之前三到五轮里出现过高强度负向情绪,那策略层会仍然保持谨慎,不主动切换营销话题。趋势上升、下降、持平三种状态分别对应不同的应对方式。这个改动上线后,因为情绪漂移导致的投诉明显减少。
5.4 过度共情导致问题解决效率下降
共情本身是好东西,但容易矫枉过正。有一版我们为了让用户感觉更“懂我”,把安抚话术做了很多,结果机器人绕来绕去就是不给出具体解决方案。用户体验是“这机器人光会安慰人,完全不办实事”,反而更生气。
我们后来在策略层加了一条硬规则:连续两轮以内必须出现行动导向的表达。比如第一轮可以共情,第二轮就必须给出明确的处理步骤或时间预期。如果安抚话术在整段对话里的占比超过40%,系统会自动削减,强制加入问题排查或解决方案。这个调整非常实际,直接让CSAT和首解率同时上升。
5.5 数据隐私与合规提示
做情感计算必然要碰用户对话数据,这里特别提醒一下合规红线。我们设计系统时严格要求:情绪的识别和存储尽量在内存中完成,不落盘原始对话文本;模型训练用到的数据必须经过脱敏处理,去除手机号、姓名、地址等个人信息;跨部门使用情绪标签时,只能提供聚合统计结果,不允许提供个人级别的情绪轨道。
虽然这看起来只是工程细节,但在实际项目里非常重要。如果一开始不考虑数据授权和隐私保护,后续业务拓展会处处受限。提前把数据脱敏和访问审计做好,后面再想加新场景就会顺利很多。
6. 经验总结与扩展方向
最后分享几条我个人的体感。情感计算在客户服务里最大的价值不是让机器人“更高级”,而是让它“更像一个靠谱的服务人员”。一个靠谱的客服不会在用户发火时背产品说明书,也不会只道歉不给方案。情感计算就是让机器人学会这个分寸感。
如果现在要新起一个类似项目,我会建议先小范围跑通“情绪识别+策略路由”的闭环,不要一上来就做很复杂的多模态。先让文本情绪识别在客服场景里真正用起来,用一段时间收集线上反馈,再逐步扩展。另外,情绪标签体系一定要业务方参与制定,技术团队自己闭门造车定义的情绪,上线后经常和真实业务不匹配。
这个系统后续还可以向三个方向扩展。第一是做智能质检:不光看客服有没有说标准话术,还可以分析客服的情绪是否匹配用户情绪,比如用户已经很激动了,客服还是用一种平淡冷漠的语气,质检系统可以自动提醒。第二是做情感趋势预测:基于用户多个会话的情绪轨迹,预判哪些用户存在流失风险,提前做关怀。第三是向语音呼叫中心深化,把声学特征模型加进去之后,客服系统就能在用户真正发火之前提前感知,给人工坐席留出准备时间。
我自己做得越久越觉得,情感计算不是一个单纯的算法问题,而是一个服务设计和产品体验的问题。技术归根到底是为人服务的,能把用户那口气理顺,整个客服体系的价值才算真正发挥出来。