背景痛点:传统客服系统为什么“慢半拍”
去年公司双11大促,客服系统差点被用户“打爆”。我们用的是Rasa + BERT 的混合方案,上线前信心满满,结果凌晨两点开始:
- 冷启动:Rasa 训练 3.2 万条语料,Docker 镜像膨胀到 4.7 GB,Pod 拉起平均 90 s,K8s 疯狂重启。
- 上下文维护:Dialogflow 的 Context 最多 10 k,会话(Session)一多就丢,用户问“那刚才说的优惠呢?”机器人一脸懵。
- 多轮改写:用户中途改需求,比如把“红色 128 G”换成“蓝色 256 G”,Slot 被覆盖,订单信息全乱。
一句话:传统 NLP 方案在“高并发 + 多轮 + 实时”场景下,太重、太慢、太贵。
FastGPT 技术对比:为什么敢称“Fast”
先放结论:FastGPT 不是“更大模型”,而是“更轻框架”。它把 7 B 参数蒸馏到 350 M,再外挂向量索引,兼顾速度与效果。
| 维度 | BERT+CRF(Rasa) | Transformer-Seq2Seq | FastGPT-350M |
|---|---|---|---|
| 意图识别准确率 | 83 % | 87 % | 92 % |
| 单句生成耗时(P99) | 420 ms | 680 ms | 120 ms |
| 上下文长度 | 512 tokens | 1 024 tokens | 8 k tokens |
| 冷启动时间 | 90 s | 120 s | 5 s |
| 长文本连贯性 | 弱 | 强 | 强 |
| 二次训练成本 | 高(需全量) | 高 | 低(LoRA 30 min) |
实测在 4 核 8 G 的容器里,FastGPT 的 QPS 是 BERT 方案的 3.4 倍,内存只占 1/3,真正做到了“小钢炮”。
核心实现:30 分钟搭一套可扩展对话引擎
1. 环境准备
官方镜像一条命令搞定:
docker run -d --name fastgpt -p 8000:8000 \ -e OPENAI_API_BASE=http://localhost:8000/v1 \ -v $(pwd)/data:/data \ ccr.ccs.tenc.com/fastgpt/inference:1.2.02. Python 异步客户端(含会话 ID 管理)
# client.py import asyncio, aiohttp, uuid from typing import Dict, Optional class FastGPTClient: def __init__(self, base_url: str, cache_ttl: int = 600): self.base_url = base_url.rstrip("/) self.session: Dict[str, list] = {} self.cache_ttl = cache_ttl async def ask( self, query: str, user_id: str, session_id: Optional[str] = None, temperature: float = 0.3 ) -> str: if session_id is None: session_id = str(uuid.uuid4()) if session_id not in self.session: self.session[session_id] = [] # 构造多轮上下文 messages = self.session[session_id][-6:] # 只保留最近 3 轮 messages.append({"role": "user", "content": query}) payload = { "model": "fastgpt-350m", "messages": messages, "temperature": temperature, "max_tokens": 256 } async with aiohttp.ClientSession() as http: async with http.post( f"{self.base_url}/v1/chat/completions", json=payload, timeout=aiohttp.ClientTimeout(total=5) ) as resp: if resp.status != 200: raise RuntimeError(f"FastGPT error: {resp.status}") data = await resp.json() answer = data["choices"][0]["message"]["content"] # 更新会话 messages.append({"role": "assistant", "content": answer}) self.session[session_id] = messages return answer3. 对话状态机(持久化到 Redis)
# state.py import redis, json, time from typing import Any, Optional r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True) class DialogueState: def __init__(self, session_id: str): self.key = f"ds:{session_id}" def get(self, field: str) -> Optional[Any]: data = r.hget(self.key, field) return json.loads(data) if data else None def set(self, field: str, value: Any, ex: int = 3600) -> None: r.hset(self.key, field, json.dumps(value)) r.expire(self.key, ex) def clear(self) -> None: r.delete(self.key) # 示例:下单流程状态 STATE_FLOW = { "INIT": 0, "AWAIT_COLOR": 1, "AWAIT_SIZE": 2, "CONFIRM": 3 }把状态机与 FastGPT 回答结合,即可实现“多轮可打断”的购物场景。
性能优化:让响应再飞一会儿
1. 请求批处理(Batch Inference)
单条 120 ms,十条还是 120 ms?FastGPT 原生支持动态批。
# batch_client.py async def batch_ask(queries: list, session_ids: list) -> list: async with aiohttp.ClientSession() as http: tasks = [ client.ask(q, uid, sid) for q, sid, uid in zip(queries, session_ids, user_ids) ] return await asyncio.gather(*tasks)Benchmark(10 并发,同机器)
- 单条顺序:1 200 ms → 1.2 s 总耗时
- 批处理:140 ms → 0.14 s 总耗时
≈ 8.5× 提升,CPU 利用率从 35 % 飙到 78 %,不亏。
2. Redis 缓存高频问答
# cache.py def get_or_cache(key: str, func, ttl: int = 300): cached = r.get(key) if cached: return cached val = func() r.setex(key, ttl, val) return val把“发货时间”“退换政策”等 Top 200 QA 预热到 Redis,命中率 68 %,平均延迟再降 40 ms。
避坑指南:上线前必须踩的坑
1. 敏感信息过滤
# filter.py import re id_pattern = re.compile(r"\b\d{15,}\b|\b\d{4}-\d{4}-\d{4}\b") phone_pattern = re.compile(r"(?:\+?86)?1[3-9]\d{9}") def mask_sensitive(text: str) -> str: text = id_pattern.sub("【ID】", text) text = phone_pattern.sub("【PHONE】", text) return text日志、外部工单全部脱敏,合规部再也不拍桌子。
2. 指数退避重试
# retry.py import asyncio, random from aiohttp import ClientError async def robust_ask(*args, **kw): for attempt in range(1, 6): try: return await client.ask(*args, **kw) except (ClientError, RuntimeError) as e: wait = 2 ** attempt + random.uniform(0, 1) await asyncio.sleep(wait) raise RuntimeError("Exhausted retries")网络抖动时把 5 % 的异常降到 0.2 %,用户无感。
代码规范小结
- 函数名小写+下划线,类名官方驼峰
- 类型注解覆盖率 ≥ 90 %,mypy 零警告
- 所有 I/O 函数加
try/except并日志到 structlog,方便 ELK 聚合 - 单元测试用 pytest-asyncio,CI 门禁 80 % 行覆盖
延伸思考:知识图谱 + FastGPT 做多跳问答
目前 FastGPT 的 8 k 上下文对“单跳”够用,但遇到“ A 的子公司 B 去年推出的产品 C 的售价是多少?”这类三跳问题,向量索引会掉召回。一个开放思路:
- 把企业图谱(Neo4j)里子图子集转成文本摘要,作为外部 Memory
- 用 FastGPT 的 function_call 机制,动态决定“是否查图谱”
- 将查询结果再拼回 Prompt,实现“图谱定位 + 生成润色”双轮驱动
这块欢迎一起尝鲜,后续有进展再发实战。
整套流程跑下来,我们生产环境 QPS 稳定在 1 200,响应 P99 从 650 ms 压到 190 ms,意图识别准确率 92 %,客服同学终于能准点下班。代码已开源在内部 GitLab,脱敏后也会同步到 GitHub,祝各位少踩坑、多飞升。