Skip to content

AI智能客服开发

一、需求背景

刚开始我们项目使用了 WebSocket 技术搭建人工客服系统,但是人工客服无法做到 7 * 24 小时在线。用户咨询客服的时间不固定,有些用户可能在半夜咨询,这时候人工客服不在线,用户体验就不太好。

后面甲方问我们有没有更好的解决方案。刚好当时 AI 应用比较火,我们就给客户推荐开发一个 AI 智能客服模块,让用户可以随时访问 AI 客服。

AI 客服主要解决两个问题:

  • 用户可以随时咨询,不受人工客服在线时间限制。
  • 常见问题由 AI 自动回答,减少人工客服压力。

当然,有些 AI 回答不了的问题,或者涉及复杂业务处理的问题,系统也支持直接转人工客服。

二、框架及模型选型

1. AI 框架选型

项目里我们先做了 AI 框架选型。当时主要考虑了两个方案:

框架特点
LangChain4j出现比较早,生态比较丰富;不是 Spring 项目也能接入;JDK 8 以上即可使用
Spring AI Alibaba和 Spring 项目集成比较方便,适合 Spring Boot 项目

我们项目本身使用的是:

  • JDK 17
  • Spring Boot 3.3 以上版本

所以最终选择了 Spring AI Alibaba。它和 Spring Boot 集成更自然,项目里接入成本也比较低。

2. 大模型选型

大模型这块主要有两种方案:

  • 本地部署大模型
  • 使用第三方大模型平台

本地部署的好处是数据可控,但是服务器成本、运维成本和模型维护成本都比较高。客户希望尽可能减少成本,所以我们最终选择了第三方大模型平台。

我们接入的是阿里百炼平台的通义千问大模型。当时选择它主要有几个原因:

  • 价格相对便宜。
  • 和阿里生态组件集成比较方便。
  • 支持 Function Calling。
  • 支持多模态能力。

当时评估的价格大概是:

类型价格
文本输入0.0018 元 / 千 Tokens
文本输出0.0069 元 / 千 Tokens
图片、视频输入0.0033 元 / 千 Tokens
图片、视频输出0.0626 元 / 千 Tokens

三、整体实现思路

我们主要从几个方面实现 AI 智能客服能力。

1. 给 AI 赋予角色

这部分主要通过 Prompt 提示词工程来实现。

我们会在提示词里告诉 AI:

  • 它是什么角色。
  • 它能回答什么问题。
  • 它不能回答什么问题。
  • 回答时应该使用什么语气。
  • 没有依据时不能乱编。
  • 遇到复杂问题时需要建议转人工。

比如智能客服的定位是:

text
你是一个电商系统的 AI 智能客服。
你需要根据知识库内容、用户上下文和业务查询结果回答用户问题。
不能编造订单状态、退款结果、物流信息。
如果无法确定答案,请提示用户转人工客服。

2. 做上下文记忆

AI 客服不是只回答单轮问题,用户经常会连续追问。

比如用户第一句问:

text
我的订单为什么还没发货?

第二句继续问:

text
那还能取消吗?

第二句里面没有明确说是哪一个订单,所以后端需要把前面的对话上下文带给模型,让模型知道用户还在问上一个订单。

所以我们做了会话存储和消息存储,让 AI 能结合上下文回答问题。

3. 使用 Function Calling / MCP 调用业务能力

有些问题不是模型自己能回答的,需要调用我们系统里的业务接口。

比如:

  • 查询订单状态。
  • 查询物流信息。
  • 查询退款进度。
  • 查询售后单状态。
  • 判断是否支持退货。

这些数据都在我们自己的业务系统里,模型本身不知道。所以我们通过 Function Calling / MCP 机制,把一些安全的只读业务能力封装成工具,让模型在需要时调用。

这里有一个原则:高风险操作不能直接交给 AI 自动执行。比如退款、取消订单、修改地址这类操作,必须走明确的业务校验和用户确认,不能让模型一句话就直接执行。

4. 使用 RAG 减少模型幻觉

为了减少模型胡编,我们做了 RAG,也就是信息检索增强。

