让 AI 客服真能用的 3 个模块:情绪感知 + 意图识别 + Agent 工具链

0 阅读7分钟

让 AI 客服真能用的 3 个模块:情绪感知 + 意图识别 + Agent 工具链

Spring AI Alibaba + 智谱 GLM-4,一个能跑的完整方案


先说结论

网上 AI 客服教程一大堆,但基本都是"用户问→大模型答"的套壳 ChatGPT。真落到业务里,这种方案根本没法用。

原因很简单:

  • 用户骂人了,AI 还在礼貌回复 → 用户更火
  • 用户说"订单到哪了"和"东西坏了要退",走的是同一条处理逻辑 → 体验拉胯
  • 没有工具调用,AI 只能说废话 → 什么业务都办不了

这篇文解决这三个问题。用 Spring AI Alibaba + 智谱 GLM-4,串起情绪感知、意图识别、Agent 工具链三个模块,做一个真能用的 AI 客服。


一、架构长这样

graph TB
    subgraph 接口层
        User[用户终端]
        API[ai-csr-api<br/>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 策略会根据情绪等级走不同分支。

@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 怎么回自动动作
CALM75-100标准客服语气
IMPATIENT50-74简洁直接,少废话提示解决方案
ANXIOUS25-49先安抚,再回答建议转高级客服
ANGRY0-24道歉,别争自动转人工

核心逻辑:愤怒用户交给人工处理,别让 AI 继续火上浇油。

对应的 Prompt 策略:

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:意图识别——不同问题走不同路

用户说"我的订单到哪了"和"东西坏了要退款",处理逻辑完全不同。意图识别就是干这个的。

@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 注解让这件事特别简单。

订单查询工具

@Component
public class OrderQueryTool {

    private final IOrderInfoService orderInfoService;

    @Tool(description = "查询用户订单信息。")
    public String queryOrder(
            @ToolParam(description = "订单号,不提供则返回最新订单") String orderId) {
        String userId = ConversationContext.getUserId();
        // 查询逻辑...
        return formatOrder(order);
    }
}

退款申请工具

@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

@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);
    }
}

退款校验逻辑(容易出问题的地方)

退款这块坑多——订单不存在、退款金额超了、重复申请,每一个都得拦住。用事务包一层:

@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;
}

五、完整对话流程

一次请求的完整链路:

sequenceDiagram
    participant User as 用户
    participant API as API层
    participant Session as 会话管理
    participant Emotion as 情绪分析
    participant Intent as 意图识别
    participant Agent as Agent工具
    participant GLM as GLM-4

    User->>API: 发送消息
    API->>Session: 加载上下文(Redis)
    Session->>Emotion: 情绪分析 → GLM-4
    Session->>Intent: 意图识别 → GLM-4
    Session->>Agent: 路由分发 → 执行工具
    Agent->>GLM: 生成回复
    GLM-->>User: 最终回复

六、数据库表设计

5 张核心表,源码里有完整建表脚本 + 模拟数据:

用途
conversation_session会话管理
message消息记录(含情绪/意图标记)
order_info订单数据
refund_application退款申请
human_transfer转人工记录

七、关键配置

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 知识库和转人工功能的,关注等更新就行。