Spring AI与RAG技术实战:构建企业级智能文档问答系统

Spring AI与RAG技术实战:构建企业级智能文档问答系统

引言

在人工智能快速发展的今天,如何将AI能力有效集成到企业应用中成为了技术团队面临的重要挑战。Spring AI作为Spring生态系统中的AI集成框架,结合RAG(检索增强生成)技术,为企业构建智能文档问答系统提供了强大的技术支撑。本文将深入探讨如何使用Spring AI和RAG技术构建高效、准确的企业级智能问答系统。

技术架构概述

核心组件

我们的智能文档问答系统主要包含以下核心组件:

  1. 文档处理层:负责文档的加载、解析和预处理
  2. 向量化层:使用Embedding模型将文本转换为向量表示
  3. 向量数据库:存储和管理文档向量,支持高效的相似性搜索
  4. 检索增强生成层:结合检索结果和生成模型提供准确回答
  5. API服务层:提供RESTful接口供前端调用

技术选型

  • 框架:Spring Boot 3.x + Spring AI
  • 向量数据库:Redis Vector Search
  • Embedding模型:OpenAI text-embedding-ada-002
  • 生成模型:OpenAI GPT-4
  • 文档处理:Apache Tika + LangChain4j

环境准备与配置

Maven依赖配置

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        <version>0.8.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-redis-spring-boot-starter</artifactId>
        <version>0.8.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

应用配置

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4
      embedding:
        options:
          model: text-embedding-ada-002
  
  data:
    redis:
      host: localhost
      port: 6379
      username: default
      password: ${REDIS_PASSWORD}

核心实现

文档处理服务

@Service
@Slf4j
public class DocumentProcessingService {
    
    @Autowired
    private EmbeddingClient embeddingClient;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public void processDocument(MultipartFile file) {
        try {
            // 解析文档内容
            String content = parseDocumentContent(file);
            
            // 分块处理
            List<String> chunks = splitContentIntoChunks(content);
            
            // 向量化并存储
            for (int i = 0; i < chunks.size(); i++) {
                String chunk = chunks.get(i);
                float[] embedding = embeddingClient.embed(chunk);
                
                DocumentChunk documentChunk = new DocumentChunk(
                    file.getOriginalFilename(),
                    chunk,
                    embedding,
                    i
                );
                
                storeInVectorDB(documentChunk);
            }
            
            log.info("文档处理完成: {}", file.getOriginalFilename());
            
        } catch (Exception e) {
            log.error("文档处理失败", e);
            throw new RuntimeException("文档处理失败", e);
        }
    }
    
    private List<String> splitContentIntoChunks(String content) {
        // 实现文档分块逻辑
        return TextSplitter.split(content, 1000, 200);
    }
    
    private void storeInVectorDB(DocumentChunk chunk) {
        // 存储到Redis向量数据库
        String key = "doc:" + chunk.getFileName() + ":" + chunk.getChunkIndex();
        redisTemplate.opsForValue().set(key, chunk);
    }
}

向量检索服务

@Service
public class VectorSearchService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private EmbeddingClient embeddingClient;
    
    public List<DocumentChunk> searchSimilarDocuments(String query, int topK) {
        // 将查询文本向量化
        float[] queryEmbedding = embeddingClient.embed(query);
        
        // 在Redis中进行向量相似性搜索
        List<DocumentChunk> results = new ArrayList<>();
        
        // 这里简化实现,实际应使用Redis的向量搜索功能
        Set<String> keys = redisTemplate.keys("doc:*");
        for (String key : keys) {
            DocumentChunk chunk = (DocumentChunk) redisTemplate.opsForValue().get(key);
            float similarity = calculateCosineSimilarity(queryEmbedding, chunk.getEmbedding());
            
            if (similarity > 0.7) { // 相似度阈值
                results.add(chunk);
                if (results.size() >= topK) {
                    break;
                }
            }
        }
        
        // 按相似度排序
        results.sort((a, b) -> Float.compare(
            calculateCosineSimilarity(queryEmbedding, b.getEmbedding()),
            calculateCosineSimilarity(queryEmbedding, a.getEmbedding())
        ));
        
        return results;
    }
    
    private float calculateCosineSimilarity(float[] vec1, float[] vec2) {
        // 计算余弦相似度
        float dotProduct = 0;
        float norm1 = 0;
        float norm2 = 0;
        
        for (int i = 0; i < vec1.length; i++) {
            dotProduct += vec1[i] * vec2[i];
            norm1 += vec1[i] * vec1[i];
            norm2 += vec2[i] * vec2[i];
        }
        
        return (float) (dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)));
    }
}

RAG问答服务

@Service
public class RAGQuestionAnsweringService {
    
    @Autowired
    private ChatClient chatClient;
    
    @Autowired
    private VectorSearchService vectorSearchService;
    
    public String answerQuestion(String question) {
        // 检索相关文档片段
        List<DocumentChunk> relevantChunks = vectorSearchService.searchSimilarDocuments(question, 3);
        
        if (relevantChunks.isEmpty()) {
            return "抱歉,没有找到相关的信息来回答您的问题。";
        }
        
        // 构建提示词
        String context = buildContextFromChunks(relevantChunks);
        String prompt = buildRAGPrompt(question, context);
        
        // 调用AI模型生成回答
        ChatResponse response = chatClient.call(new UserMessage(prompt));
        
        return response.getResult().getOutput().getContent();
    }
    
