背景痛点:传统客服到底卡在哪?
做 ToB 售后系统的朋友都懂,老版客服后台基本是“关键词+正则”堆出来的。一旦业务换套餐、上新功能,运营同学就要在几千条规则里人肉找“哪条得改”,上线周期按周算。
纯 LLM 方案看着香,实际落地却常被老板灵魂三问:
- 答案幻觉把价格说错,谁背锅?
- 十并发的接口就把 GPU 打满,成本谁掏?
- 618 大促当天知识库临时加规则,模型重训来不及,怎么热更新?
响应速度、准确率、知识新鲜度,这三座山把“智能客服”卡成了“智障客服”。RAG(Retrieval-Augmented Generation)就是冲着“不改模型、只换知识”这个目标来的。
下文把我们从 0 到 1 上线 RAG 客服的踩坑笔记摊开,给同样想“让 AI 先打工”的中级开发者一个可抄作业的版本。
技术对比:微调 vs Prompt vs RAG
| 维度 | 微调 Fine-tuning | Prompt 工程 | RAG |
|---|---|---|---|
| 训练成本 | 高(GPU 小时×百元) | 0 | 低(仅 Embedding) |
| 响应延迟 | 正常(1×LLM) | 正常(1×LLM) | 稍高(+向量检索 50~150 ms) |
| 知识更新 | 重训+发版,天级 | 换 Prompt,分钟级 | 换知识库,秒级 |
| 可解释性 | 黑盒 | 黑盒 | 检索结果可展示来源 |
| 幻觉风险 | 中 | 高 | 低(检索约束生成) |
| 多租户隔离 | 需多模型/LoRA | 靠 Prompt 区分 | 靠分库分索引 |
一句话总结:RAG 用“外挂硬盘”的思路,把成本、时效、可控性拉到可接受区间,最适合售后场景。
核心实现三步走
1. 知识库构建:Chunk 不是越小越好
- 先把存量文档(pdf、md、飞书多维表)统一成 Markdown。
- Chunk 策略:按“标题+2 级段落”做递归拆分,单段 300~400 token,重叠 10%。太小会丢上下文,太大会把价格表和免责条款混一起。
- Embedding 选型:中文用
text2vec-base-chinese(免费、512 维),对专有名词再跑 1 轮领域微调,提升 8% 召回。 - 索引:Milvus 2.3 单机版,collection 按“租户+业务线”分片,方便后期物理隔离。
2. 检索模块:别让相似度“躺平”
- 基础算法:余弦 + IP(内积)都试过,售后场景内积更稳,因为长度差异大。
- HyDE(Hypothetical Document Embeddings)优化:让 LLM 先根据用户问题“脑补”一份理想答案,再拿这份答案做向量检索,实测 top-5 命中率提高 12%。
- 检索链路:query → HyDE → Embedding → Milvus(top-20)→ 重排(Cross-Encoder,ms 级)→ top-5 进生成。
3. 生成模块:Prompt 模板 + 结果校验
- Prompt 模板(LangChain 自定义):
template = """你是一名官方客服,仅使用以下上下文回答用户。 上下文: {context} 用户问题:{question} 若上下文无法回答,请回复“暂无相关信息”。不要编造。"""- 结果校验:
- 关键词黑名单:价格、活动日期必须和检索片段正则匹配,否则打回“暂无”。
- 置信度打分:用 bert-score 计算生成答案与检索片段的语义重叠,低于 0.65 自动转人工。
代码实战:LangChain 版最小可运行框架
安装依赖:
pip install langchain==0.1.0 milvus-text2vec sentence-transformers openai核心代码(含异常、日志、参数说明):
import logging, os, time from langchain.chains import RetrievalQA from langchain.vectorstores import Milvus from langchain.embeddings import HuggingFaceEmbeddings from langchain.llms import OpenAI logging.basicConfig(level=logging.INFO) logger = logging.getLogger("rag_cs") # 1. 初始化 Embedding embed_model = HuggingFaceEmbeddings( model_name="shibing624/text2vec-base-chinese", model_kwargs={"device": "cuda"}, encode_kwargs={"normalize_embeddings": True} ) # 2. 连接 Milvus vector_db = Milvus( embedding_function=embed_model, collection_name="cs_v1", connection_args={"host": "127.0.0.1", "port": "19530"} ) # 3. 检索 QA 链 qa_chain = RetrievalQA.from_chain_type( llm=OpenAI(temperature=0, max_tokens=300), retriever=vector_db.as_retriever(search_kwargs={"k": 5}), return_source_documents=True, chain_type="stuff" ) def ask(question: str) -> str: try: start = time.time() ans = qa_chain({"query": question}) logger.info(f"latency={time.time()-start:.2f}s") return ans["result"] except Exception as e: logger.exception("rag err") return "系统繁忙,请稍后再试" # 本地测试 if __name__ == "__main__": print(ask("618 优惠券哪天过期?"))关键参数:
k=5:召回条数,高并发场景可降到 3,减少 token 费用。temperature=0:客服场景必须确定性输出。normalize_embeddings=True:保证余弦相似度计算一致性。
生产环境要考虑的三板斧
- 并发缓存
- 把“高频标准问题→向量”结果缓存到 Redis,TTL 10 min,QPS 从 1200 降到 200,GPU 省钱 40%。
- 敏感词过滤
- 采用“DFA+正则”双层过滤,政治、脏话、竞品词库每周自动爬 GitHub 开源库合并。
- 知识库版本控制
- 每次更新打 Git tag,Milvus 新建 collection 带日期后缀,双写 24 h 后切流,支持秒级回滚。
避坑指南:上线前一定检查
| 坑 | 现象 | 解决 |
|---|---|---|
| OOM | 并发一上来 GPU 显存炸掉 | 把embed_model改device="cpu",检索阶段只用 GPU 做 LLM 推理,显存降 60% |
| 冷启动延迟 | 首条请求 3 s+ | 预加载模型到内存,写一条“warmup”脚本,每天 6 点定时调用 |
| 版本漂移 | 同一条问题上午能对下午错 | 把 top-k 召回结果落日志,发现是 chunk 边界变动导致,固定 chunk_size 并加重叠长度 |
延伸思考:两条开放式作业
- 多模态场景:用户上传截图问“这个活动按钮怎么灰了”,如何把图片 OCR 结果与向量知识融合进检索?
- 强化学习微调:在 RAG 生成后,用“用户是否点踩”做奖励,能否在线微调一个小型 reward-model,让好答案排名持续上升,而不动大模型本身?
写在最后的碎碎念
RAG 不是银弹,但确实把“知识更新”从周级别砍到分钟级别,让运营同学第一次觉得“AI 听话”。
如果你也在做客服场景,不妨先搭一个最小 Milvus+LangChain 原型,把历史 FAQ 扔进去跑一周,收集用户点踩数据,再决定要不要上更重的微调或强化学习。
技术栈只是工具,能让客服少加一天班、用户少等十秒钟,就是值得的。祝各位上线不炸服,显存常够用。