Spring AI智能航空客服项目实战Java大模型RAG构建企业级AI客服系统在航空业务快速发展的今天客服系统面临着航班信息实时更新、退改签政策复杂、多语言服务需求等挑战。传统客服系统往往响应慢、知识更新不及时而基于大模型的AI智能客服能够有效解决这些问题。本文将完整演示如何使用Spring AI框架结合RAG技术搭建一个企业级的航空AI智能客服系统。1. 项目背景与核心技术介绍1.1 航空客服的业务痛点航空客服系统需要处理复杂的业务场景航班查询、票价咨询、退改签政策、行李托运规则、特殊旅客服务等。传统解决方案存在以下问题知识更新滞后航空政策频繁变更手动更新知识库效率低下多轮对话能力弱用户问题往往涉及多个关联查询传统系统难以理解上下文多语言支持成本高国际航空业务需要支持多种语言服务实时信息整合难航班动态、座位 availability等实时数据难以同步1.2 Spring AI与RAG技术优势Spring AI是Spring官方推出的AI应用开发框架提供了统一的API来接入各种大语言模型。RAGRetrieval-Augmented Generation技术通过检索增强生成将外部知识库与大模型能力结合解决了大模型幻觉问题和知识更新难题。技术栈组成Spring Boot 3.5.3基础框架Spring AIAI能力集成向量数据库知识向量化存储如Milvus、Chroma大语言模型智普AI、OpenAI等文档解析PDF、Word等格式处理2. 环境准备与项目搭建2.1 开发环境要求确保你的开发环境满足以下要求# 检查Java版本 java -version # 要求Java 17或更高版本 # 检查Maven版本 mvn -version # 要求Maven 3.6 # 推荐IDEIntelliJ IDEA或VS Code2.2 创建Spring Boot项目使用Spring Initializr创建项目基础结构!-- pom.xml 关键依赖 -- dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-M5/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-milvus-spring-boot-starter/artifactId version1.0.0-M5/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency /dependencies2.3 配置文件设置创建application.yml配置文件# application.yml spring: ai: openai: api-key: ${OPENAI_API_KEY:your-api-key} base-url: https://api.openai.com/v1 vectorstore: milvus: host: localhost port: 19530 database-name: airline_kb collection-name: airline_docs datasource: url: jdbc:mysql://localhost:3306/airline_ai?useSSLfalse username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true server: port: 80803. 核心架构设计3.1 系统架构概览航空AI客服系统采用分层架构设计表示层 → 控制层 → 服务层 → 数据层 ↓ ↓ ↓ ↓ 前端界面 → Controller → AI服务 → 向量数据库 ↓ ↓ 业务逻辑 → 关系数据库3.2 数据库设计创建航空知识核心表结构-- 航班信息表 CREATE TABLE flights ( id BIGINT PRIMARY KEY AUTO_INCREMENT, flight_number VARCHAR(10) NOT NULL, departure_city VARCHAR(50) NOT NULL, arrival_city VARCHAR(50) NOT NULL, departure_time DATETIME NOT NULL, arrival_time DATETIME NOT NULL, price DECIMAL(10,2) NOT NULL, seats_available INT NOT NULL, status VARCHAR(20) DEFAULT SCHEDULED ); -- 客服对话记录表 CREATE TABLE chat_sessions ( id BIGINT PRIMARY KEY AUTO_INCREMENT, session_id VARCHAR(64) NOT NULL UNIQUE, user_id VARCHAR(50), start_time DATETIME DEFAULT CURRENT_TIMESTAMP, end_time DATETIME, status VARCHAR(20) DEFAULT ACTIVE ); -- 知识文档表 CREATE TABLE knowledge_docs ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT NOT NULL, doc_type VARCHAR(50) NOT NULL, version VARCHAR(20) NOT NULL, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT true );4. RAG知识库构建实战4.1 文档解析与预处理航空知识文档通常包含PDF格式的运营手册、政策文件等需要先进行解析和分块// 文档解析服务 Service public class DocumentParserService { private static final Logger logger LoggerFactory.getLogger(DocumentParserService.class); public ListDocumentChunk parsePdfDocument(MultipartFile file) { ListDocumentChunk chunks new ArrayList(); try (PDDocument document PDDocument.load(file.getInputStream())) { PDFTextStripper stripper new PDFTextStripper(); String text stripper.getText(document); // 按段落分块保持语义完整性 String[] paragraphs text.split(\n\n); for (int i 0; i paragraphs.length; i) { if (paragraphs[i].trim().length() 50) { // 过滤过短段落 DocumentChunk chunk new DocumentChunk(); chunk.setContent(paragraphs[i].trim()); chunk.setChunkIndex(i); chunk.setSourceFile(file.getOriginalFilename()); chunks.add(chunk); } } } catch (Exception e) { logger.error(PDF解析失败: {}, e.getMessage()); throw new RuntimeException(文档解析失败, e); } return chunks; } } // 文档分块实体 Entity Table(name document_chunks) public class DocumentChunk { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String content; private Integer chunkIndex; private String sourceFile; private String embeddingVector; // 存储向量化结果 // getter/setter省略 }4.2 向量化与存储使用Spring AI的EmbeddingClient进行文本向量化Service public class VectorStoreService { Autowired private EmbeddingClient embeddingClient; Autowired private MilvusVectorStore vectorStore; public void storeDocumentChunks(ListDocumentChunk chunks) { ListDocument documents chunks.stream() .map(chunk - { // 为每个chunk生成向量 ListDouble embedding embeddingClient.embed(chunk.getContent()); chunk.setEmbeddingVector(embedding.toString()); return new Document(chunk.getContent(), Map.of(source, chunk.getSourceFile(), chunkIndex, chunk.getChunkIndex().toString())); }) .collect(Collectors.toList()); // 存储到向量数据库 vectorStore.add(documents); } public ListDocument similaritySearch(String query, int topK) { return vectorStore.similaritySearch( SearchRequest.query(query).withTopK(topK) ); } }4.3 知识检索优化针对航空领域特点优化检索策略Service public class KnowledgeRetrievalService { Autowired private VectorStoreService vectorStoreService; public String retrieveRelevantKnowledge(String userQuery, String context) { // 1. 查询扩展添加航空领域关键词 String expandedQuery expandQueryWithDomainKeywords(userQuery); // 2. 混合检索结合语义和关键词匹配 ListDocument semanticResults vectorStoreService .similaritySearch(expandedQuery, 5); ListDocument keywordResults keywordSearch(userQuery, 3); // 3. 结果重排序基于相关性和时效性 ListDocument mergedResults rerankResults( semanticResults, keywordResults, context); // 4. 构建上下文提示 return buildContextPrompt(mergedResults, userQuery); } private String expandQueryWithDomainKeywords(String query) { // 航空领域关键词映射 MapString, ListString domainKeywords Map.of( 行李, Arrays.asList(托运, 重量, 尺寸, 超重费), 改签, Arrays.asList(费用, 政策, 时间, 手续费), 退票, Arrays.asList(退款, 条件, 期限, 扣款) ); String expanded query; for (Map.EntryString, ListString entry : domainKeywords.entrySet()) { if (query.contains(entry.getKey())) { expanded String.join( , entry.getValue()); } } return expanded; } }5. AI对话引擎实现5.1 智能对话服务集成大语言模型处理用户查询Service public class AIChatService { Autowired private ChatClient chatClient; Autowired private KnowledgeRetrievalService knowledgeService; public ChatResponse processUserMessage(String userMessage, String sessionId) { try { // 1. 检索相关知识 String context knowledgeService.retrieveRelevantKnowledge(userMessage, sessionId); // 2. 构建提示词 String prompt buildAirlinePrompt(userMessage, context); // 3. 调用大模型 ChatResponse response chatClient.call( new Prompt(prompt, Map.of(temperature, 0.1, max_tokens, 1000)) ); // 4. 后处理验证事实准确性 String processedResponse postProcessResponse( response.getResult().getOutput().getContent(), context); return new ChatResponse(processedResponse, getConfidenceScore(processedResponse, context)); } catch (Exception e) { logger.error(AI处理失败: {}, e.getMessage()); return new ChatResponse(抱歉系统暂时无法处理您的请求请稍后再试。, 0.0); } } private String buildAirlinePrompt(String userQuery, String context) { return String.format( 你是一个专业的航空客服助手请根据以下知识库信息回答用户问题。 知识库信息 %s 用户问题%s 回答要求 1. 基于知识库信息准确回答 2. 如果知识库信息不足请明确说明 3. 回答要专业、友好、简洁 4. 涉及费用、时间等关键信息要精确 5. 不要编造知识库中没有的信息 请回答 , context, userQuery); } }5.2 多轮对话管理维护对话上下文实现连贯的多轮对话Service public class ConversationService { Autowired private ChatSessionRepository sessionRepository; Autowired private MessageRepository messageRepository; public ChatSession getOrCreateSession(String sessionId, String userId) { return sessionRepository.findBySessionId(sessionId) .orElseGet(() - { ChatSession newSession new ChatSession(); newSession.setSessionId(sessionId); newSession.setUserId(userId); newSession.setStartTime(LocalDateTime.now()); return sessionRepository.save(newSession); }); } public void saveMessage(ChatSession session, String content, MessageType type, String sender) { ChatMessage message new ChatMessage(); message.setSession(session); message.setContent(content); message.setMessageType(type); message.setSender(sender); message.setTimestamp(LocalDateTime.now()); messageRepository.save(message); // 维护对话上下文最近10轮 maintainConversationContext(session); } public String getConversationContext(String sessionId) { ListChatMessage recentMessages messageRepository .findTop10BySession_SessionIdOrderByTimestampDesc(sessionId); return recentMessages.stream() .sorted(Comparator.comparing(ChatMessage::getTimestamp)) .map(msg - msg.getSender() : msg.getContent()) .collect(Collectors.joining(\n)); } }6. 业务功能集成6.1 实时航班查询集成对接航空公司的实时航班APIService public class FlightService { Autowired private RestTemplate restTemplate; Value(${airline.api.flight-info}) private String flightApiUrl; public FlightInfo getFlightStatus(String flightNumber) { try { ResponseEntityFlightApiResponse response restTemplate.getForEntity( flightApiUrl /flights/ flightNumber, FlightApiResponse.class); if (response.getStatusCode().is2xxSuccessful() response.getBody() ! null) { return convertToFlightInfo(response.getBody()); } } catch (Exception e) { logger.warn(航班API查询失败: {}, e.getMessage()); } return getCachedFlightInfo(flightNumber); } public ListFlightInfo searchFlights(String departure, String arrival, LocalDate date) { // 实现航班搜索逻辑 return Collections.emptyList(); } } // 航班信息DTO Data public class FlightInfo { private String flightNumber; private String airline; private String departureCity; private String arrivalCity; private LocalDateTime departureTime; private LocalDateTime arrivalTime; private String status; // SCHEDULED, DELAYED, CANCELLED, etc. private String gate; private BigDecimal price; private Integer seatsAvailable; }6.2 退改签政策引擎实现智能政策解释和计算Service public class PolicyEngineService { public RefundCalculation calculateRefund(String ticketNumber, LocalDateTime requestTime) { TicketInfo ticket getTicketInfo(ticketNumber); RefundPolicy policy getApplicablePolicy(ticket, requestTime); RefundCalculation calculation new RefundCalculation(); calculation.setOriginalPrice(ticket.getPrice()); calculation.setRefundableAmount(calculateRefundAmount(ticket, policy)); calculation.setDeductionReason(policy.getDescription()); calculation.setProcessingTime(getProcessingTime(policy)); return calculation; } private BigDecimal calculateRefundAmount(TicketInfo ticket, RefundPolicy policy) { BigDecimal baseAmount ticket.getPrice(); LocalDateTime departureTime ticket.getDepartureTime(); long hoursBeforeDeparture ChronoUnit.HOURS.between( LocalDateTime.now(), departureTime); if (hoursBeforeDeparture policy.getFreeRefundHours()) { return baseAmount.multiply(BigDecimal.valueOf(policy.getRefundRate())); } else { return baseAmount.subtract(policy.getPenaltyFee()); } } }7. REST API接口设计7.1 客服对话接口RestController RequestMapping(/api/chat) public class ChatController { Autowired private AIChatService aiChatService; Autowired private ConversationService conversationService; PostMapping(/message) public ResponseEntityChatResponse sendMessage( RequestBody ChatRequest request, RequestHeader(value X-Session-Id, required false) String sessionId) { if (sessionId null) { sessionId generateSessionId(); } ChatSession session conversationService.getOrCreateSession(sessionId, request.getUserId()); conversationService.saveMessage(session, request.getMessage(), MessageType.USER, customer); ChatResponse response aiChatService.processUserMessage(request.getMessage(), sessionId); conversationService.saveMessage(session, response.getContent(), MessageType.AI, assistant); return ResponseEntity.ok() .header(X-Session-Id, sessionId) .body(response); } GetMapping(/sessions/{sessionId}/history) public ResponseEntityListChatMessage getChatHistory(PathVariable String sessionId) { ListChatMessage messages conversationService.getChatHistory(sessionId); return ResponseEntity.ok(messages); } } // 请求响应DTO Data class ChatRequest { private String message; private String userId; } Data class ChatResponse { private String content; private Double confidence; private LocalDateTime timestamp; public ChatResponse(String content, Double confidence) { this.content content; this.confidence confidence; this.timestamp LocalDateTime.now(); } }7.2 航班查询接口RestController RequestMapping(/api/flights) public class FlightController { Autowired private FlightService flightService; GetMapping(/status/{flightNumber}) public ResponseEntityFlightInfo getFlightStatus(PathVariable String flightNumber) { FlightInfo flightInfo flightService.getFlightStatus(flightNumber); return ResponseEntity.ok(flightInfo); } GetMapping(/search) public ResponseEntityListFlightInfo searchFlights( RequestParam String departure, RequestParam String arrival, RequestParam DateTimeFormat(iso DateTimeFormat.ISO.DATE) LocalDate date) { ListFlightInfo flights flightService.searchFlights(departure, arrival, date); return ResponseEntity.ok(flights); } }8. 系统监控与性能优化8.1 监控指标收集Service public class MetricsService { private final MeterRegistry meterRegistry; public MetricsService(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; } public void recordChatResponseTime(long milliseconds) { meterRegistry.timer(ai.chat.response.time) .record(milliseconds, TimeUnit.MILLISECONDS); } public void recordKnowledgeRetrievalTime(String queryType, long milliseconds) { Timer.builder(knowledge.retrieval.time) .tag(queryType, queryType) .register(meterRegistry) .record(milliseconds, TimeUnit.MILLISECONDS); } public void incrementErrorCount(String errorType) { meterRegistry.counter(system.errors, type, errorType).increment(); } }8.2 缓存策略优化Service public class CacheService { Autowired private RedisTemplateString, Object redisTemplate; private static final long CACHE_EXPIRE_HOURS 24; public void cacheFlightInfo(String flightNumber, FlightInfo flightInfo) { String key flight: flightNumber; redisTemplate.opsForValue().set(key, flightInfo, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); } public FlightInfo getCachedFlightInfo(String flightNumber) { String key flight: flightNumber; return (FlightInfo) redisTemplate.opsForValue().get(key); } public void cacheKnowledgeEmbedding(String content, ListDouble embedding) { // 缓存常用查询的向量结果减少重复计算 String key embedding: DigestUtils.md5DigestAsHex(content.getBytes()); redisTemplate.opsForValue().set(key, embedding, 7, TimeUnit.DAYS); } }9. 安全与权限控制9.1 API安全防护Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf - csrf.disable()) .authorizeHttpRequests(authz - authz .requestMatchers(/api/chat/**).permitAll() .requestMatchers(/api/flights/status/**).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() ) .sessionManagement(session - session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .oauth2ResourceServer(oauth2 - oauth2.jwt(Customizer.withDefaults())); return http.build(); } }9.2 数据隐私保护Service public class PrivacyService { public String anonymizeUserData(String originalData) { // 敏感信息脱敏处理 return originalData .replaceAll(\\b\\d{4}-\\d{4}-\\d{4}-\\d{4}\\b, ****-****-****-****) // 信用卡 .replaceAll(\\b\\d{18}\\b, **************) // 身份证 .replaceAll(\\b\\d{11}\\b, *******); // 手机号 } public boolean containsSensitiveInfo(String text) { Pattern sensitivePattern Pattern.compile( \\b(?:身份证|护照|信用卡|密码|secret|password)\\b, Pattern.CASE_INSENSITIVE ); return sensitivePattern.matcher(text).find(); } }10. 部署与运维实践10.1 Docker容器化部署创建Dockerfile和docker-compose.yml# Dockerfile FROM openjdk:17-jdk-slim VOLUME /tmp COPY target/airline-ai-customer-service-1.0.0.jar app.jar ENTRYPOINT [java,-jar,/app.jar]# docker-compose.yml version: 3.8 services: airline-ai-service: build: . ports: - 8080:8080 environment: - SPRING_PROFILES_ACTIVEprod - OPENAI_API_KEY${OPENAI_API_KEY} depends_on: - mysql - redis - milvus mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_DATABASE: airline_ai redis: image: redis:7.0-alpine milvus: image: milvusdb/milvus:v2.3.4 ports: - 19530:1953010.2 生产环境配置# application-prod.yml spring: datasource: url: jdbc:mysql://mysql:3306/airline_ai?useSSLfalse username: root password: ${DB_PASSWORD} ai: openai: api-key: ${OPENAI_API_KEY} base-url: https://api.openai.com/v1 redis: host: redis port: 6379 management: endpoints: web: exposure: include: health,metrics,info endpoint: health: show-details: always logging: level: com.airline: INFO org.springframework.ai: WARN file: name: /logs/airline-ai.log11. 常见问题与解决方案11.1 性能优化问题问题1向量检索响应慢原因向量数据库索引未优化或分片不合理解决方案为向量字段创建合适的索引调整Milvus的索引参数nlist、nprobe使用缓存减少重复检索// 索引优化配置 Configuration public class VectorStoreConfig { Bean public IndexType vectorIndexType() { return IndexType.IVF_FLAT; // 适合中等规模数据集 } Bean public MetricType vectorMetricType() { return MetricType.L2; // 欧氏距离适合文本相似度 } }问题2大模型API调用超时原因网络延迟或API限流解决方案实现请求重试机制设置合理的超时时间使用连接池管理API调用Configuration public class RestTemplateConfig { Bean public RestTemplate restTemplate() { return new RestTemplateBuilder() .setConnectTimeout(Duration.ofSeconds(10)) .setReadTimeout(Duration.ofSeconds(30)) .build(); } }11.2 知识库更新问题问题知识库更新后检索效果不佳原因向量化策略不一致或数据预处理问题解决方案建立版本化的知识库管理实现增量更新机制添加质量评估流程Service public class KnowledgeVersionService { public void updateKnowledgeBase(KnowledgeUpdateRequest request) { // 1. 备份当前版本 backupCurrentKnowledge(); // 2. 验证新文档质量 if (!validateDocumentQuality(request.getDocuments())) { throw new ValidationException(文档质量检查失败); } // 3. 增量更新向量库 incrementalUpdateVectorStore(request.getDocuments()); // 4. 验证更新效果 validateUpdateEffectiveness(); } }12. 最佳实践总结12.1 开发实践建议模块化设计将AI能力、业务逻辑、数据访问分层解耦配置外部化所有环境相关配置通过环境变量管理异常处理为不同的异常类型设计具体的处理策略日志规范结构化日志记录便于监控和排查问题12.2 运维实践建议健康检查实现完整的健康检查端点监控关键组件状态性能监控建立完整的监控指标体系包括响应时间、错误率等容量规划根据业务量预估资源需求提前进行扩容准备备份策略定期备份向量数据库和关系数据库12.3 安全实践建议API限流防止恶意请求消耗AI API额度数据加密敏感数据在传输和存储时都要加密访问控制基于角色的权限管理最小权限原则审计日志记录关键操作便于安全审计通过本文的完整实践你可以构建一个功能完善、性能优越的航空AI智能客服系统。在实际项目中建议先从核心功能开始迭代开发逐步完善各项高级特性。