work hard work smart

专注于AI+Java后端开发。 不断总结,举一反三。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

摘要: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,我们可以用更少的代码、更清晰的结构,构建强大的大模型应用!


参考资源