我们准备了一些知识库文档,主要包括:

  • 常见问题 FAQ。
  • 售后政策。
  • 退换货规则。
  • 订单说明。
  • 平台使用说明。
  • 客服标准话术。

文档格式主要是 PDF。处理流程是:

text
PDF 文档

文档加载器加载到内存

文档分割器按段落切分

Embedding 模型生成向量

写入 Qdrant 向量数据库

用户提问时检索相关片段

把检索结果和用户问题一起交给大模型回答

我们这边业务不算特别复杂,所以第一版文档切分没有做得太细,主要是按段落进行分割。后面根据效果再继续调优。

四、Function Calling 和 MCP 概念梳理

在 AI 智能客服项目里,Function Calling 和 MCP 都是为了让大模型具备“调用外部能力”的能力。简单说,大模型本身只擅长理解和生成文本,但是它不知道我们系统里的实时订单状态、物流信息、退款进度,也不能自己直接操作数据库。所以需要通过工具调用机制,把我们后端已有的业务能力提供给模型使用。

1. Function Calling 是什么

Function Calling 可以理解为:我们提前告诉模型系统里有哪些函数可以用,每个函数叫什么、需要哪些参数、能做什么。用户提问时,模型判断自己是否需要调用某个函数。

比如用户问:

text
我的订单 202406080001 到哪里了?

模型自己不知道这个订单状态,它会判断需要调用查询订单接口,然后生成类似下面的调用意图:

json
{
  "functionName": "queryOrderStatus",
  "arguments": {
    "orderNo": "202406080001"
  }
}

后端收到这个结果后,不是让模型直接查数据库,而是由 Java 后端去调用真实的订单查询接口。查询到结果后,再把订单状态返回给模型,让模型组织成用户能听懂的话。

整个流程大概是:

text
用户提问

模型判断需要调用工具

模型返回函数名和参数

Java 后端校验参数和权限

Java 后端调用真实业务接口

把业务结果交给模型整理

返回用户

Function Calling 的重点是:模型只负责判断要不要调用、调用哪个函数、参数是什么;真正执行动作的一定是后端代码。

2. Function Calling 适合解决什么问题

在智能客服里,Function Calling 比较适合处理实时业务数据查询,比如:

  • 查询订单状态。
  • 查询物流信息。
  • 查询退款进度。
  • 查询售后单状态。
  • 查询用户优惠券。
  • 判断订单是否支持取消。
  • 判断商品是否支持退货。

这些问题不能只靠知识库回答,因为知识库里只有规则,没有用户自己的实时数据。

3. MCP 是什么

MCP 全称是 Model Context Protocol,可以理解为一种更标准化的“模型连接外部工具和数据源的协议”。

Function Calling 更像是某个模型平台提供的一种函数调用能力,而 MCP 更像是一套通用协议。它把外部系统封装成 MCP Server,让模型或智能体通过统一方式访问工具、资源和上下文。

通俗一点说:

  • Function Calling 更像是“我在代码里给模型注册几个函数”。
  • MCP 更像是“我把一个系统封装成标准工具服务,模型可以按协议去调用”。

比如我们可以把订单系统、物流系统、知识库系统分别封装成 MCP Server:

text
AI 智能客服

MCP Client

订单 MCP Server / 物流 MCP Server / 知识库 MCP Server

真实业务系统

这样智能体不用关心底层系统具体怎么实现,只需要按 MCP 协议调用对应工具。

4. MCP 主要包含哪些概念

MCP 里常见的几个概念可以这样理解:

概念通俗理解
MCP Host使用 MCP 能力的宿主,比如 AI 客服应用
MCP Client负责和 MCP Server 通信的客户端
MCP Server对外提供工具、资源和能力的服务
Tools可被模型调用的工具,比如查询订单、查询物流
Resources可被读取的数据资源,比如文档、配置、文件
Prompts可复用的提示词模板

在项目里,AI 智能客服本身可以看作 MCP Host,后端集成 MCP Client,订单查询、物流查询、知识库查询这些能力可以封装成 MCP Server 提供的 Tools。

5. Function Calling 和 MCP 的区别

