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

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

基于 Spring AI 框架与 Claude 大语言模型构建企业级智能客服系统。方案涵盖分层架构设计、RAG 知识库检索增强生成、Maven 依赖配置及核心业务代码实现。包含 REST API 开发、Docker 容器化部署、Kubernetes 编排以及性能优化策略如缓存与异步处理。通过向量数据库存储知识片段,实现精准问答与多轮对话管理,确保数据安全与企业级稳定性。

MongoKing发布于 2026/3/26更新于 2026/6/1421 浏览
基于 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. 未来发展方向
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • Agentic AI 核心概念及与传统 AIGC 的区别解析
  • 基于 Python 的名中医肿瘤治疗教学案例库设计与实现
  • 大模型岗位面试总结:24 家公司,9 个 Offer
  • MySQL 事务与锁机制深度解析
  • Kotlin 结合属性动画实现糖果捕捉游戏
  • LeRobot 框架深度解析:架构、策略、硬件与数据采集实战
  • 零基础网络安全入门指南:学习路线与实战建议
  • Kubernetes CronJob 定时任务详解
  • Rokid JSAR 实战指南:Web 技术栈 AR 开发环境搭建与 3D 时钟项目详解
  • Spring Cloud 负载均衡实战:LoadBalancer 原理与策略
  • npm 安装 OpenClaw 遇到 Git 错误及权限问题处理
  • 项目管理基础:核心定义、干系人与九大知识域
  • FPGA 核心解析:从原理到应用场景详解
  • 机器人系统架构详解与数据驱动决策算法指南
  • OpenClaw 配置飞书机器人完整指南
  • 中小团队低成本搭建项目管理系统:Ubuntu 下 DooTask 私有化部署
  • Mujoco 足式机器人强化学习:URDF 转 XML 转换与配置详解
  • 2023 中国大模型落地应用案例解析:技术、趋势与生态
  • Superpowers:用工程流程纪律驯化 Claude Code 实现可靠交付
  • StarUML 6.3.3 安装与 C++ 扩展配置指南(Windows 11)

相关免费在线工具

  • 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