乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • Spring AI 学习路径
  • 第1章 Spring AI 概述与核心理念

    • 章节导读
    • Spring AI 概述与可移植 API
    • 核心模块与依赖关系
  • 第2章 快速入门与第一个AI应用

    • 章节导读
    • 环境准备与配置
    • 第一个 AI 对话应用
  • 第3章 聊天模型与ChatClient

    • 章节导读
    • ChatClient 详解
    • ChatModel 底层抽象
    • 多轮对话与 ChatMemory
  • 第4章 提示词管理与模板

    • 章节导读
    • Prompt 与 Message 体系
    • 提示词模板与动态构建
  • 第5章 输出解析与结构化响应

    • 章节导读
    • 输出解析器与 BeanOutputConverter
  • 第6章 嵌入模型与向量存储

    • 章节导读
    • EmbeddingModel 与文本向量化
    • ETL 数据注入流水线
    • 向量存储抽象与配置
  • 第7章 检索增强生成(RAG)

    • 章节导读
    • RAG 核心机制与流程
    • QuestionAnswerAdvisor 详解
    • RAG 实战案例
  • 第8章 函数调用(Function Calling)

    • 章节导读
    • Tool 注解与函数声明
    • 函数调用实战
  • 第9章 多模态

    • 章节导读
    • 图像输入与视觉模型
    • 图像生成
  • 第10章 Advisor拦截器链

    • 章节导读
    • Advisor 链与内置拦截器
  • 第11章 MCP 协议与跨语言工具集成

    • 章节导读
    • MCP 协议深入
  • 第12章 可观测性测试与最佳实践

    • 章节导读
    • 可观测性与指标监控
    • 测试策略与 Mock 实践
    • 最佳实践与面试考点汇总

白歌在白板上画了一条链:"chatClient.prompt().system(...).user(...).options(...).advisors(...).call().content()——每一步返回不同的 Spec 对象,每一层都可以定制。这就是 Fluent API 的精髓。"

ChatClient 详解

定义与作用

ChatClient 是 Spring AI 中应用与 AI 模型交互的核心门面。它提供 Fluent API(链式调用),让开发者用一条链完成 Prompt 构建、选项配置、工具注册、Advisor 绑定和模型调用。

类比传统 Spring:ChatClient 之于 AI 调用,就像 JdbcTemplate 之于数据库操作——封装底层细节,提供简单直观的编程模型。

核心原理:完整调用链路

图释:ChatClient 在发送 Prompt 前经过 Advisor 链处理(注入历史、RAG 上下文、日志),收到 Response 后再经 Advisor 链处理(过滤、脱敏),最后返回给应用。

Fluent API 全貌

ChatClient 的 Fluent API 分为三个阶段的返回类型:

阶段方法返回类型说明
构建请求.prompt()ChatClientRequestSpec发起请求构建
.prompt(Prompt)ChatClientRequestSpec传入已构造的 Prompt
设置消息.system(String)ChatClientRequestSpec设置 System Message
.user(String)ChatClientRequestSpec设置用户消息
.user(Consumer<UserSpec>)ChatClientRequestSpec多模态用户消息
.messages(Message...)ChatClientRequestSpec手动添加任意消息
配置选项.options(ChatOptions)ChatClientRequestSpec覆盖模型参数
工具注册.functions(String...)ChatClientRequestSpec本次调用启用的工具
.toolCallbacks(ToolCallback...)ChatClientRequestSpec直接注册工具回调
顾问注册.advisors(Advisor...)ChatClientRequestSpec本次调用的 Advisor
执行.call()ChatClientCallResponseSpec阻塞执行
.stream()Flux<ChatResponse>流式执行
提取结果.content()String提取文本内容
.chatResponse()ChatResponse获取完整响应

创建 ChatClient 的方式

方式一:Builder 注入(推荐)

@Configuration
public class AiConfig {

    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                .defaultSystem("你是飞翔科技大学的学生助手。")
                .defaultOptions(ChatOptions.builder()
                        .temperature(0.7)
                        .build())
                .build();
    }
}

方式二:通过 ChatModel 手动构建

@RestController
public class ManualChatController {

    private final ChatModel chatModel;

    public ManualChatController(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    @GetMapping("/manual-chat")
    public String manualChat(@RequestParam String message) {
        // 手动构建 ChatClient,不经过 Spring 容器
        ChatClient client = ChatClient.builder(chatModel).build();
        return client.prompt().user(message).call().content();
    }
}

方式三:多个 ChatClient Bean

@Configuration
public class MultiClientConfig {

    // 通用助手(默认)
    @Bean
    public ChatClient defaultClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是通用助手。").build();
    }

    // 课程顾问(专用 System Prompt)
    @Bean
    public ChatClient courseAdvisor(ChatClient.Builder builder) {
        return builder
                .defaultSystem("""
                    你是飞翔科技大学的课程顾问。你了解所有专业的课程设置、
                    学分要求和毕业条件。请基于培养学生能力的目标给出建议。
                    """)
                .defaultOptions(ChatOptions.builder()
                        .temperature(0.3)   // 课程问题需要更确定
                        .build())
                .build();
    }
}

