背景与目标
- 背景: 企业在落地生成式 AI 时,往往需要在不同平台(如 DeepSeek、阿里云百炼 DashScope、本地 Ollama)与多款模型之间灵活切换,以平衡效果、成本与可用性。
- 目标 : 基于 Spring AI 提供统一的服务,支持:
- 通过端点
/chat按名称直切多个模型(固定预置的ChatClient)。 - 通过端点
/chat2指定平台与模型并自定义temperature,支持流式返回。
- 通过端点
本文基于模块 03-multi-model-switch 的完整实现展开说明,覆盖依赖、配置、核心代码、接口调用与常见问题。
模块与依赖
模块:03-multi-model-switch
关键依赖包括 Spring Boot Web、Spring AI DeepSeek、Alibaba DashScope、Ollama、测试、Lombok:
xml
<dependencies>
<!--deepseek-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
<!--百炼-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<!--ollama-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
配置文件
src/main/resources/application.properties 中配置了 API Key、默认模型与本地 Ollama 地址等:
properties
spring.application.name=chat-client
# deepseek
spring.ai.deepseek.api-key=${DEEPSEEK_API_KEY}
spring.ai.deepseek.chat.options.model=deepseek-reasoner
# dashscope (阿里云百炼)
spring.ai.dashscope.api-key=${DASHSCOPE_API_KEY}
spring.ai.dashscope.chat.options.model=qwen-plus
# ollama (本地)
spring.ai.ollama.base-url=http://127.0.0.1:11434
spring.ai.ollama.chat.model=qwen3:0.6b
环境变量说明:
DEEPSEEK_API_KEY: DeepSeek 平台密钥DASHSCOPE_API_KEY: 阿里云百炼密钥- 如果使用本地 Ollama,请确保
http://127.0.0.1:11434可访问,且事先拉取好模型(如qwen3:0.6b)。
核心设计与代码解读
1) 统一模型装配与客户端暴露 AiConfig
提供多个 ChatClient Bean:deepseekR1(推理/Reasoner)、deepseekV3(聊天/Chat)、ollama(本地模型)。通过不同 ChatModel 与默认 options,形成"即插即用"的客户端:
java
@Configuration
public class AiConfig {
@Bean
public ChatClient deepseekR1(DeepSeekChatProperties chatProperties) {
DeepSeekApi deepSeekApi = DeepSeekApi.builder()
.apiKey(System.getenv("DEEPSEEK_API_KEY"))
.build();
DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
.deepSeekApi(deepSeekApi)
.defaultOptions(DeepSeekChatOptions.builder()
.model(DeepSeekApi.ChatModel.DEEPSEEK_REASONER)
.build())
.build();
return ChatClient.builder(deepSeekChatModel).build();
}
@Bean
public ChatClient deepseekV3() {
DeepSeekApi deepSeekApi = DeepSeekApi.builder()
.apiKey(System.getenv("DEEPSEEK_API_KEY"))
.build();
DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
.deepSeekApi(deepSeekApi)
.defaultOptions(DeepSeekChatOptions.builder()
.model(DeepSeekApi.ChatModel.DEEPSEEK_CHAT)
.build())
.build();
return ChatClient.builder(deepSeekChatModel).build();
}
@Bean
public ChatClient ollama(@Autowired OllamaApi ollamaApi,
@Autowired OllamaChatProperties options) {
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaOptions.builder()
.model(options.getModel())
.build())
.build();
return ChatClient.builder(ollamaChatModel).build();
}
}
要点:
- DeepSeek 通过
DeepSeekApi+DeepSeekChatModel以不同默认model暴露为两个ChatClient。 - Ollama 使用
OllamaApi和配置中的model作为默认本地模型。
2) 简单模型切换端点 /chat(按 Bean 名直查)
通过自动注入 Map<String, ChatClient>,用 model 参数直接索引对应 ChatClient:
java
@Slf4j
@RestController
public class MultiModelsController {
@Autowired
private Map<String, ChatClient> chatClientMap;
@PostConstruct
public void init(){
chatClientMap.forEach((key, value) ->
log.info("model: {} : {}", key, value.getClass().getName()));
}
@GetMapping("/chat")
String generation(@RequestParam String message,
@RequestParam String model) {
ChatClient chatClient = chatClientMap.get(model);
String content = chatClient.prompt().user(message).call().content();
log.info("model: {} : {}", model, content);
return content;
}
}
可用 model 值示例(取决于 Spring 注入命名):deepseekR1、deepseekV3、ollama。
调用示例:
bash
curl 'http://localhost:8080/chat?model=deepseekV3&message=用50字解释大语言模型'
3) 平台+模型+温度+流式端点 /chat2
支持 platform、model、temperature 的动态指定,并以流式文本输出:
java
@RestController
public class MultiPlatformAndModelController {
HashMap<String, ChatModel> platforms = new HashMap<>();
public MultiPlatformAndModelController(DashScopeChatModel dashScopeChatModel,
DeepSeekChatModel deepSeekChatModel,
OllamaChatModel ollamaChatModel) {
platforms.put("dashscope", dashScopeChatModel);
platforms.put("ollama", ollamaChatModel);
platforms.put("deepseek", deepSeekChatModel);
}
@RequestMapping(value = "/chat2", produces = "text/stream;charset=UTF-8")
public Flux<String> chat(String message, MultiPlatformAndModelOptions options) {
String platform = options.getPlatform();
ChatModel chatModel = platforms.get(platform);
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultOptions(ChatOptions.builder()
.temperature(options.getTemperature())
.model(options.getModel())
.build())
.build();
return chatClient.prompt().user(message).stream().content();
}
}
options 参数对象:
java
@Data
public class MultiPlatformAndModelOptions {
private String platform;
private String model;
private Double temperature;
}
调用示例(浏览器/curl 均可):
- DeepSeek(Chat):
/chat2?platform=deepseek&model=deepseek-chat&temperature=0.7&message=你是谁 - Ollama(本地 qwen3):
/chat2?platform=ollama&model=qwen3:0.6b&temperature=0.7&message=你是谁 - DashScope(通义千问):
/chat2?platform=dashscope&model=qwen-plus&temperature=0.7&message=你是谁
bash
curl -N 'http://localhost:8080/chat2?platform=ollama&model=qwen3:0.6b&temperature=0.3&message=用三点概括面向对象编程核心'
4) 启动类
java
@SpringBootApplication
public class MultiModelApplication {
public static void main(String[] args) {
SpringApplication.run(MultiModelApplication.class, args);
}
}
运行与验证
- 设置环境变量(macOS/zsh 举例):
bash
export DEEPSEEK_API_KEY='你的deepseek_key'
export DASHSCOPE_API_KEY='你的dashscope_key'
- 确保本地 Ollama(可选)正常运行,并已拉取模型:
bash
ollama run qwen3:0.6b
- 启动应用:
bash
cd /Volumes/artisan/code/2025/spring-ai-artisan/03-multi-model-switch
./mvnw spring-boot:run
- 验证直切端点:
bash
curl 'http://localhost:8080/chat?model=deepseekR1&message=解释下RAG是什么'
- 验证平台+模型+流式端点:
bash
curl -N 'http://localhost:8080/chat2?platform=dashscope&model=qwen-plus&temperature=0.2&message=用两句话说明微服务的优缺点'
设计要点与最佳实践
- 解耦平台与模型 :通过
platforms映射与ChatOptions.model,将平台选择与具体模型名解耦,便于扩展。 - 流式输出:对长文本响应更友好,前端可边收边显,降低等待感。
- 多 Bean 注入 Map :按名称索引
ChatClient实例,快速实现"按名切换"。 - 安全与配置:生产环境使用更安全的密钥管理(KMS、Vault)。模型名应与平台兼容,不匹配会报错或无效。
- 容错 :对
platform、model判空与合法性校验;上游平台不通时返回可读错误并降级(如回退到本地 Ollama)。
常见问题(FAQ)
- 为什么
/chat的model用 Bean 名而非实际模型名?/chat是"按 Bean 名直切"的简单方式,适合预置几个典型配置;实际模型名通过 Bean 内默认options指定。 /chat2的model必须与平台支持的模型名一致吗? 是。比如platform=ollama时model应是本地已就绪的 Ollama 模型名。- 如何新增平台? 新增对应
ChatModel的依赖与 Bean,将其放入platforms映射,并在配置中补充默认参数即可。 - 如何控制输出长度、系统提示词等? 在
ChatOptions中继续填充如maxTokens、system等参数;或在prompt()中添加 system/assistant 指令。
至此,基于 Spring AI 的多平台多模型动态切换方案已经打通:可在 DeepSeek、DashScope 与本地 Ollama 之间灵活切换,支持按名直切与平台+模型+温度的流式对话,便于在实际项目中快速对比与落地。