对比项Function CallingMCP
定位模型调用函数的能力模型连接外部工具和数据源的协议
使用方式在模型请求里声明函数通过 MCP Server 暴露工具和资源
适用范围比较适合少量工具调用更适合多个系统、多个工具统一接入
标准化程度更依赖具体模型平台更强调统一协议
项目复杂度接入较简单架构更清晰,但接入成本更高

项目早期功能不复杂时,Function Calling 接入更快。后面工具越来越多,比如订单、物流、售后、工单、知识库都要接入时,用 MCP 统一管理会更清晰。

6. 在智能客服项目里我是怎么用的

我们在第一版里主要使用 Function Calling,因为项目初期工具数量不算太多,主要是订单查询、物流查询、退款进度查询和售后状态查询。

每个工具都会定义清楚:

  • 工具名称。
  • 工具用途。
  • 入参字段。
  • 出参字段。
  • 权限要求。
  • 超时时间。
  • 失败兜底。

比如订单状态查询工具:

text
工具名称:queryOrderStatus
工具作用:根据订单号查询订单状态
入参:orderNo
出参:orderStatus、payStatus、deliveryStatus、canCancel
限制:只能查询当前登录用户自己的订单

后端执行工具调用前,一定会做权限校验。比如用户只能查询自己的订单,不能随便传一个订单号就查到别人的订单。

后面工具数量变多以后,我们也评估过把这些能力进一步 MCP 化。比如订单服务提供订单 MCP Server,物流服务提供物流 MCP Server,AI 客服统一通过 MCP Client 调用。这样后续其他 AI 应用也能复用这些工具能力。

7. 工具调用的安全边界

无论是 Function Calling 还是 MCP,都不能让模型绕过后端业务规则。

我在项目里主要做了这些限制:

  • 只开放必要工具,不把所有接口都暴露给模型。
  • 第一版只开放查询类工具,写操作必须人工确认。
  • 工具调用前必须校验用户身份和权限。
  • 参数必须由 Java 后端再次校验,不能直接相信模型生成的参数。
  • 工具调用要设置超时时间,避免一直阻塞。
  • 工具调用失败时要返回明确兜底。
  • 敏感字段返回给模型前要脱敏。
  • 所有工具调用都要记录日志,方便排查。

比如退款这种操作,模型最多只能判断“是否满足申请退款条件”,然后引导用户去页面确认,不能直接替用户发起退款。

这块我的理解是:模型可以做助手,但不能做最终执行者。真正改变业务状态的操作,必须由后端规则、用户确认和权限系统共同控制。

五、优化设计

调用大模型会耗时,也会产生费用。所以在真正调用模型前,我们做了几层优化。

1. 敏感词过滤

在调用模型前,先做敏感词过滤。

我们接入了一个叫 Sensitive Word 的敏感词组件,它本身自带一些敏感词规则。但默认词库不够,所以我们又通过数据库管理了一套自定义敏感词库。

处理方式是:

  • 数据库存储自定义敏感词。
  • 后台支持新增、修改、删除敏感词。
  • 敏感词变更后同步到 Sensitive Word 组件。
  • 用户输入命中敏感词时,直接拦截或提示修改。

这样可以避免一些不合规内容直接进入模型,也能减少无效调用。

2. FAQ 优先匹配

很多用户问的问题是高频问题,比如:

  • 怎么申请退款?
  • 订单多久发货?
  • 如何修改收货地址?
  • 退货运费谁承担?

这些问题没有必要每次都调用大模型。我们做了一套 FAQ 管理功能,把常见问题和标准答案维护在数据库中。

为了让用户输入能更准确地匹配 FAQ,我们没有只做关键词匹配,而是结合了向量数据库。

FAQ 写入时:

text
FAQ 问题和答案

保存到业务数据库

问题文本向量化

写入 Qdrant

用户提问时:

text
用户问题

先做向量化

到 Qdrant 匹配相似 FAQ

命中 FAQ 时直接返回标准答案

没有命中 FAQ 时再走 RAG 和模型调用

这样可以减少频繁调用大模型的问题,既提升响应速度,也节省成本。

六、模型上下文存储

为了让模型调用时能记住上下文,Spring AI Alibaba 提供了几种存储方式:

  • 内存存储
  • 数据库存储
  • Redis 存储

