做了几年客服系统,我最大的感受是:工单量永远在涨,人永远不够用。尤其是投诉工单,客户情绪激烈、描述绕来绕去、缓急程度天差地别,坐席光是把工单归到正确的类别、分给对口的处理组,就得花掉大量时间。后来我经手了一个项目,专门做“投诉工单智能分类”,用NLP技术自动识别工单文本,再通过微服务架构把分类结果对接进现有系统。这套方案上线后,工单流转效率提升非常明显。这篇就把整个项目的思路、实现细节和踩坑记录完整整理出来,给正在做类似系统的朋友一个可参考的样本。
这个项目适合谁看?有两种人。一种是做客服、售后、政企热线类系统的工程师,你的系统里也有大量“非结构化文本”等着被处理;另一种是刚接触NLP工程化落地、想了解模型不是停在实验而是真正跑在服务里的同学。我会把技术原理、服务拆分、模型训练、接口联调、问题排查一条线讲完,保证你看完能直接照着动手。
1. 需求剖析:投诉工单分类到底在解什么题
1.1 一个容易被低估的“小问题”
很多没接触过客服系统的人,以为投诉工单分类就是“分几个文件夹”的事。实际上完全不是。我见过的一家服务商,日均工单量在8000到12000条之间,工单类别有二十多个,包括“账单疑问”“网络故障”“退订挽留”“充值未到账”“信号覆盖”等等。坐席接起电话或者看到在线投诉后,需要先读懂客户诉求,再判断这个工单应该归到哪一类、应该派给哪个组、紧急程度如何。
这个过程有三个痛点。第一,人工分类速度慢,平均一单要花30到60秒去判断,高峰期根本处理不过来;第二,不同坐席分类标准不一致,同一个“网速慢”问题,有人选“网络故障”,有人选“套餐限速”,有人选“设备问题”,导致数据统计完全失真;第三,分类不准确直接连累后续处理,工单派错组就要二次转派,客户等待时间变长,投诉升级的概率跟着上升。
这个项目要解决的,就是用NLP自动完成“读文本、判类别、定紧急度、指定处理组”这一串动作。目标很明确:把工单分类准确率做到90%以上,并且把分类耗时压到秒级以内。分类模型跑通之后,工单进入系统时自动打上类别标签,坐席只需要复核确认,工作量会大幅下降。
有一个细节特别值得注意:投诉工单跟一般新闻文本、商品评论完全不一样。新闻讲究通顺客观,商品评论虽然口语化但信息密度高,而投诉工单往往是“半截话”加上情绪词,比如“话费扣得莫名其妙,再这样我就投诉到底”“宽带晚上卡爆了,玩游戏一直掉线”。这里面既没有严格的语法结构,又掺杂大量场景黑话。所以做这个项目不能简单套用现成的文本分类范式,必须针对业务语料做专门处理。
1.2 业务约束和技术选型的基本盘
做技术选型之前,必须先摸清楚业务约束。我们当时的约束条件差不多是这样:
- 工单来源包括电话录音转写文本、在线客服聊天记录、邮件、APP投诉表单,文本长度差异极大,从几十字到几百字都有;
- 类别体系是公司多年沉淀下来的,二十八个一级类别,其中投诉量排名前十的类别占了总量的82%左右;
- 每类工单的“正确归属”没有绝对标准,必须参考历史处置结果,人工质检结果作为标注依据;
- 系统峰值流量集中在工作日上午10点到11点、下午3点到4点,对接口吞吐量有要求;
- 开发周期只有六周,团队四到五个人,没办法支撑大量的人力标注。
在这些约束下,基本盘就很清晰了。模型层面,预训练大模型效果虽好,但部署成本高、推理速度慢,而且后续要升级GPU资源,团队维护起来压力大;传统机器学习加词向量的方案速度快、部署轻,在标注数据有限的情况下也更能保证稳定性。架构层面,因为工单分类只是整个客服平台的一个功能模块,后续还要不断加新的处理能力,比如自动回复、风险预警、智能质检,所以从一开始就决定用微服务架构,把分类能力独立拆成一个服务。
技术选型上最终定了这套组合:文本表示用TF-IDF加Word2Vec,分类模型主体用FastText,域名服务用Python的Flask框架提供HTTP接口,接口网关和服务注册用Spring Cloud体系,数据库用MySQL加拉一份Redis做热点数据的缓存。整条链路在保证效果的同时,把技术复杂度和运维成本都控制在了合理范围。
1.3 单体和微服务的取舍
这里多说几句微服务的取舍。原本这个分类功能完全可以写成一个Python脚本,坐席手动跑,或者塞进现有Java单体应用里当作一个接口。但我当时特别坚持拆成独立微服务,原因是看得远了一步:投诉工单分类绝对不是只做一次就跑路的项目,它要持续迭代模型、持续加规则、持续扩展类别。如果塞在单体里,每一次模型更新都要重新部署整个应用,风险太大。
把分类逻辑独立成一个微服务之后,好处非常直观:模型迭代不影响主流程,某个服务出问题可以单独降级,其他服务还能继续工作。比如分类服务节点响应超时,网关层可以直接走“默认分类”策略,工单照常入库,只是类别标记为“待人工确认”,不会拖垮整个工单系统。如果做成单体,一个模型推理卡死,可能所有坐席都没法提交工单。
当然微服务也有代价,最明显的就是服务数量多了、链路长了,排查问题难度上升。这个项目里我严格控制了服务粒度,没有一刀切地把所有功能全部微服务化。数据接入、分类推理、路由分发、监管日志这几个核心环节拆出来了,像用户权限、基础数据管理这些仍然复用原有系统,没有重复造轮子。
2. 核心方案:NLP模型与微服务怎么配合
2.1 整体处理链路:从文本进到结果出的完整路径
整个系统的处理链路我画在脑子里是这样的:投诉文本先进入接入层,接入层做基础的清洗和格式转换,把不同来源的数据统一成标准JSON结构;然后数据投递到消息队列,分类服务从队列里消费数据,完成文本预处理、模型推理,产出类别和置信度;分类结果再被路由服务接收,依据类别映射表找到对应的处理组和区域负责人,生成分派指令;最后写回业务流程。
这套链路是异步的,不是同步的。也就是说,坐席提交投诉工单后,不会一直在页面上等着分类结果返回,而是工单先入库,状态为“待分类”,分类服务处理完之后回调更新状态。这个异步设计在当时踩了很多坑之后才稳定下来,后面我会专门讲。
链路里有个很容易被忽视的点:置信度阈值。模型不是每次都能给出高置信度的分类结果,有些投诉文本写得太简短,比如“我的问题你们到底处理不处理”这种,模型很难判断该归到哪一类。面对这种数据,盲目分类反而是错的。所以我在分类服务里设置了一个阈值逻辑:置信度大于0.85,自动归类并流转;置信度在0.6到0.85之间,标记为“建议分类”,坐席确认后生效;低于0.6,直接标记“待人工分类”,不进自动流转。这个设计短期内牺牲了一点自动化率,但长期看大大提升了整体准确率,也减少了坐席对系统的不信任。
2.2 服务拆分:五个微服务各管一段
服务拆分我遵循两个原则:一是“边界清晰,独立迭代”,二是“数据封闭,不许随意跨库访问”。基于这两个原则,整个系统拆成了五个服务。
接入服务(access-service)负责接收不同渠道的投诉文本,做清洗和归一化。清洗内容包括去除HTML标签、URL、无意义的符号,繁体转简体,错别字修正一部分,全角半角统一。这个服务是所有文本数据进入系统的唯一入口,写得很薄,只做基础处理,不做业务判断。
分类服务(classify-service)是整个系统的核心,包含NLP模型的加载、预处理、推理和结果后处理。服务启动时一次性加载模型到内存,之后每次推理只是走矩阵运算,单个文本平均耗时可以压到15毫秒级别。这个服务的接口设计成POST接口,传入文本,返回类别、置信度、紧急度等信息。
路由服务(dispatch-service)根据分类结果做工单派发。它维护一张映射表,每个一级类别对应一个处理组和一条兜底规则。比如“退订挽留”类工单,统一派给存量经营组,如果工单文本里识别到“老人”或“不识字”这类附加信息,则额外打上“特殊人群”标签,派发优先级上调。这个服务还负责跟工单系统的业务表做交互,更新工单状态。
监控服务(monitor-service)负责收集全链路的处理日志、耗时指标、异常信息,并把关键指标暴露给监控大盘。模型的效果不是上线就结束的,需要持续观察。这个服务每个月跑一次离线评测,统计准确率、召回率、误判率,生成报表。没有这个服务,我后面做持续优化就完全抓瞎。
管理服务(admin-service)处理类别字典、映射关系、阈值的动态配置。这个服务看起来不起眼,但特别实用。上线之后我们经常会遇到一个问题:某类投诉在某个时间段暴涨,比如运营商降费政策一出,咨询类工单翻倍,这时候需要临时调整分类策略的优先级。有了管理服务,直接改配置就能生效,不用重新发版。
2.3 模型选型对比:从TF-IDF到BERT,我为什么选了FastText
模型选型这块,我对比了好几个方案,这里把关键对比数据列出来,方便大家直观理解。
我一开始用TF-IDF加逻辑回归做了个基线版本,训练速度非常快,预处理也简单,准确率大概在79%左右。问题在于词序信息完全丢失,像“没收到退款”和“收到没退款”在TF-IDF向量空间里几乎没有区别,而这两类投诉的处理路径完全不同。
后来尝试了Word2Vec加TextCNN,效果有提升,准确率大概到85%,但训练过程变得复杂,而且模型文件体积增加了不少。再后来试了BERT,效果确实好,准确率能到93%以上,但问题是推理慢,单条文本平均50到80毫秒,在峰值流量下需要部署至少六个GPU节点才能扛住,这还不算GPU机器的采购成本和运维成本。
FastText是我最后定下来的方案。它在准确率上跟TextCNN基本持平,但训练速度极快,CPU上跑几十万条数据也就几分钟的事;推理速度更是快,单条文本10到15毫秒,一台普通CPU服务器就能扛住整个业务量;模型文件小,压缩后不到100MB,部署成本几乎可以忽略。虽然BER T的离线指标确实最高,但在我们的业务约束下,FastText是性价比最均衡的方案。
方案的“够用”和“最好”之间的平衡,是这个项目选型最有价值的经验。
3. 关键模块实现:从训练模型到服务联调
3.1 语料处理与标注规范
数据是NLP项目的命脉,这句话我做这个项目之后体会极深。我们拿到的原始数据大概是12万条已关闭工单,但里面脏数据非常多,链接、乱码、重复工单、录音转写错字等,先要做一轮彻底清洗再进入标注流程。
清洗规则我总结了这么几条:
- 去除所有URL、邮箱、电话号码,统一替换成特定占位符,比如电话号码替换为“PHONE”,避免模型把具体号码当作特征;
- 去除XML标签和转义字符,比如“&”这类脏数据;
- 重复工单去重,同一个用户在短时间内对同一事件的重复投诉,只保留最早一条;
- 录音转写文本里经常有“嗯”“啊”“就是说”这种口语填充词,做一轮过滤,但注意不能全删,因为有些填充词其实携带情绪信号;
- 繁体转简体,全角转半角,统一格式。
清洗之后还有标注问题。12万条数据如果全部人工标注,成本太高。我们采用了一种“人工标注加半监督扩充”的组合策略:先在原有工单里按历史处理结果提取出约3万条相对干净的数据,由业务专家团队人工审核修正;然后用这3万条数据训练一个初版模型;再用初版模型对剩下的9万条数据做预测,把置信度高于0.9的筛选出来,人工抽检一部分,抽检合格后合入训练集;之后重新训练模型,迭代两轮。最终我们拿到的高质量标注数据大概是7万条。
标注的时候有个特别重要的细节:类别定义一定要跟业务专家对齐,不能自己关起门来定。我们一开始就把“停机问题”和“缴费问题”定义成两个独立类别,结果业务专家说不对,缴费之后没开机的情况应该归给“停机问题”,因为处理流程是一样的,而系统类问题才单独归类。这个对齐过程花了整整两天,看起来费时间,实际上是帮模型省了最大的麻烦。
3.2 模型训练详解
数据处理完,模型训练反而是最顺的一步,因为FastText把训练流程封装得非常简单。我在训练脚本里记录了几个关键操作。
文本预处理阶段,除了清洗,我还加了一步分词。中文分词我用的jieba,同时加载了一个自定义词典,把业务黑话放进去。比如“流量不清零”“携号转网”“家庭副卡”“宽带提速包”这些词,如果不加入词典,会被拆成乱七八糟的词片,影响特征抽取。自定义词典必须是业务侧沉淀的真实用语,不是我想当然编的。我就出过一次糗,把“净网行动”加进词典,后来发现这个场景在工单里根本不存在,白白增加了噪声。
分词之后,每个文本转成一行,格式是“标签 词1 词2 词3”,然后直接喂给FastText训练。训练参数我调了好几版,最终比较稳的是:
- 词向量维度dim=100;
- 学习率lr=0.5;
- 轮次epoch=25;
- n-gram窗口大小取3,选了ngrams=3;
- 用层次softmax加速训练,loss设为hs;
- minCount设为2,过滤掉出现频次太低的生僻词。
训练过程大概两分钟就出来了,这速度比之前调TensorFlow的时候舒服太多。我加了一个验证集,比例是8比2,验证集准确率在89%到91%之间波动,不同类别的表现差异比较大。“退订挽留”“发票问题”这类特征明显的类别,F1值可以做到0.93以上;而“其他咨询”和“售后投诉”这种边界模糊的类别,F1值只有0.74左右,容易混淆。
这里我要特别提醒一个问题:不要只看整体准确率。我第一版模型整体准确率88%,看起来很漂亮,但拆开一看,“宽带故障”类目召回率只有61%,因为很多宽带问题的文本写得太口语化,比如“家里网出问题了上不了网”,模型没识别出来。后来我在训练集里专门补充了这类口语化描述,同时给“宽带故障”类别加了几十条人工构造的模板句子做数据增强,召回率才回到85%以上。
除了主线分类模型,我还额外训练了一个紧急度判断模型。这是很多人会忽略的一点。紧急度模型是二分类,判断工单是不是“高危投诉”,标准是文本里是否包含“投诉到底”“曝光”“消费者协会”“律师函”“媒体”等关键词汇,同时结合“时间敏感词”比如“三天了还没解决”来做判断。这个模型的精度不用很高,但能帮助客服团队优先处理真正紧急的问题,非常实用。
3.3 分类服务接口实现
模型训练好之后,就要让它跑起来变成一个真正的服务。我用的Flask框架,接口设计得尽可能简洁。服务启动时加载模型,预热一次推理,然后进入监听状态。
接口格式大概是这样的:
from flask import Flask, request, jsonify import fasttext import jieba app = Flask(__name__) model = None def load_model(): global model model = fasttext.load_model("./model/ft_classify_v3.bin") # 预热推理,避免首次请求加载延迟 model.predict("宽带无法连接 报修") def preprocess(text): # 清洗逻辑省略,实际有统一清洗模块 words = [w for w in jieba.lcut(text) if w.strip()] return " ".join(words) @app.route("/classify", methods=["POST"]) def classify(): data = request.get_json(force=True) text = data.get("text", "") text = preprocess(text) labels, probs = model.predict(text, k=3) res = { "category": labels[0].replace("__label__", ""), "top3": [ {"label": l.replace("__label__", ""), "prob": p} for l, p in zip(labels, probs) ] } return jsonify(res) if __name__ == "__main__": load_model() app.run(host="0.0.0.0", port=8090, workers=4)一个容易踩的坑是并发问题。Flask自带的服务是单进程的,多线程模式下加载的FastText模型是共享的,推理本身是线程安全的,但如果不加保护的并发访问,模型predict方法偶尔会出现内存错误。解决办法有两个:一个是改用gunicorn启动多进程,每个进程独立加载模型;另一个是给接口加一个进程内锁。我最终选了gunicorn,启动4个worker进程,每个进程加载一份模型副本,内存占用多一点,但稳定性提升非常明显。
服务上线时我做了一个小优化:把高频的常见短语缓存到Redis。比如“为什么扣我话费”“宽带连不上”“发不起短信”这类句子,直接命中缓存返回结果,不重新走模型推理。缓存命中率大概在20%左右,别小看这个数据,高峰期能省下不少CPU资源。
3.4 路由分发与工单落库
分类服务产出类别之后,路由服务要做两件事:根据类别找到处理组,判断工单优先级。这块逻辑看似简单,但细节繁琐。
处理组映射表我放在数据库里,而不是写死在代码里。表结构大概是“category_code”“group_code”“group_name”“priority_level”“is_active”。这样业务调整时只需维护数据库,不用改代码。映射关系必须是一对多兜底的原则,每个类别都有一个默认处理组,同时允许配置特殊规则。比如“账单疑问”默认归“账务组”,但如果文本里识别出“家庭共享套餐”这个附加属性,就同时打上“家庭业务组”的标签,工单并入两个组,处理权归主组。
工单落库也有讲究。我没有把分类结果直接覆盖原来的“人工分类字段”,而是新增了一个“AI分类”字段,保留历史数据。这样模型升级或者分类逻辑调整后,可以回看历史分类记录做对比,效果评估和审计追溯都方便。有些工单是已经处理完的,分类错误了也改不回来了,但至少可以复盘模型在哪个环节出的问题。
落库时还有一个关键操作:把模型的中间信息一并保存。除了最终类别,还要保存top3候选类别、置信度、耗时、模型版本号。这些东西平时看着没用,一旦发生分类纠纷或者业务方质疑“系统为什么把这条工单分错了”,查一下日志就能定位问题。模型版本号尤其重要,模型迭代后,如果效果不如预期,可以快速对比是新模型的锅还是数据变化的锅。
3.5 服务间调用与超时保护
微服务架构下,服务间调用的稳定性必须认真设计。我踩过最惨的坑就是调用链超时无兜底。一开始我只是简单地用HTTP请求让路由服务调用分类服务,结果有一次分类服务因为内存溢出宕机了,路由服务还在疯狂重试,大量请求堆积,最后把整个工单系统都拖慢了。
后来我加了三个机制:
第一是超时控制。HTTP调用的超时时间设成800毫秒,超过就放弃本次调用,返回一个默认分类结果。这个超时时间不是拍脑袋定的,我统计过模型推理的正常耗时时长,P95大约是45毫秒,但加上网络传输和排队等待,整体P95在200毫秒左右,800毫秒已经留了充分余量。
第二是熔断降级。连续5次调用失败,熔断器打开,后续请求在10秒内直接走降级策略,不再发起真实调用。降级策略就是返回“待人工分类”,不阻塞主流程。10秒后熔断器半开,放少量请求试探,成功率达到阈值再恢复正常。这个机制保证了分类服务宕机时,核心工单流程仍然可用。
第三是消息队列削峰。分类服务消费的不是HTTP请求,而是消息队列里的数据。生产端把工单分类请求写到消息队列,消费端按固定速率拉取处理。高峰期请求再多,队列可以缓冲,消费端处理不过来就排队,不会直接把压力打爆服务。
4. 落地阶段踩过的坑与排查记录
4.1 样本不平衡:分类器“偏科”问题
从项目第一天起,样本不平衡问题就一直存在。投诉数据天然是长尾分布的,头部几个类别比如“账单疑问”“网络故障”“退订挽留”,数据量占了七成以上,而“设备折旧”“国际漫游”这类小众类别可能一个月就只有几百条。
刚开始训练出来的模型有明显的“偏科”现象。小类别样本太少,模型学不到足够特征,预测时几乎不会输出小类别标签,就算真的有这类投诉,也总是被判成相近的大类。我们试过直接用类别权重惩罚,但效果一般。真正有用的方案是数据扩充加合并策略。
数据扩充上,我做了两类操作:一类是找业务专家帮忙人工编写小类别的模板句子和典型表述,比如“国际漫游”领域,让业务专家列出常见的“开机失败”“无法注册网络”“漫游费异常”等表达方式;另一类是对已有小类别样本做同义词替换和句式变换,比如把“电话”替换成“手机”“终端”,“打不通”替换成“无法接通”“拨号失败”,生成一批语义等价的样本。这两招下来,小众类别的训练样本量大概翻了三倍左右。
合并策略则是另外一条思路。有些小众类别之间的边界实在太模糊,业务上也常常混在一起处理,比如“设备故障”和“终端异常”,两者处理流程几乎一样,合并成一个“终端设备问题”,反而更有利于模型学习。这事得跟业务方充分沟通,让业务方认可合并后的类别仍然满足他们的执行需求,千万不能技术自嗨直接改类别体系。
4.2 口语化表述的干扰
投诉文本的口语化程度远超预期。下面这些都是真实出现的工单描述片段:“我手机今天下午莫名其妙没信号了,重启也不行”“我爸妈家电视看不了,出现一花一花的”“说什么欠费停机,我明明月中才充的话费”“我要退掉这个破套餐,坑人”。这些文本里有语气词、有叠词、有口语缩写,甚至有错别字,“手机”写成“手鸡”,“宽带”写成“快带”的都有。
模型对这类文本的特征抽取非常不稳定。直接改进模型不如从数据侧入手。我的做法是维护了一份领域内“口语化映射表”,把这些常见口语表达转成标准书面语。比如“一花一花的”转成“画面卡顿”,“破套餐”转成“套餐不适用”,“坑人”转成“服务不满”。这个映射表是动态更新的,每个月从新出现的分类错误案例里抽取,再由业务方审核确认。
另外,录音转写文本还有一个特殊问题,就是没有标点符号,长句子一坨到底。我专门加了一步“口语断句”预处理,根据停顿词和语气词,把超长句子切成短句。比如“所以我就想说你们这个到底怎么帮我处理一下呢”切成“所以”“我就想说”“你们这个到底怎么帮我处理一下呢”三段,这样模型看到的特征更集中。
4.3 联调期最头疼的三个问题
联调期间我们遇到了三个高频问题,这里整理出来,供后来者参考。
第一个是异步回调丢失。消息队列在极端情况下会丢失消息,比如消费者处理完消息刚要提交offset,进程突然宕机,这条消息的处理结果就丢了,工单状态一直停在“分类中”。解决方案是引入一张“分类任务表”,每条工单进入队列前先插入一条任务记录,状态为“待处理”;消费端拿到消息先去查任务记录,处理完更新状态;另起一个定时任务扫描,超过两分钟还处于“待处理”的记录重新投递队列。这套方案实现成本低,但可靠性能达到99.9%以上。
第二个是模型热更新与请求并发冲突。之前模型升级都是先停服务、替换模型文件、再启动服务,但生产环境不允许有长时间停机。后来我实现了一个“模型版本热切换”机制:服务启动时不锁定模型文件路径,而是通过配置中心获取当前版本号,从版本目录加载模型;新模型上传后,更新配置中心的版本号,服务定期轮询感知变化,加载新模型到新的内存区域,再原子切换。切换期间仍服务旧模型的请求,切换完成后旧模型释放。整个过程对调用方完全透明。
第三个是排查问题定位难。微服务链路变长之后,一条工单从接入到最终落库要经过三四个服务,任何一个环节出问题都不好定位。我从一开始就要求全链路上打印trace_id,生成规则是“日期加随机串”,每个服务的日志都必须包含这个trace_id。这样排查问题时,只要拿着一个trace_id去日志平台搜索,整条链路的处理日志都能串起来。
后来我还加了一个小工具,专门统计“分类结果与人工复核结果不一致”的记录。每周跑一次对比,把差异数据导出来人工分析。很多分类错误不是模型问题,而是映射表问题。比如模型正确识别出“发票问题”,但映射表配置里忘了给这类工单指定处理组,导致工单一直派不出去。这种问题不跑差异分析很难发现。
5. 项目上线后的效果与长期维护
上线两个月后,我们做了个系统性的量化评估。整体分类准确率稳定在90.5%左右,工单在接入口的自动分类耗时平均在30毫秒以内,高峰期单台服务器每秒可以处理80到100条工单。坐席的工单处理时间平均下降了35%,二次转派率从18%降到7%。分类效果直接带动了整体客服响应速度和客户满意度提升。
按照推理计算,假设每天有1万条工单需要分类,如果靠人工每单花45秒,一天要用掉125个小时;系统自动分类后,人工只需要处理低置信度的部分,按15%的兜底比例计算,一天只需要复核18.75个小时,省下来的时间相当可观。
长期维护这块,我最大的心得是“模型会烂”。上线三个月后我们发现分类准确率已经缓慢下降到86%左右,原因是有新的营销活动上线,产生了很多新的投诉话术,旧的模型从来没见过。所以要坚持定期重训,我们定的是一个季度一小训、半年一大训,每次训练都会把最近三个月的新增语料合入训练集。另外还要保持积累“难例集”,凡是人工复核纠正过的工单,都进入难例集,重训时重点增加这些样本的权重。这比到模型彻底跑不动了再补救要省太多事。
再分享一个小技巧,非常实用。模型标签的命名最好直接跟业务分类编码保持一致,不要单独搞一套技术标签,否则服务里面多一层映射,调试、追踪、统计数据都得多绕一段路。我是踩过这个坑之后才彻底改过来的。项目里把所有类别的“技术标签”全部替换成“业务编码”,日子瞬间清爽了。
这个项目做完之后,我对NLP工程化和微服务架构都有了更深的体会。技术栈本身没有多深奥,但把它组合起来服务好业务流程,每一步都需要认真考量。希望这篇内容能帮你解决自己项目里的一部分困惑,少走几步我走过的弯路。