给200家连锁门店搭AI客服的真实经历:从想法到落地

0 阅读10分钟

从零搭一个连锁门店AI客服:RAG+Function Calling的工程实践

去年接手了一个挺有意思的项目——给一个全国快200家门店的连锁品牌做AI客服。技术上用的是RAG+Function Calling的组合,踩了不少坑,上线后效果还不错。这篇文章把整个过程复盘一下,重点放在技术决策和踩坑记录上。


一、为什么这个场景适合用AI

先看一下客服咨询的构成。我们抽样分析了三千多条真实对话:

问题类型占比
门店位置/营业时间/电话21%
优惠券/积分/会员等级25%
订单状态/退款进度18%
菜品成分/过敏原8%
食物品质投诉15%
退款争议/索赔7%
其他6%

前四类加起来占了72%。这些问题的共同特点是:有标准答案,不需要创造性判断,只是答案散落在不同的系统里——门店信息在门店数据库、订单在订单系统、积分在会员系统、菜品成分在知识库文档里。人工客服做的事情本质上就是"接收问题→去对应系统查→把查到的结果翻译成人话回复"。

这个"查询+翻译"的工作流,就是大模型最擅长的事情。15%的食品投诉和7%的退款争议还是需要人工处理——同理心、情绪安抚、赔偿金额判断这些能力AI目前还做不好。

所以技术方案的方向很明确:AI承接72%的标准化查询,人工专注28%的复杂场景。AI搞不定的自动转人工并把上下文带过去。


二、知识库搭建:RAG的细节决定成败

2.1 文档分块不是小事

客户提供了大概150份文档——员工SOP、菜品配方、会员政策、活动规则、退换货流程等等。格式五花八门:Word、PDF、在线文档链接,还有几张活动海报的照片。

分块策略我试了三版:

方案A:固定500 token一块 → 语义太碎,一个政策经常被切成两段
方案B:固定1000 token一块 → 冗余信息多,检索精度下降  
方案C:按段落边界切,500-800 token一块,相邻块重叠100 token → ✅

最终选C。额外花时间做了一件事:针对表格类文档单独写解析逻辑。会员等级表、优惠券规则表这类文档,固定分块很容易把表头和表体切断,导致检索回来的片段是残缺的表格——AI看不懂,回复自然就歪了。单独解析逻辑保证一个完整表格始终在一个块里。

2.2 Embedding模型怎么选

测了三款中文模型在客服场景下的表现:

模型Top5召回率单次检索延迟
text-embedding-3-large (OpenAI)91%45ms
bge-large-zh-v1.5 (开源)89%28ms
m3e-large (开源)86%35ms

OpenAI的效果最好但成本高。日均两万次Embedding调用,一个月下来API费用三千多。bge-large-zh本地部署在一台A10 GPU上,检索效果差不到两个点,边际成本基本为零。选了后者。

2.3 混合检索比纯向量检索准不少

裸用向量检索遇到一个问题。用户说"满30减8的那个券用不了"——向量检索把包含"券"的文档全召回了,几十条结果里真正对应用户问的那张特定优惠券的文档可能排在十几名开外。

加了一层Elasticsearch做关键词检索:"满30"和"减8"做精确匹配,跟向量检索的结果做交集。再过一个Cross-encoder Reranker(bge-reranker-v2)做二次排序,Top5召回率从87%提到了93%。

RAG做到这个程度,回答质量基本就稳定了。剩下的提升空间主要在知识库本身的完整性和准确性上。


三、Function Calling:不只是"说话",还要能"办事"

3.1 工具函数设计

用户问"我的积分有多少",AI不能只回答"您可以到APP里查看"——它应该直接告诉用户答案。这就需要AI能调用业务系统的API。

我定义了几个核心工具函数:

tools = [
    {
        "name": "query_nearby_stores",
        "description": "查询用户附近的门店,参数:city城市名, lat/lng经纬度",
        "endpoint": "GET /api/stores/nearby"
    },
    {
        "name": "query_order_status", 
        "description": "查询订单状态和物流信息,参数:order_id订单号 或 phone手机号",
        "endpoint": "GET /api/orders/status"
    },
    {
        "name": "query_member_profile",
        "description": "查询会员积分、等级、可用优惠券、储值余额",
        "endpoint": "GET /api/member/profile"
    },
    {
        "name": "request_refund",
        "description": "发起退款申请,参数:order_id, reason, amount",
        "endpoint": "POST /api/refund/create",
        "requires_user_confirm": True  # 需要用户二次确认
    }
]

3.2 函数粒度上的取舍

一开始我把函数拆得很细——查积分一个函数、查等级一个、查优惠券又一个。上线后发现一个问题:用户问"我账户里有什么",AI需要串行调用三四个函数,响应时间拉到8秒以上。用户在对话框干等着,体验很差。

改成粗粒度接口:query_member_profile一次返回积分、等级、优惠券列表、储值余额四部分数据。AI拿到全量数据后根据用户的问题自己裁剪展示。这样大部分查询只需一次函数调用,响应时间压到3秒左右。

3.3 危险操作的安全机制

退款、券核销、储值扣款这类操作,AI不能在后台静默执行。我的做法是:

  • 涉及资金操作的函数标注requires_user_confirm: True
  • AI调用这类函数前,先生成确认卡片(含金额、订单号),展示给用户
  • 用户回复明确的"确认"后,才真正发起API调用
  • 所有函数调用记录完整的审计日志——谁、什么时候、调了什么接口、参数和返回值,全部落库