    private String buildContextFromChunks(List<DocumentChunk> chunks) {
        StringBuilder contextBuilder = new StringBuilder();
        for (DocumentChunk chunk : chunks) {
            contextBuilder.append("文档片段: ").append(chunk.getContent()).append("\n\n");
        }
        return contextBuilder.toString();
    }
    
    private String buildRAGPrompt(String question, String context) {
        return String.format("""
            基于以下文档内容,请回答用户的问题。
            
            文档内容:
            %s
            
            用户问题:%s
            
            请根据文档内容提供准确、简洁的回答。如果文档中没有相关信息,请如实告知。
            """, context, question);
    }
}

RESTful API设计

@RestController
@RequestMapping("/api/rag")
public class RAGController {
    
    @Autowired
    private RAGQuestionAnsweringService ragService;
    
    @Autowired
    private DocumentProcessingService documentService;
    
    @PostMapping("/upload")
    public ResponseEntity<String> uploadDocument(@RequestParam("file") MultipartFile file) {
        try {
            documentService.processDocument(file);
            return ResponseEntity.ok("文档上传和处理成功");
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("文档处理失败: " + e.getMessage());
        }
    }
    
    @PostMapping("/ask")
    public ResponseEntity<String> askQuestion(@RequestBody QuestionRequest request) {
        try {
            String answer = ragService.answerQuestion(request.getQuestion());
            return ResponseEntity.ok(answer);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("回答问题失败: " + e.getMessage());
        }
    }
    
    @GetMapping("/health")
    public ResponseEntity<String> healthCheck() {
        return ResponseEntity.ok("RAG服务运行正常");
    }
}

@Data
class QuestionRequest {
    private String question;
}

性能优化策略

1. 向量索引优化

@Configuration
public class VectorIndexConfig {
    
    @Bean
    public RedisIndexOperations redisIndexOperations(RedisConnectionFactory connectionFactory) {
        RedisIndexOperations indexOps = new RedisIndexOperations(connectionFactory);
        
        // 创建向量索引
        indexOps.createIndex("document_chunks", VectorFieldType.FLOAT32, 1536, 
            Map.of("algorithm", "HNSW", "ef_construction", "200", "m", "16"));
        
        return indexOps;
    }
}

2. 缓存策略

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(1000));
        return cacheManager;
    }
}

@Service
public class CachedRAGService {
    
    @Autowired
    private RAGQuestionAnsweringService ragService;
    
    @Cacheable(value = "answers", key = "#question")
    public String getCachedAnswer(String question) {
        return ragService.answerQuestion(question);
    }
}

3. 异步处理

@Async
public CompletableFuture<String> processDocumentAsync(MultipartFile file) {
    return CompletableFuture.supplyAsync(() -> {
        documentService.processDocument(file);
        return "处理完成";
    });
}

测试策略

单元测试

@SpringBootTest
@ExtendWith(MockitoExtension.class)
class RAGQuestionAnsweringServiceTest {
    
    @Mock
    private ChatClient chatClient;
    
    @Mock
    private VectorSearchService vectorSearchService;
    
    @InjectMocks
    private RAGQuestionAnsweringService ragService;
    
    @Test
    void testAnswerQuestionWithRelevantContext() {
        // 模拟向量搜索返回相关文档
        DocumentChunk chunk = new DocumentChunk("test.txt", "Spring AI是Spring生态的AI集成框架", 
            new float[1536], 0);
        when(vectorSearchService.searchSimilarDocuments(anyString(), anyInt()))
            .thenReturn(List.of(chunk));
        
        // 模拟AI回答
        when(chatClient.call(any())).thenReturn(
            new ChatResponse(new Generation("Spring AI提供了AI能力集成解决方案"))
        );
        
        String answer = ragService.answerQuestion("什么是Spring AI?");
        
        assertNotNull(answer);
        assertTrue(answer.contains("Spring AI"));
    }
}

集成测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class RAGIntegrationTest {
    
    @Container
    static RedisContainer redis = new RedisContainer("redis:7-alpine");
    
    @Test
    void testFullRAGWorkflow() {
        // 测试完整的RAG工作流
        // 包括文档上传、向量化、检索和问答
    }
}

部署与监控

Docker部署

FROM openjdk:17-jdk-slim

WORKDIR /app

COPY target/rag-system-0.0.1-SNAPSHOT.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

Prometheus监控

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    tags:
      application: rag-system

总结与展望

本文详细介绍了如何使用Spring AI和RAG技术构建企业级智能文档问答系统。通过结合向量数据库、Embedding模型和大语言模型,我们实现了准确、高效的文档问答能力。

关键优势:

  1. 准确性提升:RAG技术有效减少了AI幻觉问题
  2. 可扩展性:基于Spring生态,易于集成和扩展
  3. 性能优化:通过向量索引和缓存策略确保系统性能
  4. 企业级特性:支持完整的文档处理流程和监控

未来改进方向:

  1. 支持多模态文档处理(图片、表格等)
  2. 实现实时文档更新和增量索引
  3. 加入用户反馈机制,持续优化回答质量
  4. 支持多租户和权限管理

Spring AI与RAG技术的结合为企业智能化转型提供了强有力的技术支撑,相信随着技术的不断发展,这类系统将在企业知识管理领域发挥越来越重要的作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Uranus^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值