RAG 设计
设计目标
外卖客服 RAG(Retrieval-Augmented Generation)的目标不是技术炫技,而是在客户问「能不能少辣」「多远能送」「虾新不新鲜」「怎么退款」时,找到可引用的店铺规则,让 AI 避免凭常识编造。
具体的设计目标:
- 召回准确:客户问什么,RAG 就找到对应店铺规则
- 召回可解释:每条结果可追溯到具体知识条目
- 降级可控:找不到时主动承认,不编造
- 维护可达:商家能看到命中率、有用率,知道哪条要改
- 成本可控:检索本身不占大头开销,单次查询成本忽略不计
知识库特点
外卖客服知识库的几个特点决定了 RAG 设计的取舍:
| 特点 | 含义 | 设计影响 |
|---|---|---|
| 规模小 | 单店通常 30-80 条 | 不需要高维向量召回,关键词足够 |
| 问法多变 | 同一意图有多种表达 | 必须支持同义词与近义召回 |
| 更新频繁 | 价格、活动周更 | 不能依赖向量预计算 |
| 中文为主 | 客户多用中文 | 分词器和 trigram 必须支持中文 |
| 强结构化 | 营业时间、配送范围有固定字段 | 可以用结构化 KV 加速 |
这些特点意味着外卖场景下不需要复杂的 RAG 工程——纯关键词 + trigram + 同义词扩展就能覆盖 80% 召回,向量是锦上添花。
检索架构
系统采用混合检索 + RRF 融合的轻量架构:
[客户 query]
│
↓
[查询规范化(去标点、分词、同义词扩展)]
│
┌─────────────┬──────────────┬───────────────┐
↓ ↓ ↓ ↓
[ILIKE 全文] [trigram 模糊] [同义词命中] [向量召回(可选)]
│ │ │ │
│ └──────────────┴───────────────┘
↓ │
[关键词 top-k] ↓
│ [向量 top-k]
└──────────┬─────────────────┘
↓
[RRF 融合排序]
│
↓
[取 top-3 进 prompt]
│
↓
[回答时标注引用 [1][2][3]]
四路召回各有侧重:
| 召回方式 | 优势 | 局限 |
|---|---|---|
| ILIKE 全文 | 精确匹配,零延迟 | 错别字命不中 |
| trigram 模糊 | 容错错别字 | 噪声多 |
| 同义词扩展 | 应对问法变化 | 依赖维护质量 |
| 向量召回 | 语义相似 | 需要 embedding 服务,成本高 |
RRF 融合
四路召回结果用 RRF(Reciprocal Rank Fusion)融合排序,公式:
score(d) = Σ 1 / (k + rank_i(d))
其中 k=60 是常用调和参数,rank_i(d) 是文档 d 在第 i 路召回中的排名。
RRF 的优点:
- 不同路召回的分数分布完全不同(ILIKE 是 0/1,向量是 0-1 浮点),直接相加会被高分路压制
- RRF 只看排名不看绝对分数,对所有路平等
- 实现简单,几行代码搞定
例:客户问「虾新不新鲜」
| 文档 | ILIKE 排名 | trigram 排名 | 同义词排名 | 向量排名 | RRF 总分 |
|---|---|---|---|---|---|
| 海鲜保存说明 | 1 | 2 | 1 | 3 | 0.0612 |
| 食品安全条例 | 3 | 1 | - | 2 | 0.0476 |
| 退款政策 | - | - | 5 | 4 | 0.0212 |
最终取 top-3 进入 Prompt 上下文。
中文分词与 trigram
PostgreSQL 的 pg_trgm 默认对英文优化,对中文需要额外处理:
- 客户 query 预处理:jieba 分词 + 去停用词
- 知识库索引:标题 + 正文 + 同义词合并字段建 trigram 索引
- trigram 阈值:设为 0.3(默认 0.3 偏严,可调到 0.25 提召回)
- 单字保留:分词时保留单字(避免「鱼」被吃掉)
中文 trigram 的实际效果:
- 「皮皮虾」→ 命中"虾""皮虾""皮皮"
- 「不新鲜」→ 命中"新鲜""不鲜"
- 「能退款吗」→ 命中"退款""退"
向量召回(可选)
向量召回作为开关:ENABLE_VECTOR_RAG=true/false。当前默认 false,原因:
- windhub.cc default 分组的 embedding 渠道未启用
- 单店 30-80 条规模下,关键词 + 同义词召回率已 > 85%
- 向量召回每次有 embedding 调用成本
启用条件:
- 知识库扩展到 200+ 条(关键词召回开始遗漏)
- 客户问法越来越多变(语义相似但词面差异大)
- embedding 服务成本可接受(每次 query < ¥0.0001)
上下文打包
召回结果进入 Prompt 时的格式:
# 相关知识库条目
[1] 营业时间
每天 16:00 - 次日 02:00 营业。节假日除外。
[2] 配送范围
3 公里内全部配送。3-5 公里在订单充足时配送。
[3] 海鲜保存
海鲜需冷藏保存,开封后 2 小时内食用。
打包要点:
- 每条条目带编号([1][2][3]),便于回答时引用
- 标题 + 正文,不带 metadata(如 hit_count)
- 总长度控制在 800 字以内(避免压缩客户当前问题的注意力)
- 按 RRF 排序,最相关的放最前
引用展示
AI 回答中自然嵌入引用:
[AI] 我们家每天 16:00 - 次日 02:00 营业 [1],配送范围一般在 3 公里内 [2],
高峰期 21-23 点 3-5 公里也可以送。
客户在 widget 中看到 [1] [2] 是可点击的,展开后显示对应知识条目标题。这是RAG 可解释性的关键——客户和商家都能追溯每条信息来源。
召回失败的降级
当四路召回都没有合理结果时(top-1 RRF 分数 < 阈值),系统进入降级:
- 不传任何知识进 Prompt(避免误引用)
- 在 Prompt 中显式告知"未找到相关规则"
- 模型回复要短,引导客户补充信息或转人工
- 该会话标记为
kb_miss=true,进入 fallback 队列
降级处理详见 02-conversation/04-fallback。
运营维护
RAG 的效果完全取决于知识质量。系统每日聚合下列指标:
| 指标 | 用途 |
|---|---|
| top10 query 命中率 | 是否有高频问题没召回 |
| 知识条目命中分布 | 是否有 zombie 条目(30 天 0 命中) |
| 同义词触发频率 | 是否有同义词从未生效 |
| 引用后有用率 | 召回是否真的解决问题 |
| RRF 分数分布 | 是否需要调阈值 |
命中率低的会话回流到知识库待补充列表(参见 03-knowledge/02-lifecycle 新增来源),过期条目会提示复核。RAG 不只是技术模块,而是知识运营流程的一部分。
设计取舍
| 取舍 | 原因 |
|---|---|
| 不用 BM25 | 中文 BM25 调参成本高,trigram + 同义词更直接 |
| 不用 reranker 模型 | 单店规模下 RRF 已够用 |
| 不用大模型重排 | 成本与延迟代价不值得 |
| 默认关闭向量 | embedding 渠道未稳定,且小库关键词够用 |
| 不做 chunking | 知识条目本身就短(< 200 字),无需切分 |
| 引用编号显式展示 | 牺牲一点回复美观度换可解释性 |
这些取舍让 RAG 在中小商家场景下够用、便宜、可维护。后续如果接入大型知识库或多店共享知识,再升级到向量主路 + reranker。
与其他模块的关系
RAG 与下面三个模块紧密协作:
- 意图分类(参见
02-conversation/01-intent-taxonomy):意图决定召回过滤(例如订单类不查 KB,直接调工具) - 知识库管理(参见
03-knowledge/):知识质量决定 RAG 效果上限 - Prompt 设计(参见
05-prompt/01-design-principles):Prompt 的"引用规则"决定召回结果如何被呈现
三者协同才能让 RAG 从"召回工具"升级为"客服内容工程"的核心环节。