Spring AI实现一个项目使用多个相同OpenAI API协议(以ChatGPT和千问为例)
前言
在做Spring AI项目的时候,如果想引入多个大模型,会发现:诶!好像不行!!
当然,这里指的是只导入Spring AI框架的东西,不引入各个大模型自家的SDK情况下,并且两个大模型的协议还是相同的。
原因很简单:同一个ChatModel的实现类仅能实现一个。比如我们都知道的,千问其实可以用Open AI的API协议,引入open-ai的maven,在配置文件配置千问的参数即可。
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency>但是!如果你还要再使用Chat GPT模型呢?那我怎么配置?很明显这时候就不行了。虽然这概率不是很大,但是这也确确实实是我团队的项目中遇到的一个障碍点。
ps:我们团队目前正在开发第二个AI Agent产品(面向大众娱乐型的),已经进入公测阶段。这里不介绍,感兴趣文章底部会放个链接可以去玩一下
问题原因
(这里不讲太深,不细扒底层源码,讲一下大致的流程和用到的类而已)
在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.java
QwenChatProperties.java
QwenConnectionProperties.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而且时流式输出,会出现适配的问题,虽然不知道现在还有没有,但是上面这种实现方式可以很好地解决这个问题,如果以后项目中出现千问流式调用工具的适配问题,我直接修改我自己实现的类即可。
最后这里说一下我们小团队的项目,是一个很有意思的AI Agent小程序,面向大众娱乐化的。感兴趣可以看下面:
