Spring AI 入门到实战:我如何用它让系统具备“理解能力”
我向来对“整合大模型进 Java 应用”这件事持谨慎态度。在 GPT 火了之后,我们团队最初是用 HTTP 手动调 OpenAI 接口,把它当成一个 JSON API 用。但随着业务交互变复杂,我意识到:我们需要的是一个语义系统,而不是一个封装 prompt 的 util 类。
这时,Spring AI 出现了。
它不是框架革命,而是语义层和 Spring 编程模型之间的桥梁。你可以像写 Spring Boot Controller 那样调用大模型,像写 Bean 那样组织提示词和语义模板。
以下是我亲身踩过的坑与总结。
Spring AI 是什么?一句话版本
Spring AI 是 Spring 推出的 AI 集成框架,它封装了与 OpenAI、Azure、HuggingFace、Anthropic 等主流 LLM 提供商的连接逻辑,并基于 Spring 编程模型提供统一的:
- Prompt 模板机制
- Function 调用能力(Function calling)
- Embedding 向量搜索整合(如与 Redis、PGVector、Milvus 等)
- ChatMemory 管理
- RAG(检索增强生成)支持
第一步:快速跑通 OpenAI 接入
引入依赖(当前主流版本建议使用 0.8.1 或以上):
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId><version>0.8.1</version>
</dependency>
application.yml 配置如下:
spring:ai:openai:api-key: ${OPENAI_API_KEY}base-url: https://api.openai.com/v1chat:model: gpt-4
然后写个最基础的服务类:
@Service
public class AiAssistant {private final OpenAiChatClient chatClient;public AiAssistant(OpenAiChatClient chatClient) {this.chatClient = chatClient;}public String ask(String question) {ChatResponse response = chatClient.call(question);return response.getResult().getOutput().getContent();}
}
你会惊讶于它的“Spring 味儿”有多重 —— ChatClient 就像 RestTemplate
,但背后驱动的是 LLM。
第二步:PromptTemplate 的强大之处
Spring AI 真正让我惊艳的,是 PromptTemplate
和 PromptTemplateModel
。
举个例子,我们想做一个发票解析助手:
String template = """
你是一个发票专家,请从以下文本中提取字段:
{{text}}返回格式:
{"invoiceCode": "","invoiceNumber": "","amount": "","date": ""
}
""";PromptTemplate promptTemplate = new PromptTemplate(template);
promptTemplate.add("text", ocrResult);Prompt prompt = promptTemplate.create();
ChatResponse response = chatClient.call(prompt);
为什么说它强大?因为它把 prompt 当作模板文件处理,变量和代码解耦,可调试、可重构、可测试 —— 这比我们以前把 prompt 字符串硬编码在 Java 里强太多。
第三步:函数调用(Function Call)集成
Spring AI 支持大模型的函数调用(Function Calling)能力。你可以像注册 Spring Bean 一样注册 AI 能“调用”的 Java 方法:
@Component
public class InvoiceFunction {@AiFunctionpublic String parseInvoice(@AiFunctionParameter(name = "text") String text) {// 实际是把这个方法暴露给大模型,让它决定是否调用return someService.extractInvoiceJson(text);}
}
这意味着你可以让 LLM 成为业务流程中的“指挥员”,它不再只是吐字,而是能决定调用哪个函数、传什么参数。
第四步:向量检索 + Embedding(RAG 模式)
Spring AI 原生支持嵌入向量生成和搜索。以下是一个 Redis + Embedding 的组合示例:
- 首先配置 Redis 向量存储(需使用 Redis Stack):
spring:ai:vectorstore:redis:host: localhostport: 6379
- 注入向量存储和 embedding 模型:
@Autowired
private VectorStore vectorStore;@Autowired
private EmbeddingClient embeddingClient;
- 索引知识库:
EmbeddingResponse embedding = embeddingClient.embed("这是发票的开票代码含义说明…");
vectorStore.add(List.of(new Vector("invoice-001", embedding.getEmbedding(), metadata)));
- 在对话中执行 RAG 检索:
List<Document> docs = vectorStore.similaritySearch("什么是发票代码", 3);
String context = docs.stream().map(Document::getContent).collect(Collectors.joining("\n"));
- 拼接到 Prompt 中:
String prompt = "基于以下知识回答问题:\n" + context + "\n问题:什么是发票代码?";
这样你就完成了一个端到端的 RAG 系统,模型可以“读”你自己喂的文档。
项目实战技巧总结
- Prompt 模板一定要版本管理:我们用 YAML + Git 存储每个 PromptTemplate,避免代码污染和可追踪。
- 用注解暴露函数给大模型,是一种未来的编排模式:特别适合 Workflow 逻辑。
- LLM 的不可控性,需要用 ChatMemory 管住上下文:Spring AI 提供了
MemoryChatClient
封装 chat history,避免上下文漂移。 - 别忽视异常处理:OpenAI 接口 rate limit、timeout 经常发生,Spring AI 建议用
RetryTemplate
或自定义 fallback。
写在最后:Spring AI 更像一个“语言中间件”
Spring AI 不是大模型的封装器,而是让 Spring 应用具备“语言交互能力”的中间件。
它就像当年的 Spring Data,让我们用接口操作数据库;现在我们也可以用 Spring 风格操作语义接口。
你不再需要理解 token、role、chat-completion payload,只需要关注一件事:
“业务中哪块逻辑,可以更好地用语言来处理?”
从接口设计、代码结构,到 Prompt 管理和知识检索,Spring AI 都在逐渐成为一种“主流选型”。
推荐阅读文章
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
什么是 Cookie?简单介绍与使用方法
-
什么是 Session?如何应用?
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
如何理解应用 Java 多线程与并发编程?
-
把握Java泛型的艺术:协变、逆变与不可变性一网打尽
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
如何理解线程安全这个概念?
-
理解 Java 桥接方法
-
Spring 整合嵌入式 Tomcat 容器
-
Tomcat 如何加载 SpringMVC 组件
-
“在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”
-
“避免序列化灾难:掌握实现 Serializable 的真相!(二)”
-
如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)
-
解密 Redis:如何通过 IO 多路复用征服高并发挑战!
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
-
Java 中消除 If-else 技巧总结
-
线程池的核心参数配置(仅供参考)
-
【人工智能】聊聊Transformer,深度学习的一股清流(13)
-
Java 枚举的几个常用技巧,你可以试着用用
-
由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)
-
如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系
-
HTTP、HTTPS、Cookie 和 Session 之间的关系
-
使用 Spring 框架构建 MVC 应用程序:初学者教程
-
有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
-
Java Spring 中常用的 @PostConstruct 注解使用总结
-
线程 vs 虚拟线程:深入理解及区别
-
深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
-
10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
-
探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)
-
为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)