目录
[1.1 版本信息](#1.1 版本信息)
[1.2 Maven 核心依赖](#1.2 Maven 核心依赖)
[1.3 应用配置(application.properties)](#1.3 应用配置(application.properties))
[2.1 方案一:ChatClient 高阶封装写法](#2.1 方案一:ChatClient 高阶封装写法)
[2.2 方案二:ChatModel 底层原生写法](#2.2 方案二:ChatModel 底层原生写法)
[三、ChatClient 与 ChatModel 深度对比](#三、ChatClient 与 ChatModel 深度对比)
[3.1 核心定位](#3.1 核心定位)
[3.2 多维度对比表](#3.2 多维度对比表)
[3.3 适用场景建议](#3.3 适用场景建议)
[四、实战踩坑:getText() 与 getContent() 取值问题解析](#四、实战踩坑:getText() 与 getContent() 取值问题解析)
[4.1 问题现象](#4.1 问题现象)
[4.2 根因分析](#4.2 根因分析)
[4.3 补充:阿里云 OpenAI 兼容模式原理](#4.3 补充:阿里云 OpenAI 兼容模式原理)
在基于 Spring AI 对接大模型的开发过程中,框架提供了 ChatClient 和 ChatModel 两套核心调用接口,二者定位、用法、灵活性差异较大,很多开发者容易混淆。
本文结合阿里云通义千问(qwen-max) 实战场景,基于 OpenAI 兼容模式完成接口调用,同时针对开发中遇到的 getText() / getContent() 取值方法不一致的问题进行深度复盘。完整演示两套接口的编码实现、核心区别,并梳理环境配置、踩坑原因与生产最佳实践,帮助大家彻底掌握 Spring AI 基础调用能力。
本文运行环境:Spring Boot 4.0.6 + Spring AI 2.0.0-M8,采用阿里云 DashScope OpenAI 兼容协议,使用 OpenAI 依赖直接调用通义千问模型。
一、项目环境与基础配置
1.1 版本信息
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.6</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
<!-- Spring AI 里程碑测试版 -->
<spring-ai.version>2.0.0-M8</spring-ai.version>
</properties>
1.2 Maven 核心依赖
使用 Spring AI OpenAI 启动器,借助阿里云兼容协议调用通义千问,无需额外引入 DashScope 专属依赖:
<dependencies>
<!-- Spring MVC 基础web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<!-- Spring AI OpenAI 启动器(兼容阿里云通义千问) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Spring AI 版本统一管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
1.3 应用配置(application.properties)
核心配置:对接阿里云 DashScope 兼容 OpenAI 协议地址,填入 API-Key 并指定默认模型:
spring.application.name=springai
server.port=8080
# 阿里云百炼 API Key
spring.ai.openai.api-key=
# 阿里云通义千问 OpenAI 兼容接口地址(核心)
spring.ai.openai.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1
# 默认调用模型
spring.ai.openai.chat.model=qwen-max
说明:阿里云 DashScope 提供了完整的 OpenAI 协议兼容层,接口请求、响应格式与原生 OpenAI 完全一致,因此可直接使用 OpenAI 依赖调用通义系列模型。
二、两种同步调用实现方案
下面分别使用 ChatClient(高阶封装)和 ChatModel(底层原生接口)实现同步问答接口,两段代码均基于上述环境正常运行。
2.1 方案一:ChatClient 高阶封装写法
ChatClient 是 Spring AI 在上层封装的工具类,屏蔽了底层请求构建、结果解析细节,代码极简,是业务开发首选。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
// 注入高阶客户端
private final ChatClient chatClient;
public ChatController(ChatClient chatClient) {
this.chatClient = chatClient;
}
/**
* ChatClient 同步问答接口
* 特点:链式调用,自动构建请求、解析响应
*/
@RequestMapping("/chat")
public String chat(@RequestParam(value = "message") String message) {
return this.chatClient.prompt()
.user(message) // 传入用户提问
.call() // 同步调用大模型
.content(); // 直接获取对话内容
}
}
2.2 方案二:ChatModel 底层原生写法
ChatModel 是 Spring AI 最核心的底层接口,所有模型调用最终都会基于它实现。需要手动构建请求对象、手动解析响应结果,灵活性更高。
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatModelController {
// 注入底层模型接口
private final ChatModel chatModel;
public ChatModelController(ChatModel chatModel) {
this.chatModel = chatModel;
}
/**
* ChatModel 同步问答接口
* 特点:手动构建Prompt、手动配置模型参数、手动解析响应
*/
@RequestMapping("/chat/model")
public String chatModel(@RequestParam(value = "message") String message) {
// 1. 手动构建请求,并指定模型为 qwen-max
Prompt prompt = new Prompt(message, OpenAiChatOptions.builder()
.model("qwen-max")
.build());
// 2. 同步调用模型,返回完整响应对象
ChatResponse response = chatModel.call(prompt);
// 3. 逐层解析响应,获取对话文本(当前版本使用 getText())
return response.getResult().getOutput().getText();
}
}
三、ChatClient 与 ChatModel 深度对比
结合代码实现,从设计定位、编码风格、灵活性、容错性等维度,全面对比两套接口的差异。
3.1 核心定位
- ChatModel Spring AI 体系的底层核心标准接口,是所有大模型调用的基础。框架不做额外封装,暴露原始请求、响应结构,偏向底层编程。
- ChatClient 基于
ChatModel二次封装的高阶工具类,主打简化开发,屏蔽底层复杂对象与解析逻辑,面向业务场景设计。
3.2 多维度对比表
| 对比维度 | ChatClient(高阶封装) | ChatModel(底层原生) |
|---|---|---|
| 代码复杂度 | 极低,链式调用,一行核心逻辑 | 偏高,需手动构建 Prompt、配置参数 |
| 请求构建 | 框架自动封装,无需手动创建请求对象 | 必须手动 new Prompt() 构造请求与参数 |
| 结果解析 | 自动解析,直接通过 content() 获取文本 |
逐层手动解析 ChatResponse 响应体 |
| 容错能力 | 内置空值、异常处理,安全性高 | 无自动容错,链式调用易触发空指针异常 |
| 扩展灵活性 | 较弱,仅支持通用配置,难以深度定制底层逻辑 | 极强,可精细化控制请求参数、响应处理逻辑 |
| 依赖关系 | 内部依赖 ChatModel 实现能力 |
框架基础接口,无上层依赖 |
3.3 适用场景建议
- ✅ 优先使用 ChatClient:常规业务问答、快速开发、前后端对接等通用场景,提升开发效率。
- ✅ 选择 ChatModel:底层调试、自定义模型参数、特殊请求改造、框架二次扩展等需要精细化控制的场景。
四、实战踩坑:getText() 与 getContent() 取值问题解析
4.1 问题现象
在当前 2.0.0-M8 版本中,使用 ChatModel 解析响应时:
- 调用
response.getResult().getOutput().getContent()→ 无法获取有效内容; - 替换为
response.getResult().getOutput().getText()→ 正常拿到大模型返回文本。
4.2 根因分析
该问题与大模型、协议、依赖无关 ,纯粹是 Spring AI 版本迭代导致的 API 命名变更:
- 本文使用
2.0.0-M8,属于里程碑测试版 ,框架接口尚未稳定,输出文本的读取方法为getText(); - 在 Spring AI 后续 正式版本(1.0 正式版 / 2.0 正式版) 中,框架统一规范命名,将方法修改为
getContent()。
避坑建议:升级框架版本时,务必核对官方文档的 API 命名,避免因接口变更导致功能异常。
4.3 补充:阿里云 OpenAI 兼容模式原理
很多同学疑惑:为什么引入 OpenAI 依赖,却能调用通义千问?
- 阿里云 DashScope 官方提供 OpenAI 协议兼容层 ,地址为
https://dashscope.aliyuncs.com/compatible-mode/v1; - 该兼容层完全对齐 OpenAI 的请求报文、响应报文格式;
- Spring AI OpenAI 启动器按照 OpenAI 协议解析数据,因此可以无缝对接通义千问、通义万象等阿里云模型,无需切换专属依赖。
java
private static @Nullable String getContentFromChatResponse(@Nullable ChatResponse chatResponse) {
return (String)Optional.ofNullable(chatResponse)
.map(ChatResponse::getResult) // 取结果
.map(Generation::getOutput) // 取输出消息
.map(AbstractMessage::getText) // 取文本(你的版本用getText)
.orElse((Object) null); // 空值返回null
}
我们来看一下ChatClient的底层.content的封装 就是ChatModel不就正是的return返回吗,