背景痛点:企业级智能客服到底难在哪
去年公司决定把 10086 文本客服搬到线上,领导一句“用联通 AI 智能客服,两周上线”,我们组差点集体原地爆炸。真正动手才发现,企业级场景跟 Demo 完全两码事:
- 会话持久化:用户说“帮我查套餐”,5 分钟后再回一句“那流量呢?”——这两句必须能串起来,否则体验直接翻车。
- Intent Detection/意图识别:联通业务实体太多,“宽带新装”“宽带续费”“宽带移机”字面只差两个字,模型一个打盹就给你整成“新装”,后续流程全错。
- 多轮对话管理:套餐查询要鉴权、查余额、选档位,3 轮以上就涉及槽位填充、回退、重填,状态机一乱,用户直接转人工,前面算力全白花。
一句话:高并发、高可用、高准确率,一个都不能少。
技术对比:Rasa、Dialogflow 与联通 AI 平台
选型阶段我们把三个主流方案拉到一起跑分,维度简单粗暴:中文效果、RT(Response Time)、定制成本。
| 维度 | Rasa 3.x | Dialogflow ES | 联通 AI 平台 |
|---|---|---|---|
| 中文分词 | 需自插 Jieba,歧义高 | 英文模型迁移,准确率 82% | 内置电信语料,准确率 93% |
| 平均 RT | 180 ms | 450 ms(北京 VPC) | 120 ms(同域) |
| 定制成本 | 训练数据+GPU,人日 15 | 按调用收费,0.006$/次 | 政企打包,100W 次/月 |
| 运维复杂度 | 自己搭 K8s | 全托管 | 联通侧托管,只接 SDK |
结论:ToB 场景、对中文要求高、又想省运维,联通 AI 平台最香;Rasa 留给有算法团队且想深度调参的大厂;Dialogflow 做海外业务再考虑。
系统架构与交互流程
先放一张总览图,后面代码都能在这里找到对应节点。
graph TD A[用户微信] -->|HTTPS| B[API-Gateway] B -->|JWT 转发| C[Dialog-Service:8082] C -->|OAuth2| D[LianTong-AI-SDK] C <-->|Redis| E[Context-Store] C -->|Async| F[Log-Kafka] G[敏感词服务] -.-> C核心实现一:Spring Boot 整合联通 AI SDK
下面这段是dialog-service的启动类,亮点在 OAuth2 自动刷新——access_token 有效期 2h,SDK 里如果返回 401,框架会自动拿 refresh_token 换新的,业务线程无感。
/** * 联通 AI SDK 配置类 * 遵循 Alibaba 代码规约:类名 Upper【U】,常量全大写 */ @Configuration @EnableConfigurationProperties(LianTongProperties.class) public class LianTongConfig { @Bean public LianTongClient lianTongClient(LianTongProperties prop) { return LianTongClientBuilder.create() .appId(prop.getAppId()) .appSecret(prop.getAppSecret()) .oauthUrl(prop.getOauthUrl()) .nluUrl(prop.getNluUrl()) .build(); } }调用示例:
/** * 意图识别入口 * @param text 用户原文 * @return 意图编码 */ public String detectIntent(String text) { NluRequest req = NluRequest.of(text); // 同步阻塞 RT<150ms NluResponse resp = client.nlu(req); return resp.getTopIntent().getCode(); }核心实现二:Redis 对话上下文存储
多轮场景下,把DialogContext整个对象塞进 Redis,key=session:{userId},TTL 15 分钟,用户 15 分钟不说话就自动清理,省内存。
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, DialogContext> contextRedisTemplate(RedisConnectionFactory f) { RedisTemplate<String, DialogContext> t = new RedisTemplate<>(); t.setConnectionFactory(f); Jackson2JsonRedisSerializer<DialogContext> ser = new Jackson2JsonRedisSerializer<>(DialogContext.class); t.setValueSerializer(ser); t.setKeySerializer(RedisSerializer.string()); return t; } }存储工具类:
public void save(String userId, DialogContext ctx) { redisTemplate.opsForValue().set("session:" + userId, ctx, Duration.ofMinutes(15)); }性能优化:压测 + 熔断
- 用 Apache Benchmark 模拟 200 并发,持续 30s:
ab -p body.json -T application/json -c 200 -t 30 http://gateway/dialog/route结果:QPS 3.2k,P99 280 ms,CPU 68%,满足上线要求。
- 熔断策略:联通 AI 的 SLA 承诺 99.9%,但偶尔抖到 1s+,我们加 Sentinel:
spring: cloud: sentinel: rules: - resource: nlu grade: RT count: 300 # 300ms 以上视为异常 timeWindow: 10触发后自动降级到本地缓存意图,保证核心链路可用。
避坑指南:中文分词、敏感词、日志延迟
- 中文分词歧义:用户说“我想开宽带”,模型切成“开/宽带”,业务码表要的是“开通/宽带”。解决:在
postProcess里加电信同义词映射表,把“开”→“开通”,准确率提升 4%。 - 敏感词过滤:客服场景必须秒级过滤,DFA(Deterministic Finite Automaton)算法最快。核心代码 50 行,构建一次仅 30ms,匹配 O(n)。
public class SensitiveFilter { private final WordNode root = new WordNode(); public void loadWord(List<String> words) { ... } public boolean contains(String text) { ... } }- 异步日志:一开始用
logback-async,结果高峰期线程切换把 RT 抬高 20ms。改回同步 + 批量刷盘,Latency 降回 10ms 以内。
代码规范小结
- 所有对外接口强制 Javadoc,@param/@return 写清楚单位(ms/百分比)
- 魔法值一律提常量,如
private static final int SESSION_TTL_MINUTE = 15; - 日志占位符用
{},拒绝字符串拼接,防 GC 抖动
延伸思考:支持语音输入的 WebSocket 扩展
文本客服跑稳后,领导又拍脑袋“上语音”。思路:WebSocket 全双冒,前端送 16kHz 单声道 PCM,后端用联通 AI 语音转文本插件,先拿到文本再走现有流程,返回 TTS 音频或文本均可。要点:
- 一条长连接 5 分钟心跳,防止 NAT 超时
- 语音包分片 320Byte,后端用
StreamingRecognize,边收边转,首包延迟 <300 ms - 高并发用 Netty 自己写,别拿 Spring WebSocket 硬怼,省 30% 内存
进阶实践问题
- 如果用户同时打开 App 和微信小程序,两条 Session 如何合并识别为同一用户?
- 当意图置信度 0.51 刚踩线,你会选择“直接回答”还是“反问澄清”?如何量化对 C-SAT 的影响?
- 语音输入时,遇到 3 秒静音,应触发“结束识别”还是“继续等待”?请给出可配置的阈值策略设计。
把这三个问题想透,你的智能客服就真正从“能用”进化到“好用”。祝大家开发顺利,少踩坑,早点下班。