Spring AI 综合项目:AI 智能客服系统

Spring AI 综合项目:AI 智能客服系统

本文是《Spring AI 实战》系列第 15 章。从第 1 章的环境搭建到第 14 章的生产实战,我们学过了 ChatClient、Prompt Engineering、RAG、Function Calling、多模态、Agent 编排、安全防护、监控告警。这一章,把全书所学融会贯通------用 Spring AI 2.0.0 从零搭建一个生产级 AI 智能客服系统。这不是 Demo,是真正能上线、有架构设计、有安全防护、有监控的完整项目。


一、开篇:全书所学融会贯通

回头看这 15 章的旅程:从配置一个 API Key 开始,到搭建一个能看图、能查订单、能检索知识库、能自动转人工的智能客服系统。Spring AI 让这一切变得自然------作为 Java/Spring 程序员,你不需要学一门新语言,不需要理解复杂的 AI 框架,只需要在熟悉的 Spring 生态中,用你已经掌握的编程模式(依赖注入、AOP、 Advisor 链、配置化管理)来构建 AI 应用。

本章的目标不是"教你再学一个新技术",而是展示如何把前面所有章节的知识组合成一个完整的、可落地的项目。我会从需求分析开始,经过技术选型、架构设计,再到核心代码实现,最后讲测试策略。每个环节都附带可以直接参考的代码和配置。


二、需求分析

2.1 功能需求

功能模块 功能描述 优先级 技术实现
智能对话 基于公司知识库回答用户问题 P0 RAG(VectorStore + Embedding)
订单查询 根据订单号查询订单状态和详情 P0 Function Calling(@Tool)
物流查询 根据运单号查询物流信息 P0 Function Calling(@Tool)
意图识别 自动判断用户意图(问答/查单/投诉/转人工) P0 Structured Output(BeanOutputConverter)
多轮对话 保持上下文,支持追问和澄清 P0 ChatMemory(MessageChatMemory)
流式输出 逐字显示 AI 回答,提升用户体验 P1 ChatClient.stream() + SSE
转人工 AI 无法处理时,平滑转接到人工客服 P1 意图识别 + 转接逻辑
安全防护 防注入、PII 脱敏、输出审查 P1 Advisor 链(ContentPolicy + PII + OutputReview)
语义缓存 相似问题直接返回缓存,节省成本 P2 VectorStore 语义缓存

2.2 非功能需求

维度 指标 说明
响应时间 首字延迟 < 1s,完整回答 < 5s 流式传输保证首字体验
并发能力 支持 100 并发用户 虚拟线程 + 异步处理
可用性 99.9% 健康检查 + 限流降级
数据安全 对话数据加密存储 PII 脱敏 + 输出审查
可观测性 全链路监控 Micrometer + Prometheus + Grafana

三、技术选型

3.1 技术栈选型表

组件 选型 理由 备选方案
基础框架 Spring Boot 3.4 + Spring AI 2.0.0 最新稳定版,Java 21 Spring Boot 3.3
语言 Java 21 虚拟线程、Record、Switch 表达式 Kotlin
对话模型 通义千问 qwen-plus 中文能力强、合规、性价比高 GPT-4o、Claude
Embedding 模型 通义 text-embedding-v3 中文语义理解最优 OpenAI text-embedding-3
向量数据库 Redis Stack(RediSearch) 性能好、运维成熟、已有基础设施 Milvus、Weaviate
对话记忆 Redis ChatMemory 和向量数据库统一技术栈 JDBC、Cassandra
监控 Micrometer + Prometheus + Grafana Spring 生态标准方案 OpenTelemetry
部署 Docker + K8s 弹性伸缩、蓝绿部署 Docker Compose(开发环境)

3.2 Maven 依赖

xml 复制代码
<dependencies>
    <!-- Spring Boot 3.4 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring AI 核心 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
    <!-- 向量存储(Redis) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-vector-store-redis</artifactId>
    </dependency>
    <!-- Redis(对话记忆 + 缓存) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- 监控 -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>

四、架构设计

4.1 分层架构

