Spring AI 这个名字,最近在Java圈子里出现频率明显高了不少。如果你是一名Java开发者,一直眼馋Python生态里各种大模型应用开发的便利,又不想为了一个聊天功能硬切技术栈,那Spring AI确实是一个值得关注的新手切入点。简单说,它是把大模型能力变成Java开发者熟悉的Spring风格组件:你不需要关心底层HTTP调用怎么拼、JSON怎么解析,可以用很“Spring”的方式,在主程序里写几行代码,就把大模型接进业务系统。这篇内容,我按照自己从零接触这个框架的真实路径来写,适合第一次听说它、或者刚把Demo跑通但不知道怎么落到真实项目里的同学参考。
1. 先说清楚:这个框架到底帮你省掉了什么
很多Java开发者第一次接触大模型应用开发时,脑子里第一反应是:直接拿HTTP Client调用模型接口不就行了?确实行,但只限于“发一句话、拿一段文字”的最简单场景。一旦你想做点正经东西——比如让模型按指定JSON格式返回、多轮对话带上下文、把模型接入工具函数、在Web项目里做流式打字机效果——你会发现手写的胶水代码越来越多,而且每换一家模型服务商,就要重写一遍适配。
Spring AI这个框架的核心价值,就是把这一堆重复劳动抽象成了统一的接口。它借鉴了当年Spring对数据库访问的统一抽象思路:你写代码时面对的是ChatModel、ChatClient这类稳定接口,底层接的是哪家大模型服务商,由配置文件决定。换一家服务,改配置即可,业务代码不用动。
为了说清楚这个价值,我拿“不用框架”和“用框架”来做一次直观对比:
| 开发任务 | 硬写HTTP调用 | 使用Spring AI |
|---|---|---|
| 接入模型 | 手写HTTPClient、鉴权、错误处理 | 加依赖、配key、注入ChatModel |
| 对话补全 | 拼Prompt,手动组装请求体 | ChatClient一行调用 |
| 返回JSON | 解析字符串,处理格式异常 | 结构化输出,直接映射Java对象 |
| 流式输出 | 自己解析SSE流、处理断连 | 响应式流式API,开箱即用 |
| 多轮记忆 | 自己管理历史消息数组 | Memory接口自动维护 |
| 换服务商 | 重写整个适配层 | 替换配置与依赖,代码不变 |
所以说,这个框架解决的从来不是“能不能调大模型”的问题,而是“让Java开发者用熟悉的方式、高质量地把大模型集成进业务系统”的问题。新手入门阶段,先理解这一层价值,后面学起来就不会迷茫。
2. 新手上路必须搞懂的五个抽象概念
我第一次看Spring AI文档时,发现它有不少抽象概念。逐个啃很痛苦,但如果你抓住主线——模型、提示词、客户端、输出解析、记忆——再回来理解整个调用链,就会发现其实不复杂。
2.1 ChatModel与ChatClient:模型接口与高级入口
ChatModel是这个框架里最底层的模型抽象,它封装了“给模型发一段消息、拿到回复”的完整过程。你可以把它理解为一条直连大模型的管道,方法签名很简洁,传入Prompt,返回包含回复内容的ChatResponse。但日常开发中,我们很少直接操作ChatModel,更多是用它上面的ChatClient。
ChatClient是一个更贴近业务场景的高级门面,典型用法是链式调用。比如:
String answer = chatClient.prompt() .system("你是一个精通Java的资深架构师。") .user("请用三句话介绍Spring AI的核心优势。") .call() .content();这段代码读起来几乎就是英文自然语言,完全没有“我要拼一个JSON请求体”的感觉。新手阶段我建议你优先掌握ChatClient,底层细节暂时不用深挖。
2.2 Prompt与Message:需求的载体
借用生活中的例子:你向模型提问之前,总会有一段话描述你想干什么,模型再基于这段话给出答案。在Spring AI里,Prompt就是这段请求的封装对象,里面包含一个或多个Message消息。
消息按角色分成了几种类型,最常用的是:
SystemMessage:设定模型的系统身份、行为约束,类似“员工手册”。UserMessage:用户实际提出的问题或任务内容。AssistantMessage:模型给出的回复,在多轮对话中会被作为历史记录传回。ToolMessage:工具调用返回的结果,配合后面讲到的工具调用使用。
理解这几种角色消息,对做多轮对话和复杂业务提示词非常关键。很多时候新手觉得模型“不听话”,问题往往就出在SystemMessage写得不到位。
2.3 结构化输出:让模型吐JSON而不是散文
大模型默认返回的是自然语言,但业务系统最想处理的是结构化的JSON。Spring AI提供了StructuredOutputConverter来处理这件事。你只需要定义好一个Java类,然后声明“我要让模型按这个类的结构返回”,框架会在Prompt中自动附加JSON Schema约束,并把模型输出的字符串解析成Java对象。
来看一个最直观的例子:
public record ReviewSummary( String sentiment, int score, List<String> keywords, String summary ) {} ReviewSummary result = chatClient.prompt() .user("分析这段商品评论,提取情感倾向、评分、关键词和一句话摘要:{{text}}") .input(TextTemplate.of("text", reviewText)) .call() .entity(ReviewSummary.class);相比自己写正则去抓关键词,这个能力能省下大量容错代码。后面我会专门用一个案例来讲它。
2.4 Memory:多轮对话的“聊天记忆”
大模型本身没有记忆,每次调用都是无状态的。想让模型记住“你刚才说过的内容”,你就得把历史消息一起发给它。Spring AI提供了Memory接口,常见实现是ChatMemory,可以自动存储和加载会话历史。
新手最容易犯的错,是把所有历史消息一股脑全塞给模型,结果上下文越来越长,费用越来越高,回复质量反而下降。框架里的MessageWindowChatMemory允许你通过windowSize参数控制保留最近几轮对话,超过窗口的自动丢弃。
ChatMemory chatMemory = MessageWindowChatMemory.builder() .windowSize(10) // 保留最近10轮 .build();2.5 Tool Calling:让模型能“动手干活”
如果只让模型写文字,它的能力天花板很明显。Tool Calling相当于给模型发了一套“工具箱”,模型在需要时可以主动选择一个工具去执行,再把工具执行的结果纳入自己的回复。典型场景有查数据库、查天气、计算、调内部接口。
在Spring AI里,注册工具非常简单,在@Bean方法上加上@Tool注解,声明参数的描述信息即可:
@Tool("根据城市名称查询当前天气") public String getWeather(String city) { // 这里调用真实的天气服务接口 return weatherService.query(city); }然后你在调用ChatClient时声明可用的工具:
String result = chatClient.prompt() .user("北京今天适合户外跑步吗?") .toolNames("getWeather") .call() .content();模型会决定是否调用getWeather,框架自动完成中间的工具调用流程,对你来说整个过程几乎是透明的。
3. 从零跑通第一个最小项目:三步走
纯讲概念容易飘,我建议你在IDE里真实创建一个Spring Boot项目,跟着下面的步骤跑通第一个调用。整个过程大概十分钟。
3.1 第一步:创建Spring Boot工程并引入依赖
Spring AI目前处于快速迭代阶段,版本号和对应的Spring Boot版本有严格对应关系。新手最常见的坑之一就是依赖版本对不上,启动直接报错。稳妥做法是去它的官方文档查看“Versions”表格,选中与你的Spring Boot大版本匹配的Spring AI版本。
假设你用的是Spring Boot 3.x,Maven的pom.xml大致是这样:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model</artifactId> <version>1.0.0-M5</version> </dependency> </dependencies>注意:Spring AI的starter命名可能会随版本调整,比如早先版本叫spring-ai-openai-spring-boot-starter,现在可能统一到一个核心starter下,不同模型服务商再引独立模块。不要照抄网上旧文章里的依赖坐标,以你选定版本对应的官方文档为准。
3.2 第二步:在配置文件里设置模型服务商信息
Spring AI最方便的一点是,模型服务商的对接通过配置项完成。你在application.yml里填入服务商的API Key和模型名称即可:
spring: ai: model: provider: openai-compatible api-key: ${AI_API_KEY} chat: options: model: gpt-4o-mini实际的配置Key会因为服务商不同而不同,有些服务商兼容OpenAI协议,有些需要独立配置项。我的建议是:
- 不要硬编码API Key在配置文件里,用环境变量注入。
- 第一次调试时选择模型商的小模型,速度快、费用低。
- 配置完成后,先写一个测试类确认能通,再继续往下。
3.3 第三步:写一个Controller,跑通第一个聊天接口
新建一个Controller,注入ChatClient,暴露/chat接口:
@RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/chat") public String chat(@RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }启动应用,浏览器访问http://localhost:8080/chat?message=你好,看到模型回复的那一刻,第一个Spring AI项目就跑通了。
4. 进阶实战:把模型接进真实业务项目的三个能力
跑通简单聊天之后,你会开始想:这东西到底能在公司项目里干什么?我根据自己的实操经验,整理了三个最常见、也最能体现Spring AI威力的实战能力,建议优先掌握。
4.1 用结构化输出做内容抽取与分析
业务日志、用户评论、合同文档……这些非结构化文本沉淀在系统里,以前想自动化处理很难,现在可以让模型来做字段抽取和归类。
假设你运营一个电商系统,每天有大量用户评论需要分析情感,最直接的实现方式是这样:
@Service public class ReviewAnalysisService { private final ChatClient chatClient; public ReviewAnalysisService(ChatClient.Builder builder) { this.chatClient = builder.build(); } public ReviewAnalysisResult analyze(String reviewText) { return chatClient.prompt() .system("你是一位专业的文本分析师,擅长从用户评论中提炼结构化信息。") .user("请分析以下商品评论,返回JSON格式的情感倾向(positive/neutral/negative)、1-5评分、关键词列表和一句话摘要:{{text}}") .input(TextTemplate.of("text", reviewText)) .call() .entity(ReviewAnalysisResult.class); } }定义返回的记录类型:
public record ReviewAnalysisResult( String sentiment, int score, List<String> keywords, String summary ) {}这里有几个很容易踩的细节:
- 记录类型的字段名默认会被转成JSON字段名,如果你的服务商对大小写敏感,建议在字段上直接写小写命名。
- 如果模型偶尔不按Schema输出,框架会抛解析异常,建议在
call()外层做一次try-catch,并给一个默认兜底对象。 .entity()方法的内部实现是:框架在Prompt里附加OutputConverter的格式化指令,调用模型拿到字符串,再反序列化成对象。如果你发现返回结构总是不稳定,优先检查是模型版本太旧,还是Prompt里的Schema指令被覆盖了。
4.2 流式输出,实现“打字机”对话效果
如果你做过聊天机器人,一定知道流式“打字机”效果对用户体验的提升有多大。Spring AI支持响应式流式输出,天然适配WebFlux。
Controller改成返回Flux<String>:
@RestController public class StreamChatController { private final ChatClient chatClient; public StreamChatController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping(value = "/stream-chat", produces = "text/event-stream") public Flux<String> streamChat(@RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); } }需要注意一点:如果你的项目引入的是spring-boot-starter-web传统阻塞式Servlet,那么流式输出还是能工作,但更推荐的服务端技术栈是WebFlux。前后端配合时,前端使用EventSource或ReadableStream来消费文本流。
我在实际项目中测试过,流式调用在弱网环境下可能会偶发中断,Spring AI的底层已经做了重连和错误处理,但自己最好还是在前端加一个“如果流中断,展示用户可点击重试”的回退机制。
4.3 多轮对话与业务级会话状态管理
聊天机器人最核心的体验是多轮连贯对话。Spring AI中,你不需要手动维护历史消息列表。
首先定义会话内存和ChatClient:
@Configuration public class ChatClientConfig { @Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultSystem("你是一个耐心、友善的客服助手,请用简洁的中文回答。") .defaultMemory(MessageWindowChatMemory.builder() .windowSize(8) .build()) .build(); } }Controller中按会话ID进行对话:
@RestController public class SessionChatController { private final ChatClient chatClient; public SessionChatController(ChatClient chatClient) { this.chatClient = chatClient; } @PostMapping("/session-chat") public String sessionChat(@RequestParam String sessionId, @RequestParam String content) { return chatClient.prompt() .user(content) .chatId(sessionId) .call() .content(); } }这里.chatId(sessionId)的作用是:框架会把sessionId和对应会话的历史消息绑定存储起来,下次再传同一个sessionId,自动把最近的对话记录加载进上下文。
我在真实项目里用过一个优化策略:当对话超过窗口限制时,让模型对之前的对话做一次“摘要压缩”,然后把摘要作为新的系统上下文。这样既控制token成本,又不丢失关键信息。你可以先用窗口裁剪,等业务复杂度上来后再引入摘要逻辑。
5. 一个完整的业务案例:商品评论情感分析与回复建议
概念和独立能力讲完,我组合一个相对完整的案例,模拟真实项目中的落地过程。场景是:电商运营需要一个接口,输入任意商品评论,输出情感分析结果和面向用户的回复建议。
需求拆成三个点:
- 提取评论的情感倾向、评分和关键信息。
- 生成一条针对该评论的回复建议。
- 如果评论是负面情绪,额外生成一条补偿方案建议。
Service实现如下:
@Service public class CommentAnalysisService { private final ChatClient chatClient; public CommentAnalysisService(ChatClient.Builder builder) { this.chatClient = builder.build(); } public CommentReport analyzeComment(String comment) { CommentReport draft = chatClient.prompt() .system("你是电商平台的评价分析助手,请严格按要求输出。") .user(""" 请分析这条商品评价: 评价内容:「{{comment}}」 输出要求: 1. 情感倾向:positive / neutral / negative 2. 满意度评分:1-5 的整数 3. 问题类别:物流/质量/尺码/客服/其他 4. 回复建议:20字以内的用户回复话术 5. 若为负面评价,给出补偿建议(补发/退款/优惠券/改进承诺) """) .input(TextTemplate.of("comment", comment)) .call() .entity(CommentReport.class); return draft; } public record CommentReport( String sentiment, int score, String category, String replySuggestion, String compensationSuggestion ) {} }Controller暴露接口:
@RestController @RequestMapping("/api/comment") public class CommentController { private final CommentAnalysisService analysisService; public CommentController(CommentAnalysisService analysisService) { this.analysisService = analysisService; } @PostMapping("/analyze") public CommentAnalysisService.CommentReport analyze(@RequestBody Map<String, String> body) { return analysisService.analyzeComment(body.get("comment")); } }这个案例我给了很多新手团队,他们普遍反馈“原来从Demo到业务没那么远”。但它也暴露出几个生产中必须补的工程细节:
- 接口稳定性:模型输出偶尔出错,建议在
CommentReport中额外加一个rawResponse字段,如果转换失败,至少把原文留给调用方。 - 审计与日志:每次分析请求,记录原始评论、模型回复和耗时,便于后续用真实数据微调Prompt。
- 并发与限流:大模型接口有QPS限制,单纯把
ChatClient做成单例直接并发调用,很可能会触发限流。稳妥做法是接入一个简单的信号量或队列,控制最大并发数。
6. 新手最容易踩的八个坑,提前避开
最后这部分是我见过的高频问题汇总,每条都简短说明原因和解决方向,希望帮你少走弯路。
6.1 版本迭代快,依赖坐标别抄旧文章
Spring AI目前的版本迭代速度非常快,API变动也频繁。网上很多教程基于几个月前的版本,依赖连下载都成问题。唯一可靠的信息源是官方文档的版本列表和迁移指南。新手阶段如果把大量时间浪费在解决版本冲突上,很容易产生挫败感,我推荐的做法是:建项目时直接去官方示例仓库克隆一份对应版本模板,在此基础上改代码。
6.2 API Key泄露
这个坑和框架本身无关,但新手最容易犯。我看到过有人把API Key明文写在application.yml里,甚至上传到公开仓库。正确做法是用环境变量或配置中心注入,同时开启模型服务商的消费限额,防止Key泄露后被恶意刷量。
6.3 Prompt里的变量没有正确传值
Spring AI支持模板变量,格式是{{变量名}}。新手经常忘记调.input()方法传值,导致模型看到的是字面量的{{comment}}。排查这个问题最简单的方式是:打开框架的请求日志,看最终发出去的Prompt长什么样。
6.4 上下文无限膨胀
多轮对话不控制窗口,历史消息越积越多,最终的结果是费用飙升且回复速度变慢。应对办法:设置windowSize,或用摘要压缩历史。我建议生产环境至少设定一个上限。
6.5 结构化输出的字段类型不匹配
entity()反序列化时,如果模型返回的字符串里某个字段格式不符合Java类型(比如模型把score返回成了字符串"5分"),框架会报转换异常。解决办法:在Prompt中给每个字段一个明确的格式示例,必要时使用枚举约束合法性。
6.6 依赖了模型服务商的“当前最佳”模型名
很多模型服务商会定期调整模型别名,如果你把模型名写死在配置里,某一天发现调用报404。建议把模型名放配置中心,版本升级时只需改配置。
6.7 忽略超时与重试
模型接口的响应时间不稳定,业务高峰时一个请求等十几秒很正常。如果后端调用没有超时控制,资源会被长时间占用。Spring AI允许配置底层调用的超时时间和重试次数,建议在生产环境设置合理的超时阈值,并区分“网络重试”和“模型返回低质量”两种情况——前者可以重试,后者重试意义不大。
6.8 测试不覆盖边界情况
大多数新手只测了“正常问题”的链路,模型输出为你想要的结构就收工。真正上线后会遇到空字符串、超长输入、特殊字符、敏感词拦截等情况。建议在测试集里加入至少20条极端评论,验证框架在边界条件下是否稳定。
最后说几句掏心窝的话
我接触Spring AI并把它用进真实项目,最大的感受是这个框架的迭代确实快,但这恰好说明社区投入高、前景明确。作为Java开发者,能在这个时间点接触到大模型应用开发,是一个很值得花时间投资的方向。如果你现在刚开始学,不要贪多,建议以“跑通一个最简单调用”为第一目标,再逐步加上结构化输出、流式、多轮对话和工具调用,每加一个能力就做一个端到端的小项目验证。这个路径我带着多个团队走过,比一开始就啃源码要高效得多。还有一个小建议:时刻关注版本更新日志,每次升版本后跑一遍已有用例,你会发现API变动并不可怕,反而是你加深理解的契机。