背景与目标
- 背景: 企业在落地生成式 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:
<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 地址等:
spring.application.name=chat-client# deepseekspring.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:11434spring.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,形成“即插即用”的客户端:
@Configurationpublic 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:
@Slf4j@RestControllerpublic 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。
调用示例:
curl 'http://localhost:8080/chat?model=deepseekV3&message=用50字解释大语言模型'3) 平台+模型+温度+流式端点 /chat2
支持 platform、model、temperature 的动态指定,并以流式文本输出:
@RestControllerpublic 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 参数对象:
@Datapublic 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=你是谁curl -N 'http://localhost:8080/chat2?platform=ollama&model=qwen3:0.6b&temperature=0.3&message=用三点概括面向对象编程核心'4) 启动类
@SpringBootApplicationpublic class MultiModelApplication { public static void main(String[] args) { SpringApplication.run(MultiModelApplication.class, args); }}运行与验证
- 设置环境变量(macOS/zsh 举例):
export DEEPSEEK_API_KEY='你的deepseek_key'export DASHSCOPE_API_KEY='你的dashscope_key'- 确保本地 Ollama(可选)正常运行,并已拉取模型:
ollama run qwen3:0.6b- 启动应用:
cd /Volumes/artisan/code/2025/spring-ai-artisan/03-multi-model-switch./mvnw spring-boot:run- 验证直切端点:
curl 'http://localhost:8080/chat?model=deepseekR1&message=解释下RAG是什么'- 验证平台+模型+流式端点:
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 之间灵活切换,支持按名直切与平台+模型+温度的流式对话,便于在实际项目中快速对比与落地。