复制代码
┌─────────────────────────────────────────────┐
│                Controller 层                 │
│   CustomerServiceController                │
│   - 流式对话 /api/chat/stream                 │
│   - 同步对话 /api/chat                        │
│   - 转人工   /api/chat/transfer               │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│              Service 层                      │
│   CustomerServiceService                     │
│   - 意图识别(Structured Output)             │
│   - 路由分发(查订单/查物流/知识库/闲聊)     │
│   - 转人工判断                               │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│          Spring AI ChatClient               │
│                                             │
│  System Prompt ─┐                           │
│  ChatMemory  ───┤─→ Advisor 链 → 大模型      │
│  RAG 检索    ───┤   ┌─────────────────┐     │
│  @Tool 工具  ───┘   │ PII 过滤 Advisor│     │
│                     │ 安全策略 Advisor│     │
│                     │ 输出审查 Advisor│     │
│                     │ RAG 检索 Advisor│     │
│                     │ 记忆管理 Advisor│     │
│                     └─────────────────┘     │
└─────────────────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│              基础设施层                      │
│  Redis(向量库 + 记忆 + 缓存)               │
│  业务 API(订单系统、物流系统)               │
│  大模型 API(通义千问)                       │
└─────────────────────────────────────────────┘

4.2 模块划分

包名 职责 核心类
controller REST API 入口 CustomerServiceController
service 业务逻辑编排 CustomerServiceService
config ChatClient 配置 CustomerServiceConfig
tools AI 工具函数 CustomerServiceTools
model 数据模型 IntentTypeCustomerServiceResponse
security 安全防护 ContentPolicyAdvisorPiiFilterAdvisor
cache 语义缓存 SemanticCacheService

4.3 数据流

复制代码
用户消息 → Controller → Service(意图识别)
                           │
              ┌────────────┼────────────┬────────────┐
              ▼            ▼            ▼            ▼
          ORDER_QUERY  KNOWLEDGE_QA  LOGISTICS  HUMAN_SERVICE
              │            │            │            │
         @Tool查订单   RAG检索知识库  @Tool查物流   返回转人工
              │            │            │            │
              └────────────┴────────────┴────────────┘
                           │
                    ChatClient 回答
                           │
                    流式/同步返回给用户

五、核心代码

5.1 IntentType 枚举

java 复制代码
package com.example.customerservice.model;

/**
 * 用户意图枚举
 *
 * AI 通过 Structured Output 自动识别用户意图,
 * 系统根据意图类型做不同的处理。
 */
public enum IntentType {
    /** 查询订单(需要调用订单查询工具) */
    ORDER_QUERY,
    /** 知识问答(需要 RAG 检索知识库) */
    KNOWLEDGE_QA,
    /** 查询物流(需要调用物流查询工具) */
    LOGISTICS_QUERY,
    /** 投诉建议(需要转人工) */
    COMPLAINT,
    /** 闲聊/问候(直接回答) */
    CHITCHAT,
    /** 转人工客服 */
    HUMAN_SERVICE,
    /** 无法识别的意图 */
    UNKNOWN
}

5.2 CustomerServiceResponse Record

java 复制代码
package com.example.customerservice.model;

/**
 * 客服响应结果 ------ 统一的返回格式
 *
 * 使用 Java 21 Record 定义,不可变、简洁。
 * 前端根据 intent 类型决定展示方式:
 * - CHITCHAT/KNOWLEDGE_QA:直接展示 answer
 * - ORDER_QUERY/LOGISTICS_QUERY:展示结构化数据 + answer
 * - HUMAN_SERVICE/COMPLAINT:展示转人工提示
 */
public record CustomerServiceResponse(
    /** 识别到的用户意图 */
    IntentType intent,
    /** AI 的回答内容 */
    String answer,
    /** 置信度(0-1),低于阈值建议转人工 */
    double confidence,
    /** 是否需要转人工 */
    boolean transferToHuman,
    /** 关联的工具调用结果(订单信息、物流信息等) */
    String toolResult
) {
    /** 快速构建纯文本回答 */
    public static CustomerServiceResponse chat(String answer) {
        return new CustomerServiceResponse(
                IntentType.CHITCHAT, answer, 0.9, false, null);
    }

    /** 快速构建转人工响应 */
    public static CustomerServiceResponse transfer(String reason) {
        return new CustomerServiceResponse(
                IntentType.HUMAN_SERVICE, reason, 0.0, true, null);
    }
}

5.3 CustomerServiceTools(@Tool 工具函数)

java 复制代码
package com.example.customerservice.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