我在项目里使用的是 Redis 存储。Redis 主要负责保存当前会话的短期上下文,让模型在连续对话时能拿到最近几轮消息。

同时,为了支持用户下次进入系统后还能看到历史对话,我们又把完整会话记录落到了数据库中。

也就是说:

  • Redis:保存短期上下文,服务模型多轮对话。
  • MySQL:保存完整聊天历史,服务前端历史记录展示。

七、多轮对话存储

这个项目里,多轮对话是单独设计的,不是简单把所有聊天内容都临时放在内存里。因为用户下次进入系统时,需要能看到之前的会话记录;同一个会话里继续提问时,模型也需要知道前面聊过什么。

我把会话存储拆成两张核心表。

1. 会话表

会话表主要记录一次对话主题。

字段说明
id会话 ID
user_id用户 ID
title会话标题
status会话状态
created_at创建时间
updated_at更新时间

2. 消息表

消息表记录每一轮用户和 AI 的消息。

字段说明
id消息 ID
conversation_id会话 ID
user_id用户 ID
role消息角色,比如 user、assistant、system
content消息内容
references引用的知识库来源
input_tokens输入 Token
output_tokens输出 Token
latency_ms本轮耗时
created_at创建时间

前端每次新建聊天时,后端会先创建一条会话记录,并返回 conversationId。后续用户继续在这个聊天窗口里提问,前端都会带上这个 conversationId,后端就能知道这次问题属于哪一段会话。

每轮对话处理时,我会先保存用户问题,再调用 RAG 和大模型。模型返回后,再保存 AI 回答。这样即使模型调用中间失败,也能看到用户问过什么,以及失败原因。

用户下次打开系统时,前端会先调用会话列表接口:

text
GET /api/conversations

后端按 user_id 查询这个用户自己的会话列表,返回标题、最后更新时间和最近一条消息。

用户点击某个会话后,再调用详情接口:

text
GET /api/conversations/{conversationId}/messages

后端根据 conversationId 查询消息表,把历史对话按时间顺序返回给前端展示。

这里必须校验 conversationId 是否属于当前登录用户,不能只凭会话 ID 查询,否则会有越权风险。

3. 多轮上下文怎么传给模型

多轮上下文传给模型时,我没有把全部历史消息都塞进去。因为历史越长,Token 成本越高,响应也越慢。

我的处理方式是:

  • 只取最近几轮关键对话。
  • 对更早的历史做摘要。
  • 当前问题永远优先级最高。
  • RAG 检索结果优先级高于历史闲聊内容。
  • 超过 Token 限制时,优先丢弃低价值历史。

比如用户第一轮问:

text
支付回调失败怎么排查?

第二轮继续问:

text
那 401 这种情况呢?

第二轮的问题本身不完整。后端会加载上一轮上下文,知道这里的 401 还是在支付回调排查场景下,然后再去检索接口鉴权和支付回调相关文档。

这里我遇到过一个问题:历史上下文带太多时,模型容易被前面的问题带偏。后来我做了上下文选择规则,只保留和当前问题相关的最近几轮,同时提示词里明确要求优先回答当前用户问题。这样多轮对话既能连续,又不容易跑题。

八、RAG 调优

1. 文档切片太大

早期一个片段接近两三千字,模型输入 Token 很多,响应慢,而且回答经常抓不到重点。

后来改成按 300 到 800 字左右切片,并保留一小段上下文重叠,效果明显好一些。

2. 文档切片太碎

有些制度条款被切开后,模型只看到半句话,回答不完整。

后来我们按标题层级和段落边界切分,尽量保证一个片段表达完整含义。

3. 错误码和接口名命中不好

只用向量检索时,错误码、接口名这类内容命中效果不稳定。

比如用户问某个错误码,向量相似度不一定高。后来加了关键词检索和标签过滤,把向量检索和关键词检索结合起来,错误码、接口名、订单状态这类问题命中率提升比较明显。

4. 检索结果太多会干扰模型

一开始为了怕漏资料,会塞很多片段给模型,结果模型反而容易混淆。

