Spring Ai 04 解决 ChatClient 初始化冲突问题

在基于 Spring AI 开发 AI 应用时,很多开发者会遇到一个典型问题:当项目中引入多个 AI 智能体(如 Ollama、DeepSeek)依赖后,直接使用官网默认方式初始化ChatClient会因无法确定具体智能体而抛出 Bean 冲突异常。本文将详细讲解问题原因,并提供一种优雅的多智能体集成方案,让你可以灵活切换不同的 AI 服务。

一、问题背景:默认方式的局限性

Spring AI 官网提供的ChatClient使用示例如下:

java 复制代码
@RestController
class MyController {

    private final ChatClient chatClient;

    // 直接通过Builder构建,依赖默认的ChatModel
    public MyController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/ai")
    String generation(String userInput) {
        return this.chatClient.prompt()
            .user(userInput)
            .call()
            .content();
    }
}

这种方式的核心问题在于:

  • ChatClient.Builder会自动匹配上下文唯一的ChatModel实现类
  • 当 pom.xml 中引入多个智能体依赖(如 ollama-spring-boot-starter、deepseek-spring-boot-starter)时,Spring 容器中会存在多个ChatModel Bean
  • 初始化ChatClient时无法确定具体使用哪个智能体,最终抛出NoUniqueBeanDefinitionException异常

临时解决方案:确保 pom.xml 中只保留一个智能体依赖,但这会丧失多智能体灵活切换的能力。

二、最优解决方案:自定义多 ChatClient Bean

步骤 1:配置多智能体 ChatClient Bean

创建配置类,为每个智能体单独声明ChatClient Bean,明确绑定对应的ChatModel

java 复制代码
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatConfiguration {

    /**
     * 声明Ollama智能体的ChatClient Bean
     * @param ollamaChatModel 自动注入的OllamaChatModel(由Spring AI自动配置)
     * @return Ollama专属ChatClient
     */
    @Bean
    public ChatClient ollamaChatClient(OllamaChatModel ollamaChatModel) {
        return ChatClient.builder(ollamaChatModel).build();
    }

    /**
     * 声明DeepSeek智能体的ChatClient Bean
     * @param deepSeekChatModel 自动注入的DeepSeekChatModel(由Spring AI自动配置)
     * @return DeepSeek专属ChatClient
     */
    @Bean
    public ChatClient deepseekChatClient(DeepSeekChatModel deepSeekChatModel) {
        return ChatClient.builder(deepSeekChatModel).build();
    }
}

步骤 2:在 Controller 中按需注入使用

通过@Resource注解按名称注入不同的ChatClient,实现多智能体的灵活调用:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/chatClient")
@Slf4j
public class ChatClientController {

    // 注入DeepSeek专属ChatClient
    @Resource
    private ChatClient deepseekChatClient;

    // 注入Ollama专属ChatClient
    @Resource
    private ChatClient ollamaChatClient;

    /**
     * 调用Ollama智能体实现流式响应
     * @param userInput 用户输入的问题
     * @return 流式返回的AI回答
     */
    @GetMapping(value = "/chat/ollama", produces = MediaType.TEXT_EVENT_STREAM_VALUE + ";charset=utf-8")
    public Flux<String> streamOllama(String userInput) {
        log.info("Ollama接收用户输入:{}", userInput);
        return ollamaChatClient.prompt()
                .user(userInput)
                .stream() // 流式调用
                .content(); // 提取响应内容
    }

    /**
     * 调用DeepSeek智能体实现流式响应
     * @param userInput 用户输入的问题
     * @return 流式返回的AI回答
     */
    @GetMapping(value = "/chat/deepseek", produces = MediaType.TEXT_EVENT_STREAM_VALUE + ";charset=utf-8")
    public Flux<String> streamDeepSeek(String userInput) {
        log.info("DeepSeek接收用户输入:{}", userInput);
        return deepseekChatClient.prompt()
                .user(userInput)
                .stream()
                .content();
    }

    /**
     * 非流式调用示例(同步返回)
     */
    @GetMapping("/chat/ollama/sync")
    public String syncOllama(String userInput) {
        return ollamaChatClient.prompt()
                .user(userInput)
                .call() // 同步调用
                .content();
    }
}

步骤 3:配置文件说明

在 application.yml/application.properties 中配置各智能体的连接信息:

XML 复制代码
spring:
  ai:
    # Ollama配置
    ollama:
      base-url: http://localhost:11434
      model: gemma3:4b
    # DeepSeek配置
    deepseek:
      api-key: your-deepseek-api-key
      model: deepseek-chat

三、核心优势

  1. 解耦性 :每个智能体对应独立的ChatClient,避免 Bean 冲突
  2. 灵活性:可根据业务场景(如成本、响应速度、能力)灵活选择智能体
  3. 扩展性 :新增智能体时,只需在配置类中新增对应的ChatClient Bean 即可
  4. 兼容性:完全兼容 Spring AI 的原生 API,无需修改业务逻辑

四、注意事项

  1. 确保各智能体的依赖已正确引入 pom.xml,例如:
XML 复制代码
<!-- Ollama依赖 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<!-- DeepSeek依赖 -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-deepseek-spring-boot-starter</artifactId>
</dependency>
  1. 各智能体的配置参数需正确填写,否则会导致连接失败
  2. 流式响应需指定produces = "text/stream;charset=utf-8",避免中文乱码

总结

  1. Spring AI 默认方式初始化ChatClient仅适用于单智能体场景,多智能体下会因 Bean 冲突报错;
  2. 通过自定义配置类为每个智能体声明独立的ChatClient Bean,可彻底解决多智能体集成冲突问题;
  3. 在 Controller 中按名称注入不同的ChatClient,可实现多智能体的灵活调用与切换。
相关推荐
花千树-0103 分钟前
多步骤 ReAct 实战:让 Agent 自主完成航司比价与订票
java·agent·function call·react agent·harness·j-langchain·多步骤推理
databook5 分钟前
从写代码到问问题:2026年,AI如何重构数据科学工作流
人工智能·后端·数据分析
二月龙8 分钟前
Go并发编程避坑指南:如何彻底解决死锁(Deadlock)问题
后端
xcLeigh12 分钟前
飞算 JavaAI 进阶实战:从代码生成到系统架构优化的全流程指南
java·系统架构·代码生成·java开发·飞算javaai炫技赛·javaai·飞算
m0_6948455712 分钟前
CRUD (Nestjsx)部署教程:自动生成RESTful接口
服务器·人工智能·后端·开源·自动化·restful
Go_error29 分钟前
Go 并发控制 errgroup.Group
后端·go
希望永不加班41 分钟前
SpringBoot 事件机制:ApplicationEvent 与监听器
java·开发语言·spring boot·后端·spring
IVAN不想说话1 小时前
为什么 Karpathy 的「LLM Wiki」突然火了?
后端
Nyarlathotep01131 小时前
自动内存管理(2):垃圾收集器与内存分配策略
java·jvm·后端
却话巴山夜雨时i1 小时前
互联网大厂Java面试实录:技术栈解析与场景剖析
java·大数据·spring boot·spring cloud·微服务·ai·面试