智能客服系统最怕三件事:用户问了三句,系统把第一句忘了;同一句话“我要退货”,被理解成“我要换货”;大促零点流量一冲,服务直接 502。把这三座大山——上下文保持、意图歧义消除、服务降级——搬开,源码才算真正跑通。
规则引擎 vs 机器学习:一张表看懂怎么选
| 维度 | 规则引擎 | 机器学习 |
|---|---|---|
| 响应延迟 | 1~3 ms | 20~80 ms(GPU) |
| 准确率 | 固定句式 95%+,长尾 60% | 整体 90%+,持续学习 |
| 维护成本 | 堆规则=堆人日,指数上涨 | 标注+重训,线性上涨 |
| 典型场景 | 退款、改地址等单轮、高频 | 闲聊、营销话术、多轮 |
一句话总结:规则做“保底”,模型做“拔高”,两者共存最省钱。
对话管理模块源码拆解
1. Python 状态机:让多轮不迷路
# state_machine.py from enum import Enum, auto import redis class State(Enum): INIT = auto() COLLECT_ORDER = auto() CONFIRM_RETURN = auto() END = auto() class DialogStateMachine: def __init__(self, user_id, rds: redis.Redis): self.uid = user_id self.rds = rds self.state_key = f"ds:{user_id}" def _get(self) -> State: s = self.rds.get(self.state_key) return State[int(s)] if s else State.INIT def _set(self, s: State): self.rds.setex(self.state_key, 600, s.value) # 10 min TTL def tick(self, intent: str, entities: dict): state = self._get() # --- 状态转移表 --- if state == State.INIT and intent == "return": self._set(State.COLLECT_ORDER) return "请提供订单号" if state == State.COLLECT_ORDER and entities.get("order_no"): self._set(State.CONFIRM_RETURN) return f"确认退货订单 {entities['order_no']}?" if state == State.CONFIRM_RETURN and intent == "yes": self._set(State.END) return "已提交售后" # --- 兜底 --- return "没听懂,能再说一遍吗?"状态与数据全丢 Redis,重启进程也不丢上下文。
2. Java 缓存上下文:零 GC 抖动示例
// RedisContextService.java public class RedisContextService { private final RedisTemplate<String, String> rt; public void saveCtx(String uid, Map<String,Object> ctx){ String key = "ctx:" + uid; rt.opsForHash().putAll(key, ctx); rt.expire(key, Duration.ofMinutes(30)); } public Map<String,Object> getCtx(String uid){ return rt.opsForHash().entries("ctx:" + uid); } }Hash 结构存实体,过期时间随业务拉长,防止内存泄漏。
3. BERT 意图分类:三行脚本上线
训练完保存intent.pb,用 ONNXRuntime 起 REST:
pip install fastapi onnxruntime-gpu uvicorn bert_intent:app --workers 4 --port 8001# bert_intent.py import onnxruntime as ort from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-chinese") sess = ort.InferenceSession("intent.pb") def predict(text:str)->str: x = tokenizer(text, return_tensors='np', max_length=32, truncation=True) logits = sess.run(None, {sess.get_inputs()[0].name: x['input_ids']})[0] return id2label[logits.argmax()]GPU 机器 4 核可扛 300 QPS,CPU 机器 80 QPS,按预算勾选。
性能优化实战:压测数据说话
压测环境:4C8G Docker,MySQL 8.0,Redis 6.2,Bert 服务独立 1 × T4
结果:- 纯规则链路:QPS 4200,P99 7 ms
- 带模型链路:QPS 380,P99 65 ms
瓶颈在 Bert,增加一条“意图缓存”——相同文本 5 min 内直接复用结果,QPS 提到 620。
连接池参数(HikariCP)
- maximumPoolSize = (cpu * 2) + 1 = 9
- minimumIdle = 4
- idleTimeout = 30 s
- connectionTimeout = 2 s
熔断阈值(Sentinel)
- 慢调用比例 50% + RT>100 ms
- 异常比例 20%
- 恢复时间 15 s
触发后自动降级到“关键词+规则”兜底,保证可用。
安全三板斧:输入、日志、频控
- 输入过滤:用 DFA 敏感词树,2 ms 内完成命中检测,命中直接返回“亲亲,换个说法吧”。
- 日志脱敏:正则
(?<=phone=)\d{4}(\d{4})→****$1,落盘前统一脱敏,ES 只存哈希。 - API 频控:基于 Redis + Lua 脚本,滑动窗口 1 min 60 次,超限返回 429,UID 维度隔离。
踩坑小结
- 状态机一定加“超时事件”,否则用户半小时后回来,系统还停在 COLLECT_ORDER。
- Bert 服务别和 Web 服务混布,GPU 抢占会让 P99 飙到 400 ms。
- 规则热更新用 Groovy 脚本,记得沙箱 + 白名单,防止
Runtime.exec()一把梭。
下一步,留给你
- 如何平衡模型精度与推理速度,让小模型也能打大促?
- 多租户场景下,上下文隔离与资源复用怎么兼得?
- 当用户故意“套话”触发违规内容,实时风控策略如何与对话系统联动?
把代码跑起来,把指标晒出来,答案就在日志里。祝你调试愉快!