ChatClient vs ChatModel 详细对比

17 阅读6分钟

ChatClient vs ChatModel 详细对比

一、基本概念对比

1.1 ChatModel(基础模型接口)

定位:Spring AI的核心接口,提供基础的AI对话功能

public interface ChatModel {
    // 基础对话方法
    String call(String message);
    
    // 流式对话方法
    Flux<String> stream(String message);
    
    // 高级对话方法
    ChatResponse call(Prompt prompt);
    Flux<ChatResponse> stream(Prompt prompt);
}

1.2 ChatClient(高级客户端)

定位:基于ChatModel的高级封装,提供Fluent API和更多功能

public interface ChatClient {
    // 创建提示构建器
    PromptUserSpec prompt();
    
    // 直接调用
    String call();
    Flux<String> stream();
    
    // 设置默认选项
    ChatClient.Builder defaultOptions(ChatOptions options);
}

二、API风格对比

2.1 ChatModel:简单函数式风格

@RestController
public class ChatModelController {
    @Resource
    private ChatModel chatModel;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        // 简单直接,一行代码
        return chatModel.call(msg);
    }
    
    @GetMapping("/stream")
    public Flux<String> stream(@RequestParam String msg) {
        // 流式响应
        return chatModel.stream(msg);
    }
}

2.2 ChatClient:Fluent API风格

@RestController
public class ChatClientController {
    @Resource
    private ChatClient chatClient;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        // 链式调用,语义清晰
        return chatClient
            .prompt()
            .user(msg)
            .call()
            .content();
    }
    
    @GetMapping("/stream")
    public Flux<String> stream(@RequestParam String msg) {
        // 流式响应
        return chatClient
            .prompt()
            .user(msg)
            .stream()
            .content();
    }
}

三、功能对比表

功能特性ChatModelChatClient
简单对话✅ 支持✅ 支持
流式响应✅ 支持✅ 支持
系统提示⚠️ 需要手动构建Prompt.system()
多轮对话⚠️ 需要手动构建Prompt.messages()
函数调用❌ 不支持.functions()
多模态❌ 不支持✅ 支持图片等
Fluent API❌ 不支持✅ 支持
自动注入✅ 支持❌ 需要手动创建Bean
代码可读性⚠️ 一般✅ 优秀

四、详细功能对比

4.1 系统提示(System Prompt)

ChatModel方式(复杂)
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
    // 需要手动构建Prompt
    Prompt prompt = new Prompt(
        List.of(
            new SystemMessage("你是一个专业的Java程序员助手"),
            new UserMessage(msg)
        )
    );
    return chatModel.call(prompt).getResult().getOutput().getContent();
}
ChatClient方式(简洁)
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
    // 链式调用,清晰明了
    return chatClient
        .prompt()
        .system("你是一个专业的Java程序员助手")
        .user(msg)
        .call()
        .content();
}

4.2 多轮对话

ChatModel方式(复杂)
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
    // 需要手动维护对话历史
    List<Message> messages = new ArrayList<>();
    messages.add(new SystemMessage("你是一个助手"));
    messages.add(new UserMessage("你好"));
    messages.add(new AssistantMessage("你好!有什么我可以帮助你的?"));
    messages.add(new UserMessage(msg));
    
    Prompt prompt = new Prompt(messages);
    return chatModel.call(prompt).getResult().getOutput().getContent();
}
ChatClient方式(简洁)
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
    // 链式调用,易于理解
    return chatClient
        .prompt()
        .system("你是一个助手")
        .user("你好")
        .advisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
        .user(msg)
        .call()
        .content();
}

4.3 函数调用

ChatModel方式(不支持)
// ChatModel不支持函数调用
// 需要手动处理
ChatClient方式(支持)
@GetMapping("/weather")
public String getWeather(@RequestParam String city) {
    return chatClient
        .prompt()
        .user("查询" + city + "的天气")
        .functions("getCurrentWeather")  // 函数调用
        .call()
        .content();
}

4.4 多模态(图片等)

ChatModel方式(不支持)
// ChatModel不支持多模态
ChatClient方式(支持)
@GetMapping("/describe")
public String describeImage(@RequestParam String imageUrl) {
    return chatClient
        .prompt()
        .user(userSpec -> userSpec
            .text("描述这张图片")
            .media(MimeTypeUtils.IMAGE_JPEG, imageUrl)
        )
        .call()
        .content();
}

五、依赖注入对比

5.1 ChatModel:支持自动注入

@RestController
public class ChatModelController {
    // ✅ 直接注入,无需额外配置
    @Resource
    private ChatModel chatModel;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatModel.call(msg);
    }
}

5.2 ChatClient:需要手动创建Bean

配置类
@Configuration
public class ChatClientConfig {
    
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
}
控制器
@RestController
public class ChatClientController {
    // ✅ 通过配置Bean注入
    @Resource
    private ChatClient chatClient;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatClient.prompt().user(msg).call().content();
    }
}
或者构造器中创建
@RestController
public class ChatClientController {
    private final ChatClient chatClient;
    
    // ✅ 在构造器中创建
    public ChatClientController(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel).build();
    }
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatClient.prompt().user(msg).call().content();
    }
}

六、高级功能对比

6.1 设置默认选项

ChatModel方式
// ChatModel没有内置的默认选项设置
// 每次调用都需要传递选项
ChatOptions options = ChatOptions.builder()
    .model("qwen-plus")
    .temperature(0.7)
    .build();

String response = chatModel.call(new Prompt(msg, options));
ChatClient方式
// 配置类中设置默认选项
@Bean
public ChatClient chatClient(ChatModel chatModel) {
    return ChatClient.builder(chatModel)
        .defaultOptions(ChatOptions.builder()
            .model("qwen-plus")
            .temperature(0.7)
            .build())
        .build();
}

