跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaAIjava

基于 Spring AI 和 Claude 构建企业智能客服系统:从架构到实践

综述由AI生成使用 Spring AI 框架结合 Claude 大语言模型构建企业级智能客服系统的完整方案。内容涵盖技术选型理由、分层架构设计、Maven 依赖配置及 application.yml 设置。核心实现包括智能客服服务类(RAG 检索增强生成)、知识库管理服务(文档解析、分块、向量化)以及 REST API 控制器。此外,还详细阐述了性能优化策略(缓存、异步、流式响应)、安全考虑、监控运维指标、Docker 容器化及 Kubernetes 部署配置,并提供了单元测试与集成测试示例。该方案利用 Spring 生态优势,实现了基于企业知识库的精准问答,具备高扩展性和稳定性。

GitMaster发布于 2026/4/6更新于 2026/5/2326 浏览
基于 Spring AI 和 Claude 构建企业智能客服系统:从架构到实践

基于 Spring AI 和 Claude 构建企业智能客服系统:从架构到实践

随着人工智能技术的快速发展,越来越多的企业开始构建内部智能客服系统来提升客户服务效率和质量。本文将详细介绍如何使用 Spring AI 框架结合 Claude 大语言模型,构建一个功能完善的企业级智能客服系统。

为什么选择 Spring AI + Claude 的技术组合?

Spring AI:企业级 AI 应用的理想选择

Spring AI 是 Spring 生态系统中专门为 AI 应用开发设计的框架,它具有以下核心优势:

1. 天然的 Spring 生态集成

  • 与 Spring Boot、Spring Security 等框架无缝集成
  • 遵循 Spring 的依赖注入和自动配置机制
  • 统一的配置管理和监控体系

2. 简化的 AI 开发体验

  • 提供统一的 API 抽象,屏蔽底层复杂性
  • 开箱即用的 RAG(检索增强生成)支持
  • 内置的向量数据库集成和文档处理能力

3. 企业级特性

  • 完整的可观测性和监控支持
  • 生产级的错误处理和重试机制
  • 丰富的配置选项和扩展点
Claude:强大的对话 AI 能力

Claude 作为 Anthropic 开发的大语言模型,在企业应用场景中表现出色:

  • 高质量的中文理解和生成能力
  • 良好的上下文理解和多轮对话支持
  • 可靠的安全性和合规性保障
  • 灵活的 API 调用方式

系统架构设计

整体架构概览

我们的智能客服系统采用分层架构设计,主要包含以下组件:

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端界面        │────│ Spring Boot   │────│ Claude API    │
│ (Web/Mobile)    │ │ 应用服务        │ │                 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
          │
          ▼
┌─────────────────┐
│ 知识库系统      │
│ (Vector Store)  │
└─────────────────┘
核心组件说明

1. 对话管理引擎

  • 处理用户输入和多轮对话
  • 管理会话上下文和历史记录
  • 实现智能路由和意图识别

2. 知识检索系统

  • 基于向量相似度的语义搜索
  • 支持多种文档格式的知识导入
  • 动态知识更新和版本控制

3. Claude 集成层

  • 封装 Claude API 调用
  • 实现 Prompt 工程和上下文构建
  • 处理流式响应和错误重试

项目搭建与依赖配置

Maven 依赖配置

首先,让我们配置项目的基础依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.company</groupId>
    <artifactId>intelligent-customer-service</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    <dependencies>
        <!-- Spring Boot 核心依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Spring AI 相关依赖 -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
            <version>1.0.0-M1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
            <version>1.0.0-M1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-pdf-document-reader</artifactId>
            <version>1.0.0-M1</version>
        </dependency>
        <!-- 数据库相关 -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <!-- 其他工具库 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>
</project>
应用配置

在 application.yml 中配置相关参数:

spring:
  application:
    name: intelligent-customer-service
  # 数据库配置
  datasource:
    url: jdbc:postgresql://localhost:5432/customer_service
    username: ${DB_USERNAME:postgres}
    password: ${DB_PASSWORD:password}
    driver-class-name: org.postgresql.Driver
  # JPA 配置
  jpa:
    hibernate:
      ddl-auto: update
      show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
  # Spring AI 配置
  ai:
    anthropic:
      api-key: ${ANTHROPIC_API_KEY}
      chat:
        options:
          model: claude-sonnet-4-20250514
          temperature: 0.3
          max-tokens: 2000
    vectorstore:
      pgvector:
        index-type: HNSW
        distance-type: COSINE_DISTANCE
        dimensions: 1536
