ChatModel
ChatModel 是 Spring AI 与 LLM 交互的核心抽象接口,提供统一的 call() 和 stream() 方法,屏蔽不同 AI 厂商的实现差异。
1. 接口总览
ChatModel 提供 call() 和 stream() 方法,分别用于同步和流式调用 AI 模型。
2. 核心调用方法
2.1 同步调用
ChatModel 提供三个 call() 重载,从简单到复杂逐步升级。本质上,(A) 和 (B) 都是 (C) 的快捷方式——内部自动包装为 Prompt 对象后调用 call(Prompt)。
- (A) 字符串
- (B) Message 列表
- (C) Prompt 对象
StringCallDemo.java
@Component
public class StringCallDemo implements CommandLineRunner {
private final ChatModel chatModel;
public StringCallDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
String reply = chatModel.call("用一句话介绍 Spring AI");
System.out.println(reply);
}
}
MessageListCallDemo.java
@Component
public class MessageListCallDemo implements CommandLineRunner {
private final ChatModel chatModel;
public MessageListCallDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
String reply = chatModel.call(
new SystemMessage("你是一个Java技术专家"),
new UserMessage("解释依赖注入")
);
System.out.println(reply);
}
}
PromptCallDemo.java
@Component
public class PromptCallDemo implements CommandLineRunner {
private final ChatModel chatModel;
public PromptCallDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
ChatResponse response = chatModel.call(new Prompt(
List.of(
new SystemMessage("你是一个Java技术专家"),
new UserMessage("解释依赖注入")
),
OpenAiChatOptions.builder()
.model("gpt-4o")
.temperature(0.3)
.build()
));
System.out.println(response.getResult().getOutput().getText());
}
}
2.2 流式调用
- (A) 文本流
- (B) 响应流
直接获取 Flux<String>,适合只需文本内容的场景。TextStreamDemo.java
获取完整 Flux<ChatResponse>,每个分片携带元数据(Token 用量、结束原因等)。ResponseStreamDemo.java
2.3 默认配置
ChatOptions defaults = chatModel.getDefaultOptions();
// 返回 OpenAiChatOptions{model=gpt-4o-mini, temperature=0.7, ...}
getDefaultOptions() 返回的 ChatOptions 来自 application.yml 中 spring.ai.*.chat.options.* 配置,或厂商提供的硬编码默认值。通过 Prompt 传入的 ChatOptions 会覆盖默认值。
3. 构造提示词
Prompt implements ModelRequest<List<Message>>,是 ChatModel 的标准输入。
3.1 构造方式
四种重载覆盖了从简单文本到完整控制的所有场景。
- 纯文本
- 单个 Message
- Message 列表
- 列表 + 选项
内部自动包装为 UserMessage。PlainTextPromptDemo.java
SingleMessagePromptDemo.java
@Component
public class SingleMessagePromptDemo implements CommandLineRunner {
private final ChatModel chatModel;
public SingleMessagePromptDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
Prompt prompt = new Prompt(new UserMessage("你好"));
String reply = chatModel.call(prompt).getResult().getOutput().getText();
System.out.println(reply);
}
}
MessageListPromptDemo.java
@Component
public class MessageListPromptDemo implements CommandLineRunner {
private final ChatModel chatModel;
public MessageListPromptDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
Prompt prompt = new Prompt(List.of(
new SystemMessage("你是一个翻译助手"),
new UserMessage("把'你好,世界'翻译成英文")
));
String reply = chatModel.call(prompt).getResult().getOutput().getText();
System.out.println(reply);
}
}
携带 ChatOptions 控制模型行为。PromptWithOptionsDemo.java
3.2 动态增强
augmentSystemMessage() 和 augmentUserMessage() 返回新 Prompt 实例,不修改原对象,符合不可变设计原则。
- 增强系统消息
- 追加用户消息
AugmentSystemDemo.java
@Component
public class AugmentSystemDemo implements CommandLineRunner {
private final ChatModel chatModel;
public AugmentSystemDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
Prompt original = new Prompt(List.of(
new SystemMessage("你是助手"),
new UserMessage("什么是 Spring AI")
));
// 基于已有 Prompt 追加系统指令
Prompt augmented = original.augmentSystemMessage(
text -> text + "\n请使用中文回答。"
);
String reply = chatModel.call(augmented).getResult().getOutput().getText();
System.out.println(reply);
}
}
AugmentUserDemo.java
@Component
public class AugmentUserDemo implements CommandLineRunner {
private final ChatModel chatModel;
public AugmentUserDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
Prompt original = new Prompt(List.of(
new SystemMessage("你是助手"),
new UserMessage("什么是 Spring AI")
));
// 基于已有 Prompt 追加用户消息
Prompt augmented = original.augmentUserMessage(
"额外要求:回答请控制在100字以内"
);
String reply = chatModel.call(augmented).getResult().getOutput().getText();
System.out.println(reply);
}
}
3.3 Builder 模式
通过 mutate() 获得 Builder,链式修改 Prompt 的任意字段。
PromptBuilderDemo.java
@Component
public class PromptBuilderDemo implements CommandLineRunner {
private final ChatModel chatModel;
public PromptBuilderDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
Prompt original = new Prompt("你好");
Prompt mutated = original.mutate()
.userText("换个问题")
.options(OpenAiChatOptions.builder().temperature(0.1).build())
.build();
String reply = chatModel.call(mutated).getResult().getOutput().getText();
System.out.println(reply);
}
}
4. 处理模型响应
每次 call() 或 stream() 返回 ChatResponse,数据结构如下:
ChatResponse 数据结构
4.1 获取回复文本
GetTextDemo.java
@Component
public class GetTextDemo implements CommandLineRunner {
private final ChatModel chatModel;
public GetTextDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
ChatResponse response = chatModel.call(new Prompt("Hello"));
String text = response.getResult().getOutput().getText();
System.out.println("回复: " + text);
}
}
4.2 遍历多个结果
当 n > 1(并行采样)时,getResults() 包含多个 Generation。
IterateResultsDemo.java
@Component
public class IterateResultsDemo implements CommandLineRunner {
private final ChatModel chatModel;
public IterateResultsDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
ChatResponse response = chatModel.call(new Prompt("Hello"));
for (Generation g : response.getResults()) {
String text = g.getOutput().getText();
String reason = g.getMetadata().getFinishReason();
System.out.println("文本: " + text);
System.out.println("结束原因: " + reason);
}
}
}
4.3 Token 使用量
TokenUsageDemo.java
@Component
public class TokenUsageDemo implements CommandLineRunner {
private final ChatModel chatModel;
public TokenUsageDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
ChatResponse response = chatModel.call(new Prompt("Hello"));
Usage usage = response.getMetadata().getUsage();
System.out.printf("Prompt: %d, Generation: %d, Total: %d%n",
usage.getPromptTokens(),
usage.getGenerationTokens(),
usage.getTotalTokens());
}
}
4.4 结束原因与速率限制
MetadataDemo.java
@Component
public class MetadataDemo implements CommandLineRunner {
private final ChatModel chatModel;
public MetadataDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
ChatResponse response = chatModel.call(new Prompt("Hello"));
// 结束原因: "stop" / "length" / "tool_calls"
String reason = response.getResult().getMetadata().getFinishReason();
System.out.println("结束原因: " + reason);
// 实际使用的模型名
ChatOptions used = response.getResult().getMetadata().getOptions();
System.out.println("实际模型: " + used.getModel());
// 速率限制
RateLimit rateLimit = response.getMetadata().getRateLimit();
System.out.printf("请求限制: %d, Token限制: %d%n",
rateLimit.getRequestsLimit(),
rateLimit.getTokensLimit());
}
}
当模型返回多个结果时,
ChatModel.call(String)便利方法会在链尾使用MessageAggregator确保始终有一个合法结果返回;ChatResponse.getResult()返回第一个主结果。
5. 配置模型选项
ChatOptions 是模型参数的通用抽象,各厂商提供自己的 Builder。
通用选项
| 选项 | 类型 | 说明 |
|---|---|---|
model | String | 模型名称(如 gpt-4o-mini、qwen3:8b) |
temperature | Double | 生成温度(0-2),越高越随机 |
topP | Double | 核采样阈值 |
topK | Integer | Top-K 采样 |
frequencyPenalty | Double | 重复惩罚(-2.0 ~ 2.0) |
presencePenalty | Double | 主题惩罚(-2.0 ~ 2.0) |
maxTokens | Integer | 最大输出 Token 数 |
stopSequences | List<String> | 停止序列 |
functions | Set<String> | 启用的函数名集合 |
ChatOptionsDemo.java
@Component
public class ChatOptionsDemo implements CommandLineRunner {
private final ChatModel chatModel;
public ChatOptionsDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}
@Override
public void run(String... args) {
// 构建选项
ChatOptions options = OpenAiChatOptions.builder()
.model("gpt-4o")
.temperature(0.3)
.maxTokens(2000)
.stopSequences(List.of("---"))
.build();
// 传入 Prompt
ChatResponse response = chatModel.call(new Prompt(
List.of(new UserMessage("写一篇文章")),
options
));
System.out.println(response.getResult().getOutput().getText());
// 读取实际生效的选项
ChatOptions used = response.getResult().getMetadata().getOptions();
System.out.println("实际模型: " + used.getModel());
}
}
Prompt中传入的ChatOptions优先级高于getDefaultOptions(),但各厂商实现可能对部分选项的覆盖策略有所不同,见各厂商对应文档。
6. 厂商实现切换
引入不同的 Starter 依赖即可切换厂商,ChatModel 接口保持一致:
| Starter | ChatModel 实现类 | 配置前缀 |
|---|---|---|
spring-ai-starter-model-openai | OpenAiChatModel | spring.ai.openai |
spring-ai-starter-model-ollama | OllamaChatModel | spring.ai.ollama |
spring-ai-starter-model-anthropic | AnthropicChatModel | spring.ai.anthropic |
spring-ai-starter-model-deepseek | DeepSeekChatModel | spring.ai.deepseek |
spring-ai-starter-model-zhipuai | ZhiPuAiChatModel | spring.ai.zhipuai |
spring-ai-starter-model-vertex-ai-gemini | VertexAiGeminiChatModel | spring.ai.vertex.ai.gemini |
// 无论使用哪个厂商,业务代码一致
@RestController
public class ChatController {
private final ChatModel chatModel;
public ChatController(ChatModel chatModel) { // 按类型注入
this.chatModel = chatModel;
}
@GetMapping("/ask")
public String ask(@RequestParam String q) {
return chatModel.call(q);
}
}
7. 与 ChatClient 的关系
ChatClient 对 ChatModel 进行了高层封装,提供更简洁的流式 API,详见 ChatClient 章节。
8. 示例:完整调用链路
ChatModelExample.java
@SpringBootApplication
public class ChatModelExample implements CommandLineRunner {
@Autowired
private ChatModel chatModel;
public static void main(String[] args) {
SpringApplication.run(ChatModelExample.class, args);
}
@Override
public void run(String... args) {
// 1. 最简调用
System.out.println("=== 最简调用 ===");
System.out.println(chatModel.call("用一句话介绍 Spring AI"));
// 2. 带系统提示词
System.out.println("\n=== 带系统提示词 ===");
ChatResponse response = chatModel.call(new Prompt(
List.of(
new SystemMessage("你是一个精通 Spring 生态的 Java 架构师,回答使用中文。"),
new UserMessage("解释 ChatModel 和 ChatClient 的区别")
),
OpenAiChatOptions.builder()
.temperature(0.3)
.maxTokens(500)
.build()
));
System.out.println(response.getResult().getOutput().getText());
System.out.printf("Token: prompt=%d, generation=%d, total=%d%n",
response.getMetadata().getUsage().getPromptTokens(),
response.getMetadata().getUsage().getGenerationTokens(),
response.getMetadata().getUsage().getTotalTokens());
// 3. 流式调用
System.out.println("\n=== 流式调用 ===");
chatModel.stream("写一首关于 AI 的四句诗")
.doOnNext(cr -> System.out.print(cr.getResult().getOutput().getText()))
.blockLast();
}
}