ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring AI 实战指南:Java 应用集成大模型的标准化方案

Spring AI 实战指南:Java 应用集成大模型的标准化方案

如果你正在寻找一个能快速将大模型能力集成到 Java 应用中的框架,却发现市面上的教程要么版本过时、配置复杂,要么只讲概念、缺乏实战,那么这篇文章就是为你准备的。

Spring AI 的出现,本质上解决了一个核心痛点:让 Java 开发者能以熟悉的 Spring 生态方式,像调用数据库或消息队列一样,去调用 OpenAI、Azure OpenAI、Ollama 等各类大模型服务。它抽象了不同模型供应商的 API 差异,提供了统一的编程模型,将大模型从“需要特殊处理的 HTTP 客户端”变成了“Spring 容器中的一个 Bean”。

然而,很多教程在介绍时,容易陷入两个误区:一是过度聚焦于某个特定模型(如只讲 OpenAI),忽略了 Spring AI 作为“抽象层”的核心价值;二是把示例写得太“玩具化”,没有触及生产环境中的真实配置、异常处理和最佳实践。

本文将基于最新的 Spring AI 稳定版本,带你一站式掌握其核心用法。我的核心判断是:Spring AI 的价值不在于让你“学会”调用某个 API,而在于它定义了一套标准化的“模型交互范式”。掌握它,你就能以极低的切换成本,在项目中对不同模型进行 A/B 测试、实现降级回退,甚至构建自己的模型路由策略。文章将避开那些华而不实的介绍,直接切入环境搭建、核心 API 使用、流式响应处理、提示词工程管理以及生产级配置等实战环节,确保你读完就能在项目中用起来。

1. Spring AI 解决了什么问题?为什么是现在?

在 Spring AI 出现之前,一个 Java 后端团队想要集成大模型,典型的路径是这样的:先为选定的模型(比如 OpenAI)引入一个第三方 Java SDK,然后在代码里硬编码 API Key 和 Endpoint,自己处理 HTTP 请求、解析 JSON 响应、管理连接池和超时设置。如果想换一个模型(比如切换到 Azure OpenAI 或本地部署的 Ollama),几乎需要重写所有相关代码。

这个过程存在几个明显问题:

  1. 供应商锁定:代码与特定模型的 API 强耦合,迁移成本高。
  2. 重复劳动:每个微服务都要重复实现一套相似的客户端逻辑。
  3. 配置繁琐:密钥管理、超时、重试等配置分散在各处,难以统一管理。
  4. 能力抽象缺失:缺乏对“对话”、“文生图”、“嵌入”等高层概念的统一抽象,开发者需要关注底层 HTTP 细节。

Spring AI 的定位就是成为Java 大模型应用开发的基础设施。它借鉴了 Spring Data 的成功经验:Spring Data 让你用统一的 Repository 接口操作不同的数据库(MySQL、MongoDB、Redis),而 Spring AI 则让你用统一的ChatClientEmbeddingClient等接口操作不同的大模型。

为什么现在需要关注它?因为大模型应用正在从“演示原型”走向“生产系统”。原型阶段可以忍受硬编码和散落的配置,但生产系统要求可维护性、可观测性、安全性和弹性。Spring AI 与 Spring Boot 的自动配置、Actuator 监控、Security 安全机制天然集成,为构建企业级 AI 应用提供了现成的脚手架。它降低的不是“调用 API”的难度,而是“在复杂工程体系中可靠、安全、高效地使用 AI 能力”的难度。

2. 核心概念与项目结构:理解 Spring AI 的抽象层

开始写代码前,必须理解 Spring AI 的几个核心抽象。这是避免后续混乱的关键。

2.1 核心 API 接口

Spring AI 的核心是几个高度抽象的客户端接口:

  • ChatClient: 用于与对话模型交互(如 GPT-4、Claude)。这是最常用的接口。
  • EmbeddingClient: 用于将文本转换为向量(嵌入),这是构建 RAG(检索增强生成)应用的基础。
  • ImageClient: 用于文生图、图生图等图像生成任务。
  • AudioClient: 用于语音转录、语音合成等音频任务(部分模型支持)。
  • VectorStore: 用于存储和检索向量数据,与EmbeddingClient配合使用,是 RAG 的另一个核心。

这些接口是稳定的契约。无论底层是 OpenAI、Azure、Anthropic 还是 Ollama,你的业务代码都只依赖这些接口,从而实现解耦。

2.2 提示词模板与消息抽象

