跳到主要内容
版本:1.1.1

ChatModel

ChatModel 是 Spring AI 与 LLM 交互的核心抽象接口,提供统一的 call()stream() 方法,屏蔽不同 AI 厂商的实现差异。


1. 接口总览

ChatModel 提供 call()stream() 方法,分别用于同步和流式调用 AI 模型。


2. 核心调用方法

2.1 同步调用

ChatModel 提供三个 call() 重载,从简单到复杂逐步升级。本质上,(A) 和 (B) 都是 (C) 的快捷方式——内部自动包装为 Prompt 对象后调用 call(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);
}
}

2.2 流式调用

直接获取 Flux<String>,适合只需文本内容的场景。

TextStreamDemo.java
@Component
public class TextStreamDemo implements CommandLineRunner {

private final ChatModel chatModel;

public TextStreamDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}

@Override
public void run(String... args) {
chatModel.stream("讲个笑话")
.doOnNext(chunk -> System.out.print(chunk))
.blockLast();
}
}

2.3 默认配置

ChatOptions defaults = chatModel.getDefaultOptions();
// 返回 OpenAiChatOptions{model=gpt-4o-mini, temperature=0.7, ...}

getDefaultOptions() 返回的 ChatOptions 来自 application.ymlspring.ai.*.chat.options.* 配置,或厂商提供的硬编码默认值。通过 Prompt 传入的 ChatOptions 会覆盖默认值。


3. 构造提示词

Prompt implements ModelRequest<List<Message>>,是 ChatModel 的标准输入。

3.1 构造方式

四种重载覆盖了从简单文本到完整控制的所有场景。

内部自动包装为 UserMessage

PlainTextPromptDemo.java
@Component
public class PlainTextPromptDemo implements CommandLineRunner {

private final ChatModel chatModel;

public PlainTextPromptDemo(ChatModel chatModel) {
this.chatModel = chatModel;
}

@Override
public void run(String... args) {
Prompt prompt = new Prompt("你好");
String reply = chatModel.call(prompt).getResult().getOutput().getText();
System.out.println(reply);
}
}

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);
}
}

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。

通用选项

选项类型说明
modelString模型名称(如 gpt-4o-miniqwen3:8b
temperatureDouble生成温度(0-2),越高越随机
topPDouble核采样阈值
topKIntegerTop-K 采样
frequencyPenaltyDouble重复惩罚(-2.0 ~ 2.0)
presencePenaltyDouble主题惩罚(-2.0 ~ 2.0)
maxTokensInteger最大输出 Token 数
stopSequencesList<String>停止序列
functionsSet<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 接口保持一致:

StarterChatModel 实现类配置前缀
spring-ai-starter-model-openaiOpenAiChatModelspring.ai.openai
spring-ai-starter-model-ollamaOllamaChatModelspring.ai.ollama
spring-ai-starter-model-anthropicAnthropicChatModelspring.ai.anthropic
spring-ai-starter-model-deepseekDeepSeekChatModelspring.ai.deepseek
spring-ai-starter-model-zhipuaiZhiPuAiChatModelspring.ai.zhipuai
spring-ai-starter-model-vertex-ai-geminiVertexAiGeminiChatModelspring.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();
}
}