ChatModel 与 ChatClient 关系完整指南

ChatModel 与 ChatClient 关系完整指南

本文所有 API 签名 / 类关系 / 方法列表均通过 javap 反编译 Spring AI 1.1.4 jar 实际验证。

参与 jar:spring-ai-model-1.1.4.jarspring-ai-client-chat-1.1.4.jarspring-ai-alibaba-dashscope-1.1.2.2.jar


1. 一句话结论

复制代码
ChatClient 是高层门面,底层委托给 ChatModel。
  · ChatModel   = 接口规范 + 各家实现(每个 starter 一个)
  · ChatClient  = 唯一默认实现(DefaultChatClient),内部持有一个 ChatModel

它们不是替代关系,是分层关系------ChatClient 站在 ChatModel 肩膀上,提供链式 API + Advisor / Tool / 结构化输出等高级能力。


2. 分层架构

复制代码
┌─────────────────────────────────────────────────────────────────┐
│  ChatClient(应用代码面向的高层门面)                             │
│  ── 接口 + 唯一默认实现 DefaultChatClient                         │
│  ── 所在 jar:spring-ai-client-chat                              │
│  ── 能力:链式 API、Advisor、Tool、模板、结构化输出、流式          │
└──────────────────────────┬──────────────────────────────────────┘
                           │ 内部 new 出一个具体 ChatModel
                           │ 通过它发真实请求
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│  ChatModel(能力接口,各家 starter 实现)                        │
│  ── 接口 + 各供应商各 1 实现                                      │
│  ── 所在 jar:spring-ai-model                                    │
│  ── 能力:call(Prompt) / stream(Prompt) / call(String)          │
└──────────────────────────┬──────────────────────────────────────┘
                           │ implements
      ┌────────────────────┼────────────────────┬─────────────┐
      ▼                    ▼                    ▼             ▼
OpenAiChatModel      DashScopeChatModel    OllamaChatModel  DeepSeekChatModel
(spring-ai-openai)   (spring-ai-alibaba)   (spring-ai-ollama)  ...

关键事实(javap 验证)

java 复制代码
// spring-ai-client-chat-1.1.4.jar
public interface org.springframework.ai.chat.client.ChatClient { ... }
public class     org.springframework.ai.chat.client.DefaultChatClient
    implements ChatClient { ... }       // 唯一默认实现

// spring-ai-model-1.1.4.jar
public interface org.springframework.ai.chat.model.ChatModel
    extends Model<Prompt, ChatResponse>, StreamingChatModel { ... }

// 各 starter 里
public class org.springframework.ai.openai.OpenAiChatModel
    implements ChatModel { ... }        // ✅

public class com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel
    implements ChatModel { ... }        // ✅

ChatClient 不是各家 starter 各自实现的 ------每家 starter 只实现 ChatModel 接口,ChatClient 在 Spring AI 本体里只有一个 DefaultChatClient 实现。


3. API 签名对比

3.1 ChatModel 的方法(javap 验证)

java 复制代码
public interface ChatModel extends StreamingChatModel {
    // 同步
    default String       call(String text);              // 简写
    default String       call(Message... messages);      // 多消息
    abstract ChatResponse call(Prompt prompt);           // 完整,能拿元数据

    // 流式(继承自 StreamingChatModel)
    default Flux<String>        stream(String text);
    default Flux<String>        stream(Message... messages);
    abstract Flux<ChatResponse> stream(Prompt prompt);

    default ChatOptions getDefaultOptions();
}

3.2 ChatClient 的方法

java 复制代码
public interface ChatClient {
    // 启动方式(3 个重载)
    ChatClientRequestSpec prompt();               // 从 defaults 开始
    ChatClientRequestSpec prompt(String userText); // 快捷:= prompt().user(x)
    ChatClientRequestSpec prompt(Prompt prompt);   // 以已有 Prompt 为起点
}

// 链式配置
public interface ChatClientRequestSpec {
    .system(String)
    .user(String)
    .messages(Message...)
    .options(ChatOptions)
    .advisors(Advisor...)
    .tools(Object...)
    .templateRenderer(TemplateRenderer)
    // ...
    CallResponseSpec   call();
    StreamResponseSpec stream();
}

