Spring AI Alibaba 【二】
1. chatModel 与chatClient
1.1 对话模型(Chat Model)
对话模型(Chat Model)接收一系列消息(Message)作为输入,与模型 LLM 服务进行交互,并接收返回的聊天消息(Chat Message)作为输出。相比于普通的程序输入,模型的输入与输出消息(Message)不止支持纯字符文本,还支持包括语音、图片、视频等作为输入输出。同时,在 Spring AI Alibaba 中,消息中还支持包含不同的角色,帮助底层模型区分来自模型、用户和系统指令等的不同消息。
Spring AI Alibaba 复用了 Spring AI 抽象的 Model API,并与通义系列大模型服务进行适配(如通义千问、通义万相等),目前支持纯文本聊天、文生图、文生语音、语音转文本等。以下是框架定义的几个核心 API:
- ChatModel,文本聊天交互模型,支持纯文本格式作为输入,并将模型的输出以格式化文本形式返回。
- ImageModel,接收用户文本输入,并将模型生成的图片作为输出返回。
- AudioModel,接收用户文本输入,并将模型合成的语音作为输出返回。
Spring AI Alibaba 支持以上 Model 抽象与通义系列模型的适配,并通过 spring-ai-alibaba-starter AutoConfiguration 自动初始化了默认实例,因此我们可以在应用程序中直接注入 ChatModel、ImageModel 等 bean,当然在需要的时候也可以自定义 Model 实例。
1.1.1 Chat Model
ChatModel API 让开发者可以非常方便的与 AI 模型进行文本交互,它抽象了应用与模型交互的过程,包括使用 Prompt 作为输入,使用 ChatResponse 作为输出等。ChatModel 的工作原理是接收 Prompt 或部分对话作为输入,将输入发送给后端大模型,模型根据其训练数据和对自然语言的理解生成对话响应,应用程序可以将响应呈现给用户或用于进一步处理。