# 应用自定义配置
app:
  knowledge-base:
    max-file-size: 10MB
    supported-formats: pdf,docx,txt,md
  chat:
    max-history-size: 20
    session-timeout: 30m
logging:
  level:
    org.springframework.ai: DEBUG
    com.company.customerservice: DEBUG

核心业务实现

1. 智能客服服务类
@Service
@Slf4j
public class IntelligentCustomerService {
    private final AnthropicChatModel chatModel;
    private final VectorStore vectorStore;
    private final ChatMemory chatMemory;
    private final ConversationService conversationService;

    public IntelligentCustomerService(AnthropicChatModel chatModel,
                                      VectorStore vectorStore,
                                      ChatMemory chatMemory,
                                      ConversationService conversationService) {
        this.chatModel = chatModel;
        this.vectorStore = vectorStore;
        this.chatMemory = chatMemory;
        this.conversationService = conversationService;
    }

    /**
     * 处理用户问题的核心方法
     */
    public ChatResponse handleUserQuery(ChatRequest request) {
        try {
            String userId = request.getUserId();
            String question = request.getMessage();
            log.info("处理用户 {} 的问题:{}", userId, question);

            // 1. 从知识库检索相关信息
            List<Document> relevantDocs = retrieveRelevantKnowledge(question);

            // 2. 构建系统提示词
            String systemPrompt = buildSystemPrompt(relevantDocs, request.getUserContext());

            // 3. 获取对话历史
            List<Message> conversationHistory = chatMemory.get(userId, 10);

            // 4. 构建完整的消息列表
            List<Message> messages = buildMessageList(systemPrompt, conversationHistory, question);

            // 5. 调用 Claude 获取回答
            Prompt prompt = new Prompt(messages, buildChatOptions());
            org.springframework.ai.chat.model.ChatResponse aiResponse = chatModel.call(prompt);

            // 6. 处理和保存结果
            String answer = aiResponse.getResult().getOutput().getContent();
            saveConversationHistory(userId, question, answer);

            // 7. 构建返回结果
            return ChatResponse.builder()
                    .message(answer)
                    .conversationId(request.getConversationId())
                    .timestamp(LocalDateTime.now())
                    .sources(extractSources(relevantDocs))
                    .build();
        } catch (Exception e) {
            log.error("处理用户问题时发生错误", e);
            return ChatResponse.builder()
                    .message("抱歉,我暂时无法回答您的问题,请稍后重试。")
                    .error(true)
                    .build();
        }
    }

    /**
     * 从知识库检索相关文档
     */
    private List<Document> retrieveRelevantKnowledge(String question) {
        SearchRequest searchRequest = SearchRequest.query(question).withTopK(5).withSimilarityThreshold(0.7);
        return vectorStore.similaritySearch(searchRequest);
    }

    /**
     * 构建系统提示词
     */
    private String buildSystemPrompt(List<Document> relevantDocs, UserContext userContext) {
        StringBuilder contextBuilder = new StringBuilder();
        contextBuilder.append("你是一个专业的企业内部客服助手。请基于以下知识库信息回答用户问题:\n\n");
        // 添加检索到的知识
        for (int i = 0; i < relevantDocs.size(); i++) {
            Document doc = relevantDocs.get(i);
            contextBuilder.append(String.format("知识片段 %d:\n%s\n\n", i + 1, doc.getContent()));
        }
        // 添加用户上下文信息
        if (userContext != null) {
            contextBuilder.append(String.format("用户信息:部门=%s,角色=%s\n\n", userContext.getDepartment(), userContext.getRole()));
        }
        contextBuilder.append("回答要求:\n");
        contextBuilder.append("1. 基于提供的知识库信息回答,如果信息不足请说明\n");
        contextBuilder.append("2. 回答要准确、简洁、友好\n");
        contextBuilder.append("3. 如果涉及敏感信息,请提醒用户通过正式渠道处理\n");
        contextBuilder.append("4. 使用中文回答\n");
        return contextBuilder.toString();
    }