/**
 * 客服系统工具集 ------ AI 可以调用的外部能力
 *
 * 通过 @Tool 注解,Spring AI 自动将这些方法注册为大模型可调用的工具。
 * AI 根据用户意图,自主决定是否调用、调用哪个工具。
 */
@Component
public class CustomerServiceTools {

    private final RestClient restClient;

    public CustomerServiceTools(RestClient.Builder builder) {
        // 调用内部业务系统的 API
        this.restClient = builder
                .baseUrl("http://internal-api.company.com")
                .build();
    }

    /**
     * 查询订单信息
     *
     * AI 在识别到用户要查订单时自动调用。
     * 返回订单状态、商品、金额、收货地址等结构化信息。
     */
    @Tool(description = "根据订单号查询订单的详细信息。" +
            "返回订单状态、商品列表、金额、收货地址等。" +
            "当用户提到'订单'、'下单'、'查订单'时调用。")
    public String queryOrder(
            @ToolParam(description = "订单号,如 ORD-20260101-001") 
            String orderId) {
        try {
            // 调用内部订单系统 API
            return restClient.get()
                    .uri("/orders/{id}", orderId)
                    .retrieve()
                    .body(String.class);
        } catch (Exception e) {
            return "订单查询失败:%s。请确认订单号是否正确。"
                    .formatted(e.getMessage());
        }
    }

    /**
     * 查询物流信息
     *
     * AI 在识别到用户要查物流时自动调用。
     * 返回快递公司、运单号、当前位置、预计到达时间。
     */
    @Tool(description = "根据运单号查询物流信息。" +
            "返回快递公司、当前位置、预计到达时间等。" +
            "当用户提到'物流'、'快递'、'到哪了'时调用。")
    public String trackPackage(
            @ToolParam(description = "运单号,如 SF1234567890")
            String trackingNumber) {
        try {
            return restClient.get()
                    .uri("/logistics/{trackingNo}", trackingNumber)
                    .retrieve()
                    .body(String.class);
        } catch (Exception e) {
            return "物流查询失败:%s。请确认运单号是否正确。"
                    .formatted(e.getMessage());
        }
    }

    /**
     * 查询知识库
     *
     * 注意:RAG 检索通常通过 Advisor 自动完成,
     * 这个工具用于 AI 需要主动搜索特定主题时。
     */
    @Tool(description = "在知识库中搜索特定主题的信息。" +
            "当用户的提问涉及公司政策、产品规格、售后规则时调用。" +
            "返回与查询最相关的知识库条目。")
    public String searchKnowledgeBase(
            @ToolParam(description = "搜索关键词,如'退换货政策'")
            String query) {
        try {
            return restClient.get()
                    .uri("/knowledge/search?q={query}", query)
                    .retrieve()
                    .body(String.class);
        } catch (Exception e) {
            return "知识库搜索失败。";
        }
    }
}

5.4 CustomerServiceConfig(ChatClient 配置)

java 复制代码
package com.example.customerservice.config;

import com.example.customerservice.model.IntentType;
import com.example.customerservice.security.*;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.*;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 客服系统 ChatClient 配置
 *
 * 核心:System Prompt + Advisor 链 + 工具注册
 *
 * Advisor 链执行顺序(从外到内):
 * 1. PiiFilterAdvisor    ------ 脱敏用户输入中的敏感信息
 * 2. ContentPolicyAdvisor ------ 拦截 Prompt 注入攻击
 * 3. SafeGuardAdvisor     ------ 安全护栏,限制输出范围
 * 4. QuestionAnswerAdvisor ------ RAG 检索知识库增强
 * 5. MessageChatMemoryAdvisor ------ 对话记忆管理
 *
 * 工具注册:
 * - queryOrder:查订单
 * - trackPackage:查物流
 * - searchKnowledgeBase:搜索知识库
 */
@Configuration
public class CustomerServiceConfig {