与大模型交互的核心是“提示词”。Spring AI 提供了PromptPromptTemplate类来结构化提示词。

  • Message: 代表对话中的一条消息,有SystemMessageUserMessageAssistantMessage等类型。
  • Prompt: 包含一个或多个Message的集合,代表一次请求的完整上下文。
  • PromptTemplate: 一个强大的工具,允许你创建带有占位符(如{topic})的提示词模板,并通过传入参数动态渲染。这有助于实现提示词的复用和管理。

2.3 项目依赖与模块化

Spring AI 采用模块化设计。你的pom.xmlbuild.gradle中通常会包含两部分依赖:

  1. Spring AI BOM:统一管理所有 Spring AI 模块的版本,避免依赖冲突。
  2. 具体实现 Starter:例如spring-ai-openai-spring-boot-starter表示使用 OpenAI 的实现。如果你想换用 Azure OpenAI,只需替换为spring-ai-azure-openai-spring-boot-starter,业务代码通常无需改动。

这种设计使得“更换模型提供商”变成了一个简单的依赖替换和配置更新操作。

3. 环境准备与项目初始化

我们从一个干净的 Spring Boot 3.x 项目开始。这是目前最稳定、兼容性最好的组合。

3.1 前置条件

  • JDK 17 或更高版本:Spring Boot 3.x 的最低要求。
  • Maven 3.6+ 或 Gradle 7.x+:构建工具。
  • 一个可用的模型 API:为了完成所有示例,你需要准备以下至少一项:
    • OpenAI API Key:从 platform.openai.com 获取。
    • Azure OpenAI 资源:需要 Endpoint、API Key 和 Deployment Name。
    • 本地 Ollama:在本地安装并运行 Ollama,拉取一个模型如llama3

3.2 创建 Spring Boot 项目

使用 Spring Initializr (start.spring.io) 创建项目,选择:

  • Project: Maven
  • Language: Java
  • Spring Boot: 3.2.x (建议选择当前最新的稳定版)
  • Dependencies: 先只选Spring Web

生成项目后,在 IDE 中打开。

3.3 添加 Spring AI 依赖

编辑pom.xml文件。首先,添加 Spring AI 的 BOM(物料清单)来统一版本管理。

<!-- 在 <project> 标签下,与 <parent> 标签同级添加 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>0.8.1</version> <!-- 请检查官网使用最新稳定版 --> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

然后,根据你想使用的模型,添加对应的 Starter 依赖。这里我们以OpenAIOllama为例,因为前者是云服务代表,后者是本地运行代表。

<dependencies> <!-- Spring Boot 基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI OpenAI 实现 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <!-- 版本由上面的 BOM 控制 --> </dependency> <!-- 可选:如果你想同时连接本地 Ollama --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> </dependency> <!-- 开发工具 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

关键点spring-ai-bom的引入至关重要,它能确保所有 Spring AI 相关组件的版本一致,避免潜在的兼容性问题。

4. 基础配置:连接你的大模型

配置是第一步,也是最容易出错的一步。Spring AI 的配置高度统一,遵循spring.ai.<provider>.<property>的格式。

4.1 配置 OpenAI

如果你使用 OpenAI,在application.propertiesapplication.yml中配置:

# application.properties # OpenAI 配置 spring.ai.openai.api-key=${OPENAI_API_KEY:sk-your-key-here} spring.ai.openai.chat.options.model=gpt-3.5-turbo # 可选:设置超时、代理等 spring.ai.openai.chat.options.temperature=0.7

或者使用 YAML 格式:

# application.yml spring: ai: openai: api-key: ${OPENAI_API_KEY:sk-your-key-here} chat: options: model: gpt-3.5-turbo temperature: 0.7

安全提醒:永远不要将真实的 API Key 硬编码在代码或配置文件中提交到代码仓库。这里使用${OPENAI_API_KEY:defaultValue}语法,意味着优先从环境变量OPENAI_API_KEY中读取,如果不存在则使用冒号后的默认值。生产环境中应使用配置中心或 Secrets 管理工具。

4.2 配置本地 Ollama