// 使用时无需重复设置
String response = chatClient
    .prompt()
    .user(msg)
    .call()
    .content();

6.2 Advisors(顾问模式)

ChatModel方式
// ChatModel不支持Advisors
ChatClient方式
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
    return chatClient
        .prompt()
        .user(msg)
        .advisors(
            new MessageChatMemoryAdvisor(new InMemoryChatMemory()), // 记忆
            new QuestionAnswerAdvisor(new VectorStore())            // RAG
        )
        .call()
        .content();
}

七、代码复杂度对比

7.1 简单对话场景

ChatModel(更简单)
// 1行代码
String result = chatModel.call(msg);
ChatClient(稍复杂)
// 3行代码
String result = chatClient
    .prompt()
    .user(msg)
    .call()
    .content();

7.2 复杂对话场景

ChatModel(复杂)
// 需要手动构建Prompt
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个助手"));
messages.add(new UserMessage(msg));

ChatOptions options = ChatOptions.builder()
    .model("qwen-plus")
    .temperature(0.7)
    .build();

Prompt prompt = new Prompt(messages, options);
String result = chatModel.call(prompt).getResult().getOutput().getContent();
ChatClient(清晰)
// 链式调用,语义清晰
String result = chatClient
    .prompt()
    .system("你是一个助手")
    .user(msg)
    .options(ChatOptions.builder()
        .model("qwen-plus")
        .temperature(0.7)
        .build())
    .call()
    .content();

八、性能对比

8.1 性能差异

方面ChatModelChatClient
调用开销低(直接调用)略高(多一层封装)
内存占用略高
性能差异基准<1%的差异(可忽略)

8.2 性能测试示例

@SpringBootTest
public class PerformanceTest {
    
    @Resource
    private ChatModel chatModel;
    
    @Resource
    private ChatClient chatClient;
    
    @Test
    public void testChatModelPerformance() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            chatModel.call("你好");
        }
        long end = System.currentTimeMillis();
        System.out.println("ChatModel: " + (end - start) + "ms");
    }
    
    @Test
    public void testChatClientPerformance() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            chatClient.prompt().user("你好").call().content();
        }
        long end = System.currentTimeMillis();
        System.out.println("ChatClient: " + (end - start) + "ms");
    }
}

结论:性能差异极小(<1%),可以忽略不计。


九、使用场景建议

9.1 使用ChatModel的场景

适合使用ChatModel的情况

  1. 简单对话:只需要基本的问答功能
  2. 快速原型:开发初期,快速验证想法
  3. 性能敏感:对性能要求极高的场景(虽然差异很小)
  4. 学习入门:刚开始学习Spring AI
  5. 代码简洁:希望代码尽可能简单
示例代码
@RestController
public class SimpleChatController {
    @Resource
    private ChatModel chatModel;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatModel.call(msg);
    }
}

9.2 使用ChatClient的场景

适合使用ChatClient的情况

  1. 复杂对话:需要系统提示、多轮对话等
  2. 函数调用:需要调用外部函数
  3. 多模态:需要处理图片、音频等
  4. RAG应用:需要结合知识库检索
  5. 记忆功能:需要记住对话历史
  6. 可读性:希望代码更易读、易维护
  7. 生产环境:正式项目推荐使用
示例代码
@RestController
public class AdvancedChatController {
    @Resource
    private ChatClient chatClient;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatClient
            .prompt()
            .system("你是一个专业的Java程序员助手")
            .user(msg)
            .advisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
            .call()
            .content();
    }
}

十、迁移指南

10.1 从ChatModel迁移到ChatClient

迁移前(ChatModel)
@RestController
public class OldController {
    @Resource
    private ChatModel chatModel;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatModel.call(msg);
    }
}
迁移后(ChatClient)
// 1. 配置类中创建Bean
@Configuration
public class ChatClientConfig {
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
}

// 2. 控制器中使用
@RestController
public class NewController {
    @Resource
    private ChatClient chatClient;
    
    @GetMapping("/chat")
    public String chat(@RequestParam String msg) {
        return chatClient.prompt().user(msg).call().content();
    }
}

十一、总结对比表

对比维度ChatModelChatClient
定位基础接口高级封装
API风格函数式Fluent API
简单对话✅ 简洁✅ 稍复杂
复杂对话⚠️ 复杂✅ 清晰
系统提示⚠️ 需手动构建✅ 支持
多轮对话⚠️ 需手动构建✅ 支持
函数调用❌ 不支持✅ 支持
多模态❌ 不支持✅ 支持
Advisors❌ 不支持✅ 支持
自动注入✅ 支持❌ 需配置
代码可读性⚠️ 一般✅ 优秀
学习曲线✅ 简单⚠️ 中等
性能✅ 略快⚠️ 略慢
推荐场景简单应用、快速原型生产环境、复杂应用

十二、最终建议

选择原则

  1. 新手入门:先学ChatModel,理解基本概念
  2. 简单项目:使用ChatModel,代码更简洁
  3. 复杂项目:使用ChatClient,功能更强大
  4. 生产环境:推荐ChatClient,可维护性更好
  5. 团队协作:推荐ChatClient,代码更易读

实际项目建议

// 生产环境推荐配置
@Configuration
public class ChatConfig {
    
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
            .defaultOptions(ChatOptions.builder()
                .model("qwen-plus")
                .temperature(0.7)
                .maxTokens(2000)
                .build())
            .build();
    }
    
    // ChatModel仍然可以保留,用于简单场景
    // Spring AI会自动创建
}

总结:ChatModel是基础,ChatClient是进阶。根据项目复杂度和需求选择合适的API,两者可以共存使用。