这个审计日志上线后帮了大忙。有一次用户投诉"AI擅自给我退款了",查日志发现用户确实在对话里回复了"确认退款"三个字——虽然他说他"没仔细看就点了"。


四、转人工:最容易被低估的模块

AI客服和人工客服不是二选一,是协作关系。AI能力有边界,识别到边界并及时转人工,这个能力比AI自己能回答得多好更重要。

我的实现分了三条线:

第一条线:规则引擎强制转人工。

FORCE_TRANSFER_RULES = [
    {"trigger": "keyword", "words": ["投诉", "315", "食药监", "工商", "举报"], "action": "transfer_and_mark_sensitive"},
    {"trigger": "user_frustration", "pattern": "连续两次负面反馈", "action": "transfer"},
    {"trigger": "user_request", "words": ["转人工", "人工客服", "找你们领导"], "action": "transfer"},
]

这些场景下AI不要尝试继续处理,直接转。尤其是食品安全投诉,碰了立刻标记为敏感工单并通知值班主管。

第二条线:LLM自主判断。 每次生成回复时让LLM额外输出一个置信度评分(1-5分)。低于3分时在回复末尾自动附加转人工建议:"这个问题比较复杂,我帮您转接人工客服处理吧?"

第三条线:转接时携带上下文。 这是被客服主管评价为"最实用的功能"的部分。AI转接时自动生成对话摘要——用户是谁、问了什么、AI已经查了什么、为什么建议转人工。人工坐席接手后不用让用户重述一遍。摘要模板大概是:

[AI转接摘要]
用户ID: xxx
问题: 订单TK20260715001物流异常,显示已签收但用户称未收到
AI已操作: 查询订单状态(返回已签收),查询物流轨迹(显示7月14日18:30签收,签收人非本人)
建议转人工原因: 可能存在虚假签收,需人工联系快递公司核实

五、踩过的三个坑

5.1 知识库的"脏数据"比预想的多

客户说"我们文档很全",结果退换货政策有三个版本散落在不同文件夹,没人确认哪个是现行版本。十几家门店的营业时间跟实际不符。会员积分的计算规则在A文档和B文档里互相矛盾。

大模型的能力再强,喂进去脏数据,出来的回复一定是脏的。前期花了两周多专门做知识清洗——统一版本、修正错误、补齐缺失。这个过程枯燥但没法省。而且上线后也需要持续维护——门店信息变了、活动规则改了、菜品更新了,知识库要同步更新。

5.2 线上效果跟测试集有差距

意图识别模型在测试集上跑到96%,上线第一周真实场景只有88%。两个原因:

第一,测试集基于历史人工客服对话,但用户知道对面是AI后提问方式微妙变化——更随意、更口语化、更不完整。测试集里没有"那个""就那个嘛"这类表达。

第二,多意图混杂的比例比预期高。用户说"我买的鸡腿到了但是凉的能退吗"——同时触发"物流查询"和"退款"两个意图。

上线第一周密集收集了四百多个bad case,人工标注后重新训练。第二周准确率回到94%。之后保持每两周收集一次bad case做模型优化的节奏。

5.3 AI过度热情

大模型有个特点——几乎不会说"我不知道"。即使用户问"你们跟隔壁那家比哪个更实惠",AI也会努力分析。但这类回答对企业来说风险极高。

在Prompt里加了明确限制:

如果用户问题涉及以下任何一类,请直接回复"这个问题我暂时无法回答,建议您转人工咨询",不要尝试给出任何实质性答复:
- 竞品比较、价格预测
- 超出知识库覆盖范围的内容
- 医疗、法律、投资建议

同时在输出层用规则过滤——检测回复中是否包含竞品名称(维护了品牌黑名单)、是否包含确定性承诺词("保证""绝对""一定"触发复审)。两道防线上去后,风险回复从2.3%降到0.1%以下。


六、上线数据和几点总结

试运行一个月、正式运行三个月后的数据:

指标上线前上线后
AI自动处理率71%
首次响应平均28秒1.2秒
高峰期接通率63%96%
用户满意度4.1/54.3/5

接通率从63%提到96%是最核心的改善。以前高峰期将近四成的用户找不到人,这部分流失的影响很难精确量化,但一定比AI系统的开发成本大得多。

几个核心经验:

  1. 先分析数据再动手。花两周翻聊天记录做分类统计,比上来就写Prompt有价值得多。数据会告诉你值不值得做、先做哪个场景。
  2. 知识库投入是地基,不能省。文档清洗花了项目三分之一的时间,但检索准确率很大程度上就是靠这一步撑起来的。
  3. 从内部测试开始。让客服主管和店长先内部用一周,能发现大量公网暴露会很糟糕的问题。
  4. AI客服是持续运营的产品,不是一次交付的项目。业务规则变、用户表达方式变、模型也要跟着变。每两周收集bad case做优化是基本节奏。
  5. 别让AI伪装人类。上来就说"我是AI助手",用户对诚实AI的容忍度远高于对伪装者的。

技术方案参考了一些企业AI落地的实践案例(比如 zhuatech.cn 上关于RAG和Agent的架构文档),有兴趣的可以自己去看。