1. 从单模型到统一入口:Chat Model API 接入的真实痛点
很多同学在写完 Spring AI 的第一个 Demo 之后,都会遇到一个很现实的问题:项目里散落着好几套 API Key。智谱一个、DeepSeek 一个、OpenAI 兼容的又一个,每个模型厂商的base-url和鉴权方式还不完全一样。等到要切换模型或者做灰度对比时,改配置改到怀疑人生。
Spring AI 的 Chat Model API 本身设计得挺优雅,ChatModel和StreamingChatModel两个接口把同步和流式都抽象好了,具体厂商的实现类只要遵循接口就能无缝替换。但抽象层解决的是代码层面的解耦,解决不了密钥管理和 endpoint 统一的问题。你依然要在application.yml里为每个厂商维护一份连接配置。
这篇要聊的,就是把 Chat Model API 的 endpoint 和 api-key 统一指向 TaoToken,用一套 Key 管理多个模型。TaoToken 是一个模型 API 聚合入口,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ,它提供 OpenAI 兼容的接口格式,所以 Spring AI 里基于 OpenAI 协议的那套配置可以直接复用。适合谁呢?已经有 Spring Boot 基础、手上跑过至少一个 Spring AI 对话 Demo、现在想把多模型 Key 收拢到一处的开发者。
我试过在三个模型之间来回切配置,每次都要改 yml、重启、验证,效率很低。统一到 TaoToken 之后,模型切换变成了改一个model参数的事,连接层完全不用动。下面从依赖、配置、Java 配置类到验证请求,一步步走完。
2. TaoToken 前置准备:拿到统一 Key 与确认 OpenAI 兼容端点
在动 Spring AI 的代码之前,先把 TaoToken 这边的准备工作做完。这一步不复杂,但顺序别搞反,否则后面配置填错了会浪费排查时间。
首先你需要一个 TaoToken 账号,然后到控制台创建 API Key。地址是 https://taotoken.net/api-keys ,登录后新建一个 Key,复制出来先存到安全的地方。这个 Key 就是后面application.yml里要填的api-key。
接着确认 endpoint。TaoToken 的 API 基础地址是 https://taotoken.net/api ,它兼容 OpenAI 的/v1/chat/completions路径。也就是说,Spring AI 的 OpenAI starter 把base-url指向这个地址后,请求会正常路由到对应模型。这里有个细节:Spring AI 的 OpenAI 实现默认会在 base-url 后面拼接/v1/chat/completions,所以你在配置里填的 base-url 应该是https://taotoken.net/api,不要自己再加/v1,否则会变成/api/v1/v1/...这种重复路径。
模型 ID 方面,TaoToken 支持多种模型,具体可用列表可以在模型对话页面查看,地址是 https://taotoken.net/models 。你在配置里填的model值要和平台上的一致,比如gpt-4o-mini、claude-3-5-sonnet这类。不同模型的计费和能力有差异,选一个你常用的先跑通。
如果你还没决定用哪个模型,可以先到模型对话页面手动发一条消息,确认 Key 和模型都能正常工作,再回到 Spring AI 里配置。这一步相当于把变量隔离出来,后面出问题就只可能是 Spring AI 配置的问题,而不是 Key 或模型本身的问题。
注意:API Key 不要硬编码进代码仓库。用环境变量注入,或者放到配置中心。下面示例里我会用
${TAOTOKEN_API_KEY}这种占位符。
3. 可复制配置:application.yml 与 Java 配置类指向 TaoToken
这一节是核心,给出可以直接抄的配置片段。分两部分:application.yml和 Java 配置类。先看 yml。
假设你用的是 Spring AI 的 OpenAI starter,依赖是spring-ai-starter-model-openai。在application.yml里这样写:
spring: ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: gpt-4o-mini temperature: 0.7 max-tokens: 1024这里api-key从环境变量读,base-url指向 TaoToken 的 API 地址,model填你要用的模型 ID。temperature和max-tokens是可选参数,按需调整。
如果你用的是spring-ai-starter-model-openai但想同时保留多个模型的配置,可以用 Java 配置类手动构建OpenAiChatModelBean。下面这个配置类把连接信息和模型参数都显式写出来,方便你理解每个字段的来源:
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class TaoTokenChatConfig { @Value("${spring.ai.openai.api-key}") private String apiKey; @Value("${spring.ai.openai.base-url}") private String baseUrl; @Bean public OpenAiApi openAiApi() { return OpenAiApi.builder() .baseUrl(baseUrl) .apiKey(apiKey) .build(); } @Bean public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi) { OpenAiChatOptions options = OpenAiChatOptions.builder() .model("gpt-4o-mini") .temperature(0.7) .maxTokens(1024) .build(); return OpenAiChatModel.builder() .openAiApi(openAiApi) .defaultOptions(options) .build(); } }这段配置的关键点有三个:baseUrl指向https://taotoken.net/api,apiKey用统一 Key,model指定具体模型。三件套齐了,Spring 容器里就有了一个可用的OpenAiChatModelBean。
如果你项目里同时需要多个模型,可以定义多个OpenAiChatModelBean,每个用不同的model值,然后用@Qualifier注入。比如一个用gpt-4o-mini做快速问答,一个用claude-3-5-sonnet做长文本生成。连接层共用同一个OpenAiApi,只是defaultOptions里的 model 不同。
提示:
OpenAiApi.builder()的baseUrl不要带尾部斜杠,Spring AI 内部会处理路径拼接。带斜杠可能导致双斜杠路径,部分网关会返回 404。
配置写完后,启动应用,如果日志里没有报OpenAiApi初始化失败,说明连接层已经就绪。接下来写一个 Controller 验证调用。
4. 验证请求:一次对话调用与日志断言
配置就绪后,写一个最简单的 Controller 来验证 Chat Model API 是否真的能通过 TaoToken 拿到回复。这个 Controller 用OpenAiChatModel的call方法,传入一个Prompt,然后打印响应内容和元数据。
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @RestController public class ChatVerifyController { private static final Logger log = LoggerFactory.getLogger(ChatVerifyController.class); private final OpenAiChatModel chatModel; public ChatVerifyController(OpenAiChatModel chatModel) { this.chatModel = chatModel; } @GetMapping("/verify") public String verify(@RequestParam(defaultValue = "用一句话介绍 Spring AI") String question) { UserMessage userMessage = new UserMessage(question); Prompt prompt = new Prompt(userMessage); ChatResponse response = chatModel.call(prompt); String answer = response.getResults().get(0).getOutput().getText(); log.info("TaoToken 返回内容: {}", answer); log.info("模型: {}", response.getMetadata().getModel()); log.info("Token 使用: {}", response.getMetadata().getUsage()); return answer; } }启动应用后,访问http://localhost:8080/verify?question=你好,观察控制台日志。成功的标志有三个:第一,返回内容非空,是一段正常的中文回复;第二,日志里模型字段显示的是你配置的 model ID;第三,Token 使用里有 prompt tokens 和 completion tokens 的数值。
如果返回内容正常但 Token 使用为空,可能是模型或网关没有返回 usage 字段,不影响功能,但计费统计会缺失。如果返回内容为空,先检查response.getResults()是否为空列表,再检查模型 ID 是否正确。
日志断言这块,你可以在测试类里用assertThat验证返回内容不为空:
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest class ChatModelVerifyTest { @Autowired private OpenAiChatModel chatModel; @Test void shouldReturnNonEmptyAnswer() { String answer = chatModel.call("你好"); assertThat(answer).isNotBlank(); } }跑通这个测试,说明从 Spring AI 到 TaoToken 的整条链路是通的。接下来可以在这个基础上加多轮对话、流式响应、结构化输出等高级功能。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
接入过程中最容易踩的坑集中在鉴权和路径拼接上。下面按真实报错逐个拆。
401 Unauthorized:这是最常见的。原因通常是api-key没读到或者值不对。检查环境变量TAOTOKEN_API_KEY是否真的注入到了运行环境。如果你在 IDE 里跑,确认 Run Configuration 里加了环境变量;如果打包成 jar 跑,确认启动脚本里 export 了。还有一种情况是 Key 复制时带了空格或换行,建议重新复制一次。另外,TaoToken 的 Key 和 OpenAI 官方的 Key 不通用,别混用。
local proxy failed / connection refused:这个报错说明 Spring AI 尝试连接base-url时失败了。先确认base-url写的是https://taotoken.net/api,没有多余路径。然后确认你的网络环境能正常访问这个地址,可以用curl https://taotoken.net/api/v1/models -H "Authorization: Bearer $TAOTOKEN_API_KEY"手动测一下。如果 curl 能通但 Spring AI 不通,检查是否有代理配置干扰,比如http_proxy环境变量指向了一个不可用的地址。
reading choices / Cannot deserialize:这个报错通常出现在响应格式不符合预期时。Spring AI 的 OpenAI 实现期望响应里有choices数组,如果网关返回了错误结构(比如{"error": {...}}),反序列化就会失败。先看完整报错里的 response body,确认是模型 ID 写错了还是 Key 权限不够。模型 ID 写错时,部分网关会返回 404 或 400,body 里会有明确提示。
OAuth / token endpoint 相关报错:如果你用的是某些需要 OAuth 流程的模型,Spring AI 的 OpenAI starter 默认走的是 API Key 鉴权,不涉及 OAuth。如果报错里出现 OAuth 字样,检查是不是误引入了其他 starter,或者base-url指向了一个需要 OAuth 的端点。TaoToken 的 API 走的是 Bearer Token,不需要 OAuth 流程。
排查顺序建议:先 curl 验证 Key 和 endpoint,再检查 Spring 配置,最后看代码里的 model 参数。把变量隔离,能省很多时间。
6. 统一 Key 之后的下一步:模型对话验证与 Coding Plan
链路跑通之后,你可以做两件事来巩固这套配置。第一,到模型对话页面手动发几条消息,对比不同模型的回复风格和速度,地址是 https://taotoken.net/models 。这样你能直观感受到同一个 Key 下不同模型的差异,方便后续选型。
第二,如果你打算把 Spring AI 用在长期编码或 Agent 场景里,可以了解一下 Coding Plan,地址是 https://taotoken.net/coding-plan 。它适合需要持续调用模型、对额度和稳定性有要求的开发场景。接入文档在 https://taotoken.net/doc ,里面有更详细的参数说明和示例。
回到代码层面,统一 Key 之后最直接的好处是:你可以在application.yml里只维护一份连接配置,模型切换通过ChatOptions的model参数动态指定。比如在 Controller 里根据请求参数切换模型:
@GetMapping("/switch") public String switchModel(@RequestParam String model, @RequestParam String question) { OpenAiChatOptions options = OpenAiChatOptions.builder() .model(model) .temperature(0.7) .build(); Prompt prompt = new Prompt(new UserMessage(question), options); return chatModel.call(prompt).getResults().get(0).getOutput().getText(); }这样你只需要一个OpenAiChatModelBean,就能在运行时切换不同模型。连接层、鉴权层完全不用动。实测下来,这种方式的切换延迟主要来自模型本身的响应时间,配置层没有额外开销。
最后提醒一点:多模型场景下,不同模型的max-tokens上限和temperature取值范围可能不同。切换模型时如果传了超出范围的参数,部分网关会返回 400。建议在ChatOptions里只设置通用参数,模型特有的参数在调用前根据模型 ID 做一次校验。