惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
T
The Blog of Author Tim Ferriss
aimingoo的专栏
aimingoo的专栏
A
About on SuperTechFans
Stack Overflow Blog
Stack Overflow Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog
MongoDB | Blog
MongoDB | Blog
L
LangChain Blog
WordPress大学
WordPress大学
小众软件
小众软件
IT之家
IT之家
腾讯CDC
月光博客
月光博客
量子位
Blog — PlanetScale
Blog — PlanetScale
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - work hard work smart

Java 面试1 Java 常见面试问题 手撕java常用代码 WebStorm 创建react工程 构建企业级 Text-to-SQL Agent:基于 LangGraph 的智能数据查询系统设计 Harness 工程:驾驭 AI Agent 的工程化艺术 DeepAgents 多智能体架构实战:从设计模式到后端选型 Vue 自定义组件完全指南:从零构建待办事项应用 使用 LangChain + Hugging Face 构建文本向量化服务 SQLAlchemy 使用详解 Python 中使用 Elasticsearch 的完整指南 Qdrant 向量数据库使用指南 OpenEvals 快速入门:LLM 评估指南 DeepEval 快速入门:LLM 应用评估指南 LangSmith 批量评估完全指南 Qwen-Agent 入门指南:快速构建智能体应用 LangSmith 集成实战:从追踪到评估的完整指南 初识 go-zero:一款让你写后端更规范、更高效的 Go 微服务框架 RAG 中为什么需要 Rerank,以及如何使用 Rerank LangChain4j RAG 核心组件与组合方式 如何使用 Elasticsearch 进行全文检索和向量检索 MinerU Docker 部署指南 5 分钟上手:为 Cline 配置一个免费的 MCP 天气服务 Neo4j 图数据库安装与 Spring Boot 集成实战指南 LangFuse 实战指南:用 @observe 三行代码给 LLM 应用加上全链路追踪 Function Call 深度解析:让大模型从"嘴炮"到"实干"的技术革命 Spring AI 提示词模板实战:告别硬编码,实现提示词工程化管理 LangChain4j 实战指南:用 Java 轻松构建 AI 应用 Spring AI 对话短期记忆实战:让大模型拥有"记忆力" Spring AI 提示词工程实战:让大模型更懂你的意图
Spring AI ChatClient 深度解析:优雅构建大模型应用的利器
work hard work smart · 2026-05-30 · via 博客园 - work hard work smart

摘要:ChatClient 是 Spring AI 提供的高级 API,通过链式调用和 Builder 模式,让大模型调用变得更加优雅和灵活。本文结合实际项目代码,全面讲解 ChatClient 的使用技巧和最佳实践。

一、为什么需要 ChatClient?

在使用 Spring AI 开发大模型应用时,我们有两种主要方式:

方式 1:直接使用 ChatModel

@Autowired
private ChatModel chatModel;

public String call(String message) {
    return chatModel.call(message);
}

缺点

  • 功能相对底层,需要手动构建 Prompt
  • 缺少统一的链式调用 API
  • Advisor(拦截器)机制使用不便

方式 2:使用 ChatClient(推荐)

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultSystem("你是一个专业助手")
    .defaultAdvisors(new SimpleLoggerAdvisor())
    .build();

return chatClient.prompt(message).call().content();

优势

  • ✅ 链式调用,代码更优雅
  • ✅ Builder 模式,配置更灵活
  • ✅ 内置 Advisor 机制,支持日志、缓存等切面功能
  • ✅ 统一的 API 风格,降低学习成本

二、ChatClient 核心概念

1. ChatClient 的三层结构

ChatClient(客户端)
    ├── Builder(构建器)
    │   ├── defaultSystem()     - 默认系统提示词
    │   ├── defaultAdvisors()   - 默认拦截器
    │   └── defaultOptions()    - 默认模型参数
    │
    └── PromptSpec(提示词规格)
        ├── prompt()            - 构建提示词
        ├── system()            - 覆盖系统提示词
        ├── user()              - 设置用户消息
        ├── call()              - 同步调用
        └── stream()            - 流式调用

2. 生命周期管理

ChatClient 应该在应用启动时创建,整个生命周期复用:

@RestController
public class ChatController implements InitializingBean {
    
    @Autowired
    private ChatModel chatModel;
    
    private ChatClient chatClient;
    
    @Override
    public void afterPropertiesSet() {
        // 在 Bean 初始化后创建 ChatClient
        chatClient = ChatClient.builder(chatModel)
            .defaultSystem("你是一个 AI 助手")
            .build();
    }
}

三、ChatClient 五种调用方式

方式 1:最简单调用

直接传入字符串,使用默认配置:

@GetMapping("/simple")
public String simpleCall(String message) {
    return chatClient.prompt(message).call().content();
}

测试

curl "http://localhost:8000/client/simpleCall?message=介绍一下Spring AI"

执行流程

prompt(message) 
    → 构建 Prompt 对象
    → 添加 defaultSystem 提示词
    → 调用 ChatModel
    → 返回内容

方式 2:动态覆盖 System 提示词

在运行时动态修改系统提示词:

@GetMapping("/translator")
public String translateCall(String message) {
    return chatClient.prompt(message)
        .system("你是一个专业的翻译助手,请把中文翻译成英文")
        .call()
        .content();
}

场景:同一个 ChatClient,不同接口需要不同的角色设定。

方式 3:使用 user() 方法

分离用户消息的设置:

@GetMapping("/chat")
public String chatCall(String message) {
    return chatClient.prompt()
        .user(message)
        .call()
        .content();
}

与方式 1 的区别

  • prompt(message):一步到位
  • prompt().user(message):分步构建,更灵活

方式 4:使用 Prompt 对象

完全手动控制,最大灵活性:

@GetMapping("/advanced")
public String advancedCall(String message) {
    Prompt prompt = new Prompt(
        new SystemMessage("请详细回答我的问题"),
        new UserMessage(message)
    );
    
    return chatClient.prompt(prompt)
        .call()
        .content();
}

适用场景

  • 复杂的多消息对话
  • 需要精确控制消息顺序
  • 历史对话记录复用

方式 5:流式输出

实时返回结果,适合长文本:

@GetMapping("/stream")
public Flux<String> streamCall(String message) {
    return chatClient.prompt(message)
        .stream()
        .content();
}

前端调用

const response = await fetch('/client/stream?message=写一首诗');
const reader = response.body.getReader();

while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    console.log(new TextDecoder().decode(value));
}

四、ChatClient.Builder 深度配置

完整配置示例

@Override
public void afterPropertiesSet() throws Exception {
    chatClient = ChatClient.builder(dashScopeChatModel)
        // 1. 默认系统提示词
        .defaultSystem("请用英文回答问题")
        
        // 2. 默认拦截器(Advisor)
        .defaultAdvisors(
            new SimpleLoggerAdvisor()  // 日志记录
        )
        
        // 3. 默认模型参数
        .defaultOptions(
            DashScopeChatOptions.builder()
                .model("qwen-plus")
                .temperature(0.7)
                .maxTokens(2000)
                .build()
        )
        
        .build();
}

配置项详解

1. defaultSystem() - 默认系统提示词

.defaultSystem("""
    你是一个专业的 Java 开发专家,擅长 Spring Boot 和微服务架构。
    请用简洁的代码示例回答问题,并添加详细注释。
    """)

作用:所有调用都会自动带上这个系统提示词,无需重复设置。

2. defaultAdvisors() - 默认拦截器

Advisor 是 ChatClient 的核心扩展机制:

.defaultAdvisors(
    new SimpleLoggerAdvisor(),           // 记录日志
    new MessageChatMemoryAdvisor(),      // 对话记忆
    new VectorStoreAdvisor()             // 向量数据库检索
)

常用 Advisor

Advisor 功能 场景
SimpleLoggerAdvisor 打印请求和响应日志 开发调试
MessageChatMemoryAdvisor 自动管理对话历史 多轮对话
VectorStoreAdvisor RAG 向量检索 知识库问答

3. defaultOptions() - 默认模型参数

.defaultOptions(
    DashScopeChatOptions.builder()
        .model("qwen-plus")      // 模型名称
        .temperature(0.7)         // 创造性 (0-1)
        .maxTokens(2000)          // 最大输出长度
        .topP(0.9)                // 核采样参数
        .build()
)

支持的参数(以 DashScope 为例):

  • model:指定模型(qwen-turbo、qwen-plus、qwen-max)
  • temperature:创造性(0 严谨,1 创意)
  • maxTokens:最大 token 数
  • topP:核采样参数
  • stop:停止词列表

五、实战场景

场景 1:多角色 ChatClient

不同业务场景使用不同的 ChatClient:

@Configuration
public class ChatClientConfig {
    
    @Bean
    public ChatClient translatorClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
            .defaultSystem("你是一个专业翻译,请把中文翻译成英文")
            .defaultOptions(DashScopeChatOptions.builder()
                .temperature(0.3)  // 翻译需要严谨
                .build())
            .build();
    }
    
    @Bean
    public ChatClient writerClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
            .defaultSystem("你是一个创意作家,擅长写出优美的文字")
            .defaultOptions(DashScopeChatOptions.builder()
                .temperature(0.9)  // 写作需要创意
                .build())
            .build();
    }
}

使用

@RestController
public class MultiRoleController {
    
    @Autowired
    @Qualifier("translatorClient")
    private ChatClient translatorClient;
    
    @Autowired
    @Qualifier("writerClient")
    private ChatClient writerClient;
    
    @GetMapping("/translate")
    public String translate(String message) {
        return translatorClient.prompt(message).call().content();
    }
    
    @GetMapping("/write")
    public String write(String message) {
        return writerClient.prompt(message).call().content();
    }
}

场景 2:动态模型切换

运行时动态选择模型:

@GetMapping("/chat")
public String chat(String message, String model) {
    return chatClient.prompt(message)
        .options(DashScopeChatOptions.builder()
            .model(model)  // 动态指定模型
            .build())
        .call()
        .content();
}

测试

