当前位置: 首页 > news >正文

SpringAI全流程实战手册

当Java开发者遇上大模型,会擦出怎样的火花?SpringAI给出了答案,用Spring的方式,把AI能力变成业务开发的常规武器。

本文带你从零开始,构建一个企业级智能问答系统,涵盖RAG架构、向量数据库、对话上下文、异步处理、监控告警等完整链路,最终产出一套可部署到K8s的生产级代码。

一、为什么Java开发者需要SpringAI?

在企业级AI应用开发领域,Python一度是绝对的主角,LangChain、LlamaIndex等框架生态丰富,Java开发者只能望洋兴叹。

SpringAI的出现改变了这一格局。作为Spring官方出品的AI集成框架,它延续了Spring“约定优于配置”的哲学,让Java开发者能够以熟悉的Spring风格接入OpenAI、Azure、Ollama等大语言模型。

SpringAI的核心价值:

统一抽象:ChatClientEmbeddingClient等接口屏蔽了不同AI服务商的差异

生态融合:与Spring Boot、Spring Data、Spring Cloud无缝集成

生产就绪:自带监控、缓存、重试等企业级特性

本文将以一个完整的智能问答系统为案例,带你走通SpringAI开发的全流程。

二、系统架构全景图

2.1 整体架构

┌─────────────────────────────────────────────────────────────┐ │ API Gateway │ │ (负载均衡 / 限流 / 认证) │ └─────────────────────────┬───────────────────────────────────┘ │ ┌─────────────────────────▼───────────────────────────────────┐ │ SpringAI 应用层 │ ├─────────────────────────────────────────────────────────────┤ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 问答API │ │ 文档管理 │ │ 对话管理 │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌──────▼──────────────────▼──────────────────▼───────┐ │ │ │ SpringAI 核心服务层 │ │ │ │ ChatClient │ EmbeddingClient │ VectorStore │ │ │ └──────┬──────────────────────────────────────────────┘ │ │ │ │ │ ┌──────▼──────────────────────────────────────────────┐ │ │ │ 基础设施层 │ │ │ │ PostgreSQL+pgvector │ Redis │ OpenAI API │ │ │ └──────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘

2.2 核心组件职责

组件职责技术选型
ChatClient与大模型对话,生成回答SpringAI封装
EmbeddingClient文本向量化,支持语义检索SpringAI封装
VectorStore存储和检索文档向量PGVector
对话管理维护多轮对话上下文内存缓存 + Redis
文档处理文档分割、向量化、存储自定义服务

三、环境搭建

3.1 项目依赖

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> </parent> <dependencies> <!-- SpringAI OpenAI --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>0.8.1</version> </dependency> <!-- PGVector向量数据库 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId> <version>0.8.1</version> </dependency> <!-- 常规依赖 --> <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.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> </dependencies>

3.2 配置文件

# application.yml spring: datasource: url: jdbc:postgresql://localhost:5432/ai_qa_system username: postgres password: ${DB_PASSWORD} ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-3.5-turbo temperature: 0.7 max-tokens: 2000 embedding: options: model: text-embedding-ada-002 vectorstore: pgvector: index-type: HNSW distance-type: COSINE dimensions: 1536 logging: level: org.springframework.ai: DEBUG

3.3 数据库初始化

-- 启用PGVector扩展 CREATE EXTENSION IF NOT EXISTS vector; -- 文档存储表 CREATE TABLE IF NOT EXISTS knowledge_docs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), content TEXT NOT NULL, metadata JSONB, embedding vector(1536), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- HNSW索引(余弦相似度) CREATE INDEX IF NOT EXISTS docs_embedding_idx ON knowledge_docs USING hnsw (embedding vector_cosine_ops);

四、核心实现

4.1 文档实体与Repository

@Entity @Table(name = "knowledge_docs") @Data @NoArgsConstructor @AllArgsConstructor public class KnowledgeDocument { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; @Column(columnDefinition = "TEXT") private String content; @Column(columnDefinition = "jsonb") private Map<String, Object> metadata; @Column(columnDefinition = "vector(1536)") private float[] embedding; private LocalDateTime createdAt; } @Repository public interface KnowledgeDocumentRepository extends JpaRepository<KnowledgeDocument, UUID> { @Query(value = "SELECT * FROM knowledge_docs ORDER BY embedding <=> cast(:embedding as vector) LIMIT :topK", nativeQuery = true) List<KnowledgeDocument> findSimilarDocuments( @Param("embedding") float[] embedding, @Param("topK") int topK ); }

4.2 文档处理服务

