Spring AI 2.0 实战:从 ChatClient 到让 AI 调用你的代码
摘要:ChatClient 怎么用、多模型怎么配、上下文怎么记、怎么让 AI 调你的代码——Spring AI 2.0 官方能力一条线串起来,看完就能动手
上一篇文章我写 Spring AI 2.0 入门,有人可能问:
“跑通了,但只会 chatClient.prompt().call() 这一招。多模型怎么配?对话怎么记住上下文?让 AI 调我的业务方法怎么搞?能不能串起来讲一遍?”
能。
这篇基于 Spring AI 官方文档(spring.io/projects/spring-ai,当前版本 2.0.0)重新梳理,把 ChatClient、多模型、记忆、Function Calling 串成一条线。看完你就能从"会 demo"到"能干活"。
先对齐认知:Spring AI 到底能干什么
官方文档的说法:Spring AI 提供跨模型提供商的 Portable API(可移植 API),同步、流式都支持。
大白话:不管你接 OpenAI 还是 Ollama,代码写法一模一样。它还内置了:
| 官方能力
|
是干什么的
Chat Completion
|
对话补全,最常用的聊天/生成
| |
Embedding
|
文本向量化,做相似度检索
| |
Text to Image
|
文生图
| |
Audio Transcription
|
语音转文字
| |
Text to Speech
|
文字转语音
| |
Moderation
|
内容审核
| |
Structured Outputs
|
AI 输出直接映射成 POJO
| |
Function Calling
|
让 AI 调用你写的 Java 方法
| |
Vector Database
|
向量库,RAG 的地基
| |
Chat Memory
|
对话记忆,多轮上下文
| |
MCP
|
让 AI 通过标准协议用外部工具
| |
Observability
|
观测、追踪、指标
|
模型提供商原生支持 OpenAI、Anthropic、Amazon Bedrock、Mistral、Ollama 等。DeepSeek、通义千问这类国内模型没有官方 starter,但大多有 OpenAI 兼容接口,配 base-url 就能接。
准备工作
三样东西:
1. JDK 17+
<span leaf="">java -version</span><span leaf=""><br></span>
2. Spring Boot 3.4+(Spring AI 2.0 官方基于 3.4.2 做依赖管理)
3. 一个模型
-
想省事:注册 OpenAI,拿 API Key
-
想免费:装 Ollama 本地跑(后面有示例)
第一步:建项目,依赖用 BOM 管
打开 start.spring.io,选 Spring Web + Spring AI 的 starter。
关键点:Spring AI 发布节奏比 Spring Boot 快,官方推荐用 BOM 统一管版本。 不然依赖版本对不上,启动报错查半天。
pom.xml
<span leaf=""><!-- 1. BOM 统一管理 Spring AI 版本 --></span><span leaf=""><br></span><span leaf=""><dependencyManagement></span><span leaf=""><br></span><span leaf=""> <dependencies></span><span leaf=""><br></span><span leaf=""> <dependency></span><span leaf=""><br></span><span leaf=""> <groupId>org.springframework.ai</groupId></span><span leaf=""><br></span><span leaf=""> <artifactId>spring-ai-bom</artifactId></span><span leaf=""><br></span><span leaf=""> <version>2.0.0</version></span><span leaf=""><br></span><span leaf=""> <type>pom</type></span><span leaf=""><br></span><span leaf=""> <scope>import</scope></span><span leaf=""><br></span><span leaf=""> </dependency></span><span leaf=""><br></span><span leaf=""> </dependencies></span><span leaf=""><br></span><span leaf=""></dependencyManagement></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><!-- 2. OpenAI starter,版本交给 BOM --></span><span leaf=""><br></span><span leaf=""><dependency></span><span leaf=""><br></span><span leaf=""> <groupId>org.springframework.ai</groupId></span><span leaf=""><br></span><span leaf=""> <artifactId>spring-ai-starter-model-openai</artifactId></span><span leaf=""><br></span><span leaf=""></dependency></span><span leaf=""><br></span>
用 Ollama 就把 starter 换成 spring-ai-starter-model-ollama。
第二步:配置 application.yml
<span leaf="">spring:</span><span leaf=""><br></span><span leaf=""> application:</span><span leaf=""><br></span><span leaf=""> name: spring-ai-demo</span><span leaf=""><br></span><span leaf=""> ai:</span><span leaf=""><br></span><span leaf=""> openai:</span><span leaf=""><br></span><span leaf=""> api-key: ${OPENAI_API_KEY} # Key 走环境变量,别写死在代码里</span><span leaf=""><br></span><span leaf=""> chat:</span><span leaf=""><br></span><span leaf=""> options:</span><span leaf=""><br></span><span leaf=""> model: gpt-4o-mini</span><span leaf=""><br></span>
国内网络走中转的话加 base-url:
<span leaf="">spring:</span><span leaf=""><br></span><span leaf=""> ai:</span><span leaf=""><br></span><span leaf=""> openai:</span><span leaf=""><br></span><span leaf=""> base-url: https://你的中转地址/v1</span><span leaf=""><br></span><span leaf=""> api-key: ${OPENAI_API_KEY}</span><span leaf=""><br></span>
第三步:ChatClient——一切从这开始
ChatClient 是 Spring AI 2.0 的门面,官方定位是"Fluent API,惯用法类似 WebClient"。
ChatController.java
<span leaf="">@RestController</span><span leaf=""><br></span><span leaf="">public class ChatController {</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> private final ChatClient chatClient;</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> // Spring 自动注入 Builder,build 一下就有</span><span leaf=""><br></span><span leaf=""> public ChatController(ChatClient.Builder builder) {</span><span leaf=""><br></span><span leaf=""> this.chatClient = builder.build();</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> // GET /chat?msg=你好</span><span leaf=""><br></span><span leaf=""> @GetMapping("/chat")</span><span leaf=""><br></span><span leaf=""> public String chat(@RequestParam String msg) {</span><span leaf=""><br></span><span leaf=""> return chatClient.prompt()</span><span leaf=""><br></span><span leaf=""> .user(msg)</span><span leaf=""><br></span><span leaf=""> .call()</span><span leaf=""><br></span><span leaf=""> .content();</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
10 行代码,一个 AI 接口。 没有 HttpClient、没有 JSON 解析、没有重试逻辑。
ChatClient 的几个核心返回方式,官方文档里列得很清楚:
| 调用
|
返回
|
适用场景
call().content() |
String
|
直接要文本
|
| call().entity(Class) |
对象
|
结构化输出
|
| call().entity(ParameterizedTypeReference) |
泛型对象
|
返回 List 等集合
|
| call().chatResponse() |
ChatResponse
|
要 token 数等元数据
|
| stream().content() |
Flux
|
流式打字机效果
|
进阶一:结构化输出——AI 返回直接变对象
让 AI 提取新闻的标题、地点、日期:
News.java
<span leaf="">public record News(</span><span leaf=""><br></span><span leaf=""> String title,</span><span leaf=""><br></span><span leaf=""> String location,</span><span leaf=""><br></span><span leaf=""> String date</span><span leaf=""><br></span><span leaf="">) {}</span><span leaf=""><br></span>
Controller
<span leaf="">@GetMapping("/extract")</span><span leaf=""><br></span><span leaf="">public News extract(@RequestParam String text) {</span><span leaf=""><br></span><span leaf=""> return chatClient.prompt()</span><span leaf=""><br></span><span leaf=""> .user("从新闻中提取:标题、地点、日期。\n" + text)</span><span leaf=""><br></span><span leaf=""> .call()</span><span leaf=""><br></span><span leaf=""> .entity(News.class); // 一行转对象</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
要返回集合类型(比如一批新闻),用 ParameterizedTypeReference:
<span leaf="">@GetMapping("/extractAll")</span><span leaf=""><br></span><span leaf="">public List<News> extractAll(@RequestParam String text) {</span><span leaf=""><br></span><span leaf=""> return chatClient.prompt()</span><span leaf=""><br></span><span leaf=""> .user("提取所有新闻,返回列表。\n" + text)</span><span leaf=""><br></span><span leaf=""> .call()</span><span leaf=""><br></span><span leaf=""> .entity(new ParameterizedTypeReference<List<News>>() {});</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
.entity() 这行,官方 Structured Outputs 能力帮你把模型输出映射成 POJO,不用手写 JSON 解析。
进阶二:多模型配置——不同任务用不同模型
官方文档专门有一节 Working with Multiple Chat Models,覆盖这几个真实场景:
场景 1:按任务分模型。 简单问答走便宜快的模型,复杂推理走贵的模型。
<span leaf="">@Configuration</span><span leaf=""><br></span><span leaf="">public class ChatModelsConfig {</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> @Bean</span><span leaf=""><br></span><span leaf=""> public ChatClient fastChatClient(ChatClient.Builder builder) {</span><span leaf=""><br></span><span leaf=""> // 便宜模型:gpt-4o-mini</span><span leaf=""><br></span><span leaf=""> return builder.build();</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> @Bean</span><span leaf=""><br></span><span leaf=""> public ChatClient smartChatClient(ChatClient.Builder builder) {</span><span leaf=""><br></span><span leaf=""> // 贵模型:gpt-4o</span><span leaf=""><br></span><span leaf=""> return builder</span><span leaf=""><br></span><span leaf=""> .defaultOptions(ChatOptions.builder()</span><span leaf=""><br></span><span leaf=""> .model("gpt-4o")</span><span leaf=""><br></span><span leaf=""> .build())</span><span leaf=""><br></span><span leaf=""> .build();</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
Controller 里注入两个 Bean,按业务选:
<span leaf="">@RestController</span><span leaf=""><br></span><span leaf="">public class ChatController {</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> private final ChatClient fast;</span><span leaf=""><br></span><span leaf=""> private final ChatClient smart;</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> public ChatController(</span><span leaf=""><br></span><span leaf=""> @Qualifier("fastChatClient") ChatClient fast,</span><span leaf=""><br></span><span leaf=""> @Qualifier("smartChatClient") ChatClient smart) {</span><span leaf=""><br></span><span leaf=""> this.fast = fast;</span><span leaf=""><br></span><span leaf=""> this.smart = smart;</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
场景 2:兜底(fallback)。 主模型挂了自动切备用模型,官方示例里有 FallbackModelAccessor。
场景 3:A/B 测试。 两个模型对比效果,再决定上线哪个。
场景 4:用户自己选。 前端下拉框选模型,后端用对应的 ChatClient 处理。
场景 5:组合专业模型。 翻译用一个模型、总结用一个模型,各干各的强项。
核心思想:一个项目可以有多个 ChatClient Bean,按场景注入。
进阶三:Chat Memory——让 AI 记住上下文
默认每次调用都"失忆"。要记住多轮对话,官方提供 Chat Memory 机制,用 advisor 挂上去:
<span leaf="">@RestController</span><span leaf=""><br></span><span leaf="">public class ChatController {</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> private final ChatClient chatClient;</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> public ChatController(ChatClient.Builder builder) {</span><span leaf=""><br></span><span leaf=""> this.chatClient = builder</span><span leaf=""><br></span><span leaf=""> .defaultSystem("你是一个耐心的 Java 老师")</span><span leaf=""><br></span><span leaf=""> .defaultAdvisors(new MessageChatMemoryAdvisor(</span><span leaf=""><br></span><span leaf=""> // 最多记 10 条历史消息</span><span leaf=""><br></span><span leaf=""> MessageWindowChatMemory.builder()</span><span leaf=""><br></span><span leaf=""> .maxMessages(10)</span><span leaf=""><br></span><span leaf=""> .build()))</span><span leaf=""><br></span><span leaf=""> .build();</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> @GetMapping("/chat")</span><span leaf=""><br></span><span leaf=""> public String chat(@RequestParam String msg,</span><span leaf=""><br></span><span leaf=""> @RequestParam String userId) {</span><span leaf=""><br></span><span leaf=""> return chatClient.prompt()</span><span leaf=""><br></span><span leaf=""> .user(msg)</span><span leaf=""><br></span><span leaf=""> // 会话 ID:同一 userId 共享上下文</span><span leaf=""><br></span><span leaf=""> .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, userId))</span><span leaf=""><br></span><span leaf=""> .call()</span><span leaf=""><br></span><span leaf=""> .content();</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
几个要点(都核对过官方文档):
-
会话 ID 的 key 是
ChatMemory.CONVERSATION_ID——旧版叫
CHAT_MEMORY_CONVERSATION_ID_KEY,2.0 简化了 -
记忆窗口
用
MessageWindowChatMemory.builder().maxMessages(10)控制,别让上下文无限膨胀 -
记忆存储
官方支持 6 种:In-Memory、JDBC(MySQL/PostgreSQL 等)、Cassandra、Neo4j、MongoDB、Redis。生产环境用 JDBC 或 Redis,别用默认的 In-Memory(重启就丢)
进阶四:Function Calling——让 AI 调用你的 Java 方法
这是把 AI 从"聊天机器人"变成"能干活的应用"的关键。
场景: 用户问"上海明天天气怎么样",AI 自己不知道天气,但它可以调用你的 getWeather() 方法。
官方 2.0 的写法: 注册工具时用 ToolCallback API(官方升级说明里明确:从 FunctionCallback 迁移到 ToolCallback)。
<span leaf="">@Component</span><span leaf=""><br></span><span leaf="">public class WeatherTools {</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> @Tool(description = "查询指定城市指定日期的天气")</span><span leaf=""><br></span><span leaf=""> public String getWeather(String city, String date) {</span><span leaf=""><br></span><span leaf=""> // 这里调用真实的天气服务</span><span leaf=""><br></span><span leaf=""> return city + " " + date + ":晴,26°C";</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
ChatClient 挂上工具:
<span leaf="">// 把工具类注册进去</span><span leaf=""><br></span><span leaf="">this.chatClient = builder</span><span leaf=""><br></span><span leaf=""> .defaultTools(new ObjectProvider<WeatherTools>() {</span><span leaf=""><br></span><span leaf=""> // 简化示意:直接 new 或从容器拿</span><span leaf=""><br></span><span leaf=""> }.getIfAvailable())</span><span leaf=""><br></span><span leaf=""> .build();</span><span leaf=""><br></span>
更常见的是直接注入工具 Bean:
<span leaf="">@RestController</span><span leaf=""><br></span><span leaf="">public class ChatController {</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> private final ChatClient chatClient;</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""> public ChatController(ChatClient.Builder builder, WeatherTools weatherTools) {</span><span leaf=""><br></span><span leaf=""> this.chatClient = builder</span><span leaf=""><br></span><span leaf=""> .defaultTools(weatherTools) // 注册工具</span><span leaf=""><br></span><span leaf=""> .build();</span><span leaf=""><br></span><span leaf=""> }</span><span leaf=""><br></span><span leaf="">}</span><span leaf=""><br></span>
流程是这样的:
<span leaf="">用户:上海明天天气怎么样?</span><span leaf=""><br></span><span leaf=""> ↓</span><span leaf=""><br></span><span leaf="">模型:我需要调用 getWeather(city=上海, date=明天)</span><span leaf=""><br></span><span leaf=""> ↓</span><span leaf=""><br></span><span leaf="">Spring AI 调你的 Java 方法,拿到结果</span><span leaf=""><br></span><span leaf=""> ↓</span><span leaf=""><br></span><span leaf="">模型:根据结果组织回答:"上海明天晴,26°C"</span><span leaf=""><br></span>
你写的是普通 Java 方法,AI 帮你调度它。 查数据库、调内部 API、算个价格,都行。
生产方向:官方还给了这几张牌
可观测性(Observability): 官方内置,跟 Micrometer 打通。请求耗时、token 消耗、错误原因都能上监控面板。
MCP(Model Context Protocol): 官方文档有专门章节。让 AI 通过标准协议调用外部工具和数据源,可以理解成"AI 世界的 USB 接口"。
RAG(检索增强生成): 官方支持向量库 + ETL 框架。让 AI 基于你的私有文档回答,而不是瞎编。
这三块每一块都够单独写一篇,先知道有这些能力,用到了再深入。
踩坑记录
坑 1:版本对不上,启动报错
Spring AI 依赖不指定版本,或者跟 Spring Boot 版本不匹配,启动直接红。
用 BOM 管版本,别一个个手写版本号。
坑 2:流式输出报错
<span leaf=""># 报错:streaming not supported</span><span leaf=""><br></span>
官方实现说明:流式(stream)只在响应式栈(WebFlux)下支持,同步调用走 Servlet 栈。想用流式,项目要用 WebFlux。
坑 3:ChatClient 注入失败
No qualifying bean of type ChatClient。
确认:pom 里有 starter、BOM 版本对、Spring Boot >= 3.4。
坑 4:401 认证失败
<span leaf="">export OPENAI_API_KEY=sk-xxxx</span><span leaf=""><br></span><span leaf="">mvn spring-boot:run</span><span leaf=""><br></span>
Key 用环境变量传,确认启动时真的传了。
坑 5:国内访问 OpenAI 超时
配 base-url 走中转,或换 DeepSeek / 通义千问(OpenAI 兼容接口)/ Ollama。
总结
| 需求
|
官方方案
接大模型
|
ChatClient,一行调用
| |
返回结构化数据
| .entity(Class) |
|
流式打字机
| stream()
(需 WebFlux)
| |
不同任务不同模型
|
多个 ChatClient Bean
| |
多轮对话记忆
|
ChatMemory + advisor
| |
让 AI 调你的方法
|
ToolCallback / @Tool
| |
生产观测
|
Observability
| |
接外部工具
|
MCP
|
Spring AI 2.0 的核心价值:一套 API 接所有模型,官方把结构化输出、记忆、工具调用、可观测性全给你备齐了。 你只需要写业务代码,剩下的框架管。
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/ai002/post/20260823/Spring-AI-2.0-%E5%AE%9E%E6%88%98%E4%BB%8E-ChatClient-%E5%88%B0%E8%AE%A9-AI-%E8%B0%83%E7%94%A8%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%A0%81/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com