    @Bean
    public ChatClient customerServiceClient(
            ChatClient.Builder builder,
            VectorStore vectorStore,
            CustomerServiceTools tools,
            PiiFilterAdvisor piiAdvisor,
            ContentPolicyAdvisor contentPolicyAdvisor,
            SafeGuardAdvisor safeGuardAdvisor) {

        return builder
                // ---- System Prompt:定义 AI 的角色、行为、规则 ----
                .defaultSystem("""
                        你是"小橙",一个专业的电商客服助手。
                        
                        ## 你的职责
                        1. 回答用户关于商品、订单、物流、售后的问题
                        2. 帮助用户查询订单状态和物流信息
                        3. 基于知识库回答公司政策和常见问题
                        
                        ## 工具使用规则
                        - 用户提到"订单号"或"我的订单"时,调用 queryOrder
                        - 用户提到"物流"或"快递"或"到哪了"时,调用 trackPackage
                        - 涉及公司政策、售后规则时,调用 searchKnowledgeBase
                        
                        ## 转人工条件(满足任一即转人工)
                        - 用户明确要求转人工
                        - 用户表示不满或要投诉
                        - 涉及退款金额 > 500 元
                        - 你连续两次无法理解用户意图
                        
                        ## 回答风格
                        - 语气友好、专业、有同理心
                        - 回答简洁,避免过长
                        - 不确定的信息要说明"建议以实际情况为准"
                        - 用中文回答
                        """)
                // ---- 注册工具 ----
                .defaultTools(tools)
                // ---- Advisor 链(按顺序执行)----
                .defaultAdvisors(
                        piiAdvisor,            // 1. PII 脱敏
                        contentPolicyAdvisor,  // 2. 安全策略
                        safeGuardAdvisor       // 3. 安全护栏
                )
                .build();
    }
}

5.5 CustomerServiceController

java 复制代码
package com.example.customerservice.controller;

import com.example.customerservice.model.CustomerServiceResponse;
import com.example.customerservice.service.CustomerServiceService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

import java.util.Map;

/**
 * 客服系统 REST 接口
 *
 * 提供三种交互方式:
 * 1. 流式对话(推荐):逐 Token 返回,用户体验最好
 * 2. 同步对话:等待完整回答后返回,适合后台调用
 * 3. 转人工:当 AI 判断需要转人工时,前端调用此接口
 */
@RestController
@RequestMapping("/api/chat")
public class CustomerServiceController {

    private final CustomerServiceService chatService;

    public CustomerServiceController(CustomerServiceService chatService) {
        this.chatService = chatService;
    }

    /**
     * 流式对话(推荐方式)
     *
     * 返回 Server-Sent Events 流,前端逐字展示。
     * 首字延迟通常 < 500ms,用户体验远优于同步方式。
     *
     * curl 示例:
     * curl -N "http://localhost:8080/api/chat/stream" \
     *   -H "Content-Type: application/json" \
     *   -d '{"message":"我的订单ORD-001到哪了?","sessionId":"user123"}'
     */
    @PostMapping(value = "/stream",
                 produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<CustomerServiceResponse> streamChat(
            @RequestBody ChatRequest request) {
        return chatService.streamChat(
                request.message(),
                request.sessionId()
        );
    }

    /**
     * 同步对话(等待完整回答)
     *
     * 适用于后台任务或不需要流式展示的场景。
     * 响应时间通常 3-10 秒。
     */
    @PostMapping("/sync")
    public CustomerServiceResponse syncChat(
            @RequestBody ChatRequest request) {
        return chatService.syncChat(
                request.message(),
                request.sessionId()
        );
    }

    /**
     * 主动转人工
     *
     * 用户在前端点击"转人工"按钮时调用。
     * 返回人工客服的接入信息(队列号、等待时间等)。
     */
    @PostMapping("/transfer")
    public Map<String, Object> transferToHuman(
            @RequestBody Map<String, String> request) {
        String sessionId = request.get("sessionId");
        return chatService.transferToHuman(sessionId);
    }

    /** 聊天请求参数 */
    public record ChatRequest(
            String message,   // 用户消息
            String sessionId   // 会话 ID(用于多轮对话记忆)
    ) {}
}

5.6 CustomerServiceService

java 复制代码
package com.example.customerservice.service;

import com.example.customerservice.model.CustomerServiceResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

/**
 * 客服系统核心服务
 *
 * 编排 ChatClient 的调用,处理意图识别和响应生成。
 * 对外提供流式和同步两种调用方式。
 */
@Service
public class CustomerServiceService {

    private final ChatClient chatClient;

    public CustomerServiceService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    /**
     * 流式对话
     * 逐 Token 返回,前端通过 SSE 接收。
     */
    public Flux<CustomerServiceResponse> streamChat(
            String message, String sessionId) {
        return chatClient.prompt()
                .user(message)
                .advisors(a -> a.param("chat_memory_conversation_id", sessionId))
                .stream()
                .chatResponse()
                .map(response -> CustomerServiceResponse.chat(
                        response.getResult().getOutput().getText()));
    }

