☰
轻松入门SpringAI:用TaoToken统一Key接入Spring AI其他模型
2026/10/1 14:37:12 网站建设 项目流程

1. 从单模型到多模型:Spring AI 接入的真实痛点

很多同学第一次用 Spring AI,都是照着官方 Quick Start 走一遍:加一个spring-ai-openai-spring-boot-starter依赖,在application.yml里填上api-key,然后写一个ChatClient就能对话了。跑通那一刻确实爽,但接下来问题就来了——项目里想同时用 DeepSeek 做推理、用通义千问做中文润色、用 GLM-4 做函数调用,难道要维护三套 Key、三套 Base URL、三套配置类吗?

这就是 Spring AI 多模型扩展场景里最典型的痛点。默认的ChatClient只绑定了一个OpenAiChatModel,而这个OpenAiChatModel又只认一份spring.ai.openai.*配置。你想换模型,就得改base-url、api-key、model三个字段,改完重启,之前那个模型就用不了了。更麻烦的是,不同厂商的 Key 格式、额度、计费方式都不一样,团队协作时谁用了哪个 Key 经常对不上账。

我试过最笨的办法:给每个模型写一个@Configuration类,手动 new 一个OpenAiApi,再 new 一个OpenAiChatModel,用@Qualifier区分注入。代码能跑,但配置散落在 Java 里,改一个模型名要重新编译,运维同学看了直摇头。后来换成多份application-xxx.yml,用spring.profiles.active切换,结果每次切模型都要重启服务,调试阶段效率极低。

真正让我下定决心改造的,是一次线上事故:某个模型的 Key 额度用完了,接口开始返回 401,但因为我们只配了一个模型,整个对话功能全挂了。那一刻我意识到,多模型不只是"想用哪个用哪个",更是"一个挂了能立刻切到另一个"的容灾能力。

所以这篇文章要解决的问题很具体:在 Spring AI 里,如何用一套统一的 Key 和 API 通道,平滑地从单模型切换到多模型。核心思路是把"厂商差异"收敛到一个统一的 OpenAI 兼容入口,Spring AI 侧只认一个base-url和一个api-key,具体调哪个模型通过model参数动态指定。这样你既保留了 Spring AI 原生的ChatClient写法,又获得了多模型自由切换的能力。

适合谁看?如果你已经跑通过 Spring AI 的 Hello World,现在想扩展第二个、第三个模型;或者你正在做技术选型,想快速对比几个国产模型的效果;又或者你受够了为每个模型维护一套配置——那这篇就是写给你的。接下来我会给出完整的依赖坐标、可复制的application.yml、一次真实的对话验证请求,以及踩过的坑。

2. TaoToken 前置准备:统一 Key 与 API 通道

在动手改代码之前,先把"统一入口"这件事说清楚。Spring AI 的OpenAiChatModel本质上是一个 OpenAI 协议的客户端,它只关心三件事:请求发到哪个base-url、用哪个api-key鉴权、model字段填什么。只要某个服务同时满足这三点,Spring AI 就能把它当成"OpenAI"来用。

TaoToken 在这里扮演的角色,就是提供这样一个 OpenAI 兼容的统一通道。你不需要为 DeepSeek、Qwen、GLM 分别注册账号、分别管理 Key,而是用一份 Key 走同一个base-url,通过model参数告诉它你要调哪个模型。对 Spring AI 来说,配置项从"每个模型一套"变成了"全局一套",这就是"统一 Key"的价值。

具体怎么准备?分三步。

第一步,拿到你的 API Key。访问控制台页面创建:

https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite

在控制台里创建一个新的 API Key,复制出来形如sk-xxxxxxxx的字符串。这个 Key 就是你后面填进application.yml的那一个,所有模型共用。

第二步,确认 Base URL。TaoToken 的 API 入口是:

https://taotoken.net/api

注意这里不要加/v1后缀,Spring AI 的 OpenAI starter 会自动拼接/v1/chat/completions。如果你手动加了/v1,最终请求会变成/v1/v1/chat/completions,直接 404。这个坑我在第一次配置时踩过,排查了半小时才发现是路径重复。

第三步,确认你要用的模型 ID。不同模型的 ID 命名规则不一样,比如 DeepSeek 系列常见的是deepseek-chat、deepseek-reasoner,Qwen 系列是qwen-max、qwen-plus,GLM 系列是glm-4-flash、glm-4-plus。这些 ID 就是你要填到model字段里的值。建议先去模型对话页面确认一下当前可用的模型列表:

https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite

在这里你可以直接发一条消息测试,确认模型 ID 拼写正确、Key 有额度。这一步很关键,因为 Spring AI 侧报错信息往往很模糊,先在对话页面验证能省掉大量排查时间。

注意:API Key 属于敏感凭证,不要硬编码到 Java 代码里,也不要提交到 Git。生产环境建议用环境变量或配置中心注入,application.yml里写${TAOTOKEN_API_KEY}这种占位符。

如果你后续要做长期编码或 Agent 类应用,可以了解一下 Coding Plan,它针对高频调用场景做了额度优化:

https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite

前置准备就这些。总结一下:一个 Key、一个 Base URL、一组模型 ID,三样东西备齐,接下来就是纯配置和代码的事了。

3. 可复制配置:application.yml 与依赖坐标

这一节是全文的核心,直接给你能复制粘贴的配置。先看依赖坐标。

Spring AI 的版本迭代比较快,建议用 1.0.0 及以上的正式版。在pom.xml里加两个依赖:一个是 OpenAI starter(提供OpenAiChatModel和ChatClient),一个是 Spring Boot Web(如果你要写 Controller 验证的话)。

<dependencies> <!-- Spring AI OpenAI Starter:提供 ChatClient / OpenAiChatModel --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0</version> </dependency> <!-- Web 依赖,用于写验证接口 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!-- 如果用的是里程碑版本,需要加这个仓库 --> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>

依赖加完,接下来是application.yml。这是最关键的一段,我把它拆成"基础连接"和"模型参数"两部分来看。

spring: ai: openai: # 统一入口:TaoToken 的 API 地址,注意不要加 /v1 base-url: https://taotoken.net/api # 统一 Key:所有模型共用这一个 api-key: ${TAOTOKEN_API_KEY:sk-你的key} chat: options: # 默认模型,不指定时用这个 model: deepseek-chat # 采样温度,0-2,越低越稳定 temperature: 0.7 # 最大输出 token 数 max-tokens: 2048

这段配置里,base-url和api-key是全局的,model是默认值。当你只想用一个模型时,这样配就够了,和官方 Quick Start 没区别。但多模型的关键在于——model可以在运行时动态覆盖,不需要改配置文件。

怎么动态覆盖?Spring AI 提供了OpenAiChatOptions,你可以在构造Prompt的时候传进去:

ChatOptions options = OpenAiChatOptions.builder() .model("qwen-max") // 临时切换到通义千问 .temperature(0.5) .build(); Prompt prompt = new Prompt("用一句话解释什么是依赖注入", options); String answer = chatClient.prompt(prompt).call().content();

看到没?base-url和api-key完全没动,只改了model,就完成了从 DeepSeek 到 Qwen 的切换。这就是统一 Key 通道的核心优势:连接层统一,模型层灵活。

如果你想让配置更清晰,可以把常用模型 ID 抽成常量或配置项:

app: models: reasoning: deepseek-reasoner chinese: qwen-max function-call: glm-4-flash long-context: moonshot-v1-128k

然后在 Java 里用@Value("${app.models.reasoning}")注入。这样换模型只改 yml,不用动代码。

再补充一个多ChatClient的写法。如果你希望不同业务线用不同默认模型,可以定义多个 Bean:

@Configuration public class ChatClientConfig { @Bean("reasoningClient") public ChatClient reasoningClient(OpenAiChatModel model) { return ChatClient.builder(model) .defaultOptions(OpenAiChatOptions.builder() .model("deepseek-reasoner") .build()) .build(); } @Bean("chineseClient") public ChatClient chineseClient(OpenAiChatModel model) { return ChatClient.builder(model) .defaultOptions(OpenAiChatOptions.builder() .model("qwen-max") .build()) .build(); } }

用的时候@Qualifier("reasoningClient")注入即可。注意这里两个 Bean 共用同一个OpenAiChatModel,也就是共用同一份base-url和api-key,只是默认模型不同。这就是"统一 Key 接入其他模型"在 Spring AI 里的标准落地方式。

提示:OpenAiChatOptions里的model会覆盖 yml 里的默认值,但base-url和api-key不会——它们绑定在OpenAiApi实例上,全局唯一。这正是我们想要的效果。

配置部分到此完整。下一节我们写一个真实的验证请求,确认整条链路通了。

4. 验证请求:一次对话跑通多模型切换

配置写好了,不验证等于没写。这一节给你一个最小的 Controller,发一次 HTTP 请求就能看到结果,同时验证多模型切换是否生效。

先写 Controller:

@RestController @RequestMapping("/ai") public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/chat") public String chat(@RequestParam String msg, @RequestParam(defaultValue = "deepseek-chat") String model) { ChatOptions options = OpenAiChatOptions.builder() .model(model) .temperature(0.7) .build(); return chatClient.prompt(new Prompt(msg, options)) .call() .content(); } }

这个接口接收两个参数:msg是问题,model是模型 ID,默认deepseek-chat。启动 Spring Boot 应用后,用 curl 发请求:

# 第一次:用 DeepSeek 回答 curl "http://localhost:8080/ai/chat?msg=用一句话解释什么是JVM&model=deepseek-chat" # 第二次:同一个接口,切到通义千问 curl "http://localhost:8080/ai/chat?msg=用一句话解释什么是JVM&model=qwen-max" # 第三次:切到 GLM-4 curl "http://localhost:8080/ai/chat?msg=用一句话解释什么是JVM&model=glm-4-flash"

如果配置正确,你会看到三次请求都返回 200,内容风格略有不同,但都能正常回答。关键点在于:三次请求用的是同一个base-url、同一个api-key,只有model参数不同。这就是统一 Key 通道跑通的标志。

成功返回大概长这样(内容因模型而异):

JVM 是 Java 虚拟机,它负责把 Java 字节码解释或编译成机器码, 并提供内存管理、垃圾回收等运行时能力,让 Java 程序能跨平台运行。

如果返回的是 JSON 而不是纯文本,说明你的ChatClient配置里没加.content(),或者用了entity()方法。这里我们用.content()直接取字符串,最省事。

再验证一个更贴近生产的场景:容灾切换。假设 DeepSeek 临时不可用,你希望自动降级到 Qwen。可以写一个简单的重试逻辑:

public String chatWithFallback(String msg) { String[] models = {"deepseek-chat", "qwen-max", "glm-4-flash"}; for (String model : models) { try { ChatOptions options = OpenAiChatOptions.builder() .model(model) .build(); return chatClient.prompt(new Prompt(msg, options)) .call() .content(); } catch (Exception e) { log.warn("模型 {} 调用失败,尝试下一个", model, e); } } throw new RuntimeException("所有模型均不可用"); }

这段代码的意义在于:因为所有模型走同一个通道,切换成本极低,你可以在业务层轻松实现"主模型 + 备用模型"的降级策略。如果每个模型一套配置,这种降级要写一堆 if-else 和不同的客户端实例,维护成本高得多。

验证通过后,你可以把model参数换成任意支持的模型 ID,比如moonshot-v1-128k测长上下文,deepseek-reasoner测推理。整个过程中,application.yml一行都不用改。

注意:不同模型的max-tokens上限不一样,如果返回被截断,检查一下 yml 里的max-tokens是否超过了该模型的上限。超限时部分厂商会直接报错,部分会静默截断。

到这里,从配置到验证的完整闭环就走完了。下一节我们看看实际会遇到的报错。

5. 常见报错排查:401、路径重复与模型名错误

配置和代码都对,但实际跑起来还是会遇到各种报错。这一节我把踩过的坑按报错信息分类整理,方便你对照排查。

报错一:401 Unauthorized / invalid api key

org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 Unauthorized: [no body]

这是最常见的。原因通常有三个:Key 拼写错误、Key 前后有空格、环境变量没注入成功。先检查application.yml里的api-key是不是${TAOTOKEN_API_KEY:sk-你的key}这种写法,如果是,确认启动时环境变量TAOTOKEN_API_KEY真的设置了。可以在启动日志里加一行打印:

@Value("${spring.ai.openai.api-key}") private String apiKey; @PostConstruct public void check() { log.info("API Key 前缀: {}", apiKey.substring(0, Math.min(8, apiKey.length()))); }

如果打印出来是sk-你的k这种占位符,说明环境变量没生效,Spring 用了默认值。另外注意 Key 不要带引号,YAML 里api-key: "sk-xxx"和api-key: sk-xxx都能解析,但复制时容易带上不可见字符。

报错二:404 Not Found / 路径重复

404 Not Found: [{"error":{"message":"Not Found"}}]

如果你在base-url里写了https://taotoken.net/api/v1,就会触发这个。Spring AI 的 OpenAI starter 会自动在base-url后面拼/v1/chat/completions,所以base-url只能写到/api。检查你的配置:

# 错误写法 base-url: https://taotoken.net/api/v1 # 正确写法 base-url: https://taotoken.net/api

报错三:model not found / 模型不存在

