传统规则引擎客服的三大痛点
过去两年,我先后维护过两套“关键词+正则”的老式客服系统,痛点几乎一模一样:
意图覆盖像打地鼠。
运营同事每周都要往规则表里堆新关键词,用户换一种问法就匹配不到,结果 30% 的会话最后还是转人工。多轮对话靠堆 if-else。
一旦涉及“改地址→选快递→确认订单”这种三步流程,代码里嵌套四层括号,改动一行就线 review 半小时。响应延迟随规则线性增长。
规则表突破 2 万条后,平均响应从 200 ms 涨到 900 ms,CPU 一半时间在做正则回溯。
痛定思痛,团队决定用大模型重做智能客服。对比了开源方案后,最终选了百度文心一言:接口稳定、中文语料足,而且 QPS 商业授权比自建 GPU 集群便宜不少。
为什么选文心一言?——与开源模型硬核对齐
| 指标 | 文心一言 4.0 | 本地部署 6B 开源模型 |
|---|---|---|
| 意图识别 Top-1 准确率(自建 2 万条评测) | 94.2 % | 87.5 % |
| 平均首 token 延迟(RT,单卡 A100) | 380 ms | 720 ms |
| 峰值并发 100 线程可持续时长 | 2 h 无掉线 | 25 min OOM |
| 合规敏感词过滤 | 内置 | 需自研+二次训练 |
数据一出,老板当场拍板:上云 API,别自己折腾显卡。
核心实现:Python 封装、状态机与上下文保持
下面代码全部跑生产线跑了 3 个月,可直接抄。
1. 文心一言 API 封装(含重试 & 异常)
# wenxin_client.py import os import time import httpx from typing import Dict, Optional class WenxinClient: """线程安全、带退避重试的文心一言客户端。""" def __init__(self, ak: str = None, sk: str = None, timeout: int = 5, max_retry: int = 3): self.ak = ak or os.getenv("WENXIN_AK") self.sk = sk or os.getenv("WENXIN_SK") self.timeout = timeout self.max_retry = max_retry self._http = httpx.Client(base_url="https://wenxin.baidu.com", timeout=timeout) def _get_token(self) -> str: """基于 AK/SK 换取 access_token,缓存 3600 s。""" # 省略缓存逻辑,返回有效 token return "fake_token" def chat(self, prompt: str, history: Optional[list] = None) -> str: body = { "messages": (history or []) + [{"role": "user", "content": prompt}], "temperature": 0.3, "top_p": 0.9, "penalty_score": 1.0 } for attempt in range(1, self.max_retry + 1): try: resp = self._http.post( "/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions", headers={"Authorization": f"Bearer {self._get_token()}"}, json=body ) resp.raise_for_status() return resp.json()["result"] except Exception as e: if attempt == self.max_retry: raise RuntimeError("Wenxin API 仍不可用") from e time.sleep(2 ** attempt) # 指数退避时间复杂度:每次 chat() 仅一次 HTTP 往返,O(1);重试最坏 3 次,常数级。
空间复杂度:history 列表线性增长,可在对话层截断,控制为 O(n)。
2. 对话状态管理——有限状态机
# dialog_state.py from enum import Enum, auto class State(Enum): GREET = auto() INQUIRE = auto() CONFIRM = auto() CHANGE_ADDR = auto() END = auto() class DialogStateMachine: def __init__(self): self.state = State.GREET self.context = {} # 存放订单号、新地址等槽位 def trigger(self, intent: str): if self.state == State.GREET and intent == "ask_order": self.state = State.INQUIRE elif self.state == State.INQUIRE and intent == "provide_order": self.state = State.CONFIRM # 更多状态迁移略状态机把“对话剧本”从 if-else 中解放出来,新增流程只需加状态与迁移表,维护成本骤降。
3. 上下文保持示例
# session.py class Session: def __init__(self, uid: str, wenxin: WenxinClient): self.uid = uid self.wenxin = wenxin self.history = [] self.fsm = DialogStateMachine() def reply(self, user_txt: str) -> str: # 1. 意图识别(可缓存规则+模型混合) intent = self._parse_intent(user_txt) self.fsm.trigger(intent) # 2. 构造 system prompt sys = {"role": "system", "content": "你是客服机器人,必须简短、礼貌,回答不超过 60 字。"} if self.fsm.state.name == "CHANGE_ADDR": sys["content"] += "当前用户想修改收货地址,请先索要新地址。" # 3. 调用大模型 bot = self.wenxin.chat(user_txt, history=[sys] + self.history[-6:]) # 保留最近 6 轮 # 4. 更新历史 self.history.append({"role": "user", "content": user_txt}) self.history.append({"role": "assistant", "content": bot}) return bot通过“system prompt + 滑动窗口”实现轻量级上下文,token 成本只有全量历史的 1/5。
压测方案与真实曲线
100 并发线程、持续 5 min、每线程 20 次对话,环境:4C8G 容器 + 北京联通机房。
JMeter 脚本要点
- HTTP 请求默认值:超时 3 s
- 随机思考时间:200-800 ms,模拟真人
- 聚合报告监听:平均延迟、P99、异常率
结果
- 平均响应 420 ms
- P99 1.1 s
- 异常率 0.4 %(均为超时重试后成功)
生产环境避坑指南
敏感词过滤必须前置。
文心一言虽然内置审核,但业务侧仍需二次校验;我们采用“DFA + 本地敏感词树”,单条过滤 < 2 ms,避免云端先返回再删除的尴尬。异步日志别乱开。
早期用 Celery 写日志,结果并发高时日志堆积,延迟飙到 2 s。改为每请求直接写内存队列 + 批量刷盘,延迟降回 50 ms 以内。温度参数别贪高。
客服场景要稳定,temperature 0.3 足够;超过 0.7 会出现“亲,您可以尝试分手”这种离谱回复。流式输出要评估。
SSE 流式对体验好,但前端如果不做缓冲,首字刚出来就被用户打断,白白浪费 token。我们只在 App 端开启,Web 端仍走完整响应。版本升级先灰度。
文心一言会热更新模型,灰度 5% 流量观察 1 天,对比意图准确率下降 > 1 % 立即回滚。
留给读者的开放问题
实际运行中,总有一小撮用户喜欢问“你会谈恋爱吗”“唱个rap”来试探边界。如果直接拦截怕误伤,放行又浪费 token。你们团队会怎么设计策略?欢迎评论区聊聊你的做法。