背景痛点:传统客服为什么总答非所问
客服系统最怕的不是“答不上”,而是“答得慢”和“答得错”。过去我们维护过两套典型方案:
- 关键词检索:把 FAQ 做成 Elasticsearch 索引,用户问题里出现“退款”就丢出退款政策全文。结果用户问“我昨天申请退款,为什么还没到账?”系统返回 2000 字文档,用户还得自己找答案。
- 意图模型 + 答案模板:先训练意图分类器,再为每个意图写 3-5 条“标准答案”。一旦业务线新增“组合支付”场景,就要重新标注数据、重训模型,冷启动 2-3 周,期间线上准确率从 92% 掉到 70%。
这两类方案共同的死穴是知识更新滞后。产品文档每周发版、活动规则天天变,运营同学把新 PDF 往群里一扔,开发就得熬夜拆段落、导索引、刷模型。只要有一步慢了,客服就给出过期答案,用户直接转人工,成本翻倍。
技术对比:RAG vs Fine-tuning,谁更适合“天天变”的业务
| 维度 | Fine-tuning | RAG(检索增强生成 Retrieval-Augmented Generation) |
|---|---|---|
| 训练成本 | 需要 GPU 卡 4-8 张,训练 2-6 小时,电费≈3 百元/次 | 0 训练,只调 Prompt |
| 冷启动时间 | 标注+训练+发版≥2 周 | 知识库文件落盘即可生效,分钟级 |
| 响应延迟 | 纯 LLM 推理 800-1200 ms | 检索 150 ms + LLM 400 ms ≈ 550 ms |
| 回答可控性 | 高,模型“背”住了答案 | 中,依赖检索 Top-K 质量,需重排序 rerank |
| 更新频率 | 周级 | 分钟级 |
| 错误回滚 | 重新训练或回退模型版本 | 直接回滚知识库快照 |
一句话总结:业务规则“日更”选 RAG,数据分布“季更”才考虑 Fine-tuning。客服场景显然是前者。
架构实现:用 RAGFlow 搭一套“三层”知识库
1. 分层知识库设计
RAGFlow 把“文档”抽象成 Dataset,支持多库联合召回。我们按置信度拆分:
- 产品文档库(高权威)——PDF/Markdown,版本号管理
- 用户日志库(高实时)——客服聊天记录,每日增量 ETL
- 活动 FAQ 库(高频率)——运营在飞书多维表格维护,自动同步
2. 配置 embedding & rerank
RAGFlow 默认走双塔:向量召回 + 重排序 rerank。config.yaml片段:
embedding: model: "BAAI/bge-small-zh-v1.5" # 中文 512 维,轻量 device: "cuda" batch: 256 max_len: 512 rerank: model: "BAAI/bge-reranker-base" top_k: 20 # 召回 20 再精排 threshold: 0.35 # 低于此分拒答embedding 模型第一次会在启动时自动从 HuggingFace 缓存到/models,离线环境提前huggingface-cli download即可。
3. docker-compose.yaml 关键配置
version: "3.9" services: ragflow: image: infiniflow/ragflow:v0.8.0 container_name: ragflow environment: # 向量库 - VECTOR_STORE=elasticsearch - ES_HOSTS=http://es:9200 # LLM 后端 - LLM_PROVIDER=openai - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_BASE=https://api.openai.com/v1 # 敏感词过滤 - SENSITIVE_WORDS_FILE=/app/data/sensitive.txt volumes: - ./product_doc:/app/data/product_doc - ./logs:/app/logs ports: - "8000:8000" depends_on: - es - redis restart: unless-stopped es: image: docker.elastic.co/elasticsearch/elasticsearch:8.11.1 environment: - discovery.type=single-node - xpack.security.enabled=false volumes: - esdata:/usr/share/elasticsearch/data redis: image: redis:7-alpine command: redis-server --maxmemory 2gb --maxmemory-policy allkeys-lru volumes: esdata:把上面文件保存为ragflow-prod.yaml,一键docker compose -f ragflow-prod.yaml up -d即可拉起。注意SENSITIVE_WORDS_FILE指向本地脱敏词典,下文再展开。
生产考量:让客服系统敢接“大流量”
1. 敏感数据脱敏
客服聊天记录里常见手机号、邮箱、订单号。RAGFlow 支持自定义PIFilter插件,正则 + 掩码:
import re from typing import List def mask_pii(text: str) -> str: """ 脱敏中文手机号与邮箱 :param text: 原始用户消息 :return: 脱敏后文本 """ text = re.sub(r'1[3-9]\d{9}', '****', text) text = re.sub(r'[\w\.-]+@[\w\.-]+', '****@****', text) return text在docker-compose.yaml里挂载到/app/plugins,并在配置中启用即可。
2. 并发缓存策略
向量检索本身无状态,但 rerank 模型跑 GPU 仍会成为瓶颈。我们在redis层加一层“语义缓存”:
- key =
embedding_md5(question)+top_k - value =
{"chunks": [...], "scores": [...]} - TTL = 10 min(产品文档变化频率)
压测 500 并发,缓存命中率 42%,平均延迟从 550 ms 降到 220 ms。
3. 知识库版本回滚
RAGFlow 每次上传文档会打snapshot_id,回滚接口示例:
curl -X POST http://ragflow:8000/api/v1/snapshot/rollback \ -H "Content-Type: application/json" \ -d '{"dataset": "product_doc", "snapshot_id": "20240518v1.2"}'运营在后台点“回滚”即可秒级生效,无需重启容器。
避坑指南:那些让我们加班到凌晨 2 点的细节
1. 避免 embedding 维度爆炸
很多同学习惯把整篇 30 页 PDF 直接扔进去,结果 2048 token 的段落向量把显存吃光。推荐“二级分块”:
- 按一级标题切 5-6 段
- 每段再按 256 token 滑动窗口,overlap=50 token
- 段落头加标题前缀,保证语义连贯
这样 30 页文档生成 120 个向量,内存占用降 60%,检索 F1 反而升 3%。
2. 对话历史压缩
多轮对话把历史拼进 Prompt,token 很容易爆表。我们采用“总结+最近两轮”策略:
def compress_history(history: List[str]) -> str: """保留用户最初意图与最近两轮细节""" if len(history) <= 4: return "\n".join(history) return f"[总结]{history[0]}\n..." + "\n".join(history[-4:])实测 GPT-3.5 场景下,平均节省 38% token,长对话不丢核心语义。
可复用代码片段:PEP8 带类型提示
from typing import List, Dict import httpx async def retrieve_topk( query: str, top_k: int = 20, dataset: str = "product_doc" ) -> List[Dict[str, float]]: """ 异步调用 RAGFlow 检索接口 :param query: 用户问题 :param top_k: 召回数量 :param dataset: 指定数据集 :return: [{"chunk": "...", "score": 0.87}, ...] """ url = f"http://ragflow:8000/api/v1/retrieve" payload = {"query": query, "top_k": top_k, "dataset": dataset} async with httpx.AsyncClient(timeout=10) as client: resp = await client.post(url, json=payload) resp.raise_for_status() return resp.json()["chunks"]延伸思考:换个 LLM 后端,效果差多少?
RAGFlow 支持一键切换 LLM。我们在同样 2000 条线上提问里跑了三组实验:
| 后端模型 | 平均延迟 | 回答准确率 | 每 1K token 成本 |
|---|---|---|---|
| GLM-4B-Chat(本地) | 320 ms | 82% | 0 元 |
| GPT-3.5-Turbo | 550 ms | 88% | 0.002 美元 |
| GPT-4 | 1.2 s | 93% | 0.06 美元 |
如果预算充足、对准确率极致敏感,可用 GPT-4;若想完全离线,GLM-4B 也是可用方案,只是需要把 Prompt 调得更细,必要时把 rerank threshold 降到 0.3,减少拒答。
小结:把“知识更新”从周级压到分钟级
RAGFlow 的最大价值是把“运营上传文档 → 用户可见答案”的链路压缩到 5 分钟以内,同时让开发者用 Docker Compose 就能复制出一套生产级架构。经过两个月的线上运行,我们的转人工率从 42% 降到 18%,知识库维护人力从 3 人日/周降到 0.5 人日/周。如果你也在为客服“答非所问”头疼,不妨拉下代码,按本文的 compose 文件起一套测试环境,再把你家产品文档拖进去,十分钟就能看到实测效果。祝你早日让客服机器人“说人话”,也让自己少熬几个深夜。