1.1.2 使用示例
开发完整的 ChatModel 示例应用,需要添加 spring-ai-alibaba-starter 依赖。
以下是 ChatModel 基本使用示例,它可以接收 String 字符串作为输入:
@RestControllerpublic class ChatModelController {private final ChatModel chatModel;public ChatModelController(ChatModel chatModel) {this.chatModel = chatModel;}@RequestMapping("/chat")public String chat(String input) {ChatResponse response = chatModel.call(new Prompt(input));return response.getResult().getOutput().getContent();}}
使用 Prompt 作为输入:
@RequestMapping("/chatWithPrompt")public String chatWithPrompt(String input) {Prompt prompt = new Prompt(input);ChatResponse response = chatModel.call(prompt);return response.getResult().getOutput().getContent();}
Streaming 示例:
@RequestMapping("/streamChat")public void streamChat(String input, HttpServletResponse response) throws IOException {response.setContentType("text/event-stream");response.setCharacterEncoding("UTF-8");Prompt prompt = new Prompt(input);chatModel.stream(prompt, new StreamHandler() {@Overridepublic void onMessage(ChatMessage message) {try {response.getWriter().write("data: " + message.getContent() + "\n\n");response.getWriter().flush();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void onComplete() {try {response.getWriter().write("event: complete\n\n");response.getWriter().flush();} catch (IOException e) {e.printStackTrace();}}});}
通过 ChatOptions 在每次调用中调整模型参数:
@RequestMapping("/chatWithOptions")public String chatWithOptions(String input) {ChatOptions options = ChatOptions.builder().withTemperature(0.7).withMaxTokens(150).build();Prompt prompt = new Prompt(input, options);ChatResponse response = chatModel.call(prompt);return response.getResult().getOutput().getContent();}
1.2 Chat Client
1.2.1 ChatClient 简介
ChatClient 提供了与 AI 模型通信的 Fluent API,它支持同步和反应式(Reactive)编程模型。与 ChatModel、Message、ChatMemory 等原子 API 相比,使用 ChatClient 可以将与 LLM 及其他组件交互的复杂性隐藏在背后,因为基于 LLM 的应用程序通常要多个组件协同工作(例如,提示词模板、聊天记忆、LLM Model、输出解析器、RAG 组件:嵌入模型和存储),并且通常涉及多个交互,因此协调它们会让编码变得繁琐。当然使用 ChatModel 等原子 API 可以为应用程序带来更多的灵活性,成本就是需要使用者编写大量样板代码。
ChatClient 类似于应用程序开发中的服务层,它为应用程序直接提供 AI 服务,开发者可以使用 ChatClient Fluent API 快速完成一整套 AI 交互流程的组装。
包括一些基础功能,如:
- 定制和组装模型的输入(Prompt)
- 格式化解析模型的输出(Structured Output)
- 调整模型交互参数(ChatOptions)
还支持更多高级功能:
- 聊天记忆(Chat Memory)
- 工具/函数调用(Function Calling)
- RAG
1.2.2 创建 ChatClient
使用 ChatClient.Builder 对象创建 ChatClient 实例,开发者可以自动注入由Spring Boot 自动配置创建的默认 ChatClient.Builder 实例,也可以通过编程方式自行创建一个 ChatClient.Builder 实例并用它来得到 ChatClient 实例。
1.2.2.1 使用自动配置的 ChatClient.Builder
使用 Spring Boot 自动装配默认生成的 ChatClient.Builder 的 bean,把它注入到您自定义的类中。这里是根据用户提问并从模型得到文本回答的简单例子:
@RestControllerpublic class ChatController {private final ChatClient chatClient;public ChatController(ChatClient.Builder builder) {this.chatClient = builder.build();}@GetMapping("/chat")public String chat(String input) {return this.chatClient.prompt().user(input).call().content();}}
在这个示例中,首先设置了用户消息的内容,call 方法向 AI 模型发送请求,content 方法以字符串形式返回 AI 模型的响应。
1.2.2.2 以编程方式创建 ChatClient
可以通过设置属性 spring.ai.chat.client.enabled=false 来禁用 ChatClient.Builder bean 的自动配置,若需要多个聊天模型一起使用,这会很有用,然后以编程方式创建 ChatClient.Builder,这样可以为每个聊天模型创建一个实例 ChatModel:
ChatModel myChatModel = ... // usually autowiredChatClient.Builder builder = ChatClient.builder(myChatModel);// or create a ChatClient with the default builder settings:ChatClient chatClient = ChatClient.create(myChatModel);
1.2.3 处理 ChatClient 响应
ChatClient API 提供了多种方法来格式化来自 AI 模型的响应。
1.2.3.1 返回 ChatResponse
AI 模型的响应是一种由ChatResponse类型定义的丰富结构。它包含响应生成相关的元数据,同时它还可以包含多个子响应(称为Generation),每个子响应都有自己的元数据。元数据包括用于创建响应的令牌(token)数量信息(在英文中,每个令牌大约为一个单词的 3/4),了解令牌信息很重要,因为 AI 模型根据每个请求使用的令牌数量收费。
下面的代码段显示了通过调用 chatResponse() 返回 ChatResponse 的示例,相比于调用 content() 方法,这里在调用 call() 方法之后调用 chatResponse()。
ChatResponse chatResponse = chatClient.prompt().user("Tell me a joke").call().chatResponse();
1.2.3.2 返回实体类(Entity)
如果希望返回一个预先定义好的实体类型响应,Spring AI 框架可以自动替我们完成从 String 到实体类的转换,调用entity() 方法可完成响应数据转换。
例如,给定 Java record(POJO)定义:
record ActorFilms(String actor, List<String> movies) {}
可以使用该 entity 方法轻松地将 AI 模型的输出映射到 ActorFilms 类型,如下所示:
ActorFilms actorFilms = chatClient.prompt().user("Generate the filmography for a random actor.").call().entity(ActorFilms.class);
entity 还有一种带有参数的重载方法 entity(ParameterizedTypeReference<T> type),可指定如泛型 List 等类型:
List<ActorFilms> actorFilms = chatClient.prompt().user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.").call().entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
1.2.3.3 流式响应
stream 方法是一种异步的、持续的获得模型响应的方式:
Flux<String> output = chatClient.prompt().user("Tell me a joke").stream().content();
相比于上面的 Flux<String>,还可以使用 Flux<ChatResponse> chatResponse() 方法获得 ChatResponse 响应数据流。
1.2.3.4 call() 返回值
ChatClient.call() 方法支持几种不同类型的响应格式。
String content():返回响应的字符串内容ChatResponse chatResponse():返回ChatResponse包含多个代以及有关响应的元数据的对象,例如,使用了多少个令牌来创建响应。entity返回 Java 类型- entity(ParameterizedTypeReference type):用于返回实体类型的集合。
- entity(Class type): 用于返回特定的实体类型。
- entity(StructuredOutputConverter structuredOutputConverter): 用于指定一个实例
StructuredOutputConverter,将String转换为实体类型。
1.2.3.5 stream() 返回值
还可以调用该stream方法,在指定ChatClient后调用stream方法,响应类型有几个选项:
Flux<String> content():返回由AI模型生成的字符串的Flux。Flux<ChatResponse> chatResponse():返回对象的 FluxChatResponse,其中包含有关响应的附加元数据。
1.2.4 定制 ChatClient 默认值
还可以通过修改 ChatClient.Builder 定制 ChatClient 实例。
注意,创建 ChatClient 时指定的配置将作为与模型交互时的默认参数,这样可以避免每次调用都重复设置。
1.2.4.1 设置默认 System Message
在以下示例中,为 ChatClient 设置了一个默认的 system message(以海盗风格回答所有问题),这样,当 ChatClient 与模型交互时都会自动携带这条 system message,用户只需要指定 user message 即可。
@Configurationclass Config {@BeanChatClient chatClient(ChatClient.Builder builder) {return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate").build();}}
在 Controller 中使用这个 ChatClient
@RestControllerclass AIController {private final ChatClient chatClient;AIController(ChatClient chatClient) {this.chatClient = chatClient;}@GetMapping("/ai/simple")public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {return Map.of("completion", chatClient.prompt().user(message).call().content());}}
启动示例,通过 curl 测试效果:
curl localhost:8080/ai/simple
{"generation":"Why did the pirate go to the comedy club? To hear some arrr-rated jokes! Arrr, matey!"}
在上面 builder.defaultSystem() 创建 ChatClient 的时,还可以选择使用模板,类似 “You are a friendly chat bot that answers question in the voice of a {voice}“,这样就可以在每次调用前修改请求参数。
@Configurationclass Config {@BeanChatClient chatClient(ChatClient.Builder builder) {return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}").build();}}@RestControllerclass AIController {private final ChatClient chatClientAIController(ChatClient chatClient) {this.chatClient = chatClient;}@GetMapping("/ai")Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, String voice) {return Map.of("completion",chatClient.prompt().system(sp -> sp.param("voice", voice)).user(message).call().content());}}
http localhost:8080/ai voice=='Robert DeNiro'{"completion": "You talkin' to me? Okay, here's a joke for ya: Why couldn't the bicycle stand up by itself? Because it was two tired! Classic, right?"}
1.2.4.2 其他默认设置
除了 defaultSystem 之外,还可以在 ChatClient.Builder 级别上指定其他默认提示。
-
defaultOptions(ChatOptions chatOptions):传入ChatOptions类中定义的可移植选项或特定于模型实现的如DashScopeChatOptions选项。 -
defaultFunction(String name, String description, java.util.function.Function<I, O> function):name用于在用户文本中引用该函数,description解释该函数的用途并帮助 AI 模型选择正确的函数以获得准确的响应,参数function是模型将在必要时执行的 Java 函数实例。 -
defaultFunctions(String... functionNames):应用程序上下文中定义的 java.util.Function 的 bean 名称。 -
defaultUser(String text)、defaultUser(Resource text)、defaultUser(Consumer<UserSpec> userSpecConsumer)这些方法允许您定义用户消息输入,Consumer<UserSpec>允许您使用 lambda 指定用户消息输入和任何默认参数。 -
defaultAdvisors(RequestResponseAdvisor... advisor):Advisors 允许修改用于创建Prompt的数据,QuestionAnswerAdvisor实现通过在 Prompt 中附加与用户文本相关的上下文信息来实现Retrieval Augmented Generation模式。 -
defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer):此方法允许您定义一个Consumer并使用AdvisorSpec配置多个 Advisor,Advisor 可以修改用于创建Prompt的最终数据,Consumer<AdvisorSpec>允许指定 lambda 来添加 Advisor 例如QuestionAnswerAdvisor。
可以在运行时使用 ChatClient 提供的不带 default 前缀的相应方法覆盖这些默认值。
-
options(ChatOptions chatOptions) -
function(String name, String description, java.util.function.Function<I, O> function) -
functions(String... functionNames) -
user(String text)、user(Resource text)、user(Consumer<UserSpec> userSpecConsumer) -
advisors(RequestResponseAdvisor... advisor) -
advisors(Consumer<AdvisorSpec> advisorSpecConsumer)
1.3 编码实战
1.3.1 新建Module
可以命名为ChatModelChatClient
1.3.2 修改POM文件
在依赖配置节点修改为以下配置:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--spring-ai-alibaba dashscope--><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version><configuration><compilerArgs><arg>-parameters</arg></compilerArgs><source>21</source><target>21</target></configuration></plugin></plugins></build><repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository></repositories>
1.3.3 编写配置文件
在resources目录下新建application.properties配置文件,内容如下:
server.port=8003server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
server.servlet.encoding.charset=UTF-8spring.application.name=ChatModelChatClient# ====SpringAIAlibaba Config=============
spring.ai.dashscope.api-key=${aliQwen-api}
1.3.4 编写主启动类
新建主启动类,并编写如下内容:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class ChatModelChatClientApplication{public static void main(String[] args){SpringApplication.run(ChatModelChatClientApplication.class, args);}}
1.3.5 编写配置类
新建子包config,并在其中新建配置类SaaLLMConfig
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class SaaLLMConfig{@Beanpublic DashScopeApi dashScopeApi(){return DashScopeApi.builder().apiKey(System.getenv("aliQwen-api")).build();}/*** 知识出处:* https://java2ai.com/docs/1.0.0.2/tutorials/basics/chat-client/?spm=5176.29160081.0.0.2856aa5cmUTyXC#%E5%88%9B%E5%BB%BA-chatclient* @param dashscopeChatModel* @return*/@Beanpublic ChatClient chatClient(ChatModel dashscopeChatModel){return ChatClient.builder(dashscopeChatModel).build();}
}
1.3.6 编写业务类
新建子包controller,并在其中新建业务类ChatClientController
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ChatClientController
{/*** chatModel + ChatClient 混合使用*/@Resourceprivate ChatModel chatModel;@Resourceprivate ChatClient dashScopechatClient;/*** http://localhost:8003/chatclient/dochat* @param msg* @return*/@GetMapping("/chatclient/dochat")public String doChat(@RequestParam(name = "msg",defaultValue = "你是谁") String msg){String result = dashScopechatClient.prompt().user(msg).call().content();System.out.println("ChatClient响应:" + result);return result;}/*** http://localhost:8003/chatmodel/dochat* @param msg* @return*/@GetMapping("/chatmodel/dochat")public String doChat2(@RequestParam(name = "msg",defaultValue = "你是谁") String msg){String result = chatModel.call(msg);System.out.println("ChatModel响应:" + result);return result;}
}
1.3.7 总结
在生产环境下,两者混合使用,两者之间不是非此即彼,可以同时出现交替使用;
1.4 两者对比
| 对比项 | ChatModel | ChatClient |
| 注入形式 | 自动注入 | 手动注入且依赖ChatModel,使用自动配置的 Client.Builder |
| 调用方式 | 直接调用call()或者stream()方法 | 链式调用,支持同步和反应式(Reactive)编程模型,自动封装提示词和解析响应 |
| 结构化输出 | 需手动解析响应文本 | 支持自动映射为Java对象 |
| 适用场景 | 实现简单功能和场景 | 快速开发复杂功能的场景,如企业级智能问答系统 |
| 功能扩展 | 偏弱 | 强,支持聊天记忆(Chat Memory)/工具Tool/函数调用(Function Calling)/RAG等等 |
