AI 客服

用 RAG 做电商客服:怎么用知识库边界把售后强制转人工

客服 AI 最危险的地方,不是它答不上来,而是它答得太顺口。一个「已读乱回」的机器人最多让人哭笑不得;一个在退款、赔偿、时效承诺上张口就来的机器人,会直接把公司拖进说不清的纠纷里。rag-cs-bot 这个 Demo 里最值得拿出来讲的设计,不是它怎么把 AI 接进去,而是它怎么用知识库的边界,从机制上把售后挡在 AI 之外。

背景:99+ 条消息,和 24h / 48h 的考核#

先摆事实。这个 Demo 的起点是真实的运营压力:店铺客服每天 99+ 条客户消息,而平台的回复率考核挂在 24h / 48h 这两个时限上。消息量和时限一叠加,客服工作的性质就变了——它不再是「把问题解决掉」,而首先变成「在时限内把每一条都接住」。

这类场景恰好是 LLM 擅长的:客户问的绝大多数问题是重复的、有标准答案的。项目把这些问题拆成两类:

类型典型问题处理方式
售前产品、发货、物流、价格、尺码、咨询AI 基于知识库自动回复
售后退换、退款、投诉、赔偿转人工处理,不喂给 AI

全链路长什么样#

仓库里已经跑通的链路是这样的:本地 Dify + RAG 知识库 → 客服聊天前端(Flask 后端代理)→ 浏览器自动化(UI-TARS)读取消息 → 调 Dify API 自动回复 → 回填。画成图就是:

text
TikTok Shop 买家消息
        │  (浏览器自动化读取)
        ▼
   UI-TARS / cs_web ──► Dify Chatflow API
                              │
                    ┌─────────┴─────────┐
                    ▼                   ▼
               意图识别            知识库检索(RAG)
               ├─ 售后 → 转人工      └─ 售前 → LLM 生成回答
               └─ 售前 → 知识库回答
                              │
                              ▼
                       回复买家消息

对应到仓库目录:

tree
rag-cs-bot/
├── README.md                 # 项目说明
├── 售前AI客服搭建操作指南.md   # Dify 侧完整搭建步骤
├── cs_web/                   # 客服聊天前端 + 后端代理
│   ├── app.py                # Flask 后端:转发到 Dify API + 去 DeepSeek 思考
│   └── static/index.html     # 聊天界面
├── ui_tars_demo/             # UI-TARS 浏览器自动化 Demo
│   ├── tars_demo.py          # 读取浏览器消息框 → 调 Dify API → 回填回复
│   └── demo_msg.html         # 模拟消息框页面
└── dify/                     # Dify 开源平台源码(langgenius/dify,已去除运行时产物)

核心设计:知识库只放售前#

整篇文章如果只留一段,应该是这一段:

核心设计

知识库只放售前内容,售后内容不喂给 AI,从而强制售后转人工。

这句话是 README 里原样写着的「关键设计」。它的巧妙之处在于,它把一条业务规则(售后必须人工处理)翻译成了一条数据规则(知识库里没有售后内容)。而这个翻译带来三个连锁效果:

  • 模型没有可引用的依据。RAG 的输出天然被检索结果约束;知识库里没有退款政策,模型就检索不到任何可以拿来承诺的条款。
  • 失败是可见的。如果 AI 面对售后问题还硬答,说明意图分流那一层出了问题——这是一个可以被发现、被修复的 bug,而不是一个藏在话术里的隐患。
  • 维护成本更低。售后政策会变,而且经常涉及金额与责任认定。把它们排除在知识库之外,等于把「谁来更新知识库」这个问题也一并简化了。

为什么不能靠提示词阻止模型谈退款#

很自然会想到另一种做法:知识库照放,然后在提示词里写一句「遇到退款问题请转人工」。这条路在 Demo 里能跑,在生产里靠不住,理由有三层:

做法失效方式
提示词里禁止谈退款用户的问法千变万化,「这个钱什么时候能到我账上」可能就不会命中这条禁令;而且上下文一长,指令的约束力会衰减
知识库里放售后条款一旦被检索到,模型就会引用它,并且倾向于给出比条款更「热情」的承诺
让模型自己判断该不该转人工把「转不转人工」变成一个不确定的决策,而这恰恰是最不该不确定的地方

关键区别在于:提示词是「建议」,知识库内容是「证据」。模型可以无视建议,但它没法凭空造出一份不存在的证据(造出来就是幻觉,而幻觉是能被检测和拦截的)。所以把约束放在数据层,比放在提示词层可靠得多——这就是「从机制上强制转人工」的含义。

意图分流:售前走 AI,售后转人工#

分流发生在 Dify 侧。README 的操作指南里,这一步被定义为一个「售前 AI / 售后人工」的 Chatflow 工作流:先做意图识别,然后按结果分叉——售前走向知识库检索,售后直接转人工。

mermaid 图 1 · 意图分流:售前走 AI,售后转人工
flowchart TD M["买家消息"] --> T["UI-TARS 读取浏览器消息框"] T --> W["cs_web/app.py(Flask 代理)"] W --> D{"Dify Chatflow:意图识别"} D -->|"售前:产品 / 发货 / 物流 / 价格 / 尺码"| K["知识库检索(RAG)"] K --> A["LLM 生成回答"] D -->|"售后:退换 / 退款 / 投诉 / 赔偿"| H["转人工,不喂给 AI"] A --> C["clean_answer():剥离思考过程"] C --> R["回填消息框,回复买家"]
分流发生在 Dify 的 Chatflow 里。售后那条线不是“AI 答得差一点”,而是根本不进检索:知识库里没有售后内容,模型拿不到任何可以拿来承诺的条款。

这一层与知识库边界是两道独立的闸门,而不是一道:

闸门位置失效时的表现
意图识别分流Chatflow 的判断节点问题被送进售前分支,但检索不到内容
知识库边界语料本身即使被送进售前分支,也拿不到可承诺的条款
mermaid 图 2 · 两道闸门:任何一次售后问询都要连闯两关
flowchart TD Q["售后类问询"] --> G1{"闸门 1:意图识别分流"} G1 -->|"判成售后"| H["转人工"] G1 -->|"误判成售前"| G2{"闸门 2:知识库里有没有售后条款"} G2 -->|"没有(设计如此)"| N["检索不到可承诺的条款"] N --> H G2 -->|"有(不该出现)"| BAD["模型引用条款并给出承诺"]
第一道是 Chatflow 的判断节点,第二道是语料本身。冗余是有意的 —— 只有两道同时失效,一次售后问询才会变成一句 AI 承诺。

两道闸门同时存在,意味着任何一次售后相关的问询都要连闯两关才会变成一句「AI 承诺」。这种冗余在客服场景里是值得的:省下的每一次误答,都比多花的那点算力贵。

剥掉思考过程:clean_answer()#

链路里有一个很接地气的问题:接进来的推理模型(DeepSeek 一类)会把思考过程一起返回,格式通常是 ...</think>回答 或 ...</thinking>回答。如果不处理,客户看到的回复里会带着一大段自言自语。

cs_web/app.py 里的 clean_answer() 用一个正则把这段剥掉,只保留最终回复,两种格式都覆盖。这一环之所以必须由后端代理来做,而不是在前端过滤,是因为代理层是链路上唯一一个「所有回复都必须经过」的位置:

text
买家消息 → cs_web/app.py(Flask 代理)
              ├── 转发到 Dify API
              ├── clean_answer():剥离思考过程
              └── 返回干净回复

把清洗放在必经之路上,而不是放在每个调用点,是这类系统能长期维护的关键。否则每加一个新的入口,都要重新记得「这里也要去一次思考过程」。

浏览器自动化那一环#