    /**
     * 构建消息列表
     */
    private List<Message> buildMessageList(String systemPrompt, List<Message> history, String currentQuestion) {
        List<Message> messages = new ArrayList<>();
        // 添加系统消息
        messages.add(new SystemMessage(systemPrompt));
        // 添加历史对话
        messages.addAll(history);
        // 添加当前问题
        messages.add(new UserMessage(currentQuestion));
        return messages;
    }

    /**
     * 构建 Chat 选项
     */
    private AnthropicChatOptions buildChatOptions() {
        return AnthropicChatOptions.builder()
                .withModel("claude-sonnet-4-20250514")
                .withTemperature(0.3)
                .withMaxTokens(2000)
                .build();
    }

    /**
     * 保存对话历史
     */
    private void saveConversationHistory(String userId, String question, String answer) {
        // 保存到内存中的对话历史
        chatMemory.add(userId, new UserMessage(question));
        chatMemory.add(userId, new AssistantMessage(answer));
        // 保存到数据库(用于分析和审计)
        conversationService.saveConversation(userId, question, answer);
    }

    /**
     * 提取知识来源
     */
    private List<String> extractSources(List<Document> documents) {
        return documents.stream()
                .map(doc -> doc.getMetadata().get("source"))
                .filter(Objects::nonNull)
                .map(Object::toString)
                .distinct()
                .collect(Collectors.toList());
    }
}
2. 知识库管理服务
@Service
@Slf4j
public class KnowledgeBaseService {
    private final VectorStore vectorStore;
    private final KnowledgeDocumentRepository documentRepository;
    private final TextSplitter textSplitter;

    @Value("${app.knowledge-base.max-file-size:10MB}")
    private String maxFileSize;

    public KnowledgeBaseService(VectorStore vectorStore, KnowledgeDocumentRepository documentRepository) {
        this.vectorStore = vectorStore;
        this.documentRepository = documentRepository;
        this.textSplitter = new TokenTextSplitter(500, 50);
    }

    /**
     * 添加文档到知识库
     */
    @Transactional
    public void addDocument(MultipartFile file, String category, String uploadedBy) {
        try {
            // 1. 验证文件
            validateFile(file);
            // 2. 读取文档内容
            List<Document> documents = readDocument(file);
            // 3. 文档分块
            List<Document> chunks = splitDocuments(documents);
            // 4. 添加元数据
            enrichDocuments(chunks, file.getOriginalFilename(), category, uploadedBy);
            // 5. 向量化并存储
            vectorStore.add(chunks);
            // 6. 保存文档记录
            saveDocumentRecord(file, category, uploadedBy, chunks.size());
            log.info("成功添加文档到知识库:{}, 分块数:{}", file.getOriginalFilename(), chunks.size());
        } catch (Exception e) {
            log.error("添加文档到知识库失败:{}", file.getOriginalFilename(), e);
            throw new KnowledgeBaseException("文档处理失败:" + e.getMessage());
        }
    }

    /**
     * 读取文档内容
     */
    private List<Document> readDocument(MultipartFile file) throws IOException {
        String filename = file.getOriginalFilename();
        String extension = getFileExtension(filename);
        DocumentReader reader = switch (extension.toLowerCase()) {
            case "pdf" -> new PagePdfDocumentReader(file.getResource());
            case "docx" -> new TikaDocumentReader(file.getResource());
            case "txt", "md" -> new TextDocumentReader(file.getResource());
            default -> throw new UnsupportedOperationException("不支持的文件格式:" + extension);
        };
        return reader.get();
    }

    /**
     * 文档分块
     */
    private List<Document> splitDocuments(List<Document> documents) {
        List<Document> allChunks = new ArrayList<>();
        for (Document document : documents) {
            List<Document> chunks = textSplitter.split(document);
            allChunks.addAll(chunks);
        }
        return allChunks;
    }

    /**
     * 丰富文档元数据
     */
    private void enrichDocuments(List<Document> chunks, String filename, String category, String uploadedBy) {
        for (int i = 0; i < chunks.size(); i++) {
            Document chunk = chunks.get(i);
            Map<String, Object> metadata = chunk.getMetadata();
            metadata.put("source", filename);
            metadata.put("category", category);
            metadata.put("uploadedBy", uploadedBy);
            metadata.put("chunkIndex", i);
            metadata.put("totalChunks", chunks.size());
            metadata.put("uploadTime", LocalDateTime.now().toString());
        }
    }

    /**
     * 搜索知识库
     */
    public List<KnowledgeSearchResult> searchKnowledge(String query, int limit) {
        SearchRequest searchRequest = SearchRequest.query(query).withTopK(limit).withSimilarityThreshold(0.6);
        List<Document> results = vectorStore.similaritySearch(searchRequest);
        return results.stream().map(this::convertToSearchResult).collect(Collectors.toList());
    }

    /**
     * 删除文档
     */
    @Transactional
    public void deleteDocument(Long documentId) {
        KnowledgeDocument document = documentRepository.findById(documentId)
                .orElseThrow(() -> new EntityNotFoundException("文档不存在"));
        // 从向量数据库删除
        vectorStore.delete(List.of(document.getFilename()));
        // 从数据库删除记录
        documentRepository.delete(document);
        log.info("成功删除文档:{}", document.getFilename());
    }

    /**
     * 获取知识库统计信息
     */
    public KnowledgeBaseStats getStatistics() {
        long totalDocuments = documentRepository.count();
        long totalChunks = vectorStore.similaritySearch(SearchRequest.query("").withTopK(Integer.MAX_VALUE)).size();
        Map<String, Long> categoryStats = documentRepository.findCategoryStatistics();
        return KnowledgeBaseStats.builder()
                .totalDocuments(totalDocuments)
                .totalChunks(totalChunks)
                .categoryStatistics(categoryStats)
                .lastUpdated(LocalDateTime.now())
                .build();
    }

    // 辅助方法
    private void validateFile(MultipartFile file) {
        if (file.isEmpty()) {
            throw new IllegalArgumentException("文件不能为空");
        }
        String filename = file.getOriginalFilename();
        if (filename == null || filename.trim().isEmpty()) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        // 验证文件大小和格式
        // ... 具体验证逻辑
    }

    private String getFileExtension(String filename) {
        if (filename == null || !filename.contains(".")) {
            return "";
        }
        return filename.substring(filename.lastIndexOf(".") + 1);
    }

    private void saveDocumentRecord(MultipartFile file, String category, String uploadedBy, int chunkCount) {
        KnowledgeDocument document = KnowledgeDocument.builder()
                .filename(file.getOriginalFilename())
                .fileSize(file.getSize())
                .category(category)
                .uploadedBy(uploadedBy)
                .chunkCount(chunkCount)
                .uploadTime(LocalDateTime.now())
                .build();
        documentRepository.save(document);
    }

    private KnowledgeSearchResult convertToSearchResult(Document document) {
        return KnowledgeSearchResult.builder()
                .content(document.getContent())
                .source(document.getMetadata().get("source").toString())
                .category(document.getMetadata().get("category").toString())
                .relevanceScore(0.0) // 实际项目中需要计算相似度分数
                .build();
    }
}
3. REST API 控制器
@RestController
@RequestMapping("/api/chat")
@Slf4j
@Validated
public class ChatController {
    private final IntelligentCustomerService customerService;
    private final KnowledgeBaseService knowledgeBaseService;

    public ChatController(IntelligentCustomerService customerService, KnowledgeBaseService knowledgeBaseService) {
        this.customerService = customerService;
        this.knowledgeBaseService = knowledgeBaseService;
    }

