Spring AI · Model 核心概念
上一篇
springAi-介绍把 Spring AI 在 Java AI 生态里"做什么、不做什么"讲清楚了。这一篇专门把 Spring AI 最核心的那块抽象 —— Model —— 拆开:四类模型(ChatModel/EmbeddingModel/ImageModel/AudioModel)、模型能力维度(多模态、工具调用、流式、重试、可观测性、结构化输出)、以及它们如何与"本地运行时 / OpenAI 兼容端点"解耦。
📖 官方参考:本文涉及的所有抽象、接口与配置,均以 Spring AI Reference 为准。文中示例对应
spring-ai 1.0.x系列;若你引入的 BOM 版本更新或与文中不一致,以项目实际依赖版本的参考页为准。
1. Model 在 Spring AI 中的位置
Spring AI 的所有能力,几乎都挂在 "Model" 这一个抽象根上。可以把它想成 Spring 生态里早已存在的 JdbcTemplate、RestTemplate、RedisTemplate 的 AI 版本:

设计目标只有一句:
让上层业务代码以"模型无关"的方式写,把厂商差异收敛到配置和依赖里。
2. 顶层抽象:Model<TR, TC> 标记接口
Spring AI 几乎所有模型接口都继承自一个空的标记接口,只用来表达"我是一个模型":
@FunctionalInterface
public interface Model<TReq, TRes> extends Model<TReq, TRes> {
TRes call(TReq request);
}ChatModel:call(Prompt) → ChatResponseEmbeddingModel:call(EmbeddingRequest) → EmbeddingResponseImageModel:call(ImagePrompt) → ImageResponseAudioModel(以及SpeechModel/AudioTranscriptionModel)
这种设计的实际收益是——所有模型都遵守同一套"输入-处理-输出"的形状,上层包装(Advisor、Retry、Observation、流式转换)可以完全复用。
3. Chat Model —— 对话模型
3.1 接口边界
public interface ChatModel extends Model<Prompt, ChatResponse> {
default String call(String message) { ... } // 简化入口
ChatResponse call(Prompt prompt); // 同步调用
Flux<ChatResponse> stream(Prompt prompt); // 流式调用
}Prompt 内部是一个有序的 Message 列表,ChatResponse 内部是 Generation 列表,这样设计是为了兼容"一次调用返回多个候选"的厂商(如 n>1 时)。
3.2 ChatClient 才是真正的高频入口
业务代码里几乎不会直接 new ChatModel,而是注入 Spring 自动装配好的 ChatClient:
@RestController
class ChatController {
private final ChatClient chatClient;
ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个严谨的中文技术写作助手。")
.defaultFunctions("searchDocs", "fetchIssue") // 全局生效的工具
.build();
}
@GetMapping("/ask")
String ask(@RequestParam String q) {
return chatClient.prompt(q).call().content();
}
}ChatClient 是"对 ChatModel 的薄封装 + 装饰器链",统一处理:系统提示词、Advisor、工具调用、消息历史、模型选项(temperature、top_p 等)。
3.3 主流实现
| Starter 依赖 | 提供方 |
|---|---|
spring-ai-openai-spring-boot-starter | OpenAI / 任何 OpenAI 兼容端点 |
spring-ai-azure-openai-spring-boot-starter | Azure OpenAI |
spring-ai-anthropic-spring-boot-starter | Anthropic Claude |
spring-ai-vertex-ai-gemini-spring-boot-starter | Google Vertex AI Gemini |
spring-ai-ollama-spring-boot-starter | 本地 Ollama |
spring-ai-mistral-ai-spring-boot-starter | Mistral |
spring-ai-zhipuai-spring-boot-starter / 社区 | 智谱、通义、DeepSeek 等 |
切换模型的代价是:pom.xml 换一个 starter + application.yml 改一组 key,业务代码不动。
4. Embedding Model —— 嵌入模型
4.1 接口边界
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
float[] embed(String text); // 单条便捷入口
float[] embed(Document document); // 文档便捷入口
EmbeddingResponse call(EmbeddingRequest request); // 批量入口
int dimensions(); // 向量维度,做索引设计时要用
}dimensions() 这条方法很关键——它直接决定下游 VectorStore 的索引容量规划。
4.2 三个典型使用场景
- 构建 RAG 索引:把切分好的文档块经
EmbeddingModel转成向量,写入VectorStore。 - 语义检索 / 去重 / 聚类:对业务文本直接做相似度计算。
- **作为 LLM 的"前置记忆压缩":把多轮对话压缩成嵌入向量再喂给模型,降低 token 成本。
4.3 主流实现与维度速查
| 模型 | 维度 | 强项 |
|---|---|---|
text-embedding-3-small (OpenAI) | 1536(可截断) | 性价比高 |
text-embedding-3-large (OpenAI) | 3072(可截断) | 长文本检索 |
text-embedding-v3 (通义) | 1024(可变) | 中文友好 |
bge-m3 / nomic-embed-text (Ollama) | 1024 / 768 | 本地可跑 |
cohere-embed-v3 | 1024 | 多语言、压缩检索 |
切换 embedding 模型时,记得:维度变了就要重建 VectorStore 的索引,否则相似度计算会无声地算错。
5. Image Model —— 图像模型
Spring AI 把图像相关的能力拆成两类:
5.1 图像生成(text → image)
public interface ImageModel extends Model<ImagePrompt, ImageResponse> { ... }
ImageResponse response = imageModel.call(
new ImagePrompt("赛博朋克风格的城市夜景,雨夜,街道反光")
);
String url = response.getResult().getOutput().getUrl(); // 或 getB64Json()典型实现:OpenAI Image(DALL·E)、Stability(SDXL)、Midjourney(通过代理),中文社区也有"通义万相"、"可灵"等的适配。
5.2 图像理解(image → text,进入多模态对话)
这个能力 不通过独立的 ImageModel,而是通过下面的 §7 Multimodality 与 ChatModel 一起使用——UserMessage 携带图像附件,交给多模态 ChatModel 识别。
记忆点:
ImageModel只负责"生图","看图"归多模态ChatModel。
6. Audio Model —— 语音模型
语音能力分成两个方向,Spring AI 给的是两条独立接口:
public interface SpeechModel // 文本 → 语音(TTS)
extends Model<SpeechPrompt, SpeechResponse> { ... }
public interface AudioTranscriptionModel // 语音 → 文本(STT/ASR)
extends Model<AudioTranscriptionPrompt, AudioTranscriptionResponse> { ... }典型实现:
| 方向 | 接口 | 主流实现 |
|---|---|---|
| 语音合成 TTS | SpeechModel | OpenAI tts-1/tts-1-hd、ElevenLabs、Azure Speech |
| 语音转写 STT | AudioTranscriptionModel | OpenAI whisper-1、Azure Speech、Google STT |
落场景:客服通话实时转写会议纪要、播客自动字幕、TTS 给 Agent 装一副"嗓子"。
7. Multimodality —— 模型能处理的输入类型
7.1 输入类型清单
| 输入类型 | Spring AI 载体 | 典型用途 |
|---|---|---|
text | UserMessage 字符串 | 日常对话、问答 |
image(URL / Base64 / 字节流) | Media 包装,挂在 UserMessage | 看图说话、图像问答、OCR |
audio | Media 包装,挂在 UserMessage | 听音辨义(走多模态 ChatModel) |
video | 通过多帧抽帧成图像序列传入 | 视频问答 / 摘要(常见做法) |
file(PDF/CSV 等结构化) | Resource + DocumentReader | 文件问答,RAG 链路 |
7.2 构造一条多模态消息
UserMessage userMessage = new UserMessage(
"请描述这张图里的内容,并给我一句可以让视障人士理解的描述。",
new Media(MimeTypeUtils.IMAGE_PNG, imageBytes) // 也可以传 URL
);
ChatResponse response = chatModel.call(new Prompt(userMessage));7.3 选型要点
- 不是所有 ChatModel 都支持图像——支持的多模态模型(GPT-4o、Gemini、Qwen-VL、Claude 3/3.5)在
model名称上通常带-vision/-vl后缀。 - 图像大小与张数有限制,传给模型前要压缩 / 抽帧。
- 多模态 ≠ 多模态生成:多模态输入的是"图 + 文",输出可能仍只有文字;真正"图 + 文出图 + 文"走 Agent + 工具(用
ImageModel反向生成)。
8. Tools / Function Calling —— 工具调用
8.1 @Tool 注解,普通方法变成模型可调用的工具
class WeatherTools {
@Tool(description = "根据城市名查询当前天气,返回简短中文描述")
String currentWeather(
@ToolParam(description = "中国城市名,例如:杭州、北京") String city
) {
return city + " 当前晴,25°C,东南风 3 级";
}
@Tool(description = "把摄氏度转成华氏度")
double celsiusToFahrenheit(@ToolParam(description = "摄氏度") double c) {
return c * 9 / 5 + 32;
}
}8.2 用 ChatClient 注册并触发
chatClient.prompt("杭州现在多少度?换算成华氏度呢?")
.tools(new WeatherTools()) // 把 @Tool 方法一次性注册进去
.call()
.content();发生的事:
- Spring AI 把
@Tool方法反射成 JSON Schema(函数名 / 描述 / 参数),随 prompt 一起发给模型。 - 模型按需决定"先调
currentWeather,再调celsiusToFahrenheit,最后给一句话总结"。 - Spring AI 自动执行本地方法、把结果回灌给模型,直到模型产出最终回答(终态 token)。
- 整个过程完全在你的 JVM 里完成,工具实现随便写 Java 代码。
8.3 关键细节
- 描述写得好不好 = 模型调得好不好。
@Tool(description=...)与@ToolParam(description=...)是给模型"读"的,语义模糊就调不准。 - 可观测:工具调用各轮次可在
ChatResponse的Metadata里看,方便调试 Agent 流程。 - MCP:
spring-ai-mcp把同样的协议换成客户端/服务端,允许工具跨进程、跨语言——可视为 Function Calling 的"网络版"。
9. Streaming —— 流式响应
9.1 为什么要流式
把"等模型把整段话生成完再返回"改成"一边生成一边吐 token",首字延迟从几秒降到几百毫秒,体验上是质变。
9.2 接口形态
ChatModel 直接暴露的是 Reactor 的 Flux<ChatResponse>:
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<String> stream(@RequestParam String q) {
return chatClient.prompt(q).stream().content(); // 流式取文本片段
}落地时一般配 Spring WebFlux + SSE(text/event-stream),浏览器 / 前端用 EventSource 接。
9.3 流式 + 工具调用的注意点
- 流式 + Function Calling 时,工具调用阶段必须先完成(等模型产出"调用工具的指令"),再开始吐文本片段。
- Spring AI 把这两个阶段都放在同一条
Flux<ChatResponse>里,通过ChatResponse.getResult().getMetadata().getFinishReason()区分。
10. Retry —— 重试机制
Spring AI 把"瞬时错误重试"做成模型客户端的一等公民,基于 Spring Retry:
spring:
ai:
openai:
chat:
retry:
max-attempts: 5
on-error:
- com.fasterxml.jackson.core.JsonParseException
- org.springframework.web.client.ResourceAccessException
- java.util.concurrent.TimeoutException
back-off:
initial-interval: 2s
multiplier: 2
max-interval: 30s要点:
- 只对可恢复异常重试(网络抖动、429 限流、临时 5xx),别把 401/400/422 也重试。
- 指数退避 + 抖动是默认套路。
- 重试会被 Micrometer Observation 计为同一次调用的多个 span,而不是多个调用——避免把"重试指标"误算成"调用量"。
11. Observability —— 可观测性
Spring AI 默认接入 Micrometer Observation,把每一次模型调用都打成一个标准的"观测事件":
// 1. 引入 micrometer-tracing-bridge-otel + opentelemetry-exporter-otlp
// 2. application.yml 配 OpenTelemetry / Zipkin / Prometheus 任一端点暴露的指标维度大致包括:
| 维度 | 含义 |
|---|---|
spring.ai.chat.client | ChatClient 入口调用次数 |
spring.ai.chat.model | ChatModel 调用次数 |
spring.ai.chat.embedding | Embedding 调用次数 |
spring.ai.chat.retry | 重试次数 |
spring.ai.chat.tokens | 输入 / 输出 token 用量 |
spring.ai.chat.duration | 调用延迟分布 |
provider / model | 厂商 + 模型名标签 |
对工程上很重要的一条结论:模型调用一定是可以被 trace 的。配合 Spring Boot Actuator,/actuator/metrics/spring.ai.chat.* 与 /actuator/prometheus 直接可用,出问题随时回溯。
12. Built-in JSON —— 结构化输出
Spring AI 提供三种"内建 JSON 输出"路径,从轻到重:
12.1 原生 JSON Mode(最快)
ChatOptions opts = ChatOptions.builder()
.withResponseFormat(new ResponseFormat(ResponseFormat.Type.JSON_OBJECT))
.build();
String json = chatClient.prompt("给一个杭州的城市简介,JSON 输出。")
.options(opts)
.call()
.content();适用:厂商原生支持 JSON Mode(OpenAI response_format={"type":"json_object"} 等)。
12.2 BeanOutputConverter<T>(最常用)
record CityIntro(String name, int populationMillion, List<String> highlights) {}
BeanOutputConverter<CityIntro> conv = new BeanOutputConverter<>(CityIntro.class);
String raw = chatClient.prompt()
.user(u -> u.text("介绍杭州").param("format", conv.getFormat())) // 把 schema 塞进 prompt
.call()
.content();
CityIntro intro = conv.convert(raw);实现机制是:用 jackson 生成 JSON Schema 文档,塞回 prompt 让模型按 schema 生成,再用 jackson 反序列化。任意厂商都能用。
12.3 MapOutputConverter / ListOutputConverter
通用集合输出,不绑死 POJO 结构。
落地建议:业务接口不要直接收
String,首选BeanOutputConverter<T>拿到结构化结果——这一步能消掉 80% 的"模型输出不稳定"问题。
13. Local Deployment —— 本地部署
Spring AI 通过 starter 与运行时(主要是 Ollama)天然打通:
spring:
ai:
ollama:
chat:
model: qwen2.5:7b
base-url: http://localhost:11434
embedding:
model: nomic-embed-text
base-url: http://localhost:11434可以选的本地运行时:
| 运行时 | 适用 |
|---|---|
| Ollama | 一行命令跑 GGUF 模型,自带 OpenAI 兼容端点 |
| vLLM | 高吞吐推理服务,自带 OpenAI 兼容端点 |
| LocalAI | 多种模型聚合,OpenAI 兼容 |
| LM Studio | 桌面 GUI,启动后给本地 HTTP(兼容 OpenAI) |
| Transformers / ONNX Runtime(社区 starter) | 嵌入式场景,模型在 JVM 进程内直接跑 |
判断标准:
- 要快、要图形界面 → LM Studio / Ollama
- 要生产高吞吐 → vLLM / TGI / Triton
- 要 in-process 零依赖 → Transformers / ONNX 社区 starter
14. OpenAI API Compatibility —— OpenAI 兼容端点
几乎所有现代推理服务都提供 OpenAI 兼容的 HTTP 端点(/v1/chat/completions、/v1/embeddings、/v1/audio/*)。Spring AI 对此的策略是:
直接复用
spring-ai-openai-spring-boot-starter,配置换成自定义的base-url即可。
spring:
ai:
openai:
api-key: ollama # 多数本地运行时随便填
base-url: http://localhost:11434 # Ollama / vLLM / LocalAI ...
chat:
options:
model: qwen2.5:7b # 本地运行时注册的模型名
embedding:
options:
model: nomic-embed-text还能跨厂商这么玩:
# DeepSeek(OpenAI 兼容)
spring.ai.openai.base-url: https://api.deepseek.com
spring.ai.openai.api-key: sk-xxx
spring.ai.openai.chat.options.model: deepseek-chat注意事项:
- 能力子集:OpenAI 兼容 ≠ OpenAI 全功能。
tools、response_format、stream一般都能用,但response_format={"type":"json_schema", "schema":{...}}、logprobs等可能缺失,使用时按需降级。 - API Key 校验:本地运行时一般不校验 key,但中间的反向代理 / 网关可能校验,生产环境别留空。
- 多模态:只有当目标运行时真的实现了"OpenAI 兼容的多模态接口"才能发图像/音频附件,不要假设能行。
15. 选型速查表
按"你的项目需要 X"反查 Spring AI 的对应能力:
| 你想做的事 | 用 Spring AI 的哪一块 |
|---|---|
| 接入 LLM、对接多厂商 | spring-ai-*-spring-boot-starter |
| 统一调用入口、写业务代码 | ChatClient |
| 单次对话 + 流式输出 | ChatClient.stream() |
| 让模型调本地方法 | @Tool + .tools(...) |
| 把模型输出按 JSON 拿回来 | BeanOutputConverter<T> |
| 检索增强生成 | RetrievalAugmentationAdvisor + VectorStore |
| Embedding 与向量检索 | EmbeddingModel + VectorStore |
| 看图说话、听音识义 | ChatModel 多模态输入(Media) |
| 文生图 | ImageModel |
| 语音合成 / 转写 | SpeechModel / AudioTranscriptionModel |
| 在调用上加日志 / 拦截 / 重写 | Advisor 链 |
| 在调用上加重试 | spring.ai.<provider>.retry.* 配置 |
| 把调用接到 Prometheus / Zipkin | Micrometer Observation 自动生效 |
| 接本地 Ollama / vLLM / DeepSeek | OpenAI 兼容 starter,改 base-url |
| 跨进程 / 跨语言复用工具 | spring-ai-mcp |
16. 进一步阅读
- Spring AI Reference — Models:
https://docs.spring.io/spring-ai/reference/concepts/model.html - Spring AI Reference — Chat / Streaming / Tool Calling:
https://docs.spring.io/spring-ai/reference/ - 本目录上一篇:
springAi-介绍.md - 本地模型运行时在
5-AI手记/1-模型服务/(Ollama 等)中有专门介绍
