
本文是《Spring AI 实战》系列第 3 章。真实项目中,你可能今天用 OpenAI,明天就要换成 Ollama 降成本,后天又要接入通义千问满足合规要求。Spring AI 的模型抽象层让这一切变得简单------切换模型只需改配置文件,Java 代码一行不动。
一、真实场景:老板要换模型降成本
假设你的 AI 客服系统已经上线,使用的是 OpenAI GPT-4o,每月 API 费用 5000 美元。某天老板看了账单,要求把成本降下来。
如果你用的是直接调用 OpenAI API 的方式:
java
// 硬编码了 OpenAI 的 API 地址、请求格式、认证方式
// 换模型 = 重写所有 HTTP 调用代码
Request request = new Request.Builder()
.url("https://api.openai.com/v1/chat/completions") // 硬编码
.addHeader("Authorization", "Bearer " + openaiApiKey) // 硬编码
.post(RequestBody.create(openaiFormatJson, ...)) // 硬编码
.build();
换成本地 Ollama 或通义千问?对不起,上面的代码全部作废,推倒重来。 API 地址不同、认证方式不同、请求格式不同、响应格式不同。
但如果你用的是 Spring AI,只需要两步:
- 换 Starter 依赖(
pom.xml) - 换配置(
application.yml)
Java 代码一行不动。 这不是魔法,这是 Spring AI 模型抽象层的威力。
二、Spring AI 的模型抽象层原理
2.1 ChatModel 接口设计
Spring AI 定义了一个核心接口 ChatModel,所有模型厂商的实现都遵循这个接口:
java
// Spring AI 的核心接口(简化版)
public interface ChatModel {
/**
* 调用大模型,传入 Prompt,返回 ChatResponse
* 不管底层是 OpenAI、Ollama 还是通义千问,调用方式完全一样
*/
ChatResponse call(Prompt prompt);
}
各厂商提供的实现类:
ChatModel(接口)
├── OpenAiChatModel → 调用 OpenAI GPT 系列
├── OllamaChatModel → 调用本地 Ollama 模型
├── AnthropicChatModel → 调用 Anthropic Claude
├── GeminiChatModel → 调用 Google Gemini
├── MistralAiChatModel → 调用 Mistral AI
└── ...更多厂商实现
2.2 为什么切换模型只需改配置
Spring Boot 的自动配置机制根据你引入的 Starter,自动创建对应的 ChatModel Bean:
java
// 引入 spring-ai-starter-model-openai 后
// Spring AI 自动创建 OpenAiChatModel Bean
// 引入 spring-ai-starter-model-ollama 后
// Spring AI 自动创建 OllamaChatModel Bean
// 你的代码只依赖 ChatModel 接口,不依赖具体实现
// 所以切换 Starter = 切换底层实现,上层代码无感知
这就是经典的"面向接口编程"------和 Spring Data 切换数据库驱动(MySQL → PostgreSQL → Oracle)是同一个道理。
三、OpenAI 接入详解
3.1 Starter 依赖
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
3.2 application.yml 配置
yaml
spring:
ai:
openai:
# API Key(必填,从环境变量读取)
api-key: ${OPENAI_API_KEY}
# 自定义 API 地址(可选)
# 如果你使用代理或兼容 OpenAI 协议的第三方服务,需要设置这个
# 默认值:https://api.openai.com
# base-url: https://your-proxy.com/v1
chat:
options:
# 模型名称
model: gpt-4o-mini
# 温度参数(0-2,越高越有创造性)
temperature: 0.7
# 单次回复最大 token 数
max-tokens: 2048
# 核采样参数(0-1)
top-p: 1.0
3.3 完整的 Service 调用代码
java
@Service
public class OpenAiChatService {
private final ChatClient chatClient;
public OpenAiChatService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个专业的 AI 助手,回答简洁清晰")
.build();
}
/**
* 简单对话
*/
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
/**
* 对话并获取 token 用量(用于成本监控)
*/
public ChatResult chatWithUsage(String message) {
ChatResponse response = chatClient.prompt()
.user(message)
.call()
.chatResponse();
var usage = response.getMetadata().getUsage();
return new ChatResult(
response.getResult().getOutput().getText(),
usage.getPromptTokens(),
usage.getGenerationTokens(),
usage.getTotalTokens()
);
}
// 使用 Java Record 作为返回值
public record ChatResult(
String content,
long promptTokens,
long generationTokens,
long totalTokens
) {}
}
3.4 OpenAI 计费详解
| 模型 | 输入价格(每 1M tokens) | 输出价格(每 1M tokens) | 上下文窗口 | 推荐场景 |
|---|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | 128K | 复杂推理、长文档分析、高质量生成 |
| GPT-4o-mini | $0.15 | $0.60 | 128K | 日常对话、分类、摘要(性价比之王) |
| GPT-3.5-turbo | $0.50 | $1.50 | 16K | 简单任务(已被 4o-mini 取代) |
| o1-mini | $3.00 | $12.00 | 128K | 复杂数学、编程推理 |
| o1-preview | $15.00 | $60.00 | 128K | 最强推理,但价格极高 |
实战建议:大多数场景先用 GPT-4o-mini。它的成本只有 GPT-4o 的 1/16,但质量已经非常好了。只有当 4o-mini 的输出质量不满足要求时,再升级到 GPT-4o。
3.5 踩坑提醒
- API Key 管理:OpenAI 的 API Key 泄露 = 你的信用卡被盗刷。务必使用环境变量,不要提交到 Git。建议在 OpenAI 后台设置 Monthly Limit(月度消费上限)。
- 速率限制:OpenAI 有 RPM(每分钟请求数)和 TPM(每分钟 Token 数)限制。免费账号限制更严格。生产环境需要实现重试和降级策略。
- 网络问题 :中国大陆直连 OpenAI API 不稳定,需要配置代理。可以在
base-url中设置代理地址。
四、Ollama 本地模型接入
4.1 安装方式
bash
# Windows
winget install Ollama.Ollama
# Mac
brew install ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
安装后 Ollama 作为后台服务运行,默认监听 http://localhost:11434。
4.2 常用模型推荐
| 模型名 | 参数量 | 中文能力 | 显存需求 | 适用场景 |
|---|---|---|---|---|
| qwen2.5 | 7B | 优秀 | 8GB(推荐 16GB) | 中文对话、问答、翻译,中文场景首选 |
| llama3.1 | 8B | 一般 | 8GB(推荐 16GB) | 英文对话、代码生成,英文能力最强 |
| deepseek-r1:8b | 8B | 良好 | 8GB(推荐 16GB) | 复杂推理、数学、逻辑分析 |
| gemma2:9b | 9B | 良好 | 8GB(推荐 16GB) | 通用对话、指令跟随 |
| phi3.5:3.8b | 3.8B | 一般 | 4GB | 轻量级任务、资源受限环境 |
bash
# 拉取推荐的中文模型
ollama pull qwen2.5:7b
# 验证模型可用
ollama run qwen2.5:7b "你好,请介绍一下自己"
4.3 切换依赖:pom.xml
xml
<!-- 注释掉 OpenAI Starter -->
<!-- <dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency> -->
<!-- 换成 Ollama Starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
4.4 切换配置:application.yml
yaml
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: qwen2.5:7b
# Ollama 也支持 temperature 等参数
temperature: 0.7
4.5 Java 代码完全不变
java
// 这个 Controller 在 OpenAI 和 Ollama 两种配置下都能正常工作
// 因为 ChatClient 依赖的是 ChatModel 接口,不是具体实现
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
return chatClient.prompt()
.user(msg)
.call()
.content();
}
}
这就是 Spring AI 的核心价值:代码与模型解耦。
4.6 踩坑提醒
- 模型拉取慢 :7B 模型约 4.5GB,国内下载可能很慢。解决方案:设置 Ollama 镜像环境变量
OLLAMA_HOST或使用国内镜像站。 - 端口冲突 :Ollama 默认使用 11434 端口。如果该端口被占用,可以通过环境变量
OLLAMA_HOST=0.0.0.0:11435修改。 - Mac M 芯片兼容:Apple Silicon 原生支持,使用 Metal 加速,体验很好。但 14B+ 的模型可能需要 32GB 以上内存。
- 模型质量差异:本地 7B 模型的能力远不如 GPT-4o。对于简单的分类、提取任务够用;复杂推理任务建议用更大的模型或云端 API。
五、通义千问百炼平台接入
5.1 为什么国内推荐通义千问
| 对比项 | OpenAI(海外) | 通义千问百炼(国内) |
|---|---|---|
| 网络延迟 | 200-500ms(海外服务器) | 50-100ms(国内服务器) |
| 数据合规 | 数据出境,需评估合规风险 | 数据不出境,天然合规 |
| 中文能力 | 优秀 | 优秀(专门优化中文) |
| 价格 | GPT-4o-mini: 0.15/0.6 | qwen-plus: 更便宜(有免费额度) |
| 支付方式 | 需要国际信用卡 | 支付宝即可 |
结论:如果你是面向国内用户的项目,通义千问是生产环境的首选。
5.2 注册百炼平台获取 API Key
- 打开 阿里云百炼平台
- 登录阿里云账号(没有就注册一个)
- 进入"API-KEY 管理",创建一个 API Key
- 复制 API Key,设置环境变量
bash
# Windows PowerShell
$env:DASHSCOPE_API_KEY="sk-xxxxxxxxxxxxxxxx"
# Linux/Mac
export DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxx
5.3 兼容 OpenAI 协议的配置方式
通义千问兼容 OpenAI 的 API 协议,所以可以直接使用 Spring AI 的 OpenAI Starter,只需要修改 base-url:
xml
<!-- 依赖不变,还是用 OpenAI Starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
yaml
spring:
ai:
openai:
# 关键:把 base-url 改成通义千问的兼容地址
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
# API Key 使用百炼平台的 Key
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
# 使用通义千问的模型
model: qwen-plus
temperature: 0.7
看到没有?依赖和代码都没变,只改了 base-url 和 api-key。 这就是 OpenAI 兼容协议的好处------通义千问、DeepSeek、智谱、Moonshot 等国内大模型都兼容 OpenAI 协议,都可以用同样的方式接入。
5.4 通义模型选择表格
| 模型 | 定位 | 输入价格 | 输出价格 | 上下文窗口 | 适合场景 |
|---|---|---|---|---|---|
| qwen-turbo | 快速响应 | 0.8元/百万tokens | 2元/百万tokens | 128K | 高并发、实时对话 |
| qwen-plus | 均衡性价比 | 4元/百万tokens | 8元/百万tokens | 128K | 日常对话、内容生成(推荐) |
| qwen-max | 旗舰模型 | 20元/百万tokens | 60元/百万tokens | 32K | 复杂推理、长文本分析 |
| qwen-long | 超长上下文 | 0.5元/百万tokens | 2元/百万tokens | 1000K+ | 长文档处理(100万字) |
实战建议 :大多数场景用
qwen-plus。它的性价比最高,中文能力优秀。只有当qwen-plus的输出质量不满足要求时(比如复杂的数学推理),再升级到qwen-max。
六、模型参数调优指南
不管你用哪个模型,这三个参数都是核心调优点:
6.1 temperature 详解
| 值 | 效果 | 类比 |
|---|---|---|
| 0 | 完全确定性,每次返回相同结果 | 查字典------同一个词永远查到同一个释义 |
| 0.3 | 基本确定,微小变化 | 教科书------严谨准确 |
| 0.7 | 适中,有一定创造性 | 有经验的同事------专业但不死板 |
| 1.0 | 有创造性,每次可能不同 | 脑暴会议------想法多样 |
| 1.5+ | 高创造性,可能胡说八道 | 喝醉了的创意总监------天马行空但不可靠 |
6.2 top_p 详解
top_p 是核采样参数(也叫 nucleus sampling),它和 temperature 一起控制输出的随机性。
top_p = 1.0:不限制,考虑所有可能的 tokentop_p = 0.9:只考虑概率累计前 90% 的 token(排除 10% 的低概率 token)top_p = 0.5:只考虑概率累计前 50% 的 token(非常保守)
实战建议 :一般情况下用 top_p = 1.0(默认值),通过 temperature 来控制随机性即可。两者同时调整的效果不是简单的叠加,一般不需要同时大幅调整。
6.3 max_tokens 详解
控制 AI 单次回复的最大 token 数量。
- 1 token 大约 0.75 个英文单词或 0.5 个中文字
max_tokens = 100:大约 50-75 个中文字max_tokens = 2048:大约 1000-1500 个中文字max_tokens = 4096:大约 2000-3000 个中文字
注意 :max_tokens 设置过小会导致回复被截断;设置过大会浪费 token(计费)和增加延迟。
6.4 五种场景的参数推荐
| 场景 | temperature | top_p | max_tokens | 原因 |
|---|---|---|---|---|
| 代码生成 | 0.1-0.2 | 1.0 | 2048 | 需要确定性输出,代码不能乱编 |
| 问答/知识查询 | 0.3-0.5 | 1.0 | 1024 | 需要准确,但允许适当变化 |
| 翻译 | 0.1-0.3 | 1.0 | 2048 | 翻译需要精确,不应该"创造性发挥" |
| 创意写作 | 0.7-1.0 | 0.9 | 4096 | 需要创造力和多样性 |
| 数据分析 | 0.2-0.4 | 1.0 | 2048 | 需要准确的数据分析,不能编造数据 |
yaml
# 场景化配置示例
spring:
ai:
openai:
chat:
options:
# 代码生成场景
model: gpt-4o-mini
temperature: 0.2
max-tokens: 2048
6.5 踩坑提醒
- temperature 过高导致胡说八道:这是最常见的坑。如果你的应用需要准确输出(代码、数据、事实),务必把 temperature 设在 0.3 以下。很多 AI "幻觉"问题就是 temperature 设置过高导致的。
- 不同模型对参数的敏感度不同:GPT-4o 对参数变化不太敏感(在 0.1-1.0 范围内都比较稳定),但较小的本地模型可能对 temperature 非常敏感。测试时注意对比。
- max_tokens 和成本的关系:max_tokens 设置得越大,单次调用的最大成本就越高。建议根据实际需求设置合理上限。
七、多模型路由
7.1 场景
你的系统有不同类型的请求:简单的问候、常见问题用便宜快速的模型;复杂的技术问题用好模型;代码生成用专门的代码模型。
如何根据请求类型自动路由到不同的模型?
7.2 @Qualifier 多 Bean 配置
如果你的应用同时引入了多个 Starter(比如同时引入 OpenAI 和 Ollama),Spring Boot 会自动创建多个 ChatModel Bean。你需要用 @Qualifier 区分它们:
java
@Configuration
public class MultiModelConfig {
/**
* 主力模型:用于日常对话和一般任务
* 使用 OpenAI GPT-4o-mini
*/
@Bean
@Primary // 默认的 ChatModel
public ChatClient primaryChatClient(ChatClient.Builder builder) {
return builder.build();
}
/**
* 高级模型:用于复杂推理和分析任务
* 需要单独注入 OpenAiChatModel 并构建
*/
@Bean("advancedChatClient")
public ChatClient advancedChatClient(
@Qualifier("openAiChatModel") ChatModel advancedModel,
ChatClient.Builder builder) {
// 用更高级的模型构建(需要在 yml 中配置第二个模型)
return builder
.defaultModel(advancedModel)
.defaultSystem("你是一个资深的技术专家")
.build();
}
}
7.3 路由 Service 代码
java
@Service
public class SmartChatService {
private final ChatClient simpleClient; // 便宜快速的模型
private final ChatClient advancedClient; // 贵但强大的模型
public SmartChatService(
@Primary ChatClient simpleClient,
@Qualifier("advancedChatClient") ChatClient advancedClient) {
this.simpleClient = simpleClient;
this.advancedClient = advancedClient;
}
/**
* 智能路由:根据问题复杂度选择不同的模型
*
* 简单问题(问候、闲聊、常见问题)→ 便宜模型
* 复杂问题(技术分析、代码生成、推理)→ 好模型
*/
public String smartChat(String message) {
if (isSimpleQuestion(message)) {
// 简单问题用便宜模型,省钱省时间
return simpleClient.prompt()
.user(message)
.call()
.content();
} else {
// 复杂问题用好模型,保证质量
return advancedClient.prompt()
.user(message)
.call()
.content();
}
}
/**
* 判断问题是否简单(简化版)
* 实际项目中可以用轻量级分类器或规则引擎
*/
private boolean isSimpleQuestion(String message) {
String msg = message.toLowerCase();
// 简单问候
return msg.matches(".*(你好|hi|hello|谢谢|再见|拜拜).*")
// 短问题(通常比较简单)
|| message.length() < 10;
}
}
7.4 另一种方案:单模型 + 不同参数
如果你的应用只用一个模型厂商(比如只用 OpenAI),但需要根据场景调整参数:
java
@Service
public class ParameterRouterService {
private final ChatModel chatModel;
public ParameterRouterService(ChatModel chatModel) {
this.chatModel = chatModel;
}
/**
* 根据场景动态调整参数
*/
public String chatWithParams(String message, String scenario) {
// 根据场景构建不同的参数
var options = switch (scenario) {
case "code" -> ChatOptions.builder()
.temperature(0.2) // 代码生成:低温
.maxTokens(4096) // 允许较长的代码输出
.build();
case "creative" -> ChatOptions.builder()
.temperature(1.0) // 创意写作:高温
.maxTokens(2048)
.build();
default -> ChatOptions.builder()
.temperature(0.7) // 默认
.maxTokens(1024)
.build();
};
Prompt prompt = new Prompt(
List.of(new UserMessage(message)),
options
);
return chatModel.call(prompt)
.getResult().getOutput().getText();
}
}
八、小结
本章我们学习了 Spring AI 的多模型接入能力:
- 模型抽象层原理 :
ChatModel接口 + 各厂商实现,面向接口编程 - OpenAI 接入:Starter + 配置 + 计费详解,生产环境首选 GPT-4o-mini
- Ollama 本地模型:零成本、数据不出本地,推荐 qwen2.5:7b
- 通义千问百炼:国内首选,兼容 OpenAI 协议,改 base-url 即可
- 模型参数调优:temperature、top_p、max_tokens 的原理和场景推荐值
- 多模型路由:根据请求复杂度自动选择模型,省钱又保质
下一章,我们将学习 Prompt Engineering 和 Advisor 机制------同样的模型,好的 Prompt 能让输出质量翻倍。