背景:规则引擎的“天花板”
做客服系统的老同学一定踩过这些坑:
- 运营三天两头往知识库里加“关键词”,意图规则膨胀到上万条,改一条就可能牵一发而动全身;
- 用户一句“我昨天买的那个东西能退吗?”里既没商品名也没订单号,规则直接懵;
- 618 大促并发一高,Redis 里那坨状态字段像毛线团,排查全靠 grep;
- 每上线一次新活动,就要重新写一套“if/else”,发版比淘宝秒杀还刺激。
一句话:规则引擎在长尾 query 和动态业务面前,维护成本指数级上涨,体验却线性下降。LLM 带来的生成能力,正好把“穷举”变成“理解”,让系统从“堆人力”转向“堆算力”。
技术选型:把合适的模型放在合适的位置
先给结论,再解释:
| 模型 | 场景匹配度 | 成本 | 数据隐私 | 备注 |
|---|---|---|---|---|
| GPT-4 | ★★★★☆ | 高 | 低 | 推理质量最好,适合兜底 |
| GPT-3.5-turbo | ★★★☆☆ | 中 | 低 | 速度快,适合冷启动 |
| Claude-3-Sonnet | ★★★★☆ | 中 | 低 | 幻觉少,长上下文好 |
| ChatGLM3-6B(本地) | ★★★☆☆ | 低 | 高 | 需要 GPU,意图识别够用 |
| Llama2-Chinese-13B(本地) | ★★★☆☆ | 中 | 高 | 中文 SFT 后效果不错 |
选型口诀:
- 对客答案先“本地”后“云端”,能答的绝不出网;
- 高价值售前/售后转人工前,用 GPT-4 做“最后一道保险”;
- 并发高但答案简单的场景(如订单状态),用 6B 级本地模型 + 缓存,成本直接砍到 1/20。
核心架构三板斧
1. 对话状态机:把“聊天”抽象成有向图
状态机不是炫技,是给 LLM 一个“坐标”。
节点 = 业务阶段(欢迎语→收集信息→确认→完结),边 = 意图或槽位是否齐全。
代码里用 Python-enum 一把梭哈:
from enum import Enum, auto class State(Enum): INIT = auto() COLLECT = auto() CONFIRM = auto() HANDOFF = auto() # 转人工 END = auto()LangChain 的ConversationBufferWindowMemory只负责“记”,状态机负责“跳”。两者解耦,调试时一眼看出“用户卡在哪”。
2. 意图识别 + 实体抽取:协同而不是串行
传统做法先意图后实体,结果“我要退”被分到退货意图,但商品名没抽出来,下游照样抓瞎。
改进:把“意图+实体”做成联合预测,用 Pydantic 定义 Schema,一次性喂给 LLM:
class ReturnRequest(BaseModel): intent: Literal["return", "exchange", "cancel"] product: Optional[str] order_id: Optional[str] reason: Optional[str]LLM 输出 JSON → 校验 → 缺槽位就反问,状态机保持在 COLLECT,直到齐全才允许跳到 CONFIRM。
这样做把两轮对话压成一轮,实测少 18% 轮次。
3. 上下文记忆:三档套餐按场景喂
- 热数据(30 min 内):Redis List,<1 KB,随用随扔;
- 温数据(7 天):MongoDB,按 session_id 存,压缩后平均 3 KB;
- 冷数据(全生命周期):S3 + Parquet,仅当用户再次进线才拉回温区。
内存里只给 LLM 看“热+温”,长度过长时做 embedding 检索,把最相关的 3 条历史拼进 prompt,token 节省 35% 以上。
代码实战:30 行 LangChain 骨架
下面示例演示“多轮收集订单号”的最小闭环,异常、超时、重答全齐:
import asyncio, os, time from langchain import ConversationChain, PromptTemplate from langchain.memory import ConversationBufferWindowMemory from pydantic import ValidationError template = """ You are a customer service bot. Extract user info according to schema. History: {history} Human: {input} Assistant: Let me confirm, you want to return product <product>, order <order_id>, reason <reason>. (y/n) """ prompt = PromptTemplate(input_variables=["history","input"], template=template) memory = ConversationBufferWindowMemory(k=3) chain = ConversationChain(llm=llm, memory=memory, prompt=prompt) async def chat(session_id: str, user_input: str): try: # 1. 防超时:30 分钟无互动清内存 last = redis.get(f"last_{session_id}") if last and time.time() - float(last) > 1800: memory.clear() # 2. 调用链 ans = await chain.arun(input=user_input) # 3. 解析 & 校验 try: data = ReturnRequest.parse_raw(ans) except ValidationError as e: return f"信息好像不全,请补充:{e.errors()}" # 4. 更新状态机 if data.intent == "return" and data.order_id: sm.move(session_id, State.CONFIRM) redis.setex(f"last_{session_id}", 1800, time.time()) return ans except Exception as e: # 5. 兜底:任何异常转人工 sm.move(session_id, State.HANDOFF) return "正在为您转接人工客服,请稍候..."要点都写在注释里,复制即可跑。异常分支千万别省,上线第一周就救了我三回。
生产环境:高并发不是“加机器”那么简单
- 异步化:用 FastAPI +
asyncio.gather(),把 LLM 调用、数据库、缓存 IO 全部异步,8 核机器轻松扛 500 QPS。 - 缓存:
- embedding 结果缓存 10 分钟,命中率 42%;
- 完全命中缓存的请求 P99 延迟从 1.2 s 降到 180 ms。
- 限流:
- 按“用户级”做漏桶,防止单用户狂刷;
- 按“模型级”做 token 分钟配额,超了自动降级到本地 6B。
- 敏感过滤:
- 正则先扫一遍身份证、手机号;
- 再用本地轻量审核模型打标签,高风险内容直接替换为“*”,并写审计日志。
避坑锦囊
- 冷启动数据:
别指望 LLM 万能,先拿 3 个月人工日志做“种子问答对”,用 LlamaIndex 构建检索知识库,上线首日准确率就能到 72%,否则用户会教你做人。 - 对话漂移:
设置“主题一致性”检测,当连续两轮 embedding 余弦 < 0.82 就拉回主流程,防止用户聊天气→股票→外卖。 - 模型幻觉:
- 让 LLM 先输出“可观测中间步骤”(Chain-of-Thought),再提取答案;
- 高危险场景(金额、药品)加“置信度阈值”,低于 0.85 强制转人工。
结尾:微调 or 不微调?
本文的骨架全部基于“提示工程 + 检索增强”。但业务越垂直,幻觉越隐蔽——
如果让你来决策,你会选择:
- 继续堆提示,把 prompt 写到 2 k token?
- 用 LoRA 在 13B 模型上微调一层,冻结底层?
- 还是干脆把知识图谱也塞进训练集,做全量 SFT?
欢迎在评论区聊聊你的落地思路,一起把“智能客服”做成“真客服”。