第三章 ChatModel 与 ChatClient 的深度对比

s01 > s02 > [ s03 ] s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > s13 > s14 > s15 > s16 > s17 > s18


一、为什么需要两个 API?

1.1 初学者的困惑

很多初学者看到 Spring AI 同时提供了 ChatModelChatClient 都会问:

  • "这两个不都是用来调用 AI 的吗?"
  • "到底该用哪一个?"
  • "他们有什么区别?"

这一章我们就来彻底搞清楚这个问题!

1.2 比喻理解

让我用一个生活化的比喻来解释:

ChatModel(对话模型) 就相当于手机的「原始电话功能」

  • 只能打电话(调用 send/receive)
  • 功能简单直接
  • 需要自己处理各种细节

ChatClient(对话客户端) 相当于「智能手机」

  • 在电话基础上,增加了:

    • 通讯录管理(系统提示词)
    • 短信应用(Prompt 模板)
    • 语音助手(工具调用)
    • 智能外设(向量存储、记忆功能)
  • 功能更丰富,使用更便捷


二、ChatModel 详解

2.1 什么是 ChatModel?

ChatModel 是 Spring AI 的核心底层接口,它直接封装了与 AI 模型的通信。

大白话解释:把 ChatModel 想象成"AI 模型的翻译官",你给它发中文,它能听懂;AI 返回的内容,它也能翻译成 Java 对象给你。

public interface ChatModel extends Model, ChatOptionsDesktop {
    // 核心方法就两个:
    
    // 方法1:一次性返回完整结果
    ChatResponse call(Prompt prompt);
    
    // 方法2:流式返回(一个字一个字吐出来)
    Flux<ChatResponse> stream(Prompt prompt);
}

2.2 ChatModel 的特点

特点说明
底层接口直接与 AI 服务商通信
功能单一只负责发送消息、接收回复
灵活度高可以精确控制请求的每个细节
使用难度需要了解 Prompt、Message 等概念

2.3 代码示例

// 使用 ChatModel 调用 AI
@RestController
public class ChatModelController
{
    @Resource
    private ChatModel chatModel;

    @GetMapping("/chatmodel/hello")
    public String hello(String msg)
    {
        // ChatModel 的调用方式:直接传入字符串
        String result = chatModel.call(msg);
        return result;
    }
}

三、ChatClient 详解

3.1 什么是 ChatClient?

ChatClient 是构建在 ChatModel 之上的高级 API,它提供了链式调用(Builder模式)的便捷写法。

大白话解释ChatClient 就像一个"智能点餐机",不仅能帮你叫外卖,还支持:

  • 定义口味偏好(系统提示词)
  • 选择优惠券(模板变量)
  • 凑单推荐(RAG 检索)
  • 会员积分(对话记忆)

3.2 ChatClient 的链式调用

// ChatClient 的经典使用方式:链式调用
String result = chatClient.prompt()           // 第一步:创建提示词
    .system("你是一个Java助手")                // 第二步:设置系统提示词
    .user("什么是反射?")                      // 第三步:设置用户问题
    .call()                                    // 第四步:执行调用
    .content();                                // 第五步:获取结果

这种链式写法比原生 ChatModel 更加直观易读。

3.3 代码示例

// 使用 ChatClient 调用 AI
@RestController
public class ChatClientController
{
    // 注意:ChatClient 不能直接注入,需要通过 Builder 构建
    private final ChatClient dashScopeChatClient;

    // 构造函数注入,并构建 ChatClient
    public ChatClientController(ChatModel dashScopeChatModel)
    {
        // 通过 ChatModel 创建 ChatClient
        this.dashScopeChatClient = ChatClient.builder(dashScopeChatModel).build();
    }

    @GetMapping("/chatclient/hello")
    public String hello(String msg)
    {
        // 链式调用,语义清晰
        String result = dashScopeChatClient.prompt()
            .user(msg)      // 设置用户消息
            .call()         // 调用 AI
            .content();    // 获取文本内容
        return result;
    }
}

四、两者对比

4.1 功能对比表

特性ChatModelChatClient
层级底层接口高级封装
API 风格直接调用链式 Builder
系统提示词需手动构造 Prompt 对象.system() 方法直接设置
模板变量不支持支持 .param() 替换占位符
结构化输出需额外配置.entity() 方法直接映射
对话记忆不支持支持 Advisor 扩展
工具调用需手动配置支持 .tools() 方法
适用场景需要精确控制的底层开发快速构建 AI 功能

4.2 如何选择?

使用 ChatModel 的场景

  1. 你需要精确控制请求的每个参数
  2. 你是库/框架开发者,需要底层能力
  3. 你需要实现自定义的 AI 逻辑

使用 ChatClient 的场景

  1. 日常的 AI 对话开发(推荐)
  2. 需要快速构建原型
  3. 需要用到系统提示词、模板、工具调用等高级功能

4.3 ChatModel 也能实现 ChatClient 的功能(但代码更多)