// 同步响应读取
public interface CallResponseSpec {
    String        content();
    ChatResponse  chatResponse();
    <T> T         entity(Class<T>);                     // ★ 结构化输出
    <T> T         entity(ParameterizedTypeReference<T>);
    <T> T         entity(StructuredOutputConverter<T>);
}

// 流式响应读取
public interface StreamResponseSpec {
    Flux<String>              content();
    Flux<ChatResponse>        chatResponse();
    Flux<ChatClientResponse>  chatClientResponse();
    // ⚠️ 没有 entity()!流式无法反序列化到实体
}

4. 同一功能的两种写法对比

4.1 最简问答

java 复制代码
// ChatModel(一行最短)
String answer = chatModel.call("你好");

// ChatClient(链式稍长,但可扩展)
String answer = chatClient.prompt().user("你好").call().content();
// 或
String answer = chatClient.prompt("你好").call().content();

单次调用 ChatModel 更短,但后续加功能(系统提示/记忆/RAG)时不用重写。

4.2 加系统提示

java 复制代码
// ChatModel:手工构造 Prompt
Prompt prompt = new Prompt(List.of(
        new SystemMessage("你是翻译助手"),
        new UserMessage(query)
));
String text = chatModel.call(prompt).getResult().getOutput().getText();

// ChatClient:一行
String text = chatClient.prompt()
        .system("你是翻译助手")
        .user(query)
        .call()
        .content();

4.3 多轮对话记忆

java 复制代码
// ChatModel:自己维护 List<Message>
List<Message> history = loadHistory(userId);           // 你自己写存储
history.add(new UserMessage(query));
Prompt prompt = new Prompt(history);
ChatResponse resp = chatModel.call(prompt);
history.add(resp.getResult().getOutput());
saveHistory(userId, history);                          // 你自己写存储
String text = resp.getResult().getOutput().getText();

// ChatClient:一行 advisor
String text = chatClient.prompt()
        .user(query)
        .advisors(a -> a.param(MessageChatMemoryAdvisor.CONVERSATION_ID, userId))
        .call()
        .content();
// (前提:LLMConfig 里把 MessageChatMemoryAdvisor 作为 defaultAdvisor 注册过)

4.4 结构化输出

java 复制代码
// ChatModel:自己拼 JSON 格式说明 + 自己 parse
Prompt prompt = new Prompt("给我一本书,严格按格式返回:{\"name\":\"...\", \"author\":\"...\"}");
String raw = chatModel.call(prompt).getResult().getOutput().getText();
String cleaned = raw.replaceAll("```json|```", "").trim();
Book book = objectMapper.readValue(cleaned, Book.class);    // 可能抛 JsonParseException

// ChatClient:一行
Book book = chatClient.prompt()
        .user("给我一本书")
        .call()
        .entity(Book.class);
// 背后 Spring AI 自动:生成 Schema → 追加到 prompt → 剥 markdown → Jackson 反序列化

4.5 工具调用(Function Calling)

java 复制代码
// ChatModel:手写 ToolCallback 注册 + 自己处理 tool_calls 响应 + 回送
// (约 50~100 行代码)

// ChatClient:一行
String answer = chatClient.prompt()
        .user("查北京今天天气")
        .tools(new WeatherTool())        // 类上加 @Tool 方法即可
        .call()
        .content();

4.6 流式响应

java 复制代码
// ChatModel
Flux<String> flux = chatModel.stream(query);              // 最简 1 行
// 或拿元数据:
Flux<ChatResponse> flux2 = chatModel.stream(prompt);

// ChatClient
Flux<String> flux = chatClient.prompt().user(query).stream().content();
Flux<ChatResponse> flux2 = chatClient.prompt().user(query).stream().chatResponse();

4.7 拿 token 用量 / 元数据

