限时福利领取


背景痛点:通用型智能客服的“万能”幻觉

过去两年,我先后参与了三款智能客服的从0到1。,最深的体会是:
“通用”往往等于“谁都不满意”。

  • 高净值客户被当成小白用户,一句“转人工”要等30秒,直接投诉。
  • 小白用户被推送晦涩的API文档,一脸懵,转头去微博吐槽。
  • 运营想给刚流失的客户推优惠券,结果系统把券发给了年度续费刚付完的VIP,场面一度尴尬。

根源是“千人一面”的问答路由。
传统关键词→FAQ→兜底模板的链路,没有“客户”只有“问题”,一旦问题表述多样,立刻雪崩。
于是“客户细分”成了智能客服要落地的第一个词:先知道对面是谁,再决定怎么说话。

技术选型:规则、模型还是混搭?

做细分前,团队拉了张对比表,结论一句话:
规则快,模型准,混搭才稳。

方案 优点 缺点 适用场景
规则引擎(Drools、Easy Rules) 毫秒级、可解释、好回滚 维度>10 维护地狱 冷启动、头部客户
机器学习(LR、LightGBM、NN) 自动交叉、精度高 需要样本、热更新复杂 数据充足后
混合(规则+模型+兜底) 互补、灰度可控 架构重 生产主流

我们最终采用“三阶段漏斗”:
规则预筛→模型打分→兜底默认。
灰度发布时,先把20%流量切到模型桶,对比“转人工率”和“首次解决率”,达标后再全量。
上线一周,转人工率下降6.8%,客服中心直接省出一排座席。

核心实现:让每一类客户都有专属通道

1. 客户画像到底要几维?

拍脑袋容易把维度堆成山,结果线上延迟爆炸。
实践下来,三类维度足够把80%客户分出层:

  • 基础属性:地域、终端、语言、注册时长
  • 行为特征:近30天访问频次、功能使用深度、是否触发异常报错
  • 价值等级:合同金额、续费次数、流失预警分

每一维都给出“可计算”的定义,例如“功能使用深度=核心模块UV/总模块UV”,避免“活跃度高”这种模糊词。
维度池维护在配置中心,运营可随时上下架,重启0秒生效。

2. 实时分类算法(Python示例)

线上峰值QPS 3k,模型必须在30ms内给出结果,于是选了LightGBM+特征缓存的组合。
特征工程代码如下,全部符合PEP8,可直接丢进微服务。

# features.py
import json
import hashlib
from datetime import datetime
from typing import Dict, List

class FeaturePipe:
    """实时特征计算 + 缓存"""
    def __init__(self, redis_cli):
        self.r = redis_cli

    def _key(self, uid: str) -> str:
        return f"u:{uid}:feat"

    def build(self, uid: str, raw: Dict) -> List[float]:
        """
        raw: 上游消息体,含用户基础+行为快照
        返回模型所需的12维向量
        """
        key = self._key(uid)
        # 先读缓存
        cached = self.r.get(key)
        if cached:
            return json.loads(cached)

        # 1. 基础属性
        reg_days = (datetime.utcnow() - datetime.fromisoformat(raw["reg_time"])).days
        lang = 1.0 if raw["lang"] == "zh" else 0.0

        # 2. 行为特征
        visit_30d = len(raw["visit_days"])
        err_rate = raw["error_count"] / max(raw["visit_count"], 1)

        # 3. 价值等级
        mrr = raw["monthly_recurring_revenue"]
        level = 2.0 if mrr > 5000 else 1.0 if mrr > 500 else 0.0

        vec = [reg_days, lang, visit_30d, err_rate, mrr, level]
        # 省略其余6维交叉特征……
        self.r.setex(key, 300, json.dumps(vec))  # 5分钟缓存
        return vec

模型推理封装成独立服务,接口如下:

# model_service.py
import lightgbm as tf  # 实际用lightgbm.Booster
import numpy as np

class LgbModel:
    def __init__(self, model_path: str):
        self.bst = tf.Booster(model_file=model_path)

    def predict(self, vec: List[float]) -> int:
        X = np.array([vec])
        # 返回类别 0=普通,1=高价值,2=流失预警
        return int(self.bst.predict(X)[0])

线上实测,单次predict 8ms,加上特征缓存总耗时<20ms,留10ms网络抖动buffer。

3. 动态路由策略

模型只给出“类别”,真正的“路由”由状态机完成。
我们用了Spring StateMachine的轻量级版本,核心思想:
“类别+意图+对话轮次”→“目标技能组”。

状态机片段(YAML配置,方便运营改):

---
s1:
  condition: segment==1 && intent=="billing"
  target: vip_human
  max_wait_ms: 5000
s2:
  condition: segment==0 && round>3
  target: general_human
  max_wait_ms: 15000

状态机与业务解耦,新活动上线只需加一条配置,灰度发布无代码改动。
上线三个月,配置项从37条膨胀到112条,依旧稳态。

性能优化:高并发下的三把斧

  1. 并发请求:
    推理服务无状态,K8s HPA按CPU 60%阈值弹性,3k→8k QPS自动扩Pod,缩容延迟<30s。

  2. 模型热更新:
    采用“双模型+流量影子”策略:新模型加载到内存后,先复制5%影子流量对比LogLoss,一小时无异常再切100%。

  3. 降级策略:
    特征缓存 miss 或推理超时>100ms,立即退回规则桶,保证核心链路可用。
    降级事件写入Prometheus,Granfa面板红灯告警,方便排查。

避坑指南:血与泪的总结

  • 特征漂移监控
    每天凌晨跑离线任务,对比线上分布与训练分布的PSI(Population Stability Index),PSI>0.2自动发飞书告警。
    曾提前两周发现“疫情居家”导致访问深度整体抬高,及时重训,避免准确率下滑。

  • 多租户数据隔离
    同集群不同库,特征缓存加“租户前缀”,模型文件按t{tenant_id}_lgb.txt隔离。
    杜绝A租户重训模型后误刷B租户文件,曾踩坑一次,被销售部点名。

  • 对话连贯性保障
    细分切换只发生在新会话,会话内固定技能组,防止客户被来回踢。
    同时把“细分标签”写入消息头,人工座席可实时看到,避免重复询问。

架构图

开放性问题

当细分维度超过50个时,如何平衡计算复杂度和分类精度?
是把高维稀疏向量扔进FM压缩,还是先做AutoGroup聚类再喂模型?
或者干脆在推理侧做特征选择,只保留当日PSI最低的前20维?
欢迎留言聊聊你的解法。


把细分引擎插进客服链路后,最大的成就感来自一线客服的反馈:
“现在一接起电话就能看到客户画像,终于不用反复问‘您贵姓’了。”
技术价值,大概就藏在这些微小的瞬间里。

限时福利领取


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