版本:Spring AI 2.0.1
目标:弄清
ChatModel契约、常见 Provider 如何选型,以及为什么教程常用 OpenAI Options 当标本。 ChatClient 解决「怎么发请求」;本章解决「请求最后落到哪家模型实现」。

5.1 ChatClient 与 ChatModel:先分清两层
业务日常优先用 ChatClient;ChatModel 是抽象层的聊天入口。两者共用同一套 Message / Prompt / ChatResponse,只是装备不同:
| ChatClient | ChatModel | |
|---|---|---|
| 层次 | 应用层 | 抽象层 |
| 擅长 | DSL、默认值、Advisors、Tools、entity | 薄调用、call / stream |
| 典型场景 | Controller / 领域服务 | 图节点、批处理、单测 mock |

口诀:
业务优先 ChatClient;需要更薄或更可控时,再直接用 ChatModel。 换 Provider 时,尽量只换 starter / Options / Bean,不改业务主路径。
5.2 ChatModel 契约:call 与 stream
ChatModel 同步核心是:
ChatResponse call(Prompt)
此外还有便捷重载,例如 call(String) / call(Message...),本质只是帮你组装 Prompt。
2.0 起默认选项优先通过 getOptions() 获取;旧的 getDefaultOptions() 已废弃。
流式能力来自 StreamingChatModel(ChatModel 通常一并实现):
Flux<ChatResponse> stream(Prompt)
若实现并未真正支持流式,调用可能直接抛出不支持异常。ChatClient.stream() 最终也会落到这里。
同步调用(完整示例)
import java.util.List;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
ChatResponse response = chatModel.call(
new Prompt(
List.of(
new SystemMessage("用中文简要回答"),
new UserMessage("ping")
),
chatModel.getOptions().mutate()
.temperature(0.0)
.build()
)
);
String text = response.getResult().getOutput().getText();
便捷写法:
String text = chatModel.call("你好");
与 ChatClient 对照(同模型、同默认选项时语义接近):
String a = chatModel.call("你好");
String b = chatClient.prompt("你好").call().content();
// ChatClient 多了 Advisor、Tools、entity 等装备栏
流式调用
中间帧可能没有完整 Generation / Usage,读取增量文本时要做空判断:
import reactor.core.publisher.Flux;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.Prompt;
Flux<String> tokens = chatModel.stream(prompt)
.map(cr -> {
Generation g = cr.getResult();
return g == null || g.getOutput() == null
? ""
: g.getOutput().getText();
})
.filter(s -> s != null && !s.isEmpty());
完整 Usage 更常见于结束帧;不要假设每一帧都有 token 统计。
5.3 不只是 Chat:同源模型接口
Spring AI 在模型层还有一组平行抽象。选型时不要只盯着 Chat,但也不必在本章一次学完:
| 接口 | 典型用途 |
|---|---|
EmbeddingModel | 文本 / 文档向量化(检索前置) |
ImageModel | 文生图等 |
TextToSpeechModel / TranscriptionModel | 语音合成与转写;2.0.1 强化了流式路径 |
ModerationModel | 内容审核 |
Embedding 契约可先记成:
EmbeddingResponse call(EmbeddingRequest)
float[] embed(String) / embed(Document)
Image:
ImageResponse call(ImagePrompt)
音频注意:OpenAI 的流式 Speech 在 2.0.1 更接近真实音频分块,不要再习惯性地 blockFirst() 当成整段结果。
本章主线仍是 ChatModel + Provider;其它模态接口只要建立「同源、分 starter」的印象即可。
5.4 Provider 怎么选
Provider 实现按模块拆分,再通过对应 spring-ai-starter-model-* 引入。不必先背完整清单,按场景选:
只要对话 Anthropic、DeepSeek、Bedrock Converse、Google GenAI、Mistral、Ollama、OpenAI 都是常见选项。
要尽量全的模态 OpenAI 通常最省事:Chat、Embedding、Image、Speech、Transcription、Moderation 较集中。很多兼容网关也借用这套 starter,只改 base-url / api-key / model。
走本地 Chat / Embedding 可看 Ollama;只要本地 Embedding,也可看 Transformers(DJL)或 PostgresML。
走特定云生态
| 需求 | 常见选择 | 注意 |
|---|---|---|
| Claude | Anthropic | Options 有自家字段 |
| Gemini 生态 | Google GenAI | Chat / Embedding / Image 分 starter |
| AWS | Bedrock Converse(Chat) | Embedding 可能在另一套模块 |
| Mistral 生态 | Mistral AI | 升级后旧模型常量可能被清理 |
只要画图或 TTS Image 可看 OpenAI / Stability / Google GenAI Image;TTS 可看 OpenAI Speech / ElevenLabs。
读能力表时记住一句:
表上的「支持」指 Spring AI 模块实现,不保证每个兼容网关都开全。
5.5 为什么常用 OpenAI 当标本
spring-ai-starter-model-openai 把多种模态收在同一条学习路径里:
-
OpenAiChatModel -
OpenAiEmbeddingModel -
OpenAiImageModel -
OpenAiAudioSpeechModel/OpenAiAudioTranscriptionModel -
OpenAiModerationModel
跟通 OpenAI 后,再看 Anthropic、Ollama 等会快很多:差异主要在 client 与 Options,不在 ChatClient DSL。
依赖与配置(最小回顾)
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com # 兼容网关时改这里
chat:
options:
model: gpt-4o-mini
temperature: 0.7
Options 分组
import org.springframework.ai.openai.OpenAiChatOptions;
OpenAiChatOptions options = OpenAiChatOptions.builder()
.model("gpt-4o-mini")
.temperature(0.2)
.maxTokens(512)
.strict(false) // 2.0.1 默认更偏向 false
.parallelToolCalls(true)
.streamOptions(OpenAiChatOptions.StreamOptions.builder()
.includeUsage(true)
.build())
.build();
字段可以粗分成几组:
| 分组 | 常见字段 |
|---|---|
| 连接 | baseUrl、凭证、组织 ID、超时、代理、自定义 Header;Azure / GitHub Models 一类部署开关 |
| 生成 | model、temperature、topP、maxTokens、stop、seed |
| 工具与结构化 | toolChoice、parallelToolCalls、strict、responseFormat |
| 其它 | 输出模态、音频参数、reasoning 相关旋钮、extraBody、streamOptions |
在 ChatClient 中覆盖单次选项
String text = chatClient.prompt()
.user(question)
.options(OpenAiChatOptions.builder()
.temperature(0.0)
.maxTokens(256)
.build()) // 别漏 build()
.call()
.content();
单次 .options(...) / defaultOptions 会覆盖自动配置默认值;冲突时通常以更靠近请求的为准。
5.6 其它 Provider 的 Options 习惯
原则:
ChatClient 代码尽量依赖
ChatOptions的通用字段;供应商特有字段再落到具体 Builder,避免把一家的专用配置写进另一家路径。
常见习惯:
| Provider | 习惯 |
|---|---|
| Ollama | 本地模型名、上下文长度等;模型需先 pull |
| Anthropic | maxTokens、thinking 一类能力 |
| Mistral | 注意升级后旧模型常量可能被清理 |
| Google GenAI | Chat / Embedding / Image 分模块引入;加了 Chat starter ≠ 自动有 Image |
| Bedrock Converse | 与 AWS 凭证链集成;Chat 与 Embedding 不要找错模块 |
Google 可以记成三件套:
…-google-genai → Chat
…-google-genai-embedding → Embedding
…-google-genai-image → Image
Ollama 最小配置示例:
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: qwen2.5:7b
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
5.7 多模型 Bean
一个应用里同时存在多个 ChatModel 完全合法。注入时用 @Qualifier 或明确 Bean 名;更干净的做法,是为每个模型各建一个 ChatClient。
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class MultiModelConfig {
@Bean
ChatClient primaryChatClient(ChatModel chatModel) {
// 默认主模型(由当前启用的 model starter 自动配置)
return ChatClient.builder(chatModel)
.defaultSystem("你是主助手")
.build();
}
@Bean
ChatClient ollamaChatClient(
@Qualifier("ollamaChatModel") ChatModel ollamaChatModel) {
return ChatClient.builder(ollamaChatModel)
.defaultSystem("你是本地助手")
.build();
}
}
服务里按场景选用:
@Service
class AnswerService {
private final ChatClient primaryChatClient;
private final ChatClient ollamaChatClient;
AnswerService(
ChatClient primaryChatClient,
@Qualifier("ollamaChatClient") ChatClient ollamaChatClient) {
this.primaryChatClient = primaryChatClient;
this.ollamaChatClient = ollamaChatClient;
}
String answer(String q, boolean local) {
ChatClient client = local ? ollamaChatClient : primaryChatClient;
return client.prompt().user(q).call().content();
}
}
Bean 名称以各自动配置为准;不确定时可在启动后查看容器中的 Bean 定义。同时启用多个 model starter 时,注意谁是 @Primary,避免注入歧义。
5.8 什么时候直接用 ChatModel
优先 ChatClient。直接使用 ChatModel 更适合这些场景:
-
状态图 / 自研编排节点里的薄调用
-
需要对
Prompt做更细控制的批处理 -
自定义 Advisor 单测中的 mock
-
不需要 DSL、只要一次
call(Prompt)的内部服务
不适合的理由如果只是「想少写几行」,通常仍应留在 ChatClient。
5.9 重试、错误与兼容网关
模型自动配置可能配合重试机制处理瞬时 HTTP 错误。Options 里的 timeout / maxRetries 与之叠加时,要两边一起看,避免重试过于激进。
| HTTP / 现象 | 先查什么 |
|---|---|
| 401 | 密钥、环境变量名、凭证链 |
| 400 | schema / strict / 不支持的参数 |
| 404 | 模型名错误;Ollama 尚未 pull |
| 429 | 限流,需要退避 |
| 超时 | timeout 过短,或代理缓冲不当 |
用 OpenAI starter 接第三方兼容网关时,建议逐项核对:
-
base-url是否包含正确 path 前缀(有的需要/v1) -
模型名是否为网关定制 ID
-
是否支持 tools / stream / JSON schema
-
Usage 字段是否完整
-
多模态字段是否被网关剥离
「能 chat」不等于「tools / structured / vision 都通」——兼容层最容易在这里踩坑。
5.10 观测(本章边界)
模型层有独立的 Observation 体系,与 ChatClient 观测分层。调用次数、耗时、token 等会在模型调用点打点。
本章只需建立印象:换 Provider 不应丢掉观测挂钩;统一运维、日志脱敏与安全策略属于工程化专题,这里不展开。
5.11 常见坑(本章范围)
| 现象 | 先查什么 |
|---|---|
注入多个 ChatModel 冲突 | 是否多个 starter 同时启用;谁是 @Primary;是否该用 @Qualifier |
| Options 不生效 | 是否 .build();单次 options 是否被更外层覆盖;是否写错了厂商 Options 类型 |
| 流式空帧 / NPE | getResult() / getOutput() 是否为空;Usage 是否只在结束帧 |
| Ollama 404 | 模型是否已拉取;base-url 与 model 名是否一致 |
| 兼容网关「半通」 | tools / stream / schema 是否被网关阉割;path 前缀是否缺 /v1 |
| 加了 Google Chat 却没有 Image | Image 是另一个 starter |
5.12 本章小结
到这里应能:
-
说明
ChatModel/stream与 ChatClient 的关系,并判断何时该直接用ChatModel -
按场景选择 Provider,而不是背模块名
-
用 OpenAI Options 理解连接、生成、工具与结构化旋钮,并正确覆盖单次选项
-
处理多模型注入,以及兼容网关 / 常见 HTTP 错误的排查顺序
掌握抽象层入口后,业务代码可以稳定面向 ChatClient / ChatModel 编程;换模型时主要改 starter 与 Options,而不是推翻调用链。
1068



被折叠的 条评论
为什么被折叠?