java 复制代码
// ChatModel:call(Prompt) 直接返回 ChatResponse
ChatResponse resp = chatModel.call(new Prompt(query));
String text       = resp.getResult().getOutput().getText();
Usage usage       = resp.getMetadata().getUsage();
String id         = resp.getMetadata().getId();
String finishReason = resp.getResult().getMetadata().getFinishReason();

// ChatClient:.chatResponse() 拿到同样的对象
ChatResponse resp = chatClient.prompt().user(query).call().chatResponse();
// 以下字段取法一致

5. 能力对比总表

场景 ChatModel ChatClient
单轮问答(最短代码) ✅ 1 行 call(String) ⚠️ 4 步链式
加系统提示词 ❌ 手工构造 Prompt .system(...) 一行
多轮对话记忆 ❌ 自己管 List .advisors(memoryAdvisor)
RAG 检索增强 ❌ 自己查向量库 .advisors(ragAdvisor)
工具调用 ❌ 自己处理 ToolCallback .tools(new XxxTool())
结构化输出→实体 ❌ 自己 parse JSON .entity(Book.class)
日志观测 ❌ 自己埋点 SimpleLoggerAdvisor
流式 SSE ✅ 一样支持 ✅ 一样支持
拿 token 元数据 ✅ 直接 ChatResponse .chatResponse()
厂商独家参数 new Prompt(msg, DashScopeChatOptions) .options(DashScopeChatOptions)
多供应商混用 ✅ 注入 DashScopeChatModel / OllamaChatModel 分别使用 ⚠️ 需要多 Builder + @Qualifier

结论

  • 任务越复杂,ChatClient 优势越大(一行 advisor 完成 ChatModel 几十行代码的事)
  • 单次一问一答 / 多供应商并行使用,ChatModel 更直接

6. 注入姿势对照

6.1 单 starter 项目

java 复制代码
// 方式 A:注入通用接口(只有一个实现,Spring 自动匹配)
@Autowired ChatModel chatModel;

// 方式 B:注入具体子类(写死供应商)
@Autowired DashScopeChatModel dashScopeChatModel;

// 方式 C:注入 ChatClient.Builder,自己 build(官方推荐应用代码使用)
@Autowired ChatClient.Builder builder;
ChatClient chatClient = builder.build();

// 方式 D:@Bean ChatClient(LLMConfig 统一配好后)
@Autowired ChatClient chatClient;

6.2 多 starter 共存(例如同时装了 openai + dashscope + ollama)

java 复制代码
// 必须注入具体子类,否则 @Autowired ChatModel 会报 bean 歧义
@Autowired OpenAiChatModel      openAi;
@Autowired DashScopeChatModel   dashScope;
@Autowired OllamaChatModel      ollama;

// ChatClient 场景:需要通过 @Qualifier 区分不同 Builder
@Autowired @Qualifier("openAiChatClientBuilder")    ChatClient.Builder openAiBuilder;
@Autowired @Qualifier("dashScopeChatClientBuilder") ChatClient.Builder dashScopeBuilder;

7. 选型决策树

复制代码
你要做什么?
│
├─ 写底层工具 / 框架 / 自定义 Advisor(需要精细控制请求响应)
│  └─→ ChatModel
│
├─ 多家供应商并行使用(ChatGPT 做 A 功能、DeepSeek 做 B 功能)
│  └─→ ChatModel(各自注入 XxxChatModel)
│
├─ 只用一家供应商,且要拿 token 元数据、finishReason
│  └─→ ChatModel 最直接;ChatClient .chatResponse() 也行
│
├─ 业务代码(对话、客服、AI 助手、RAG 应用)
│  └─→ ChatClient ✅ 推荐
│       后续要加记忆 / 工具 / 结构化输出时一行搞定
│
└─ 学习 Spring AI
   └─→ 先学 ChatModel 理解底层,再学 ChatClient 写业务

8. 类比理解

Spring AI 传统 Java Web 特点
ChatModel 各家实现 JDBC Driver(MySQL Driver / PG Driver) 各供应商自家实现
ChatModel 接口 JDBC API 规范
ChatClient / DefaultChatClient JdbcTemplate / MyBatis 建在驱动之上的好用门面
Advisor Servlet Filter / Spring AOP 环绕通知 拦截器
ChatOptions Connection Properties 请求参数
Prompt SQL 语句 + 参数 完整请求载体

