前言
在做 Spring AI 项目的时候,如果想引入多个大模型,会发现似乎无法直接通过单一配置实现。
当然,这里指的是只导入 Spring AI 框架的东西,不引入各个大模型自家的 SDK 情况下,并且两个大模型的协议还是相同的。
原因很简单:同一个 ChatModel 的实现类仅能实现一个。比如我们都知道的,千问其实可以用 OpenAI 的 API 协议,引入 open-ai 的 maven,在配置文件配置千问的参数即可。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
但是,如果你还要再使用 ChatGPT 模型呢?那我怎么配置?很明显这时候就不行了。虽然这概率不是很大,但是这也确确实实是我团队的项目中遇到的一个障碍点。
问题原因
(这里不讲太深,不细扒底层源码,讲一下大致的流程和用到的类而已)
在 Spring AI 框架中,调用大模型 API 的流程其实就是你配置一个客户端 ChatClient,ChatClient 会配置 ChatModel,真正发起大模型调用的是这个 ChatModel,在 ChatModel 会按照自家的 API 协议封装请求体,比如 OpenAI API 协议用的是 org.springframework.ai.openai.api.OpenAiApi。调用完成后,再封装成 Spring AI 框架的 ChatResponse 返回。
以上是最简单的流程。如果我同时要实现 ChatGPT 的调用和千问的调用,发现用的都是 OpenAI 协议,而使用这个协议的 ChatModel 目前只有 OpenAiChatModel。你不能通过配置文件实现两个 OpenAiChatModel。你要么配置 ChatGPT 的配置,要么配置 Qwen 的配置。最终 Spring 框架也只会自动给你注入一个 Bean(这块在 Spring AI 框架中的 OpenAIChatAutoConfiguration 中)。
下面就来说说怎么解决。
解决后的效果
配置文件跟原生配置方式一样一点都不用变。
配置客户端的时候直接用我们写好的 QwenChatModel 即可,跟使用 OpenAIChatModel 一样,自动装配的,无需操心。这样子你的项目里既可以有调用 GPT 的 ChatClient 也可以再有一个调用 Qwen 的 ChatClient 了。而且非常简单方便,完全都是原生相同的操作。
接下来我们看看实现方式吧
实现方式
核心是我们再实现一个同样适用 OpenAIAPI 接口的 ChatModel 实现类,且自动装配要按照我们自己的前缀 (不能使用 openai 前缀不然就冲突了)
理解其实就很简单了,撸起袖子加油干!
配置类
首先看一下 Spring 原来的 OpenAIChatModel 是怎么自动装配的:
org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration
关键在他的装配条件:
@ConditionalOnClass(OpenAiApi.class)
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiChatProperties.class })
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OPENAI, matchIfMissing = true)
意思是:当项目的 classpath 中存在 OpenAiApi 类,并且应用配置中明确指定(或未指定但默认)使用 OpenAI 作为聊天模型时,Spring 容器才会自动启用并加载 OpenAiConnectionProperties 和 OpenAiChatProperties 这两个配置属性类。
OpenAiConnectionProperties 和 OpenAiChatProperties 都继承了 OpenAiParentProperties。
那我们就仿造他的这三个配置属性类自己实现:
需要创建以下文件:
QwenParentProperties.javaQwenChatProperties.javaQwenConnectionProperties.java
可以看到我们使用配置前缀是 spring.ai.qwen。当然你想改成什么都可以。
在 Properties 类中,有用到 Options 类,也就是具体的配置参数。这里虽然绝大部分和 OpenAI 协议一样,但我们还自己写一个,方便后面自己扩展。
QwenChatOptions.java
这里太长了,下面是代码部分。完整的直接参考:org.springframework.ai.openai.OpenAiChatOptions。只需要把 OpenAiChatOptions 改成 QwenChatOptions 即可。
好,接下来我们就要自动装配我们的 ChatModel 了。可以先把自动装配类写了,ChatModel 下一步再实现。
QwenChatAutoConfiguration.java
现在,QwenChatAutoConfiguration 就可以帮我们实现根据配置文件的配置,装配一个我们想要的第二个 ChatModel,这里我叫做 qwenChatModel。
注意,上面这段里一句:
QwenAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, chatProperties, "chat");
这是哪来的?我们是仿造 OpenAI 的 Configuration,使用先去看一下他的这段的作用:他是解析参数的,我们直接仿造他写一个即可,这里细节一点就是提示信息可以变动一下,不然以后报错都不能一眼看出是哪的配置出问题了。
QwenAutoConfigurationUtil.java
核心 ChatModel 的实现类
很好,现在我们就完成了自动装配的功能。下面着重实现 ChatModel,也就是核心部分。核心思想其实也就是把类名换成自己的比如 QwenChatModel,然后里面的 Options 使用上面我们自己实现的即可。
QwenChatModel.java
原代码太长了,我压缩了很多换行和空格。如果不是应急的话可以自己去实现一下。参考 org.springframework.ai.openai.OpenAiChatModel。
使用
到这里我们的项目已经可以根据 Properties 类中配置的前缀注入我们自定义的 ChatModel 的 Bean 了。那么我们怎么去使用?其实跟 OpenAIChatModel 使用一样。在配置类里配置一个 ChatClient 使用我们自己的 ChatModel 即可。调用的时候调用这个 ChatClient。
最后
到这里我们就可以实现根据自己的配置前缀为我们自己的项目配置多个相同协议的 ChatModel 的实现了。这个场景来源于真实的项目里,这个任务并不是很急所以我并没有引入千问的 sdk 而是保持 Spring AI 框架这一套的导入,在里面添加完成这个需求。
在 25 年 7 月的时候,如果用 spring-ai-starter-model-openai (当时还不是这个名字) 调用千问,如果大模型调用了工具@Tool 而且时流式输出,会出现适配的问题,虽然不知道现在还有没有,但是上面这种实现方式可以很好地解决这个问题,如果以后项目中出现千问流式调用工具的适配问题,我直接修改我自己实现的类即可。