400 Bad Request: {"error":{"message":"model 'deepseek' not found"}}

这是模型 ID 拼错了。注意模型 ID 是大小写敏感的,deepseek-chat和DeepSeek-Chat可能不一样。建议先去模型对话页面确认准确的 ID,再填到代码里。另外有些模型有版本后缀,比如glm-4-flash和glm-4-flash-250414是两个不同的 ID,用错了会报错。

报错四:reading choices / 响应解析失败

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type ... from Object value (token `JsonToken.START_OBJECT`)

这个报错通常出现在你用了非 OpenAI 兼容的接口,或者base-url指向了一个返回格式不同的服务。Spring AI 的OpenAiChatModel期望响应里有choices数组,如果服务返回的是别的结构(比如某些厂商的原生协议),就会解析失败。解决办法是确认你走的是 OpenAI 兼容通道,TaoToken 的/api入口就是兼容格式,正常不会出现这个问题。

报错五:OAuth / token 过期

401 Unauthorized: {"error":"token expired"}

如果你用的是某些需要 OAuth 换 token 的厂商原生接口,会遇到这个问题。但走统一 Key 通道时,Key 是长期有效的,不会出现 OAuth 刷新问题。如果你确实遇到了,检查是不是误配了某个厂商的原生base-url,而不是统一入口。

报错六:连接超时 / connection timed out

java.net.SocketTimeoutException: Connect timed out

先确认网络能访问https://taotoken.net/api,可以用 curl 直接测:

curl -X POST "https://taotoken.net/api/v1/chat/completions" \ -H "Authorization: Bearer sk-你的key" \ -H "Content-Type: application/json" \ -d '{"model":"deepseek-chat","messages":[{"role":"user","content":"hi"}]}'

如果 curl 能通但 Java 不通,检查是不是代理配置干扰了。如果 curl 也不通,那就是网络环境问题,换个网络再试。

排查顺序建议:先 curl 验证 Key 和网络,再检查 yml 配置,最后看 Java 代码里的model参数。大部分问题都出在前两步。

6. 多模型扩展的下一步:从验证到生产

跑通验证请求之后,你手里就有了一套可工作的多模型接入方案。接下来聊聊怎么把它用到真实项目里,以及几个实用的经验。

第一,模型选择不要拍脑袋。我建议先用同一个问题在几个模型上跑一遍,对比响应质量和延迟。比如"用 Java 写一个线程安全的单例"这种题,DeepSeek 和 GLM-4 的代码风格差异很明显。你可以写一个批量测试脚本,把同一批问题发给不同模型,人工评估后再定默认模型。这个过程用统一 Key 通道做特别快,改个model参数就行。

第二,给模型调用加超时和重试。Spring AI 默认的超时可能偏长,生产环境建议显式配置:

spring: ai: openai: chat: options: model: deepseek-chat # 连接超时和读取超时 base-url: https://taotoken.net/api

如果 starter 没暴露超时配置项,可以通过自定义RestClient.Builder或WebClient.Builder来设置。重试逻辑参考上一节的 fallback 写法,注意重试次数不要太多,避免放大故障。

第三,记录每次调用用了哪个模型。多模型场景下,出问题时第一件事就是确认"这次请求走的哪个模型"。建议在日志里打上model和traceId:

log.info("AI 调用 model={} msgLength={}", model, msg.length());

如果后续要做成本核算,这个日志就是数据来源。不同模型的计费差异很大,没有日志根本算不清账。

第四,长上下文场景单独处理。像moonshot-v1-128k这种长上下文模型,输入 token 数可能很大,请求体也大。建议对输入做长度检查,超过阈值时先截断或分段,避免一次性发太多导致超时。同时注意max-tokens要设合理,设太大可能触发厂商的硬限制。

第五,Key 轮换和额度监控。统一 Key 虽然方便,但也是单点。建议在控制台设置额度告警,快用完时提前换 Key。如果团队多人共用,可以按人分配不同的 Key,方便追踪用量。控制台里可以管理多个 Key:

https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite

最后说一个我自己的习惯:新项目接入时,先只配一个模型跑通全链路,确认无误后再加第二个、第三个。不要一上来就配五个模型,出问题时排查面太大。等单模型稳定了,多模型切换其实就是改一个字符串的事。

如果你在配置过程中遇到本文没覆盖的报错,可以对照接入文档里的错误码说明:

https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

文档里有各厂商模型 ID 的完整列表和常见错误码解释。把base-url、api-key、model这三件套对齐,Spring AI 的多模型扩展就没有想象中那么复杂。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询