版本:Spring AI 2.0.1 · Spring Boot 4.x · JDK 17+(推荐 21) 目标:用 BOM + model starter + 最小配置,跑通一次同步调用和一次流式调用。 完整 starter 清单见 第 17 章;本章只走最小可用路径。
四步心智模型:
引入 spring-ai-bom
→ 加 spring-ai-starter-model-*(本章用 openai 或 ollama)
→ 配置 api-key / base-url / model
→ ChatClient.call / stream 跑通
2.1 准备工作
| 项 | 要求 |
|---|---|
| JDK | 17+(日常推荐 21) |
| Spring Boot | 4.0 / 4.1(与 Spring AI 2.0.x 对齐) |
| 构建 | Maven 3.9+ 或 Gradle 8+ |
| 模型后端 | OpenAI 兼容 API Key,或本机 Ollama |
本章不需要 Docker、向量库、Redis。若本机有 Spring AI 上游源码,只适合对照接口,不要当工程依赖源。
密钥放入环境变量(不要写进仓库):
# Linux / macOS
export OPENAI_API_KEY=sk-...
# Windows PowerShell
$env:OPENAI_API_KEY = "sk-..."
2.2 用 BOM 把版本钉住
Spring AI 各模块必须齐步走。手写零散版本(一部分 2.0.1、一部分旧版)容易出现 NoSuchMethodError 或配置前缀对不上。BOM import 是默认做法,不是可选项。
Maven(最小可运行片段)
父工程用 Boot 父 POM(或自行 import spring-boot-dependencies),再 import Spring AI BOM:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0</version> <!-- 以你选用的 Boot 4.x 为准 -->
<relativePath/>
</parent>
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.1</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 同步 HTTP:MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 流式 Hello 返回 Flux:加 WebFlux(可与 web 并存做演示) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<!-- 不要写 <version>,交给 BOM -->
</dependency>
</dependencies>
Gradle(Kotlin DSL)
dependencies {
implementation(platform("org.springframework.ai:spring-ai-bom:2.0.1"))
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.ai:spring-ai-starter-model-openai")
}
2.3 starter 实际带来了什么
以 spring-ai-starter-model-openai 为例:
spring-ai-starter-model-openai
├─ openai 实现(如 OpenAiChatModel)
├─ autoconfigure-model-openai(条件装配 @Bean)
└─ 传递依赖:model 抽象 / client-chat / commons …
启动成功后,容器里通常已有:
| Bean | 用途 |
|---|---|
ChatModel | 与供应商对话的抽象 |
ChatClient.Builder | 组装默认 system / advisors / tools |
| Options / Observation 钩子 | 模型参数与可观测性 |
按场景记几组常见 starter 即可(细节见第 17 章):
-
Model:
…-openai/…-ollama/…-anthropic -
Memory:
…-chat-memory+ repository -
Vector:
…-vector-store-* -
MCP:
…-mcp-server-webmvc等
同时引入多个 model starter 时,容器里会有多个 ChatModel。不要裸 @Autowired ChatModel,改用 @Qualifier,或为每个模型各建一个 ChatClient Bean。
2.4 最小配置
OpenAI 及兼容网关
# application.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com # 兼容网关时只改这里与 key/model
chat:
options:
model: gpt-4o-mini
temperature: 0.7
注意:model 落在 spring.ai.openai.chat.options 下,不要写成 openai 根下的平铺字段(部分旧文会写错)。
Ollama(本地,无云端 Key)
依赖换成:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: llama3.1
本机先执行:ollama pull llama3.1,并确认 ollama list 能看到该模型。
Hello 阶段先记住的默认值
| 配置 / 行为 | 默认 | 含义 |
|---|---|---|
spring.ai.tools.resolution.fallback.enabled | false | 只执行当前请求 / defaultTools 上挂的工具 |
OpenAI strict | false | 需要严格 JSON schema 时再显式打开 |
ChatClient.Builder | 自动配置提供 | 业务里通常 @Bean ChatClient 补默认 system |
2.5 工程骨架
建议包结构:
src/main/java/com/example/demo/
DemoApplication.java
config/AiConfig.java
web/HelloController.java
src/main/resources/
application.yml
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.demo.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfig {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
// Builder 由自动配置注入;这里只补默认值并 build
return builder
.defaultSystem("你是简洁准确的中文助手,回答控制在必要篇幅内。")
.build();
}
}
为什么要 @Bean ChatClient:Controller 直接注入成品客户端,避免每个接口重复写 system / advisors。需要按请求覆盖时,再在 prompt() 链上临时 .system(...) / .advisors(...)。
2.6 Hello World:同步调用
package com.example.demo.web;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/demo")
public class HelloController {
private final ChatClient chatClient;
public HelloController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/sync")
public String sync(
@RequestParam(defaultValue = "用一句话解释依赖注入") String q) {
return chatClient.prompt()
.user(q)
.call()
.content();
}
}
调用链拆开看:
prompt() → ChatClientRequestSpec
.user(q) → 写入 UserMessage
.call() → 走 Advisor 链,末端到达 ChatModel.call
.content() → 取出助手文本(空结果时要警惕,生产需判空)
需要 Usage、finishReason 时:
ChatResponse response = chatClient.prompt()
.user(q)
.call()
.chatResponse();
String text = response.getResult().getOutput().getText();
var usage = response.getMetadata().getUsage();
更短的写法(整段当作 user 文本):
chatClient.prompt(q).call().content();
验证:
curl "http://localhost:8080/demo/sync?q=你好"
2.7 Hello World:流式调用
import org.springframework.http.MediaType;
import reactor.core.publisher.Flux;
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(
@RequestParam(defaultValue = "讲一个很短的冷知识") String q) {
return chatClient.prompt()
.user(q)
.stream()
.content();
}
要点:
-
依赖:返回
Flux做 SSE,本章示例已加spring-boot-starter-webflux。若坚持纯 MVC,改用SseEmitter桥接(见第 12 章),不要在请求线程上blockLast()。 -
出口:
.stream().content()是文本增量;要 finishReason / 分块元数据用.stream().chatResponse()。 -
范围:Hello 阶段不要挂 Tool。流式下的工具循环更复杂,放到工具 / 流式专章。
验证(-N 关闭缓冲,才能看到逐块输出):
curl -N "http://localhost:8080/demo/stream?q=你好"
2.8 补充:Options、手工组装与多模型
请求级 Options
import org.springframework.ai.openai.OpenAiChatOptions;
@GetMapping("/sync-options")
public String syncOptions(@RequestParam String q) {
return chatClient.prompt()
.user(q)
.options(OpenAiChatOptions.builder()
.temperature(0.2)
.maxTokens(256)
.build()) // 记得 build()
.call()
.content();
}
.options(...) 的入参类型以 IDE 中 ChatClientRequestSpec#options 为准;OpenAI 路径通常传 OpenAiChatOptions。
临时覆盖 system
String answer = chatClient.prompt()
.system("你是气象解说员,只用短句。")
.user("用科普口吻说明什么是露点温度")
.call()
.content();
关闭自动配置时的手工组装(单测 / 自定义节点)
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
ChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder().apiKey(apiKey).build())
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4o-mini")
.build())
.build();
ChatClient client = ChatClient.create(chatModel);
String answer = client.prompt("ping").call().content();
生产仍建议 starter + yaml。
多模型预期
-
一个应用可有多个
ChatModel,用 Bean 名或@Qualifier区分。 -
许多国产 / 代理网关仍用
spring-ai-starter-model-openai,改base-url、api-key、model即可。 -
改了
base-url≠ Image / Audio 一定可用;能力以该兼容端实际支持为准。
2.9 测试时怎么接模型
| 方式 | 适用 |
|---|---|
| 真调用 | @SpringBootTest + 测试 Key(管费用与 CI 密钥) |
Stub ChatModel | 单测业务编排,不花 token |
| Testcontainers | 向量库 / Redis 等周边;Chat Hello 通常不需要 |
最小 stub:
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
ChatModel stub = prompt -> {
AssistantMessage msg = new AssistantMessage("pong");
return new ChatResponse(List.of(new Generation(msg)));
};
ChatClient client = ChatClient.create(stub);
assertThat(client.prompt("ping").call().content()).isEqualTo("pong");
(Generation / ChatResponse 构造若随小版本调整,以你依赖中的签名为准;语义都是「固定返回一句助手消息」。)
2.10 常见问题排查
| 现象 | 先查什么 |
|---|---|
找不到 ChatModel / Builder | 是否漏了 model starter;api-key 是否为空导致自动配置跳过;Boot 是否仍是 3.x |
| 401 / 403 | 环境变量名与 ${OPENAI_API_KEY} 是否一致;兼容网关是否要额外 Header |
| 400:schema / strict / tools | 是否误开 strict(true) 却声明可选字段;Tool 名是否含空格等非法字符 |
.getResult() 为空 | 先看 getResults();Hello 用 .content(),生产要处理空结果与异常 |
| 流式卡住或只有一块 | 客户端是否未缓冲(curl -N);是否缺 WebFlux / SSE 配置 |
NoSuchMethodError | 是否手写了旧版 spring-ai-*;是否混入 Spring AI Alibaba 坐标 → 统一 BOM 2.0.1 |
| Tool 挂了却不调 | 2.0.1 fallback 默认关;确认挂在当前请求或 defaultTools(见第 8 章) |
2.11 本章边界与验收
Docker Compose / Testcontainers 用于数据库、向量库等周边,不是 Chat Hello 的前提。
到这里应能:
-
用 BOM + model starter 建起最小工程(含密钥环境变量)
-
完成
/demo/sync与/demo/stream并完成 curl 验证 -
区分配置问题、鉴权问题与依赖冲突
下一章看一次调用里真正流动的对象:Message、Prompt、ChatResponse。