解决springai 项目中引入多个chatModel存在冲突问题
报错:
Parameter 1 of method chatClientBuilder in org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration required a single bean, but 3 were found:- alibabaOpenAiChatModel: defined by method 'alibabaOpenAiChatModel' in class path resource [com/spring/springai/config/CommonConfiguration.class]- ollamaChatModel: defined by method 'ollamaChatModel' in class path resource [org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.class]- openAiChatModel: defined by method 'openAiChatModel' in class path resource [org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.class]This may be due to missing parameter name informationAction:Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
问题原因:
这个错误是因为 Spring 容器中存在多个
ChatModel
类型的 bean,但ChatClientAutoConfiguration
只需要一个。
方案一:使用 @Primary
注解(推荐)
在公共配置文件 CommonConfiguration.java 中给 alibabaOpenAiChatModel使用 @Primary
注解,
例如:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.ai.chat.model.ChatModel;@Configuration
public class CommonConfiguration {@Bean@Primary // 添加这个注解public ChatModel alibabaOpenAiChatModel() {// 你的配置代码return ...;}
}
完整代码如下
package com.spring.springai.config;import com.spring.springai.constants.SystemConstants;
import com.spring.springai.model.AlibabaOpenAiChatModel;
import com.spring.springai.tools.CourseTools;
import io.micrometer.observation.ObservationRegistry;
import org.apache.pdfbox.util.Vector;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.model.SimpleApiKey;
import org.springframework.ai.model.tool.ToolCallingManager;
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatProperties;
import org.springframework.ai.model.openai.autoconfigure.OpenAiConnectionProperties;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
//import org.springframework.ai.vectorstore.redis.RedisVectorStore;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
//import redis.clients.jedis.JedisPooled;@Configuration
public class CommonConfiguration {/*** pdf 问答** @param model* @param chatMemory* @param vectorStore* @return*/@Beanpublic ChatClient pdfChatClient(OpenAiChatModel model, ChatMemory chatMemory, VectorStore vectorStore) {return ChatClient.builder(model).defaultSystem("请根据上下文回答问题,遇到上下文没有的问题,不要随意编造。").defaultAdvisors(new SimpleLoggerAdvisor())//配置日志advisor,添加日志记录.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())// 添加会话记忆功能.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).// 添加向量库searchRequest(SearchRequest.builder().similarityThreshold(0.6).// 设置相似度阈值topK(2).// 设置返回文档数量build()).build()).build();}/*** 解析拆分PDF* 这是使用的是基于内存的 SimpleVectorStore向量库** @param embeddingModel* @return*/@Beanpublic VectorStore vectorStore(OpenAiEmbeddingModel embeddingModel) {return SimpleVectorStore.builder(embeddingModel).build();}// @Bean
// public RedisVectorStore redisVectorStore(JedisPooled jedis,OpenAiEmbeddingModel embeddingModel) {
// return RedisVectorStore.builder(jedis,embeddingModel).build();
// }/*** 定义记忆存储方式,注册ChatMemory对象* 与视频讲解中不同的是,SpirngAI中,ChatMemory的实现,现在统一为:MessageWindowChatMemory* return new InMemoryChatMemoryRepository();** @return*/@Beanpublic ChatMemory chatMemory() {return MessageWindowChatMemory.builder().chatMemoryRepository(new InMemoryChatMemoryRepository()) // 设置存储库.maxMessages(20) // 记忆窗口大小(保留最近的10条消息).build();}//这个用不到@Beanpublic ChatMemory chatMemory2() {// 使用 MessageWindowChatMemory 作为默认内存策略(窗口消息保留)// 默认使用的存储库就是InMemory,默认窗口大小是20return MessageWindowChatMemory.builder().build();}//智能机器人@Beanpublic ChatClient chatClient(AlibabaOpenAiChatModel model, ChatMemory chatMemory) {return ChatClient.builder(model).defaultOptions(ChatOptions.builder().model("qwen-omni-turbo-latest").build()) //单独设置用这个全模态模型.defaultSystem("你是热心、可爱的智能助手,你的名字叫小团团,请以小团团的身份和语气回答问题.").defaultAdvisors(new SimpleLoggerAdvisor())//配置日志advisor,添加日志记录.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())// 添加会话记忆功能.build();}//女友哄哄模拟器 客户端@Beanpublic ChatClient gameChatClient(OpenAiChatModel model, ChatMemory chatMemory) {return ChatClient.builder(model).defaultSystem(SystemConstants.GAME_SYSTEM_PROMPT).defaultAdvisors(new SimpleLoggerAdvisor())//配置日志advisor,添加日志记录.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())// 添加会话记忆功能.build();}//智能客服@Beanpublic ChatClient serviceChatClient(OpenAiChatModel model, ChatMemory chatMemory, CourseTools courseTools) {return ChatClient.builder(model).defaultSystem(SystemConstants.SERVICE_SYSTEM_PROMPT_3).defaultAdvisors(new SimpleLoggerAdvisor())//配置日志advisor,添加日志记录.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())// 添加会话记忆功能.defaultTools(courseTools).build();}//阿里巴巴模型 兼容springAi 对 音频文件的处理@Bean@Primary // 添加这个注解public AlibabaOpenAiChatModel alibabaOpenAiChatModel(OpenAiConnectionProperties commonProperties,OpenAiChatProperties chatProperties,ObjectProvider<RestClient.Builder> restClientBuilderProvider,ObjectProvider<WebClient.Builder> webClientBuilderProvider,ToolCallingManager toolCallingManager,RetryTemplate retryTemplate,ResponseErrorHandler responseErrorHandler,ObjectProvider<ObservationRegistry> observationRegistry,ObjectProvider<ChatModelObservationConvention> observationConvention) {// 配置基础参数String baseUrl = StringUtils.hasText(chatProperties.getBaseUrl()) ?chatProperties.getBaseUrl() : commonProperties.getBaseUrl();String apiKey = StringUtils.hasText(chatProperties.getApiKey()) ?chatProperties.getApiKey() : commonProperties.getApiKey();String projectId = StringUtils.hasText(chatProperties.getProjectId()) ?chatProperties.getProjectId() : commonProperties.getProjectId();String organizationId = StringUtils.hasText(chatProperties.getOrganizationId()) ?chatProperties.getOrganizationId() : commonProperties.getOrganizationId();// 构建连接头信息Map<String, List<String>> connectionHeaders = new HashMap<>();if (StringUtils.hasText(projectId)) {connectionHeaders.put("OpenAI-Project", List.of(projectId));}if (StringUtils.hasText(organizationId)) {connectionHeaders.put("OpenAI-Organization", List.of(organizationId));}// 获取HTTP客户端构建器RestClient.Builder restClientBuilder =restClientBuilderProvider.getIfAvailable(RestClient::builder);WebClient.Builder webClientBuilder =webClientBuilderProvider.getIfAvailable(WebClient::builder);// 构建OpenAI API客户端OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(baseUrl).apiKey(new SimpleApiKey(apiKey)).headers(CollectionUtils.toMultiValueMap(connectionHeaders)).completionsPath(chatProperties.getCompletionsPath()).embeddingsPath("/v1/embeddings").restClientBuilder(restClientBuilder).webClientBuilder(webClientBuilder).responseErrorHandler(responseErrorHandler).build();// 构建自定义聊天模型AlibabaOpenAiChatModel chatModel = AlibabaOpenAiChatModel.builder().openAiApi(openAiApi).defaultOptions(chatProperties.getOptions()).toolCallingManager(toolCallingManager).retryTemplate(retryTemplate).observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)).build();// 设置观察约定observationConvention.ifAvailable(chatModel::setObservationConvention);return chatModel;}
}
方案二:使用 @Qualifier
注解
在需要注入的地方指定具体的 bean 名称:
chatServiceImpl.java 完整代码如下
package com.spring.springai.service.impl;import com.spring.springai.service.ChatService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;/*** 实现类*/
@Service
public class ChatServiceImpl implements ChatService {private ChatClient chatClient;//构造函数 构造器注入,自动配置方式(推荐)public ChatServiceImpl(@Qualifier("openAiChatModel") ChatModel chatModel) {this.chatClient = ChatClient.builder(chatModel).defaultSystem("你是一个非常聪明的人工智能助手,可以帮我解决很多问题,我为你取一个好听的名字叫'旺仔'").build();}@Overridepublic String chatTest(String prompt) {return chatClient.prompt(prompt).call().content();}
}