    /**
     * 处理聊天消息
     */
    @PostMapping("/message")
    public ResponseEntity<ApiResponse<ChatResponse>> sendMessage(@Valid @RequestBody ChatRequest request, HttpServletRequest httpRequest) {
        try {
            // 从请求中获取用户信息
            String userId = getUserIdFromRequest(httpRequest);
            request.setUserId(userId);
            // 处理用户问题
            ChatResponse response = customerService.handleUserQuery(request);
            return ResponseEntity.ok(ApiResponse.success(response));
        } catch (Exception e) {
            log.error("处理聊天消息失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ApiResponse.error("服务暂时不可用,请稍后重试"));
        }
    }

    /**
     * 获取对话历史
     */
    @GetMapping("/history/{conversationId}")
    public ResponseEntity<ApiResponse<List<ConversationHistory>>> getConversationHistory(
            @PathVariable String conversationId,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "50") int size) {
        try {
            List<ConversationHistory> history = customerService.getConversationHistory(conversationId, page, size);
            return ResponseEntity.ok(ApiResponse.success(history));
        } catch (Exception e) {
            log.error("获取对话历史失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ApiResponse.error("获取对话历史失败"));
        }
    }

    /**
     * 清除对话历史
     */
    @DeleteMapping("/history/{conversationId}")
    public ResponseEntity<ApiResponse<Void>> clearConversationHistory(@PathVariable String conversationId) {
        try {
            customerService.clearConversationHistory(conversationId);
            return ResponseEntity.ok(ApiResponse.success(null));
        } catch (Exception e) {
            log.error("清除对话历史失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ApiResponse.error("清除对话历史失败"));
        }
    }

    private String getUserIdFromRequest(HttpServletRequest request) {
        // 从 JWT token 或 session 中获取用户 ID
        // 这里简化处理,实际项目中需要根据认证方案实现
        return request.getHeader("X-User-ID");
    }
}

/**
 * 知识库管理 API
 */
@RestController
@RequestMapping("/api/knowledge")
@Slf4j
public class KnowledgeController {
    private final KnowledgeBaseService knowledgeBaseService;

    public KnowledgeController(KnowledgeBaseService knowledgeBaseService) {
        this.knowledgeBaseService = knowledgeBaseService;
    }

    /**
     * 上传文档到知识库
     */
    @PostMapping("/upload")
    public ResponseEntity<ApiResponse<String>> uploadDocument(
            @RequestParam("file") MultipartFile file,
            @RequestParam("category") String category,
            HttpServletRequest request) {
        try {
            String uploadedBy = getUserIdFromRequest(request);
            knowledgeBaseService.addDocument(file, category, uploadedBy);
            return ResponseEntity.ok(ApiResponse.success("文档上传成功"));
        } catch (Exception e) {
            log.error("上传文档失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ApiResponse.error("文档上传失败:" + e.getMessage()));
        }
    }

    /**
     * 搜索知识库
     */
    @GetMapping("/search")
    public ResponseEntity<ApiResponse<List<KnowledgeSearchResult>>> searchKnowledge(
            @RequestParam String query,
            @RequestParam(defaultValue = "10") int limit) {
        try {
            List<KnowledgeSearchResult> results = knowledgeBaseService.searchKnowledge(query, limit);
            return ResponseEntity.ok(ApiResponse.success(results));
        } catch (Exception e) {
            log.error("搜索知识库失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ApiResponse.error("搜索失败"));
        }
    }

    /**
     * 获取知识库统计信息
     */
    @GetMapping("/stats")
    public ResponseEntity<ApiResponse<KnowledgeBaseStats>> getStatistics() {
        try {
            KnowledgeBaseStats stats = knowledgeBaseService.getStatistics();
            return ResponseEntity.ok(ApiResponse.success(stats));
        } catch (Exception e) {
            log.error("获取统计信息失败", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ApiResponse.error("获取统计信息失败"));
        }
    }

    private String getUserIdFromRequest(HttpServletRequest request) {
        return request.getHeader("X-User-ID");
    }
}

系统优化与最佳实践

1. 性能优化策略

缓存机制

  • 对频繁查询的知识片段进行缓存
  • 使用 Redis 缓存用户会话和对话历史
  • 实现智能的缓存失效策略

异步处理

  • 文档上传和处理使用异步队列
  • 长时间的 AI 推理任务异步执行
  • 实现流式响应提升用户体验

资源优化

  • 合理配置数据库连接池
  • 优化向量检索的参数设置
  • 实现请求限流和熔断保护
2. 安全考虑

数据安全

  • 所有敏感配置使用环境变量管理
  • 实现完整的用户认证和授权机制
  • 对上传文档进行安全扫描

API 安全

  • 实现请求签名验证
  • 添加频率限制和防爬虫机制
  • 记录详细的审计日志
3. 监控和运维

应用监控

@Component
public class ChatServiceMetrics {
    private final MeterRegistry meterRegistry;
    private final Counter chatRequestCounter;
    private final Timer responseTimeTimer;

    public ChatServiceMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.chatRequestCounter = Counter.builder("chat.requests.total")
                .description("Total number of chat requests")
                .register(meterRegistry);
        this.responseTimeTimer = Timer.builder("chat.response.time")
                .description("Chat response time")
                .register(meterRegistry);
    }

    public void recordChatRequest() {
        chatRequestCounter.increment();
    }

    public Timer.Sample startTimer() {
        return Timer.start(meterRegistry);
    }
}

健康检查

@Component
public class ChatServiceHealthIndicator implements HealthIndicator {
    private final AnthropicChatModel chatModel;
    private final VectorStore vectorStore;

    @Override
    public Health health() {
        try {
            // 检查 Claude API 连接
            checkClaudeConnection();
            // 检查向量数据库连接
            checkVectorStoreConnection();
            return Health.up()
                    .withDetail("claude", "Available")
                    .withDetail("vectorStore", "Available")
                    .build();
        } catch (Exception e) {
            return Health.down().withDetail("error", e.getMessage()).build();
        }
    }

    private void checkClaudeConnection() {
        // 简单的健康检查请求
        chatModel.call(new Prompt("Hello"));
    }

    private void checkVectorStoreConnection() {
        // 检查向量数据库连接
        vectorStore.similaritySearch(SearchRequest.query("test").withTopK(1));
    }
}

部署与运维

Docker 容器化
FROM openjdk:17-jdk-slim
COPY target/intelligent-customer-service-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
Docker Compose 配置
version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - DB_USERNAME=postgres
      - DB_PASSWORD=password
    depends_on:
      - postgres
      - redis
  postgres:
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_DB=customer_service
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
volumes:
  postgres_data:
  redis_data:
Kubernetes 部署配置
apiVersion: apps/v1
kind: Deployment
metadata:
  name: intelligent-customer-service
  labels:
    app: customer-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: customer-service
  template:
    metadata:
      labels:
        app: customer-service
    spec:
      containers:
        - name: customer-service
          image: company/intelligent-customer-service:1.0.0
          ports:
            - containerPort: 8080
          env:
            - name: ANTHROPIC_API_KEY
              valueFrom:
                secretKeyRef:
                  name: api-secrets
                  key: anthropic-api-key
            - name: DB_USERNAME
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: db-username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secrets
                  key: password
          resources:
            requests:
              memory: "1Gi"
              cpu: "500m"
            limits:
              memory: "2Gi"
              cpu: "1000m"
          livenessProbe:
            httpGet:
              path: /actuator/health
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: customer-service-service
spec:
  selector:
    app: customer-service
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: ClusterIP

测试策略

单元测试示例
@ExtendWith(MockitoExtension.class)
class IntelligentCustomerServiceTest {
    @Mock
    private AnthropicChatModel chatModel;
    @Mock
    private VectorStore vectorStore;
    @Mock
    private ChatMemory chatMemory;
    @Mock
    private ConversationService conversationService;

    @InjectMocks
    private IntelligentCustomerService customerService;

    @Test
    void shouldHandleUserQuerySuccessfully() {
        // Given
        ChatRequest request = ChatRequest.builder()
                .userId("user123")
                .message("如何申请年假?")
                .conversationId("conv456")
                .build();
        List<Document> mockDocs = Arrays.asList(new Document("年假申请需要提前 2 周提交申请表..."));
        when(vectorStore.similaritySearch(any(SearchRequest.class))).thenReturn(mockDocs);
        when(chatMemory.get(eq("user123"), eq(10))).thenReturn(Arrays.asList());
        org.springframework.ai.chat.model.ChatResponse mockResponse = new org.springframework.ai.chat.model.ChatResponse(
                Arrays.asList(new Generation(new AssistantMessage("根据公司政策,年假申请需要..."))));
        when(chatModel.call(any(Prompt.class))).thenReturn(mockResponse);

        // When
        ChatResponse response = customerService.handleUserQuery(request);

        // Then
        assertThat(response).isNotNull();
        assertThat(response.getMessage()).contains("年假申请");
        assertThat(response.isError()).isFalse();
        verify(vectorStore).similaritySearch(any(SearchRequest.class));
        verify(chatModel).call(any(Prompt.class));
        verify(chatMemory, times(2)).add(eq("user123"), any(Message.class));
    }

    @Test
    void shouldHandleEmptyKnowledgeBase() {
        // Given
        ChatRequest request = ChatRequest.builder()
                .userId("user123")
                .message("这是一个新问题")
                .build();
        when(vectorStore.similaritySearch(any(SearchRequest.class))).thenReturn(Arrays.asList());

        // When & Then
        ChatResponse response = customerService.handleUserQuery(request);
        assertThat(response).isNotNull();
        // 验证系统能够优雅处理空知识库的情况
    }
}
集成测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class CustomerServiceIntegrationTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("pgvector/pgvector:pg16")
            .withDatabaseName("test_customer_service")
            .withUsername("test")
            .withPassword("test");

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private KnowledgeBaseService knowledgeBaseService;

    @MockBean
    private AnthropicChatModel chatModel;

    @Test
    void shouldCompleteFullChatFlow() throws Exception {
        // 1. 准备测试数据 - 添加知识文档
        MockMultipartFile testFile = new MockMultipartFile("file", "test-doc.txt", "text/plain",
                "这是一个测试文档,包含公司政策信息。".getBytes());
        knowledgeBaseService.addDocument(testFile, "policy", "test-user");

        // 2. 模拟 Claude 响应
        org.springframework.ai.chat.model.ChatResponse mockResponse = new org.springframework.ai.chat.model.ChatResponse(
                Arrays.asList(new Generation(new AssistantMessage("基于提供的文档,我可以回答您的问题..."))));
        when(chatModel.call(any(Prompt.class))).thenReturn(mockResponse);

        // 3. 发送聊天请求
        ChatRequest chatRequest = ChatRequest.builder()
                .message("请告诉我公司政策")
                .userId("test-user")
                .conversationId("test-conv")
                .build();
        HttpHeaders headers = new HttpHeaders();
        headers.set("X-User-ID", "test-user");
        HttpEntity<ChatRequest> request = new HttpEntity<>(chatRequest, headers);

        // 4. 验证响应
        ResponseEntity<ApiResponse> response = restTemplate.postForEntity("/api/chat/message", request, ApiResponse.class);
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody().isSuccess()).isTrue();
    }
}

性能调优与扩展

1. 缓存优化
@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        RedisCacheManager.Builder builder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory())
                .cacheDefaults(cacheConfiguration());
        return builder.build();
    }

    private RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

