使用Spring AI构建AI驱动的Java应用程序:完整指南
使用Spring AI构建AI驱动Java应用程序的完整指南——涵盖ChatClient、提示模板、结构化输出、RAG、函数调用、顾问器、聊天记忆、嵌入向量、多模态以及提供商配置。
Spring AI为何改变Java开发者的一切
Spring AI为人工智能世界带来了Spring Boot开发者所喜爱的相同生产力和可移植性。针对干净的抽象编写AI代码一次,然后通过配置更改(而非代码重写)切换提供商——OpenAI、Anthropic Claude、Google Gemini、Ollama、AWS Bedrock。
于2025年5月以1.0 GA版本发布,Spring AI提供:统一的聊天API、映射到Java Record的结构化输出、内置RAG支持、函数/工具调用、聊天记忆、顾问器、嵌入向量、图像生成、多模态和评估——全部带有Spring Boot自动配置。
入门
将BOM和提供商starter添加到你的pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Pick ONE provider starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
在application.yml中配置:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7
核心抽象:ChatModel、ChatClient、Prompt
Spring AI的强大之处在于与提供商无关的接口。你的代码面向抽象编程;Spring Boot注入具体的提供商。
ChatClient — 流式API(推荐)
@RestController
class ChatController {
private final ChatClient chatClient;
ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("You are a helpful coding assistant.")
.build();
}
@GetMapping("/chat")
String chat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
消息类型
SystemMessage设置指令。UserMessage携带用户输入(文本+多模态媒体)。AssistantMessage保存模型回复。ToolResponseMessage返回工具/函数结果。
流式响应
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<String> streamChat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}
提示模板
使用Spring AI的PromptTemplate通过变量替换保持提示的可复用性:
// Inline template
String answer = chatClient.prompt()
.user(u -> u
.text("List {count} best practices for {topic}")
.param("count", "5")
.param("topic", "REST API design"))
.call()
.content();
// System prompt template
String systemText = """
You are an expert in {domain}.
Reply in the style of a {style}.
""";
SystemPromptTemplate systemTemplate = new SystemPromptTemplate(systemText);
Message systemMessage = systemTemplate.createMessage(Map.of(
"domain", "distributed systems",
"style", "senior engineer"
));
// Load from classpath resource
@Value("classpath:/prompts/analysis.st")
private Resource analysisTemplate;
PromptTemplate template = new PromptTemplate(analysisTemplate);
提示工程最佳实践
要具体:告诉模型你确切需要的格式、语气和约束。使用系统消息:在系统提示中设置角色和规则,在用户提示中放用户内容。单样本/少样本:包含一个期望输出的示例。思维链:要求模型"逐步思考"以处理复杂推理。结构化输出:请求JSON并映射到Record(见下一节)。
结构化输出 — AI响应作为Java Record
将AI生成的文本直接映射到类型化的Java对象。无需手动JSON解析。
// Define your record
record BookRecommendation(String title, String author,
String genre, String summary) {}
// Single entity
BookRecommendation book = chatClient.prompt()
.user("Recommend a classic science fiction novel.")
.call()
.entity(BookRecommendation.class);
// List of entities
List<BookRecommendation> books = chatClient.prompt()
.user("Recommend 5 classic sci-fi novels.")
.call()
.entity(new ParameterizedTypeReference<List<BookRecommendation>>() {});
// Map output
Map<String, Object> data = chatClient.prompt()
.user("List the population of Tokyo, London, and New York")
.call()
.entity(new ParameterizedTypeReference<Map<String, Object>>() {});
RAG:检索增强生成
RAG让你的AI使用你自己的数据回答问题,通过从向量存储中检索相关文档并将其作为上下文注入提示中。
步骤1:文档摄取(ETL)
@Component
class DocumentIngestionService {
private final VectorStore vectorStore;
DocumentIngestionService(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public void ingest(String pdfPath) {
// 1. Read documents (PDF, JSON, HTML, Markdown, DOCX supported)
PagePdfDocumentReader reader = new PagePdfDocumentReader(pdfPath,
PdfDocumentReaderConfig.builder()
.withPagesPerDocument(1).build());
// 2. Split into chunks
TokenTextSplitter splitter = TokenTextSplitter.builder()
.withChunkSize(800)
.withMinChunkSizeChars(350).build();
// 3. Store — embeddings generated automatically
vectorStore.write(splitter.apply(reader.read()));
}
}
步骤2:使用QuestionAnswerAdvisor查询
ChatResponse response = chatClient.prompt()
.advisors(QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.similarityThreshold(0.75)
.topK(5).build())
.build())
.user("What does our refund policy say about digital products?")
.call()
.chatResponse();
高级RAG与查询改写
Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
.queryTransformers(RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder.build().mutate()).build())
.documentRetriever(VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.50)
.topK(5).build())
.build();
String answer = chatClient.prompt()
.advisors(ragAdvisor)
.user("How do I configure SSL?")
.call()
.content();
向量存储配置(PGVector)
# application.yml
spring:
ai:
vectorstore:
pgvector:
initialize-schema: true
dimensions: 1536
distance-type: cosine_distance
支持的向量存储:PGVector、Chroma、Pinecone、Redis、Milvus、Weaviate、Qdrant、Elasticsearch、MongoDB Atlas、Neo4j等。
函数调用 / 工具使用
让AI模型调用你的Java方法来获取实时数据或执行操作。
使用@Tool的声明式方法
class WeatherTools {
@Tool(description = "Get current weather for a given city")
String getWeather(
@ToolParam(description = "City name") String city,
@ToolParam(description = "Temperature unit", required = false) String unit) {
// Call a real weather API here
return "Weather in %s: 22 degrees %s, sunny."
.formatted(city, unit != null ? unit : "Celsius");
}
}
// Use it — the model decides when to call the function
String response = chatClient.prompt()
.user("What's the weather like in London?")
.tools(new WeatherTools())
.call()
.content();
作为Spring Bean的函数
public record CurrencyRequest(String from, String to, double amount) {}
public record CurrencyResponse(double convertedAmount, double rate) {}
@Bean
@Description("Convert an amount from one currency to another")
Function<CurrencyRequest, CurrencyResponse> convertCurrency() {
return request -> {
double rate = fetchExchangeRate(request.from(), request.to());
return new CurrencyResponse(request.amount() * rate, rate);
};
}
// Reference by bean name
String answer = chatClient.prompt()
.user("Convert 100 USD to EUR")
.toolNames("convertCurrency")
.call()
.content();
工具上下文 — 传递额外数据
class CustomerTools {
@Tool(description = "Get customer by ID")
Customer getCustomer(Long id, ToolContext ctx) {
String tenantId = (String) ctx.getContext().get("tenantId");
return customerRepo.findByIdAndTenant(id, tenantId);
}
}
String answer = chatClient.prompt()
.user("Tell me about customer #42")
.tools(new CustomerTools())
.toolContext(Map.of("tenantId", "acme-corp"))
.call()
.content();
顾问器 — AI调用的拦截器
顾问器在提示到达模型之前修改它们,并在响应返回时处理——就像Spring MVC拦截器用于AI。
聊天记忆 — 对话历史
ChatMemory memory = MessageWindowChatMemory.builder()
.chatMemoryRepository(new InMemoryChatMemoryRepository())
.maxMessages(20).build();
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build())
.build();
// First call
client.prompt().user("My name is Alice")
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "session-1"))
.call().content();
// Second call — remembers the name
client.prompt().user("What is my name?")
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "session-1"))
.call().content(); // "Your name is Alice"
自定义顾问器 — 延迟追踪
public class LatencyAdvisor implements CallAdvisor {
public String getName() { return "LatencyAdvisor"; }
public int getOrder() { return 0; }
public ChatClientResponse adviseCall(ChatClientRequest request,
CallAdvisorChain chain) {
long start = System.currentTimeMillis();
ChatClientResponse response = chain.nextCall(request);
log.info("AI call took {}ms", System.currentTimeMillis() - start);
return response;
}
}
组合多个顾问器
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(memory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build(),
new SimpleLoggerAdvisor(),
new LatencyAdvisor()
).build();
嵌入向量
将文本转换为数值向量,用于相似性搜索、聚类和RAG。
@Service
class EmbeddingService {
private final EmbeddingModel embeddingModel;
// Single text
float[] embed(String text) {
return embeddingModel.embed(text);
}
// Batch
List<float[]> embedBatch(List<String> texts) {
return embeddingModel.embed(texts);
}
// Vector dimensions
int dimensions() { return embeddingModel.dimensions(); }
}
多模态 — 图像 + 文本
与GPT-4o、Claude 3或Gemini等模型一起发送图像和文本:
String description = chatClient.prompt()
.user(u -> u
.text("Describe what you see in this image.")
.media(MimeTypeUtils.IMAGE_PNG,
new ClassPathResource("/images/diagram.png")))
.call()
.content();
// From URL
String analysis = chatClient.prompt()
.user(u -> u
.text("What's in this image?")
.media(MimeTypeUtils.IMAGE_JPEG,
URI.create("https://example.com/photo.jpg")))
.call()
.content();
图像生成
@RestController
class ImageController {
private final ImageModel imageModel;
@GetMapping("/generate-image")
String generateImage(@RequestParam String description) {
ImageResponse response = imageModel.call(
new ImagePrompt(description,
OpenAiImageOptions.builder()
.quality("hd").N(1)
.height(1024).width(1024).build()));
return response.getResult().getOutput().getUrl();
}
}
评估 — 测试你的AI
Spring AI提供评估器来检查相关性和捕获幻觉:
// Is the response relevant to the question and context?
RelevancyEvaluator evaluator = new RelevancyEvaluator(ChatClient.builder(chatModel));
EvaluationResponse eval = evaluator.evaluate(
new EvaluationRequest(question, context, aiResponse));
assertThat(eval.isPass()).isTrue();
// Fact-checking — detect hallucinations
FactCheckingEvaluator factChecker = new FactCheckingEvaluator(
ChatClient.builder(chatModel));
EvaluationResponse result = factChecker.evaluate(
new EvaluationRequest(knownFacts, Collections.emptyList(), claim));
assertFalse(result.isPass()); // claim contradicts known facts
提供商配置速查表
| 提供商 | Starter组件 | 关键配置 |
|---|---|---|
| OpenAI | spring-ai-starter-model-openai | spring.ai.openai.api-key |
| Anthropic Claude | spring-ai-starter-model-anthropic | spring.ai.anthropic.api-key |
| Ollama(本地) | spring-ai-starter-model-ollama | spring.ai.ollama.base-url |
| AWS Bedrock | spring-ai-starter-model-bedrock | spring.ai.bedrock.aws.region |
| Azure OpenAI | spring-ai-starter-model-azure-openai | spring.ai.azure.openai.api-key |
| Google Gemini | spring-ai-starter-model-vertex-ai | spring.ai.vertex.ai.gemini.project-id |
| PGVector | spring-ai-starter-vector-store-pgvector | spring.ai.vectorstore.pgvector.* |
核心要点
提供商无关:编写一次,通过配置切换AI提供商。没有供应商锁定。
Spring原生:自动配置、依赖注入、配置文件——Spring开发者所期望的一切。
生产就绪模式:RAG、工具调用、聊天记忆、顾问器、评估和结构化输出都已内置。
从简单开始:ChatClient.prompt().user("...").call().content()——这就是你的第一个AI调用。根据需要添加RAG、工具和记忆。
Spring AI让AI集成感觉就像任何其他Spring依赖一样——导入starter、配置、注入并使用。