很多初学者误以为 ChatModel 功能太简单,做不到 ChatClient 的那些高级功能。其实 ChatModel 通过手写样板代码完全可以实现相同的功能,只不过代码量会多一些。

让我们对比一下两者的实现方式:

场景:让 AI 扮演 Java 助手,回答技术问题

ChatClient 的写法(简洁)

String result = chatClient.prompt()
    .system("你是一个专业的Java工程师")  // 系统提示词
    .user("什么是反射?")                 // 用户问题
    .call()
    .content();

ChatModel 的写法(样板代码多)

// ChatModel 需要手动构造 Prompt 对象
// 第一步:创建系统消息
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(
    "你是一个专业的%s", "Java工程师"      // 模板变量
);
Message systemMessage = systemPromptTemplate.createMessage(
    Map.of("role", "professional", "specialty", "Java工程师")
);

// 第二步:创建用户消息
UserMessage userMessage = new UserMessage("什么是反射?");

// 第三步:组合成对话消息列表
List<Message> messages = new ArrayList<>();
messages.add(systemMessage);
messages.add(userMessage);

// 第四步:创建 Prompt 对象
Prompt prompt = new Prompt(messages);

// 第五步:调用并获取结果
ChatResponse response = chatModel.call(prompt);
String result = response.getResult().getOutput().getText();

从上面的对比可以看出:

  • ChatClient:3 行代码搞定(链式调用)
  • ChatModel:需要 10+ 行样板代码(手动构造)
那 ChatModel 还有什么用?

虽然 ChatModel 代码多,但它给了你最大的控制权

  • 可以自定义消息类型
  • 可以精确控制每个参数
  • 可以在发送前对消息进行任意处理

五、项目代码详解

5.1 项目结构

SAA-03ChatModelChatClient/
├── pom.xml                                    # 依赖配置
├── src/main/java/com/atguigu/study/
│   ├── config/
│   │   └── SaaLLMConfig.java                  # 配置文件
│   ├── controller/
│   │   ├── ChatClientController.java          # ChatClient 示例
│   │   ├── ChatClientControllerV2.java        # ChatClient V2写法
│   │   └── ChatModelController.java           # ChatModel 示例
│   └── Saa03ChatModelChatClientApplication.java

5.2 配置类

package com.atguigu.study.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类:基于自动注入的 ChatModel 构建 ChatClient
 */
@Configuration
public class SaaLLMConfig
{
    /**
     * 创建一个命名的 ChatClient(基于 dashScopeChatModel)
     * 名称为 "dashScopeChatClient"
     */
    @Bean("dashScopeChatClient")
    public ChatClient dashScopeChatClient(ChatModel dashScopeChatModel)
    {
        return ChatClient.builder(dashScopeChatModel).build();
    }
}

5.3 ChatClientController V2 写法(推荐)

package com.atguigu.study.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 使用 @Autowired 注入 ChatClient
 */
@RestController
public class ChatClientControllerV2
{
    // 直接注入已注册的 ChatClient Bean
    @Autowired
    private ChatClient chatClient;

    @GetMapping("/chatclientv2/dochat")
    public String doChat(@RequestParam(name = "msg", defaultValue = "2加9等于几") String msg)
    {
        // 简洁的链式调用
        return chatClient.prompt()
            .user(msg)          // 设置用户消息
            .call()             // 调用
            .content();        // 获取文本内容
    }
}

六、进阶:ChatClient 的多种用法

6.1 返回 Flux 流式响应

@GetMapping("/stream")
public Flux<String> stream(String msg)
{
    return chatClient.prompt()
        .user(msg)
        .stream()              // 开启流式模式
        .content();
}

6.2 方法引用写法

// 更加简洁的 Lambda 写法
@GetMapping("/chat")
public String chat(String msg)
{
    return chatClient.prompt(msg).call().content();
}

// 或者更复杂点
ChatResponse response = chatClient.prompt()
    .system("你是一个专业的%s", "Java工程师")
    .user(msg)
    .call()
    .chatResponse();

String result = response.getResult().getOutput().getText();

七、本章小结

7.1 核心知识点

概念说明
ChatModel底层AI调用接口,直接与模型通信
ChatClient高级封装,提供链式调用的便捷API
@BeanSpring 中用于注册组件的注解
链式调用通过 Builder 模式实现的方法链

7.2 选择建议

┌─────────────────────────────────────────────────┐
│                  选择决策树                       │
│                                                  │
│  需要精细控制底层请求? ──YES──→ 使用 ChatModel   │
│         │                                       │
│         NO                                      │
│         ↓                                       │
│  直接开发AI应用功能? ──YES──→ 使用 ChatClient   │
│                                                  │
└─────────────────────────────────────────────────┘

本章重点

  1. 理解 ChatModel 与 ChatClient 的定位差异
  2. 掌握 ChatClient 的链式调用写法
  3. 能够根据场景选择合适的 API

下章剧透(s04):


本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com