# 使用 qwen-plus
curl "http://localhost:8000/client/chat?message=你好&model=qwen-plus"

# 使用 deepseek-v3
curl "http://localhost:8000/client/chat?message=你好&model=deepseek-v3"

场景 3:对话记忆(多轮对话)

结合 ChatMemory 实现上下文对话:

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        new MessageChatMemoryAdvisor(
            new InMemoryChatMemory(),  // 内存存储
            "user-123",                 // 用户 ID
            10                          // 保留 10 条历史消息
        )
    )
    .build();

@GetMapping("/conversation")
public String conversation(String message) {
    return chatClient.prompt(message).call().content();
}

六、完整 Controller 示例

package cn.hollis.llm.llmentor.controller;

import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/client")
public class ChatClientDemoController implements InitializingBean {

    @Autowired
    private ChatModel dashScopeChatModel;

    private ChatClient chatClient;

    /**
     * 简单调用
     */
    @GetMapping("/simple")
    public String simpleCall(String message) {
        return chatClient.prompt(message).call().content();
    }

    /**
     * 覆盖默认 System 提示词
     */
    @GetMapping("/translator")
    public String translateCall(String message) {
        return chatClient.prompt(message)
            .system("你是一个翻译助手,请把中文翻译成英文")
            .call()
            .content();
    }

    /**
     * 使用 user() 方法
     */
    @GetMapping("/chat")
    public String chatCall(String message) {
        return chatClient.prompt()
            .user(message)
            .call()
            .content();
    }

    /**
     * 使用 Prompt 对象
     */
    @GetMapping("/advanced")
    public String advancedCall(String message) {
        Prompt prompt = new Prompt(
            new SystemMessage("请详细回答问题"),
            new UserMessage(message)
        );
        return chatClient.prompt(prompt).call().content();
    }

    /**
     * 流式输出
     */
    @GetMapping("/stream")
    public Flux<String> streamCall(String message) {
        return chatClient.prompt(message).stream().content();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        chatClient = ChatClient.builder(dashScopeChatModel)
            // 默认系统提示词
            .defaultSystem("请用英文回答问题")
            
            // 默认拦截器
            .defaultAdvisors(new SimpleLoggerAdvisor())
            
            // 默认模型参数
            .defaultOptions(
                DashScopeChatOptions.builder()
                    .temperature(0.7)
                    .build()
            )
            .build();
    }
}

七、最佳实践

1. ChatClient 应该复用

错误做法(每次请求都创建):

@GetMapping("/bad")
public String badPractice(String message) {
    ChatClient client = ChatClient.builder(chatModel).build();  // 每次都创建!
    return client.prompt(message).call().content();
}

正确做法(启动时创建,全局复用):

private ChatClient chatClient;

@Override
public void afterPropertiesSet() {
    chatClient = ChatClient.builder(chatModel).build();
}

2. 合理使用 Advisor

开发环境开启日志,生产环境关闭:

@Bean
public ChatClient chatClient(ChatModel chatModel, Environment env) {
    ChatClient.Builder builder = ChatClient.builder(chatModel);
    
    // 只在开发环境添加日志 Advisor
    if (env.acceptsProfiles(Profiles.of("dev"))) {
        builder.defaultAdvisors(new SimpleLoggerAdvisor());
    }
    
    return builder.build();
}

3. 分离配置和调用

@Configuration
public class ChatClientConfig {
    @Bean
    public ChatClient chatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel)
            .defaultSystem("你是一个 AI 助手")
            .defaultOptions(DashScopeChatOptions.builder()
                .temperature(0.7)
                .build())
            .build();
    }
}

@RestController
public class ChatController {
    @Autowired
    private ChatClient chatClient;  // 直接注入使用
    
    @GetMapping("/chat")
    public String chat(String message) {
        return chatClient.prompt(message).call().content();
    }
}

4. 错误处理

@GetMapping("/safe")
public String safeCall(String message) {
    try {
        return chatClient.prompt(message).call().content();
    } catch (Exception e) {
        log.error("调用大模型失败", e);
        return "抱歉,服务暂时不可用,请稍后重试";
    }
}

八、ChatClient vs ChatModel 对比

特性 ChatModel ChatClient
API 风格 底层 API 链式调用
配置方式 每次调用设置 Builder 预设
Advisor 支持 手动管理 内置支持
适用场景 简单调用、底层控制 企业级应用、复杂场景
代码优雅度 ⭐⭐⭐ ⭐⭐⭐⭐⭐
学习成本

九、总结

ChatClient 是 Spring AI 提供的高级 API,通过以下特性让大模型调用更优雅:

  1. 链式调用prompt().system().user().call().content()
  2. Builder 模式:一次性配置,全局复用
  3. Advisor 机制:日志、缓存、RAG 等切面功能
  4. 灵活覆盖:默认配置 + 运行时覆盖

推荐做法

  • 企业级项目优先使用 ChatClient
  • 简单测试可以使用 ChatModel
  • 合理配置 Advisor,提升开发效率

通过 ChatClient,我们可以用更少的代码、更清晰的结构,构建强大的大模型应用!


参考资源