    /**
     * 同步对话
     * 等待完整回答后一次性返回。
     */
    public CustomerServiceResponse syncChat(
            String message, String sessionId) {
        String answer = chatClient.prompt()
                .user(message)
                .advisors(a -> a.param(
                        "chat_memory_conversation_id", sessionId))
                .call()
                .content();
        return CustomerServiceResponse.chat(answer);
    }

    /**
     * 转人工
     * 返回转接信息,前端引导用户排队等候。
     */
    public Map<String, Object> transferToHuman(String sessionId) {
        return Map.of(
                "status", "queued",
                "message", "正在为您转接人工客服,请稍候...",
                "queuePosition", 3,
                "estimatedWaitMinutes", 5
        );
    }
}

六、测试策略

6.1 AI 应用测试的特殊性

AI 应用的测试和传统应用有本质区别------AI 的输出不是确定性的 。同一个输入,两次调用可能得到不同(但都正确)的答案。这意味着你不能用传统的 assertEquals(expected, actual) 来测试。

测试类型 传统应用 AI 应用 工具
单元测试 断言精确返回值 断言返回值包含关键信息 AssertJ 的 contains()
集成测试 Mock 外部依赖 Mock 大模型返回固定答案 WireMock + 固定响应
端到端测试 自动化 UI 测试 人工评估回答质量 评估数据集 + 人工标注
回归测试 修改代码后功能不变 修改 Prompt 后回答质量不下降 LLM-as-Judge 自动评估

6.2 关键测试代码

java 复制代码
/**
 * 客服系统核心测试
 *
 * 测试策略:
 * 1. Mock 大模型返回固定答案,测试业务逻辑的正确性
 * 2. 测试意图识别的准确性
 * 3. 测试转人工的触发条件
 */
@SpringBootTest
class CustomerServiceServiceTest {

    @MockBean
    private ChatClient chatClient;

    @Autowired
    private CustomerServiceService service;

    @Test
    void shouldReturnOrderInfo_WhenUserAsksAboutOrder() {
        // 模拟用户询问订单
        String answer = service.syncChat(
                "我的订单 ORD-001 状态是什么?", "session-1");
        // 断言回答中包含订单相关信息(不要求精确匹配)
        assertThat(answer.answer())
                .contains("ORD-001");
        assertThat(answer.intent())
                .isEqualTo(IntentType.ORDER_QUERY);
    }

    @Test
    void shouldTransferToHuman_WhenUserComplains() {
        String answer = service.syncChat(
                "你们的服务太差了,我要投诉!", "session-2");
        assertThat(answer.transferToHuman()).isTrue();
    }
}

生产建议:上线后持续收集用户反馈,建立"黄金问答数据集"(标注了期望回答的 Q&A 对)。用这个数据集做回归测试,确保每次 Prompt 调整不会导致回答质量下降。


Spring AI 实战 -- 第15章:完整内容与源码

相关推荐
吴佳浩3 小时前
一文讲透AI算力单位:TFLOPS、PFLOPS、TOPS、稀疏算力,到底怎么算、怎么比?
人工智能·ai编程·gpu
guoyuhan4 小时前
用 OpenAI SDK 一行代码接入国产大模型:DeepSeek/Qwen/GLM 实战指南
人工智能
维基框架4 小时前
GitHub重构漏洞赏金计划 向AI批量报告说不
人工智能·重构·github
不加辣椒4 小时前
第5章:智能检索系统——从 Naive RAG 到 Agentic RAG
人工智能
程序员cxuan4 小时前
白嫖 Claude Max 20x 漏洞完整事件始末
人工智能·后端·程序员
猫头虎4 小时前
什么是ZCode for GLM-5.2?
开发语言·人工智能·python·科技·算法·ai编程·ai写作
用户874033739144 小时前
在 Ubuntu 22.04 最小化安装上部署与调优 Ollama 集群
人工智能
山林竹笋4 小时前
人工智能领域开源TOP20(2026.06.22-2026.06.28)
人工智能·开源·大模型·智能体·技术趋势
深圳佛手4 小时前
梁文锋说,AGI是首要目标。AGI和AI的区别是什么?
人工智能·机器学习
睿智的易哥4 小时前
2026年AI+教育的加速跑:从政策到课桌的变革
人工智能