ui_tars_demo/tars_demo.py 负责把 AI 的回复送回给买家:读取浏览器消息框中的买家消息 → 调 Dify API 生成回答 → 回填到消息框。仓库里还带了一个 demo_msg.html 作为模拟消息框页面,方便在没有真实店铺的情况下把这一环跑通。

「模拟页面」这个细节值得单独提一句:它让整条链路可以被分段验证——前端、代理、Dify 工作流、浏览器自动化,每一段都能单独跑。相比之下,一上来就对着真实店铺联调,出问题时你连是哪一段坏了都分不清。

README 的声明里写得很清楚:本项目是技术 Demo,生产环境接入 TikTok 需使用官方开放平台 API 或桥接服务。浏览器自动化适合验证链路,不适合当作长期的生产接入方式。

索引卡住的坑:连接池耗尽#

仓库专门留了一节「关键踩坑经验」给二次开发的人,其中一条很有代表性:Dify 知识库索引会因 SQLAlchemy 连接池耗尽而停滞,需要控制并发、串行处理文档。

这类问题的麻烦之处在于症状:任务不报错,只是一直停在「索引中」。你以为它在慢慢跑,其实它已经卡死了。所以处理方式不是「多等等」,而是从提交侧就不给连接池压力:

  • 控制并发。文档不要一次性全部丢进去。
  • 串行处理。一份处理完再提交下一份,牺牲速度换稳定性。
  • 把「建知识库」当成有状态的运维动作,而不是一次性的初始化脚本——它会失败,失败后要能接着跑。

另外两条踩坑经验也一并记在这里:一是思考过程的剥离(见上一节),二是「知识库只放售前」这条核心手段本身。三条经验合起来看,恰好覆盖了这类项目最容易出问题的三个位置:模型输出、基础设施、业务边界。

跑起来需要什么#

本地起 Dify 用的是官方 compose:

bash
cd dify/docker
docker compose up -d
# 访问 http://localhost:80,完成管理员初始化

然后按操作指南接入模型(DeepSeek / 通义千问 / 智谱)、建售前知识库、搭「售前 AI / 售后人工」分流的 Chatflow。前端代理需要两个环境变量:

变量作用默认值
DIFY_BASE_URLDify API 地址http://localhost/v1
DIFY_API_KEYChatflow 应用 API Key需要自己配置
bash
cd cs_web
pip install -r requirements.txt   # flask, requests
python3 app.py
# 访问 http://localhost:8000

仓库里的 dify/ 目录是 langgenius/dify 的开源平台源码(保留其原始 LICENSE),已经去掉了运行时产物,用于本地私有化部署。客服 Demo 部分与 Dify 源码各自遵循原项目的许可。

这套方案的边界#

把它当 Demo 看,它解决了一个真问题;把它当生产方案看,还有几件事要先想清楚:

  • 接入方式。浏览器自动化能验证链路,但生产环境应当走官方开放平台 API 或桥接服务。
  • 转人工之后的闭环。「转人工」不是一个句号,人工接手的时效、话术、记录都需要另外一套流程来兜。
  • 知识库的更新责任。价格、尺码、物流时效都会变,谁在什么时候更新知识库,决定了这套系统的保质期。
  • 考核指标的口径。24h / 48h 回复率算的是「有没有回」,而不是「回得对不对」——用自动化满足前者的时候,别把后者丢掉。

小结#

rag-cs-bot 最值得借鉴的,不是「用 Dify 接了 RAG」这个动作,而是它对能力边界的处理方式:不是告诉模型「这件事你不能做」,而是让它在数据里根本找不到做这件事的材料。前者是请求,后者是机制。

这个思路可以直接迁移到别的场景:凡是「一旦答错就不可挽回」的问题类型,都不该交给模型自由发挥,而应该在检索层就把它排除掉。换句话说,限制一个 AI 系统最可靠的方式,是控制它能看到什么。