后来控制 Top K 数量,并设置相似度阈值,只传最相关的几段。

九、模型响应时间优化

上线测试时,最明显的问题是响应慢。慢的地方主要有三个:

  • RAG 检索。
  • 业务工具调用。
  • 大模型生成。

我做了几类优化。

1. 接入流式响应

前端使用 SSE 接收模型输出,后端边拿到模型内容边推给前端。

虽然总耗时没有完全消失,但用户能更快看到首字,体验提升比较明显。

2. 控制提示词长度

RAG 只传最相关的几个片段,不再把整篇文档塞进去。

多轮会话也不直接拼接全部历史,而是只保留最近几轮和必要摘要。

3. 控制输出长度

普通知识问答要求简洁回答,复杂排障问题才允许输出更长步骤。

这样既降低耗时,也减少 Token 消耗。

4. 高频问题做缓存

像报销标准、请假流程、接口鉴权说明这类高频问题,会把相似问题和答案缓存起来。

命中缓存时不再调用模型。

5. 模型分层

简单意图识别、问题分类用成本更低的小模型或规则判断。

真正需要总结、生成、归纳时,再调用主模型。

十、成本控制

AI 应用成本主要来自 Token。输入越长、输出越长、调用次数越多,费用越高。

我当时做了几个控制点。

1. 限制输入长度

用户问题超过长度会提示用户缩短,避免一次请求带入大量无关内容。

2. 限制输出长度

提示词里要求普通回答控制在一定长度内,排障类问题才允许分步骤详细说明。

3. 减少上下文

多轮对话不会无限拼接历史记录,而是保留最近对话和摘要。

4. 缓存高频问题

缓存命中后可以直接返回,不走模型调用。

5. 记录成本明细

每次调用都会记录:

  • 用户。
  • 会话。
  • 模型。
  • 输入 Token。
  • 输出 Token。
  • 耗时。
  • 是否成功。

后面可以按用户、部门、功能统计消耗。

6. 确定性逻辑不用模型

权限判断、参数校验、路由判断、是否为空、是否超长,这些全部用 Java 代码处理。

模型只负责理解、总结和生成。

十一、边界状态和兜底

AI 项目最怕模型一本正经地答错,所以我对边界状态做了很多兜底。

边界情况处理方式
用户问题为空直接返回“请输入要咨询的问题”,不调用模型
用户问题过长提示用户缩短问题,避免浪费 Token
知识库没有命中可靠资料返回“当前知识库中没有找到明确依据”
检索结果相似度低不进入大模型回答流程,或者提示用户换个问法
模型接口超时返回友好提示,并记录超时日志
模型输出 JSON 格式错误先做一次格式修复重试,失败后走兜底
用户访问无权限知识库直接拦截,相关文档不会进入检索范围
工具调用失败明确提示“业务数据查询失败”
返回内容包含敏感字段后端做脱敏处理
模型回答不了直接转人工客服

敏感字段包括:

  • 手机号
  • 身份证号
  • Token
  • 密钥
  • 用户隐私信息

十二、面试中可以这样总结

我做的这个 AI 智能客服项目,核心不是简单调用大模型接口,而是围绕真实客服场景做了一套完整的智能体流程。

整体上,用户提问后,系统会先做敏感词过滤和 FAQ 匹配。FAQ 命中时直接返回标准答案;FAQ 没命中时,再走 RAG 检索知识库,把相关文档片段和用户问题一起交给大模型生成回答。

对于需要查询业务数据的问题,比如订单状态、物流信息、退款进度,我通过 Function Calling / MCP 把业务只读接口封装成工具,让模型在需要时调用。

为了支持多轮对话,我用 Redis 保存短期上下文,用 MySQL 保存完整历史会话。用户下次进入系统时,可以看到上次的聊天记录;同一个会话里继续追问时,模型也能结合上下文回答。

项目中我重点解决了几个问题:模型幻觉、RAG 命中不准、响应慢、Token 成本高、模型输出格式不稳定和人工客服兜底。最终通过 RAG、FAQ 缓存、敏感词过滤、流式响应、上下文摘要、成本统计和转人工机制,让这个 AI 客服系统能够比较稳定地落地。