@Service @Slf4j public class DocumentProcessingService { @Autowired private EmbeddingClient embeddingClient; @Autowired private KnowledgeDocumentRepository repository; /** * 处理文档:分割 → 向量化 → 存储 */ @Transactional public void processDocument(String content, Map<String, Object> metadata) { // 1. 文档分割(按500字符分块,重叠50字符) List<String> chunks = splitText(content, 500, 50); // 2. 批量向量化 List<List<Double>> embeddings = embeddingClient.embed(chunks); // 3. 存储到向量库 for (int i = 0; i < chunks.size(); i++) { KnowledgeDocument doc = new KnowledgeDocument(); doc.setContent(chunks.get(i)); Map<String, Object> docMeta = new HashMap<>(metadata); docMeta.put("chunk_index", i); docMeta.put("total_chunks", chunks.size()); doc.setMetadata(docMeta); doc.setEmbedding(toFloatArray(embeddings.get(i))); repository.save(doc); } log.info("文档处理完成: {} 个分块", chunks.size()); } /** * 语义检索 */ public List<KnowledgeDocument> search(String query, int topK) { List<Double> queryVector = embeddingClient.embed(query); return repository.findSimilarDocuments(toFloatArray(queryVector), topK); } private List<String> splitText(String text, int chunkSize, int overlap) { // 按句号、换行等自然边界分割 List<String> chunks = new ArrayList<>(); // ... 实现略 return chunks; } private float[] toFloatArray(List<Double> list) { float[] result = new float[list.size()]; for (int i = 0; i < list.size(); i++) { result[i] = list.get(i).floatValue(); } return result; } }

4.3 RAG问答服务

@Service @Slf4j public class IntelligentQAService { @Autowired private ChatClient chatClient; @Autowired private DocumentProcessingService documentService; /** * RAG问答:检索 → 增强 → 生成 */ public AnswerResponse ask(String question) { // 1. 检索相关文档片段 List<KnowledgeDocument> docs = documentService.search(question, 5); // 2. 构建增强Prompt String prompt = buildRAGPrompt(question, docs); // 3. 调用大模型生成答案 String answer = chatClient.call(new UserMessage(prompt)); // 4. 返回结果(附引用来源) return AnswerResponse.builder() .question(question) .answer(answer) .sources(docs.stream() .map(d -> d.getMetadata().get("source")) .collect(Collectors.toList())) .build(); } private String buildRAGPrompt(String question, List<KnowledgeDocument> docs) { StringBuilder context = new StringBuilder(); for (int i = 0; i < docs.size(); i++) { context.append(String.format("[参考%d]: %s\n\n", i + 1, docs.get(i).getContent())); } return String.format(""" 你是一个专业的智能助手。请基于以下参考信息回答用户问题。 如果参考信息不足以回答问题,请明确告知用户“根据现有资料无法回答该问题”。 【参考信息】 %s 【用户问题】 %s 请给出准确、简洁的回答: """, context.toString(), question); } }

4.4 REST API接口

@RestController @RequestMapping("/api/qa") @Tag(name = "智能问答", description = "基于SpringAI的RAG问答接口") public class QAController { @Autowired private IntelligentQAService qaService; @Autowired private DocumentProcessingService documentService; @PostMapping("/ask") public ResponseEntity<AnswerResponse> ask(@RequestBody @Valid QuestionRequest request) { AnswerResponse response = qaService.ask(request.getQuestion()); return ResponseEntity.ok(response); } @PostMapping("/documents") public ResponseEntity<Void> uploadDocument(@RequestBody DocumentUploadRequest request) { documentService.processDocument(request.getContent(), request.getMetadata()); return ResponseEntity.ok().build(); } }

五、高级特性

5.1 多轮对话上下文管理

@Component public class ConversationManager { private final Map<String, List<Message>> sessions = new ConcurrentHashMap<>(); private static final int MAX_HISTORY = 20; public Prompt createContextualPrompt(String sessionId, String userInput) { List<Message> history = sessions.getOrDefault(sessionId, new ArrayList<>()); List<Message> messages = new ArrayList<>(history); messages.add(new UserMessage(userInput)); return new Prompt(messages); } public void appendResponse(String sessionId, String assistantResponse) { sessions.computeIfAbsent(sessionId, k -> new ArrayList<>()) .add(new AssistantMessage(assistantResponse)); // 保持最近N条记录 List<Message> history = sessions.get(sessionId); if (history.size() > MAX_HISTORY) { sessions.put(sessionId, new ArrayList<>(history.subList(history.size() - MAX_HISTORY, history.size()))); } } }

5.2 流式输出

@PostMapping(value = "/ask/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<String>> askStream(@RequestBody QuestionRequest request) { return chatClient.stream(new UserMessage(request.getQuestion())) .map(chunk -> ServerSentEvent.builder(chunk.getResult().getOutput().getContent()).build()) .onErrorResume(e -> Flux.just(ServerSentEvent.builder("错误: " + e.getMessage()).build())); }

5.3 缓存优化

@Service @Slf4j public class CachedQAService { @Autowired private IntelligentQAService qaService; // 相同问题1小时内直接返回缓存结果 @Cacheable(value = "qa_cache", key = "#question", unless = "#result == null") public AnswerResponse getCachedAnswer(String question) { return qaService.ask(question); } }

5.4 监控指标

@Component public class AIMetrics { private final MeterRegistry meterRegistry; private final Timer ragTimer; private final Counter errorCounter; public AIMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.ragTimer = Timer.builder("ai.rag.duration") .description("RAG问答耗时") .register(meterRegistry); this.errorCounter = Counter.builder("ai.errors.total") .description("AI调用错误总数") .register(meterRegistry); } public <T> T recordRAG(Supplier<T> supplier) { return ragTimer.record(supplier); } public void recordError(String type) { errorCounter.increment(); meterRegistry.counter("ai.errors", "type", type).increment(); } }

六、部署与运维

6.1 Docker镜像

FROM openjdk:17-jdk-slim AS builder WORKDIR /app COPY . . RUN ./mvnw package -DskipTests FROM openjdk:17-jdk-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 ENV JAVA_OPTS="-Xms512m -Xmx1024m" ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

6.2 Kubernetes部署

apiVersion: apps/v1 kind: Deployment metadata: name: springai-qa spec: replicas: 3 selector: matchLabels: app: springai-qa template: metadata: labels: app: springai-qa spec: containers: - name: app image: springai-qa:latest ports: - containerPort: 8080 env: - name: OPENAI_API_KEY valueFrom: secretKeyRef: name: openai-secret key: api-key resources: requests: memory: "1Gi" cpu: "500m" limits: memory: "2Gi" cpu: "2000m" livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 30

七、总结

本文从零构建了一个企业级智能问答系统,核心要点如下:

模块技术选型关键考量
AI接入SpringAI + OpenAI统一抽象,便于切换模型
向量存储PostgreSQL + pgvector降低架构复杂度,事务支持
文档检索RAG架构增强回答准确性,减少幻觉
对话管理内存会话 + 多轮上下文支持连续对话
性能Caffeine缓存 + 异步批处理高并发场景优化
可观测性Micrometer + Prometheus实时监控AI调用耗时与错误率

SpringAI让Java开发者不再是大模型时代的旁观者。通过这套框架,你可以像写普通Spring Boot应用一样,将AI能力融入企业级系统。

下一步可以拓展的方向:

接入私有化部署模型

引入Agent多智能体协作

增加Rerank模块提升检索精度

支持多模态文档(PDF、Word、图片)

希望本文能成为你进入SpringAI世界的实战地图。

http://www.jsqmd.com/news/811723/

相关文章:

  • DSP28335串口调试别再抓瞎了!手把手教你重定向printf到串口(附完整代码)
  • MBTI十六型人格职业性格测试源码完整版 亲测源码
  • 解决每次打开JFlash就提示:Device: TLE9863QXW20: Flash bank 0x11000000: No loader specified的问题
  • ContextPacker MCP Server:让AI编程助手精准读取GitHub代码库
  • 2026年GEO优化公司哪家靠谱?TOP5热门服务商选型指南 - 科技焦点
  • 通过curl命令快速测试Taotoken的OpenAI兼容接口与模型响应
  • Taotoken 的 Token Plan 套餐在实际项目中如何节省开支
  • 医疗技术创新为何难落地?从临床需求到法规资本的全链路解析
  • G-PCC编解码器核心模块解析:从八叉树到属性编码的技术演进
  • Shipwright:AI驱动的产品经理操作系统,从提示词到质量系统
  • 如何用Seraphine提升英雄联盟游戏体验:新手必备的智能助手完整指南
  • AI小白必看:收藏这份从零入门大模型的核心概念指南
  • 洛谷 P4097 【模板】李超线段树 / [HEOI2013] Segment - Rye
  • 技术新人最常犯的5个错误,第3个几乎人人都中招——软件测试从业者深度指南
  • A2 如何向AI描述需求(提示词模板库)
  • Deeplearning4j完全指南
  • 别再为进度条出图发愁了!手把手教你扩展Unity UGUI Image组件,让Filled模式完美支持九宫格
  • 如何永久免费使用AI编程助手:Cursor Free VIP完整指南
  • AI从入门到精通:一条清晰的脉络,带你读懂机器学习、深度学习与大模型的底层逻辑!
  • 实在Agent实测:解决采购合同审核流程冗长与原材料交付周期拉长的架构之道
  • 说说损失膝盖的行为和保护膝盖的方法
  • NSGA-III算法详解:从‘参考点’这个核心概念出发,彻底搞懂多目标优化新思路
  • 2026.5.9
  • 进阶篇如何学习编写 Shell 脚本?
  • AI工程化实战:四层驾驭模型解决开发盲区,打造稳定智能工作流
  • AI生物标志物发现:从海量数据中找真正的信号
  • Cursor Pro激活器:3分钟永久解锁AI编程助手高级功能
  • 2711P-K7C4D1 触摸屏面板
  • 数据流架构芯片深度科普:打破指令围墙,让数据像水一样流动
  • 【Oracle数据库指南】第32篇:Oracle归档日志管理与LogMiner日志分析