如果你是一个Java后端工程师,2026年的今天,你可能已经无数次被一个问题困扰:“我们系统要不要接入大模型?怎么接?”
打开GitHub,搜到的AI相关SDK大多是Python生态的——LangChain、LlamaIndex、Transformers——仿佛AI世界和Java世界之间隔着一道难以逾越的墙。那些习惯了Spring Boot的清爽、依赖注入的优雅、AOP的简洁的Java工程师,面对Python里那些动态类型的、函数式风格浓厚的AI框架,常常有种“客场作战”的局促感。
这正是Spring AI诞生的背景。它不是要重新发明轮子,而是要把大模型的能力,用Spring开发者最熟悉的那套方式,包装好、端到面前来。你可以像写JdbcTemplate一样写ChatClient,像用RestTemplate一样调用大模型API,像定义Repository一样定义向量存储。所有的复杂度,都被一层熟悉的Spring外壳包裹住了。
本文就带你快速上手Spring AI,看它如何用“几行代码”把大模型请进你的Spring Boot应用。
在开始写代码之前,先理解Spring AI的设计基调。它做了三件关键的事:
ChatClient、EmbeddingClient、ImageClient接口,底层可以切换OpenAI、Azure、Ollama、通义千问、智谱等不同提供商,切换时业务代码几乎零改动。PromptTemplate把变量嵌入提示词,就像用MessageFormat或String.format一样自然。BeanOutputConverter直接把模型输出映射成Java对象。有了这三层,Java工程师几乎不需要学习任何新范式——依赖注入、配置绑定、面向接口编程、自动配置——全都是Spring生态玩了十几年的老本行。
第一步,在pom.xml里引入Spring AI的starter。以OpenAI为例:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M4</version>
</dependency>然后在application.yml里配置好API密钥:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.7接下来,写一个最简的Controller,几行代码搞定一个对话接口:
@RestController
@RequestMapping("/ai")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}启动应用,访问 http://localhost:8080/ai/chat?message=你好,请用一句话介绍石家庄,你就会收到模型的流利回复。
注意看这里:ChatClient的构建方式是通过Builder注入的——这是Spring AI刻意保持的Spring Boot风格。不需要手动实例化OpenAI客户端,不需要管理连接池,一切交给Spring的自动配置。
当你的提示词开始变长、包含动态变量时,如果还在用String.format或+拼接,代码会迅速腐烂。Spring AI提供的PromptTemplate可以帮你优雅地管理这些模板。
先定义一个提示词模板文件,放在src/main/resources/prompts/目录下:
你是一个{role},用{style}的语气回答用户的问题。
用户的咨询内容是:
{userInput}
你的回答要求:
1. 专业且准确
2. 控制在{maxWords}字以内
3. 如果信息不足,请直接告知用户然后在代码里使用它:
@GetMapping("/consult")
public String consult(
@RequestParam String role,
@RequestParam String style,
@RequestParam String userInput) {
PromptTemplate template = new PromptTemplate("""
你是一个{role},用{style}的语气回答用户的问题。
用户的咨询内容是:
{userInput}
你的回答要求:
1. 专业且准确
2. 控制在200字以内
3. 如果信息不足,请直接告知用户
""");
Prompt prompt = template.create(Map.of(
"role", role,
"style", style,
"userInput", userInput
));
return chatClient.prompt(prompt).call().content();
}PromptTemplate底层会负责任何特殊字符的转义,你只需要关心业务变量本身。这比在代码里层层拼接字符串要干净一个数量级。
大模型最让人头疼的事情之一,是它返回的是非结构化的文本。你想让它返回JSON,它可能多给你一段解释文字;你想让它返回一个日期格式,它可能给你带上了星期几。
Spring AI的BeanOutputConverter就是来终结这种痛苦的。你只需要定义一个POJO,告诉模型“你必须按照这个格式返回”,框架会自动将模型的文本输出反序列化成Java对象:
public record WeatherInfo(String city, String date, double temperature, String condition) {}
@GetMapping("/weather")
public WeatherInfo getWeather(@RequestParam String city) {
String promptText = """
请查询{city}今天的天气。
严格按照以下JSON格式返回,不要添加任何额外文字:
{"city": "城市名", "date": "日期", "temperature": 温度数值, "condition": "天气状况"}
""";
// 创建输出转换器,指定目标类型
BeanOutputConverter<WeatherInfo> converter =
new BeanOutputConverter<>(WeatherInfo.class);
String userPrompt = promptText.replace("{city}", city);
// 在提示词中追加格式要求
String fullPrompt = userPrompt + "\n\n" + converter.getFormat();
String response = chatClient.prompt()
.user(fullPrompt)
.call()
.content();
// 直接从响应字符串解析成对象
return converter.convert(response);
}这里最巧妙的设计是:converter.getFormat()会自动生成类似“请输出符合以下JSON Schema的JSON”的指令,并告知模型字段的类型和约束。你甚至不需要手写JSON Schema——从Java Record的结构反射生成。
这意味着,如果你的WeatherInfo里有一个LocalDateTime字段,框架会自动要求模型按照ISO-8601格式输出日期字符串,并在反序列化时帮你转换成LocalDateTime对象。全程类型安全,没有JSONObject.getXXX那种手动强转的隐患。
这是Spring AI最吸引人的能力之一。在此之前,如果你想让模型知道“今天的实时天气”,你必须在调用模型之前先把天气查好,塞进提示词里。现在你可以把getWeather这个函数直接注册给模型,当模型觉得需要这个信息时,它会主动调用它。
@Component
public class WeatherTools {
@Tool(description = "获取指定城市的实时天气信息")
public WeatherInfo getWeather(String city) {
// 这里调用真实的天气API
return weatherService.fetchByCity(city);
}
}然后在构建ChatClient时注册这个工具:
@Bean
public ChatClient chatClient(ChatClient.Builder builder, WeatherTools weatherTools) {
return builder
.defaultTools(weatherTools.getWeather())
.build();
}当用户问“石家庄今天热吗”,模型会判断:我需要调用getWeather这个工具,传入参数city=石家庄。框架自动执行这个方法,把返回的WeatherInfo塞回给模型,模型再基于真实数据生成最终回复。整个过程对调用方来说是透明的——它看到的只是一个返回了气温描述的自然语言回答。
这种模式,让大模型从“会聊天的百科全书”进化成了“能主动调配后端服务的智能路由”。你写的每一个@Tool标注的方法,都像是给大模型装上了一只“可伸出去的手”。
企业级应用里最尴尬的场景是:模型虽然博学,但它不认识你公司的内部文档、产品手册、售后记录。直接问它“我们公司的退货政策是什么”,它大概率会编一个。
Spring AI统一了向量存储的抽象接口,无论你用的是Redis、Elasticsearch还是PgVector,代码层都是同一套API:
@Service
public class DocumentService {
private final VectorStore vectorStore;
private final ChatClient chatClient;
public void indexDocument(String content) {
// 将文档分块并向量化存入
List<Document> chunks = splitIntoChunks(content);
vectorStore.add(chunks);
}
public String askFromKnowledge(String question) {
// 检索最相关的3个文档片段
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.builder()
.query(question)
.topK(3)
.build()
);
// 构建RAG提示词
String context = relevantDocs.stream()
.map(Document::getContent)
.collect(Collectors.joining("\n---\n"));
return chatClient.prompt()
.user("""
请基于以下参考资料回答用户的问题。
如果参考资料中没有相关信息,请直接说"找不到相关答案"。
参考资料:
{context}
用户问题:{question}
""".replace("{context}", context)
.replace("{question}", question))
.call()
.content();
}
}VectorStore的接口极其稳定,底层换存储引擎时,只有配置变更,业务代码纹丝不动。这是Spring生态最擅长的事——用统一的抽象层,屏蔽底层实现差异。
Spring AI目前还处于快速迭代期,API会有变动,版本号还在M里程碑阶段。但它传递的信号已经非常清晰:Java生态正在以自己最擅长的方式,拥抱大模型时代。
Python的AI框架胜在灵活和先锋,但Spring AI胜在规范、稳定、可维护。对于那些已经用Spring Boot构建了复杂业务系统的团队而言,接入Spring AI意味着几十万行存量代码可以继续丝滑运行,AI能力只是一个附加的、可插拔的模块,而非一次伤筋动骨的重构。
在Spring AI的世界里,你写出来的代码依然是你熟悉的那个味道——Controller、Service、Repository的分层依然存在,@Autowired依然在用,单元测试依然可以Mock掉ChatClient。唯一的变化是:你调用的不再是一个数据库或一个REST接口,而是一个可能改变人类工作方式的智能引擎。
而这一切的开始,只需要那几行代码。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。