让 AI 客服真能用的 3 个模块:情绪感知 + 意图识别 + Agent 工具链
Spring AI Alibaba + 智谱 GLM-4,一个能跑的完整方案
先说结论
网上 AI 客服教程一大堆,但基本都是"用户问→大模型答"的套壳 ChatGPT。真落到业务里,这种方案根本没法用。
原因很简单:
- 用户骂人了,AI 还在礼貌回复 → 用户更火
- 用户说"订单到哪了"和"东西坏了要退",走的是同一条处理逻辑 → 体验拉胯
- 没有工具调用,AI 只能说废话 → 什么业务都办不了
这篇文解决这三个问题。用 Spring AI Alibaba + 智谱 GLM-4,串起情绪感知、意图识别、Agent 工具链 三个模块,做一个真能用的 AI 客服。
一、架构长这样
REST + WebSocket] end subgraph 业务层 Session[会话管理] Router[对话编排] Response[响应构造] end subgraph AI能力层 Emotion[情绪感知] Intent[意图识别] Agent[Agent 工具链] RAG[RAG 知识库] end subgraph 数据层 DAL[MyBatis-Plus] DB[(PostgreSQL)] Redis[(Redis)] end User --> API --> Session --> Router Router --> Emotion & Intent & Agent & RAG Agent --> DAL --> DB Session --> Redis
技术栈:Spring Boot 3.5.7 + Spring AI 1.1.2 + 智谱 GLM-4-Flash + MyBatis-Plus + Redis + PostgreSQL
二、模块 1:情绪感知------别让 AI 惹用户更火
用户消息进来之后,先过情绪分析,输出评分和等级。后面的 Prompt 策略会根据情绪等级走不同分支。
java
@Service
public class ZhipuEmotionAnalyzer implements EmotionAnalyzer {
private final ChatModel chatModel;
@Override
public EmotionResult analyze(String text) {
String prompt = buildEmotionPrompt(text);
String response = chatModel.call(new Prompt(prompt))
.getResult().getContent().getText();
return parseEmotionResult(response);
}
private String buildEmotionPrompt(String text) {
return String.format("""
分析以下用户消息的情绪状态,返回 JSON 格式:
{"score": 0-100, "level": "CALM|IMPATIENT|ANXIOUS|ANGRY", "keywords": [...]}
用户消息:%s
""", text);
}
}
情绪等级怎么用?看这张映射表:
| 情绪等级 | 分值 | AI 怎么回 | 自动动作 |
|---|---|---|---|
| CALM | 75-100 | 标准客服语气 | 无 |
| IMPATIENT | 50-74 | 简洁直接,少废话 | 提示解决方案 |
| ANXIOUS | 25-49 | 先安抚,再回答 | 建议转高级客服 |
| ANGRY | 0-24 | 道歉,别争 | 自动转人工 |
核心逻辑:愤怒用户交给人工处理,别让 AI 继续火上浇油。
对应的 Prompt 策略:
java
public class EmotionPromptStrategy {
public String buildSystemPrompt(EmotionLevel level) {
return switch (level) {
case CALM -> "你是一个专业友好的客服助手,请耐心解答用户问题。";
case IMPATIENT -> """
用户可能有些着急,请:
1. 快速给出核心答案
2. 主动提供可能的解决方案
3. 避免过多解释
""";
case ANXIOUS -> """
用户可能感到焦虑,请:
1. 首先表达理解和关心
2. 快速响应
3. 提供明确的行动指引
4. 如问题复杂,主动建议转高级客服
""";
case ANGRY -> """
用户情绪激动,请:
1. 首先真诚道歉
2. 不要争论或辩解
3. 承认问题,表达改进意愿
4. 立即转接人工客服处理
""";
};
}
}
踩坑提醒:别用关键词匹配做情绪分析。我最早用"烦死了"→ANGRY,结果"太烦人了居然这么好用"也被判成愤怒,尴尬得不行。换成大模型分类之后准确率好很多。
三、模块 2:意图识别------不同问题走不同路
用户说"我的订单到哪了"和"东西坏了要退款",处理逻辑完全不同。意图识别就是干这个的。
java
@Service
public class ZhipuIntentClassifier implements IntentClassifier {
private final ChatModel chatModel;
@Override
public IntentResult classify(String message, ConversationContext context) {
String prompt = buildFewShotPrompt(message, context);
String response = chatModel.call(new Prompt(prompt))
.getResult().getContent().getText();
return parseIntentResult(response);
}
private String buildFewShotPrompt(String message, ConversationContext context) {
return """
你是一个客服意图分类器。请将用户消息分类:
意图类型:
- ORDER_QUERY:查询订单状态、物流信息
- REFUND:申请退款、取消订单
- RAG:询问产品功能、使用方法
- HUMAN_TRANSFER:明确要求转人工
- GENERAL:闲聊、问候
示例:
"我的订单到哪了" → {"intent": "ORDER_QUERY", "confidence": 0.95}
"东西坏了,要求退款" → {"intent": "REFUND", "confidence": 0.92}
"这个产品怎么用" → {"intent": "RAG", "confidence": 0.88}
"转人工" → {"intent": "HUMAN_TRANSFER", "confidence": 0.99}
用户消息:""" + message;
}
}
Few-shot 示例是关键。几个样本就能让 GLM-4 的分类准确率到 90%+,比你自己写 if-else 靠谱得多。
四、模块 3:Agent 工具链------让 AI 真正干活
识别出意图后,Agent 得去查订单、退退款、查知识库。Spring AI 的 @Tool 注解让这件事特别简单。
订单查询工具
java
@Component
public class OrderQueryTool {
private final IOrderInfoService orderInfoService;
@Tool(description = "查询用户订单信息。")
public String queryOrder(
@ToolParam(description = "订单号,不提供则返回最新订单") String orderId) {
String userId = ConversationContext.getUserId();
// 查询逻辑...
return formatOrder(order);
}
}
退款申请工具
java
@Component
public class RefundTool {
private final IRefundApplicationService refundService;
@Tool(description = "申请退款。仅退款或退货退款。")
public String applyRefund(
@ToolParam(description = "订单号") String orderId,
@ToolParam(description = "退款金额") BigDecimal amount,
@ToolParam(description = "退款原因") String reason,
@ToolParam(description = "退款类型:REFUND_ONLY/RETURN_AND_REFUND") String type) {
// 退款逻辑...
return "退款申请已提交";
}
}
底层查询:MyBatis-Plus Lambda
java
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo>
implements IOrderInfoService {
@Override
public OrderInfo getOrderEntityByOrderId(String orderId) {
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getOrderId, orderId);
return getOne(wrapper);
}
@Override
public List<OrderInfo> getOrderEntitiesByUserId(String userId) {
LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderInfo::getUserId, userId)
.orderByDesc(OrderInfo::getCreatedAt);
return list(wrapper);
}
}
退款校验逻辑(容易出问题的地方)
退款这块坑多------订单不存在、退款金额超了、重复申请,每一个都得拦住。用事务包一层:
java
@Override
@Transactional(rollbackFor = Exception.class)
public RefundApplication applyRefund(RefundApplication applyVO) {
// 1. 校验订单存在
OrderInfo order = orderInfoService.getOrderEntityByOrderId(applyVO.getOrderId());
if (order == null) throw new BizException(ResultCode.NOT_FOUND, "订单不存在");
// 2. 校验订单归属
if (!order.getUserId().equals(applyVO.getUserId()))
throw new BizException(ResultCode.FORBIDDEN, "无权操作此订单");
// 3. 校验订单状态(仅已支付或已收货可退款)
if (!"PAID".equals(order.getStatus()) && !"DELIVERED".equals(order.getStatus()))
throw new BizException(ResultCode.BUSINESS_ERROR, "订单状态不支持退款");
// 4. 校验无重复申请
if (hasPendingRefund(applyVO.getOrderId()))
throw new BizException(ResultCode.BUSINESS_ERROR, "已有待处理退款申请");
// 5. 校验退款金额
if (applyVO.getRefundAmount().compareTo(order.getTotalAmount()) > 0)
throw new BizException(ResultCode.PARAM_ERROR, "退款金额超限");
// 6. 创建退款申请
RefundApplication refund = new RefundApplication();
refund.setStatus(RefundStatus.PENDING.getCode());
save(refund);
return refund;
}
五、完整对话流程
一次请求的完整链路:
六、数据库表设计
5 张核心表,源码里有完整建表脚本 + 模拟数据:
| 表 | 用途 |
|---|---|
| conversation_session | 会话管理 |
| message | 消息记录(含情绪/意图标记) |
| order_info | 订单数据 |
| refund_application | 退款申请 |
| human_transfer | 转人工记录 |
七、关键配置
yaml
spring:
ai:
zhipuai:
api-key: ${ZHIPUAI_API_KEY}
chat:
options:
model: glm-4-flash
temperature: 0.7
max-tokens: 2048
emotion:
anger-threshold: 24
anxiety-threshold: 49
impatient-threshold: 74
session:
history-max-rounds: 20
history-ttl-minutes: 30
GLM-4-Flash 便宜,客服高频调用场景必须控成本。128K 上下文够用。
八、跟入门 Demo 比到底强在哪
| 能力 | 入门 Demo | 本文方案 |
|---|---|---|
| 情绪感知 | 无 | 实时评分 + 分级策略 |
| 意图识别 | 关键词匹配 | GLM Few-shot 多分类 |
| 工具调用 | 单个示例 | 完整工具链 |
| Prompt 策略 | 固定 | 按情绪动态切换 |
| 会话记忆 | 简单拼接 | Redis 缓存 + 滑动窗口 |
| 项目结构 | 单体 | 多模块 Maven |
| 数据访问 | 模拟 | MyBatis-Plus Lambda |
| 异常处理 | 无 | 统一异常体系 |
说几句掏心窝的话
这篇文写了挺久的。不是代码多难写,而是怎么把"情绪感知 + 意图识别 + Agent 工具链"串起来,我自己踩了很多坑。
比如情绪分析那个模块,最早我用关键词匹配------"烦死了"就判 ANGRY,结果用户说"太烦人了居然这么好用"也被判成愤怒。后来换成让大模型做分类,准确率才上来。这种细节文章里没法全展开,但源码里都有注释。
说个实话:光看这篇文,你大概率跑不起来。
不是文章写得不行,而是有些东西天然塞不进一篇文里------完整的项目目录结构怎么建、模块之间怎么引用、数据库初始化脚本怎么跑、application.yml 里那些配置项的坑......零零散散加起来比正文还多。
所以我推荐你拿到源码照着跑一遍。跑通的感受,和光看文章的感受完全不一样。
源码怎么拿
公众号 「亦暖筑序」 底部菜单 【获取源码】,完整的 Gitee 仓库直接拉。
源码里除了文章提到的这些,还有几个文章没展开的:
- 数据库初始化脚本(5 张表,带模拟数据,跑起来就能测)
- 完整的多模块 pom 依赖配置(不用自己一个个试版本了)
- 情绪分析/意图识别的完整 Prompt 模板(文章里只是核心片段,源码里是完整可运行的)
- Postman 测试集合(一键导入,所有接口直接测)
说白了吧,看完文章理解思路,拿到源码照着跑,这才是最省时间的路径。不拿源码硬看,你会卡在"这行代码放哪个模块"这种低级问题上浪费半天。
你现在在做什么类型的 AI 项目?评论区说一句,后面我写文章可以往那个方向靠。
想看后续怎么加 RAG 知识库和转人工功能的,关注等更新就行。