如果你使用本地运行的 Ollama(默认地址是http://localhost:11434),配置更简单:

# Ollama 配置 spring.ai.ollama.base-url=http://localhost:11434 spring.ai.ollama.chat.options.model=llama3

Ollama 无需 API Key,适合本地开发和测试。

4.3 配置多个模型客户端

Spring AI 支持同时配置多个同类型客户端(比如两个不同的ChatClient),并通过@Qualifier注解来注入指定的那个。这为 A/B 测试和故障转移奠定了基础。

首先,在配置中定义多个连接。以下示例展示如何配置一个 OpenAI 和一个 Ollama 客户端:

spring: ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4 enabled: true # 启用这个客户端 ollama: base-url: http://localhost:11434 chat: options: model: llama3 enabled: true # 启用这个客户端

然后,在代码中可以通过 Bean 名称来注入特定的客户端。Spring AI 会自动根据配置生成名为openaiChatClientollamaChatClient的 Bean。

5. 核心使用:从简单对话到流式响应

配置完成后,我们就可以在 Service 或 Controller 中注入并使用ChatClient了。

5.1 基础对话调用

创建一个简单的 Service 类:

// 文件路径:src/main/java/com/example/demo/service/ChatService.java package com.example.demo.service; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; @Service public class ChatService { private final ChatClient chatClient; // 构造器注入。如果只有一个 ChatClient Bean,Spring 会自动注入它。 public ChatService(ChatClient chatClient) { this.chatClient = chatClient; } public String generate(String message) { // 最简调用:直接传入用户消息字符串 return chatClient.prompt() .user(message) .call() .content(); } }

创建一个 REST 控制器来暴露接口:

// 文件路径:src/main/java/com/example/demo/controller/AiController.java package com.example.demo.controller; import com.example.demo.service.ChatService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class AiController { private final ChatService chatService; public AiController(ChatService chatService) { this.chatService = chatService; } @GetMapping("/chat") public String chat(@RequestParam(value = "msg", defaultValue = "Hello") String message) { return chatService.generate(message); } }

启动应用,访问http://localhost:8080/chat?msg=介绍一下Spring AI,你应该能收到模型的文本回复。

5.2 使用 Prompt 和 Message 构建复杂上下文

上面的例子是单轮对话。要实现多轮对话或设置系统指令,需要使用PromptMessage

// 在 ChatService 中添加新方法 import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import java.util.List; public ChatResponse chatWithContext(String userInput, String systemInstruction) { // 1. 构建消息列表 List<Message> messages = List.of( new SystemMessage(systemInstruction), // 系统指令,设定 AI 角色 new UserMessage(userInput) // 用户输入 ); // 2. 创建 Prompt Prompt prompt = new Prompt(messages); // 3. 调用并返回完整的 ChatResponse(包含元数据) return chatClient.call(prompt); } // 调用示例 public String getTranslation(String englishText) { ChatResponse response = chatWithContext( "Translate the following English text to Chinese: " + englishText, "You are a professional translator." ); // 从 ChatResponse 中提取助理的回复内容 return response.getResult().getOutput().getContent(); }

ChatResponse对象包含了更丰富的信息,如使用到的 token 数量、模型名称、完成原因等,对于监控和调试非常有用。

5.3 实现流式响应(Server-Sent Events)

流式响应对于生成长文本时的用户体验至关重要,可以实时看到模型生成的内容。Spring AI 的ChatClient原生支持流式调用。

// 文件路径:src/main/java/com/example/demo/controller/StreamController.java package com.example.demo.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @RestController public class StreamController { private final ChatClient chatClient; public StreamController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamChat(@RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); // 返回一个 Flux<String>,每个元素是实时生成的文本块 } }

使用curl或前端 EventSource API 测试这个端点,你可以看到文字逐词返回的效果:

curl -N http://localhost:8080/chat/stream?message=写一个关于春天的短诗

6. 进阶功能:提示词模板与函数调用

6.1 使用 PromptTemplate 管理提示词

将提示词模板化是生产应用的最佳实践。Spring AI 提供了强大的PromptTemplate

// 在 ChatService 中添加方法 import org.springframework.ai.chat.prompt.PromptTemplate; import java.util.Map; public String generateWithTemplate(String topic, String style) { // 定义模板字符串,使用 {parameter} 作为占位符 String templateString = """ Please write a {style} paragraph about {topic}. The paragraph should be engaging and suitable for a general audience. """; // 创建 PromptTemplate PromptTemplate promptTemplate = new PromptTemplate(templateString); // 传入参数 Map 来渲染模板 Map<String, Object> params = Map.of("topic", topic, "style", style); Prompt renderedPrompt = promptTemplate.create(params); // 使用渲染后的 Prompt 进行调用 return chatClient.call(renderedPrompt).getResult().getOutput().getContent(); }

你可以更进一步,将模板字符串存储在数据库或配置文件中,实现动态的提示词管理。

6.2 函数调用(Tool Calling)

让大模型调用外部工具或函数是构建智能 Agent 的关键。Spring AI 通过@Bean定义工具函数,并自动将其描述注入到对话上下文中。

首先,定义一个工具函数接口及其实现:

// 文件路径:src/main/java/com/example/demo/tool/WeatherService.java package com.example.demo.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; @Component public class WeatherService { @Tool(description = "Get the current weather for a given city") public String getWeather(String city) { // 这里应该是调用真实天气 API 的逻辑 // 为演示,返回模拟数据 return "The weather in " + city + " is sunny with a temperature of 22°C."; } }

然后,在配置中启用函数调用,并在调用时指定可用的工具:

// 在 Controller 或 Service 中 import org.springframework.ai.chat.client.advisor.ToolCallAdvisor; @GetMapping("/chat-with-weather") public String chatWithTool(@RequestParam String question) { return chatClient.prompt() .user(question) .advisors(new ToolCallAdvisor()) // 启用工具调用顾问 .call() .content(); }

当你问“北京天气怎么样?”时,模型会识别出需要调用getWeather工具,Spring AI 会拦截这个请求,执行真实的getWeather方法,并将结果返回给模型,模型再整合成最终回答。这极大地扩展了大模型的能力边界。

7. 向量数据库与 RAG 快速入门

RAG 是当前最主流的让大模型获取“新知识”并减少幻觉的方法。Spring AI 提供了VectorStore抽象和EmbeddingClient来简化实现。

7.1 生成嵌入向量

首先,确保你的模型支持嵌入(如 OpenAI 的text-embedding-ada-002)。配置EmbeddingClient(配置方式与ChatClient类似,通常与 Chat 模型来自同一个提供商)。

// 注入 EmbeddingClient private final EmbeddingClient embeddingClient; public List<Double> embedText(String text) { // 将单段文本转换为向量 List<Double> embedding = embeddingClient.embed(text); return embedding; }

7.2 使用简单的内存向量存储

Spring AI 内置了InMemoryVectorStore,适合演示和开发测试。

// 文件路径:src/main/java/com/example/demo/service/RagService.java package com.example.demo.service; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingClient; import org.springframework.ai.vectorstore.InMemoryVectorStore; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Service public class RagService { private final VectorStore vectorStore; public RagService(EmbeddingClient embeddingClient) { // 初始化一个内存向量存储,需传入 EmbeddingClient this.vectorStore = new InMemoryVectorStore(embeddingClient); } // 1. 向向量库添加文档 public void addDocuments(List<String> texts) { List<Document> documents = texts.stream() .map(text -> new Document(text)) // 可以添加元数据 .collect(Collectors.toList()); vectorStore.add(documents); } // 2. 相似性搜索 public List<Document> search(String query, int topK) { return vectorStore.similaritySearch(query, topK); } // 3. 简单的 RAG 查询 public String ragQuery(String question) { // a. 检索相关文档 List<Document> relevantDocs = search(question, 3); String context = relevantDocs.stream() .map(Doc::getContent) .collect(Collectors.joining("\n\n")); // b. 构建增强后的提示词 String promptTemplate = """ Answer the question based only on the following context: {context} Question: {question} If the context doesn't contain the answer, say "I cannot answer based on the provided information." Answer: """; // 使用 PromptTemplate 渲染(此处省略具体渲染代码) // c. 调用 ChatClient 获取答案 // ... 调用 chatClient return "最终答案"; } }

对于生产环境,你需要将InMemoryVectorStore替换为PgVectorStore(PostgreSQL)、RedisVectorStoreMilvusVectorStore等持久化存储。

8. 生产环境配置与最佳实践

将 Spring AI 用于生产,需要注意以下几点:

8.1 配置管理

  • 密钥管理:使用环境变量、云厂商的 Secrets Manager(如 AWS Secrets Manager、Azure Key Vault)或 Spring Cloud Config。
  • 连接池与超时:配置 HTTP 客户端参数,防止慢请求拖垮应用。
    spring: ai: openai: client: connect-timeout: 10s read-timeout: 30s
  • 多环境配置:使用application-dev.yml,application-prod.yml区分不同环境的模型和配置。

8.2 异常处理与重试

大模型 API 调用可能因网络、限流等原因失败。务必添加健壮的异常处理和重试机制。

import org.springframework.retry.annotation.Retryable; import org.springframework.retry.annotation.Backoff; @Service public class RobustChatService { @Retryable( value = { RuntimeException.class }, // 重试的异常类型 maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2) // 指数退避 ) public String reliableGenerate(String prompt) { // 调用 ChatClient // 如果抛出 RuntimeException,会自动重试最多3次 return chatClient.prompt().user(prompt).call().content(); } // 还可以使用 @Recover 定义重试全部失败后的降级处理 @Recover public String recover(RuntimeException e, String prompt) { return "Service is temporarily unavailable. Please try again later."; } }

同时,在pom.xml中添加spring-retry依赖以启用此功能。

8.3 监控与可观测性

  • Actuator 端点:Spring Boot Actuator 可以暴露应用健康指标。
  • 自定义指标:使用 Micrometer 记录每次调用的耗时、Token 使用量、成功率等。
  • 日志记录:为ChatClientEmbeddingClient的调用添加详细的日志,但注意不要记录包含敏感信息的完整提示词和响应

8.4 成本与性能优化

  • 缓存:对相似的 Embedding 请求或 Chat 结果进行缓存(如使用 Spring Cache)。
  • 批处理:对于 Embedding 操作,如果可能,将多个文本批量发送以减少请求次数。
  • 模型选择:在非关键路径或内部工具中使用更便宜、更快的模型(如 GPT-3.5-Turbo),在面向用户的核心功能中使用能力更强的模型(如 GPT-4)。

9. 常见问题与排查思路

在开发和集成过程中,你可能会遇到以下问题:

问题现象可能原因排查方式解决方案
启动报错:No qualifying bean of type 'ChatClient'1. 未添加对应模型的 Starter 依赖。
2. 配置错误(如 API Key 为空)。
3. 多个ChatClientBean 存在冲突。
1. 检查pom.xml依赖。
2. 检查application.yml配置,确保api-keybase-url正确。
3. 查看启动日志,确认 Bean 创建情况。
1. 添加正确的 Starter。
2. 配置正确的密钥或地址。
3. 使用@Qualifier("beanName")指定注入的 Bean。
调用 API 超时1. 网络问题。
2. 模型响应慢。
3. 客户端超时设置过短。
1. 检查网络连通性。
2. 查看模型服务状态。
3. 检查connect-timeoutread-timeout配置。
1. 优化网络或使用代理。
2. 考虑使用更快的模型或优化提示词。
3. 适当增加超时时间,并配置重试。
流式响应不工作1. 控制器 produces 类型不是MediaType.TEXT_EVENT_STREAM_VALUE
2. 前端 EventSource 使用方式错误。
3. 模型不支持流式响应。
1. 检查@GetMapping注解。
2. 用curl -N测试后端接口是否正常流式输出。
3. 查阅模型供应商文档。
1. 确保 produces 类型正确。
2. 修正前端代码或使用正确的测试工具。
3. 更换模型或使用非流式调用。
提示词模板渲染错误1. 模板字符串中的占位符与传入的 Map key 不匹配。
2. 使用了不支持的表达式语法。
1. 检查PromptTemplate创建时传入的参数 Map。
2. 查看 Spring AI 文档确认模板语法。
1. 确保参数 Map 的 key 与模板中的{key}一致。
2. 使用简单的{key}语法,或查阅文档使用高级特性。
向量搜索返回空结果1. 向量库中未添加文档。
2. Embedding 模型与创建向量时使用的模型不一致。
3. 搜索相似度阈值过高。
1. 确认addDocuments方法被成功调用。
2. 检查EmbeddingClient的配置是否一致。
3. 调试查看生成的向量维度。
1. 确保数据已成功入库。
2. 确保查询和入库使用相同的EmbeddingClient配置。
3. 调整搜索的相似度阈值参数(如果向量存储支持)。

掌握 Spring AI 意味着你掌握了在 Java 世界高效集成 AI 能力的标准化方法。它不是一个孤立的工具,而是 Spring 生态向 AI 时代自然延伸的一部分。建议从本文的示例出发,先在一个小的内部工具或辅助功能中尝试集成,重点体验配置的简洁性和 API 的一致性。然后,逐步探索更复杂的场景,如基于 RAG 的智能知识库、利用函数调用连接内部业务系统的智能助手,或是构建多模型路由的策略服务。在这个过程中,持续关注官方文档和版本更新,因为 Spring AI 正在快速发展,越来越多的模型供应商和向量数据库正在被集成进来。

返回列表