@Service
public class CachedKnowledgeService {
    @Cacheable(value = "knowledge-search", key = "#query + '-' + #limit")
    public List<KnowledgeSearchResult> searchWithCache(String query, int limit) {
        return knowledgeBaseService.searchKnowledge(query, limit);
    }

    @CacheEvict(value = "knowledge-search", allEntries = true)
    public void clearSearchCache() {
        // 当知识库更新时清除缓存
    }
}
2. 异步处理优化
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean(name = "documentProcessingExecutor")
    public TaskExecutor documentProcessingExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("doc-processing-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Bean(name = "chatProcessingExecutor")
    public TaskExecutor chatProcessingExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(8);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(200);
        executor.setThreadNamePrefix("chat-processing-");
        executor.initialize();
        return executor;
    }
}

@Service
public class AsyncDocumentProcessor {
    @Async("documentProcessingExecutor")
    public CompletableFuture<Void> processDocumentAsync(MultipartFile file, String category, String uploadedBy) {
        try {
            knowledgeBaseService.addDocument(file, category, uploadedBy);
            // 发送处理完成通知
            notificationService.sendProcessingComplete(uploadedBy, file.getOriginalFilename());
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            log.error("异步文档处理失败", e);
            notificationService.sendProcessingError(uploadedBy, file.getOriginalFilename(), e.getMessage());
            throw new CompletionException(e);
        }
    }
}
3. 流式响应实现
@RestController
public class StreamingChatController {
    @GetMapping(value = "/api/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> streamChat(@RequestParam String message, @RequestParam String userId) {
        return Flux.create(sink -> {
            try {
                // 构建流式请求
                ChatRequest request = ChatRequest.builder()
                        .message(message)
                        .userId(userId)
                        .build();
                // 调用支持流式响应的服务
                customerService.handleUserQueryStream(request).subscribe(
                        chunk -> sink.next(ServerSentEvent.<String>builder().data(chunk).build()),
                        error -> sink.error(error),
                        () -> sink.complete()
                );
            } catch (Exception e) {
                sink.error(e);
            }
        });
    }
}