9. 常见误区

9.1 误区:ChatClient 是"全新的聊天 API"

❌ 错。ChatClient 底层仍然调 ChatModel。它不是替代 ,是装饰

9.2 误区:".defaultSystem() 写过后,.system() 会追加"

❌ 错。字节码实测:.system(String) 做的是 this.systemText = text 赋值替换,会把默认值完全覆盖。

java 复制代码
// LLMConfig
builder.defaultSystem("你是严谨助手");

// Controller
chatClient.prompt().system("你是脱口秀演员").user(q).call().content();
// → 实际 system = "你是脱口秀演员",默认的"严谨助手"没了

9.3 误区:".stream().entity(Book.class)" 能流式拿结构化

❌ 编译不过。StreamResponseSpec 没有 entity() 方法------流式 JSON 片段无法反序列化。要"流式 + 结构化"只能:流式显示文本 → 结束后拼完整 JSON → 自己 parse。

9.4 误区:多 starter 共存时 @Autowired ChatModel 能自动选一个

❌ 错。多个 ChatModel bean 共存会触发 NoUniqueBeanDefinitionException。必须注入具体子类或用 @Qualifier

9.5 误区:ChatClient 不能拿厂商独家参数

❌ 错。可以:

java 复制代码
chatClient.prompt()
        .user(q)
        .options(DashScopeChatOptions.builder().enableSearch(true).build())
        .call()
        .content();

独家参数走 .options() 照样好用------不是非 ChatModel 不可。


10. 验证方式

bash 复制代码
# 验证 ChatModel 接口
javap -cp spring-ai-model-1.1.4.jar \
  org.springframework.ai.chat.model.ChatModel

# 验证 ChatClient 接口 + DefaultChatClient 实现
javap -cp spring-ai-client-chat-1.1.4.jar \
  org.springframework.ai.chat.client.ChatClient

jar tf spring-ai-client-chat-1.1.4.jar | grep DefaultChatClient

# 验证 DashScopeChatModel 实现了 Spring AI 的接口
javap -cp spring-ai-alibaba-dashscope-1.1.2.2.jar \
  com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel | head -2
# 输出:public class ... implements org.springframework.ai.chat.model.ChatModel

11. 参考资料

  • Spring AI 官方文档:https://docs.spring.io/spring-ai/reference/
  • spring-ai-01quickstart/SPRING-AI-ARCHITECTURE.md(本项目)------ 整体架构
  • spring-ai-alibaba-06advisors/ADVISORS-AND-MEMORY.md(本项目)------ Advisor 深入
  • spring-ai-alibaba-04chatmodel(本项目)------ ChatModel 代码示例
  • spring-ai-alibaba-05chatclient(本项目)------ ChatClient 代码示例
相关推荐
0xDevNull2 小时前
Java 深度解析:for 循环 vs Stream.forEach 及性能优化指南
java·开发语言·性能优化
博风2 小时前
在tomcat应用里添加了一个线程池对象,向这个线程池发送任务,让其执行。 我希望在tomcat停机时,能等待线程池里的任务执行完了再停机,要如何实现?
java·tomcat
studyForMokey2 小时前
【Android面试】Java专题 todo
android·java·面试
一只大袋鼠2 小时前
MyBatis 特性(三):缓存、延迟加载、注解开发
java·数据库·笔记·sql·缓存·mybatis
老毛肚3 小时前
Redis高级
java·数据库·redis
小Y._3 小时前
AQS同步器核心原理深度剖析
java·源码分析·juc·aqs
南棱笑笑生3 小时前
20260420给万象奥科的开发板HD-RK3576-PI适配瑞芯微原厂的Buildroot时使用ll命令
java·大数据·elasticsearch·rockchip
StockTV3 小时前
韩国市场API技术对接指南,涵盖实时行情、历史数据、指数信息、公司详情等功能
java·开发语言·python·php
缪懿4 小时前
javaEE:文件IO
java·java-ee