完整示例一:带 SystemMessage 和 ChatOptions 的链式调用

场景说明

高英(用户运营)需要分析学生反馈。她希望 AI 以运营专家的角色来分析,且输出要简洁。

关键代码

@RestController
public class FeedbackController {

    private final ChatClient chatClient;

    public FeedbackController(ChatClient.Builder builder) {
        this.chatClient = builder
                .defaultSystem("你是飞翔科技大学的用户运营分析专家。")
                .build();
    }

    @GetMapping("/analyze-feedback")
    public String analyze(@RequestParam String feedback) {
        return chatClient.prompt()
                .user("分析以下学生反馈,给出改进建议(不超过3条):" + feedback)
                .options(ChatOptions.builder()
                        .temperature(0.2)     // 分析任务需要低温度
                        .maxTokens(300)
                        .build())
                .call()
                .content();
    }
}

运行结果

学生反馈:"选课系统太卡了,每次都抢不到热门课。"

AI 分析:
1. 技术层面:建议引入消息队列异步处理选课请求,缓解峰值压力。
2. 策略层面:可考虑分批次开放选课(高年级优先),分散瞬时流量。
3. 体验层面:增加选课等待队列和实时排队状态提示,降低学生焦虑。

完整示例二:流式调用与 SSE 输出

场景说明

孔蓝要求课程问答像聊天一样实时显示。小崔用流式调用实现。

关键代码

@RestController
public class StreamController {

    private final ChatClient chatClient;

    public StreamController(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    // 流式 SSE
    @GetMapping(value = "/stream", produces = "text/event-stream")
    public Flux<String> stream(@RequestParam String question) {
        return chatClient.prompt()
                .user(question)
                .stream()
                .content();
    }

    // 流式 + 获取完整 ChatResponse(含 Token 用量)
    @GetMapping(value = "/stream/detail", produces = "text/event-stream")
    public Flux<String> streamDetail(@RequestParam String question) {
        return chatClient.prompt()
                .user(question)
                .stream()
                .chatResponse()
                .map(resp -> {
                    String content = resp.getResult().getOutput().getContent();
                    if (content == null) return "";
                    // 最后一条附带 Token 用量
                    var usage = resp.getMetadata().getUsage();
                    if (usage != null && resp.getResults().size() == 1
                            && resp.getResult().getMetadata().getFinishReason() != null) {
                        return content + "\n[Token: " + usage.getTotalTokens() + "]";
                    }
                    return content;
                });
    }
}

易错场景与面试考点

易错场景一:忘记调用 .call() 或 .content()

// ❌ 错误:ChatClientRequestSpec 转 String 会输出对象描述
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
    return chatClient.prompt().user(msg).toString();
    // 输出:ChatClientRequestSpec@6d06d69c
}

问题分析

Fluent API 的每个阶段返回不同类型,必须完整走完链才能拿到文本。

// ✅ 正确:完整链
return chatClient.prompt().user(msg).call().content();

易错场景二:.stream() 后调用 .content() 的类型混淆

// ❌ 错误:stream().content() 返回 Flux<String>,不能直接赋值给 String
@GetMapping("/chat")
public String chat(@RequestParam String msg) {
    return chatClient.prompt().user(msg).stream().content();
    // 编译错误:Flux<String> 不能转为 String
}

问题分析

.stream() 返回 Flux<ChatResponse>,.content() 将其映射为 Flux<String>,必须用 Flux<String> 接收。

// ✅ 正确:返回 Flux<String>
@GetMapping(value = "/chat", produces = "text/event-stream")
public Flux<String> chat(@RequestParam String msg) {
    return chatClient.prompt().user(msg).stream().content();
}

面试高频题

Q1:ChatClient 的 defaultSystem 和请求时的 .system() 有什么区别?

defaultSystem 在创建 Bean 时设置,对所有请求生效。.system() 在单次请求中设置,会覆盖 defaultSystem。前者用于全局角色设定(如"你是学生助手"),后者用于临时场景切换。

Q2:Fluent API 中 .options() 做了什么?

.options() 覆盖本次请求的模型参数(temperature、maxTokens 等)。它接收的 ChatOptions 会与 defaultOptions 合并,request 级别的优先级更高。

本章小结

  • ChatClient 是 Spring AI 的核心门面,Fluent API 涵盖 prompt → message → options → functions → advisors → call → content 的完整链
  • 创建方式:Builder 注入(推荐)、手动构建、多 Bean 专用化
  • defaultSystem 设全局角色,.system() 按需覆盖
  • 流式调用返回 Flux<String>,必须配合 produces = "text/event-stream"
  • 链式调用每个阶段返回不同类型,不能中途跳过
上一页
章节导读
下一页
ChatModel 底层抽象