总结与展望

基于 Spring AI 和 Claude 构建企业智能客服系统,我们获得了以下核心优势:

技术优势
  1. 开发效率大幅提升:Spring AI 提供的统一抽象层大大简化了 AI 集成的复杂度
  2. 企业级稳定性:完整的 Spring 生态支持确保了系统的可靠性和可维护性
  3. 灵活的扩展能力:模块化设计支持快速添加新功能和集成其他 AI 服务
业务价值
  1. 智能化客服体验:基于企业知识库的精准回答提升了服务质量
  2. 成本效益显著:自动化处理减少了人工客服的工作量
  3. 数据安全可控:内部部署确保了企业数据的安全性
未来发展方向

随着 AI 技术的快速发展,我们的智能客服系统还可以在以下方面进行增强:

多模态支持

  • 集成图像理解能力,支持图文混合问答
  • 添加语音交互功能,提供更自然的交互体验

智能化升级

  • 实现意图识别和情感分析
  • 支持主动推荐和个性化服务
  • 集成工作流自动化能力

性能优化

  • 实现更智能的缓存策略
  • 优化向量检索算法
  • 支持大规模并发处理

通过本文的详细介绍,相信您已经掌握了使用 Spring AI 和 Claude 构建企业智能客服系统的核心技术和实践方法。这套方案不仅技术先进,而且具有良好的工程实践性,能够满足企业级应用的各种需求。

