Skip to content

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 生态里早已存在的 JdbcTemplateRestTemplateRedisTemplate 的 AI 版本:

Spring AI Model 抽象架构

设计目标只有一句:

让上层业务代码以"模型无关"的方式写,把厂商差异收敛到配置和依赖里。

2. 顶层抽象:Model<TR, TC> 标记接口

Spring AI 几乎所有模型接口都继承自一个空的标记接口,只用来表达"我是一个模型":

java
@FunctionalInterface
public interface Model<TReq, TRes> extends Model<TReq, TRes> {
    TRes call(TReq request);
}
  • ChatModel:call(Prompt) → ChatResponse
  • EmbeddingModel:call(EmbeddingRequest) → EmbeddingResponse
  • ImageModel:call(ImagePrompt) → ImageResponse
  • AudioModel(以及 SpeechModel / AudioTranscriptionModel)

这种设计的实际收益是——所有模型都遵守同一套"输入-处理-输出"的形状,上层包装(Advisor、Retry、Observation、流式转换)可以完全复用。

3. Chat Model —— 对话模型

3.1 接口边界

java
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:

java
@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、工具调用、消息历史、模型选项(temperaturetop_p 等)。

3.3 主流实现

Starter 依赖提供方
spring-ai-openai-spring-boot-starterOpenAI / 任何 OpenAI 兼容端点
spring-ai-azure-openai-spring-boot-starterAzure OpenAI
spring-ai-anthropic-spring-boot-starterAnthropic Claude
spring-ai-vertex-ai-gemini-spring-boot-starterGoogle Vertex AI Gemini
spring-ai-ollama-spring-boot-starter本地 Ollama
spring-ai-mistral-ai-spring-boot-starterMistral
spring-ai-zhipuai-spring-boot-starter / 社区智谱、通义、DeepSeek 等

切换模型的代价是:pom.xml 换一个 starter + application.yml 改一组 key,业务代码不动。

4. Embedding Model —— 嵌入模型

4.1 接口边界

java
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 三个典型使用场景

  1. 构建 RAG 索引:把切分好的文档块经 EmbeddingModel 转成向量,写入 VectorStore
  2. 语义检索 / 去重 / 聚类:对业务文本直接做相似度计算。
  3. **作为 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-v31024多语言、压缩检索

切换 embedding 模型时,记得:维度变了就要重建 VectorStore 的索引,否则相似度计算会无声地算错。

5. Image Model —— 图像模型

Spring AI 把图像相关的能力拆成两类:

5.1 图像生成(text → image)

java
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 给的是两条独立接口:

java
public interface SpeechModel             // 文本 → 语音(TTS)
        extends Model<SpeechPrompt, SpeechResponse> { ... }

public interface AudioTranscriptionModel  // 语音 → 文本(STT/ASR)
        extends Model<AudioTranscriptionPrompt, AudioTranscriptionResponse> { ... }

典型实现:

方向接口主流实现
语音合成 TTSSpeechModelOpenAI tts-1/tts-1-hd、ElevenLabs、Azure Speech
语音转写 STTAudioTranscriptionModelOpenAI whisper-1、Azure Speech、Google STT

落场景:客服通话实时转写会议纪要、播客自动字幕、TTS 给 Agent 装一副"嗓子"。

7. Multimodality —— 模型能处理的输入类型

7.1 输入类型清单

输入类型Spring AI 载体典型用途
textUserMessage 字符串日常对话、问答
image(URL / Base64 / 字节流)Media 包装,挂在 UserMessage看图说话、图像问答、OCR
audioMedia 包装,挂在 UserMessage听音辨义(走多模态 ChatModel)
video通过多帧抽帧成图像序列传入视频问答 / 摘要(常见做法)
file(PDF/CSV 等结构化)Resource + DocumentReader文件问答,RAG 链路

7.2 构造一条多模态消息

java
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 注解,普通方法变成模型可调用的工具

java
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 注册并触发

java
chatClient.prompt("杭州现在多少度?换算成华氏度呢?")
    .tools(new WeatherTools())         // 把 @Tool 方法一次性注册进去
    .call()
    .content();

发生的事:

  1. Spring AI 把 @Tool 方法反射成 JSON Schema(函数名 / 描述 / 参数),随 prompt 一起发给模型。
  2. 模型按需决定"先调 currentWeather,再调 celsiusToFahrenheit,最后给一句话总结"。
  3. Spring AI 自动执行本地方法、把结果回灌给模型,直到模型产出最终回答(终态 token)。
  4. 整个过程完全在你的 JVM 里完成,工具实现随便写 Java 代码。

8.3 关键细节

  • 描述写得好不好 = 模型调得好不好@Tool(description=...)@ToolParam(description=...) 是给模型"读"的,语义模糊就调不准。
  • 可观测:工具调用各轮次可在 ChatResponseMetadata 里看,方便调试 Agent 流程。
  • MCP:spring-ai-mcp 把同样的协议换成客户端/服务端,允许工具跨进程、跨语言——可视为 Function Calling 的"网络版"。

9. Streaming —— 流式响应

9.1 为什么要流式

把"等模型把整段话生成完再返回"改成"一边生成一边吐 token",首字延迟从几秒降到几百毫秒,体验上是质变。

9.2 接口形态

ChatModel 直接暴露的是 Reactor 的 Flux<ChatResponse>:

java
@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:

yaml
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,把每一次模型调用都打成一个标准的"观测事件":

java
// 1. 引入 micrometer-tracing-bridge-otel + opentelemetry-exporter-otlp
// 2. application.yml 配 OpenTelemetry / Zipkin / Prometheus 任一端点

暴露的指标维度大致包括:

维度含义
spring.ai.chat.clientChatClient 入口调用次数
spring.ai.chat.modelChatModel 调用次数
spring.ai.chat.embeddingEmbedding 调用次数
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(最快)

java
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>(最常用)

java
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)天然打通:

yaml
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 即可。

yaml
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

还能跨厂商这么玩:

yaml
# 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

注意事项:

  1. 能力子集:OpenAI 兼容 ≠ OpenAI 全功能。toolsresponse_formatstream 一般都能用,但 response_format={"type":"json_schema", "schema":{...}}logprobs 等可能缺失,使用时按需降级。
  2. API Key 校验:本地运行时一般不校验 key,但中间的反向代理 / 网关可能校验,生产环境别留空。
  3. 多模态:只有当目标运行时真的实现了"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 / ZipkinMicrometer Observation 自动生效
接本地 Ollama / vLLM / DeepSeekOpenAI 兼容 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 等)中有专门介绍