在实际项目中,建议根据具体的业务场景和技术栈情况,对架构和实现细节进行适当调整。同时,持续关注 Spring AI 和 Claude 的更新动态,及时采用新功能来进一步提升系统能力。

企业智能客服系统的建设是一个持续迭代的过程,通过不断优化和完善,必将为企业带来更大的价值和竞争优势。

目录

  1. 基于 Spring AI 和 Claude 构建企业智能客服系统:从架构到实践
  2. 为什么选择 Spring AI + Claude 的技术组合?
  3. Spring AI:企业级 AI 应用的理想选择
  4. Claude:强大的对话 AI 能力
  5. 系统架构设计
  6. 整体架构概览
  7. 核心组件说明
  8. 项目搭建与依赖配置
  9. Maven 依赖配置
  10. 应用配置
  11. 数据库配置
  12. JPA 配置
  13. Spring AI 配置
  14. 应用自定义配置
  15. 核心业务实现
  16. 1. 智能客服服务类
  17. 2. 知识库管理服务
  18. 3. REST API 控制器
  19. 系统优化与最佳实践
  20. 1. 性能优化策略
  21. 2. 安全考虑
  22. 3. 监控和运维
  23. 部署与运维
  24. Docker 容器化
  25. Docker Compose 配置
  26. Kubernetes 部署配置
  27. 测试策略
  28. 单元测试示例
  29. 集成测试
  30. 性能调优与扩展
  31. 1. 缓存优化
  32. 2. 异步处理优化
  33. 3. 流式响应实现
  34. 总结与展望
  35. 技术优势
  36. 业务价值
  37. 未来发展方向
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 通过 Vue 实例劫持突破 Web 编辑器粘贴限制
  • Browser Use 使用指南:AI 自动化控制浏览器
  • 基于 CLIProxyAPI 与 New API 的 AI 模型统一代理中转站搭建指南
  • 无线联邦学习:隐私保护下的 AI 协同进化
  • MySQL 数据库数据类型详解与选型建议
  • FreeCAD 修复 STL 网格并转换为实体模型的 5 步流程
  • Python 核心知识体系总结:基础语法、内存模型与面向对象编程
  • 腾讯位置服务 AI+地图赛道:选题方向、投稿要求与拿分思路
  • 鸿蒙应用运维监控、生态运营与变现实战
  • OpenClaw 集成本地 llama.cpp 后端配置
  • 清华大学发布 SuperBench 大模型评测报告:文心一言中文表现领先
  • C++ ODB ORM 框架入门与实战
  • 海外程序员接单平台推荐:Freelancer、Upwork、Fiverr 与 Toptal 详解
  • 大数据 MR 实战:多输入源成绩数据处理案例
  • 2026 年主流 AI Agent 框架横评:OpenClaw 及衍生版本对比与场景推荐
  • 使用 n8n 打造个人社交媒体内容同步机器人
  • 大模型幻觉问题深度治理:技术体系、工程实践与未来演进
  • OpenClaw:意外爆红的 AI 助手如何改写开源规则
  • 资深工程师的 Web 前端开发全维准备指南
  • 基于 Redis+Caffeine+ 腾讯云的图片库查询上传加载存储优化与分布式 Session 登录

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online