当前位置: 首页 > news >正文

深度剖析Spring AI源码(三):ChatClient详解,优雅的流式API设计

深度剖析Spring AI源码(三):ChatClient详解,优雅的流式API设计

“The best APIs are those that make simple things simple and complex things possible.” —— Alan Kay (计算机科学巨匠)

Spring AI的ChatClient API正是这句话的完美诠释。它既能让新手在几分钟内快速上手,发出第一个AI请求,又能通过丰富的扩展点满足企业级应用的复杂需求。今天,让我们深入这个被誉为“AI界的WebClient/RestTemplate”的流式API,看看它是如何做到优雅与强大并存的。

开场:为什么需要ChatClient?

在上一章中,我们已经了解了Model抽象层的底层设计。但如果直接使用Model接口进行AI调用,代码往往是这样的:

// 原始的Model调用方式
ChatModel chatModel = new OpenAiChatModel(openAiApi);
Prompt prompt = new Prompt(List.of(new SystemMessage("You are a helpful assistant"),new UserMessage("Hello, how are you?")
));
ChatResponse response = chatModel.call(prompt);
String content = response.getResult().getOutput().getContent();

这种方式虽然功能完整,但暴露了几个痛点:

  • 冗长繁琐:仅仅为了发送一个简单的请求,就需要手动构建PromptMessage列表,样板代码过多。
  • 不够直观:核心的业务意图(比如“问一个问题”)被大量的技术细节所掩盖。
  • 难以组合:在需要动态构建Prompt、添加工具调用或应用重试等复杂场景下,代码会迅速变得混乱,可读性极差。

ChatClient的诞生,正是为了解决这些痛点,它提供了一个更高层次的抽象,让AI调用变得像使用Spring的WebClient调用REST API一样简单、直观和强大。

ChatClient接口体系架构

在深入代码之前,我们先通过一张类图来鸟瞰ChatClient的整体设计。这套接口体系遵循了流式API的设计思想,引导开发者一步步构建请求和处理响应。

«interface»
ChatClient
+prompt() : ChatClientRequestSpec
+prompt(String) : ChatClientRequestSpec
+prompt(Prompt) : ChatClientRequestSpec
+mutate() : Builder
«interface»
ChatClientRequestSpec
+system(String) : ChatClientRequestSpec
+user(String) : ChatClientRequestSpec
+advisors(Advisor...) : ChatClientRequestSpec
+call() : CallResponseSpec
+stream() : StreamResponseSpec
«interface»
CallResponseSpec
+content() : String
+entity(Class) : T
+chatResponse() : ChatResponse
«interface»
StreamResponseSpec
+content() : Flux<String>
+chatResponse() : Flux<ChatResponse>
«interface»
Builder
+defaultSystem(String) : Builder
+defaultAdvisors(Advisor...) : Builder
+build() : ChatClient

核心接口深度解析

1. ChatClient主接口

ChatClient是所有交互的入口,它定义了发起AI对话的起点。让我们深入其接口设计(位于spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java):

public interface ChatClient {// 静态工厂方法 - 简单创建static ChatClient create(ChatModel chatModel) {return create(chatModel, ObservationRegistry.NOOP);}// 带监控的创建方法static ChatClient create(ChatModel chatModel, ObservationRegistry observationRegistry) {return create(chatModel, observationRegistry, null);}// 完整的创建方法static ChatClient create(ChatModel chatModel, ObservationRegistry observationRegistry,@Nullable ChatClientObservationConvention observationConvention) {Assert.notNull(chatModel, "chatModel cannot be null");Assert.notNull(observationRegistry, "observationRegistry cannot be null");return builder(chatModel, observationRegistry, observationConvention).build();}// Builder模式入口static Builder builder(ChatModel chatModel) {return builder(chatModel, ObservationRegistry.NOOP, null);}// 核心方法 - 开始构建请求ChatClientRequestSpec prompt();ChatClientRequestSpec prompt(String content);ChatClientRequestSpec prompt(Prompt prompt);// 可变构建器 - 基于当前实例创建新的BuilderBuilder mutate();
}

这个接口的设计充满了Spring的味道,精妙之处在于:

  • 静态工厂方法:提供了多种createbuilder方法,无论是简单场景还是需要深度定制的场景,都能找到合适的创建方式。
  • 流式接口:所有prompt()方法都返回ChatClientRequestSpec接口,这使得方法调用可以像链条一样串联起来,代码一气呵成。
  • 不可变性ChatClient实例一旦通过build()创建,其默认配置就是不可变的,这使得它在多线程环境下可以被安全地共享和复用。mutate()方法则提供了一种“写时复制”的机制来创建新的、可修改的构建器。

2. 流式请求构建:ChatClientRequestSpec

ChatClientRequestSpecChatClient流式API的核心,它负责定义一次具体的AI请求。这个接口提供了丰富的方法来构建一个完整的Prompt

public interface ChatClientRequestSpec {// 可变构建器Builder mutate();// Advisor支持 - AOP模式的体现ChatClientRequestSpec advisors(Consumer<AdvisorSpec> consumer);ChatClientRequestSpec advisors(Advisor... advisors);ChatClientRequestSpec advisors(List<Advisor> advisors);// 消息构建ChatClientRequestSpec messages(Message... messages);ChatClientRequestSpec messages(List<Message> messages);// 模型选项<T extends ChatOptions> ChatClientRequestSpec options(T options);// 工具调用支持ChatClientRequestSpec toolNames(String... toolNames);ChatClientRequestSpec tools(Object... toolObjects);ChatClientRequestSpec toolCallbacks(ToolCallback... toolCallbacks);ChatClientRequestSpec toolContext(Map<String, Object> toolContext);// 系统消息 - 设定AI行为ChatClientRequestSpec system(String text);ChatClientRequestSpec system(Resource textResource, Charset charset);ChatClientRequestSpec system(Consumer<PromptSystemSpec> consumer);// 用户消息 - 实际的问题或指令ChatClientRequestSpec user(String text);ChatClientRequestSpec user(Resource text, Charset charset);ChatClientRequestSpec user(Consumer<PromptUserSpec> consumer);// 模板渲染器ChatClientRequestSpec templateRenderer(TemplateRenderer templateRenderer);// 执行调用CallResponseSpec call();      // 同步调用StreamResponseSpec stream();  // 流式调用
}

3. 响应处理:CallResponseSpec与StreamResponseSpec

当请求构建完毕,通过.call().stream()执行后,返回的响应处理接口同样设计得非常考究:

public interface CallResponseSpec {// 结构化输出 - 直接转换为Java对象@Nullable<T> T entity(ParameterizedTypeReference<T> type);@Nullable<T> T entity(StructuredOutputConverter<T> structuredOutputConverter);@Nullable<T> T entity(Class<T> type);// 包装响应 - 获取完整的响应信息ChatClientResponse chatClientResponse();// 原始响应@NullableChatResponse chatResponse();// 简单文本内容@NullableString content();// 响应实体包装<T> ResponseEntity<ChatResponse, T> responseEntity(Class<T> type);
}public interface StreamResponseSpec {// 流式响应Flux<ChatClientResponse> chatClientResponse();Flux<ChatResponse> chatResponse();Flux<String> content();
}

使用体验:从简单到复杂

1. 最简单的使用方式

对于最常见的“一问一答”场景,ChatClient的使用简单到了极致:

// 创建ChatClient
ChatClient chatClient = ChatClient.create(chatModel);// 最简单的调用
String response = chatClient.prompt("给我讲个笑话").call().content();System.out.println(response);

这就是ChatClient的魅力所在——让简单的事情变得极其简单,开发者无需关心底层的PromptMessage结构,专注于传递核心信息即可。

2. 稍微复杂的场景

当需要为AI设定角色时,可以链式调用.system().user()

String response = chatClient.prompt().system("你是一个专业的Java开发者").user("解释一下Spring Boot的自动配置原理").call().content();

3. 企业级复杂场景

在真实的企业级应用中,AI调用往往伴随着安全、日志、重试、多模态输入和结构化输出等复杂需求。ChatClient通过其强大的Builder和可扩展的Advisor机制,从容应对:

// 使用Builder预配置
ChatClient chatClient = ChatClient.builder(chatModel).defaultSystem("你是一个AI助手,专门帮助解决技术问题").defaultAdvisors(new SafeGuardAdvisor(),           // 安全防护new LoggingAdvisor(),             // 日志记录new RetryAdvisor()                // 重试机制).defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.7).maxTokens(1000).build()).build();// 复杂的调用
PersonInfo person = chatClient.prompt().user(userSpec -> userSpec.text("分析这个简历:{resume}").param("resume", resumeText).media(MimeType.IMAGE_JPEG, resumeImage)).advisors(new ResumeAnalysisAdvisor()).tools(resumeAnalysisService)  // 工具调用.call().entity(PersonInfo.class);     // 结构化输出

核心实现解析

1. DefaultChatClient的实现

让我们深入DefaultChatClient的内部,看看它是如何工作的(位于spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java):

public class DefaultChatClient implements ChatClient {private final ChatModel chatModel;private final ChatClientRequestSpec defaultRequestSpec;private final ObservationRegistry observationRegistry;// 构造函数 - 依赖注入DefaultChatClient(ChatModel chatModel, ChatClientRequestSpec defaultRequestSpec,ObservationRegistry observationRegistry) {this.chatModel = chatModel;this.defaultRequestSpec = defaultRequestSpec;this.observationRegistry = observationRegistry;}@Overridepublic ChatClientRequestSpec prompt() {// 返回默认请求规范的副本return this.defaultRequestSpec.mutate().build();}@Overridepublic ChatClientRequestSpec prompt(String content) {return prompt().user(content);}@Overridepublic ChatClientRequestSpec prompt(Prompt prompt) {return prompt().messages(prompt.getInstructions());}
}

2. 请求构建的实现:DefaultChatClientRequestSpec

DefaultChatClientRequestSpec是流式API的核心实现,它像一个状态机,一步步收集构建请求所需的所有信息。

class DefaultChatClientRequestSpec implements ChatClientRequestSpec {private final ChatModel chatModel;private final List<Message> messages = new ArrayList<>();private final List<Advisor> advisors = new ArrayList<>();private ChatOptions chatOptions;private Map<String, Object> toolContext = new HashMap<>();@Overridepublic ChatClientRequestSpec system(String text) {this.messages.add(new SystemMessage(text));return this;}@Overridepublic ChatClientRequestSpec user(String text) {this.messages.add(new UserMessage(text));return this;}@Overridepublic ChatClientRequestSpec advisors(Advisor... advisors) {this.advisors.addAll(Arrays.asList(advisors));return this;}@Overridepublic CallResponseSpec call() {return new DefaultCallResponseSpec(buildRequest());}@Overridepublic StreamResponseSpec stream() {return new DefaultStreamResponseSpec(buildRequest());}// 构建最终的请求对象private ChatClientRequest buildRequest() {return ChatClientRequest.builder().prompt(new Prompt(this.messages, this.chatOptions)).advisors(this.advisors).toolContext(this.toolContext).build();}
}

3. Advisor链的执行

ChatClient最精彩、最能体现其AOP思想的部分,莫过于Advisor链的执行逻辑:

class DefaultCallResponseSpec implements CallResponseSpec {private final ChatClientRequest request;@Overridepublic String content() {// 执行Advisor链ChatClientResponse response = executeAdvisorChain(request);return response.getResult().getOutput().getContent();}private ChatClientResponse executeAdvisorChain(ChatClientRequest request) {// 构建Advisor链List<CallAdvisor> callAdvisors = request.getAdvisors().stream().filter(CallAdvisor.class::isInstance).map(CallAdvisor.class::cast).sorted(AnnotationAwareOrderComparator.INSTANCE)  // 按Order排序.toList();// 添加默认的ChatModel调用AdvisorList<CallAdvisor> allAdvisors = new ArrayList<>(callAdvisors);allAdvisors.add(new ChatModelCallAdvisor(chatModel));// 执行链式调用CallAdvisorChain chain = new DefaultCallAdvisorChain(allAdvisors);return chain.nextCall(request);}
}

设计模式的精妙运用

1. 建造者模式的完美实现

ChatClientBuilder设计堪称教科书级别,它将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

public interface Builder {// 默认配置方法Builder defaultAdvisors(Advisor... advisor);Builder defaultOptions(ChatOptions chatOptions);Builder defaultUser(String text);Builder defaultSystem(String text);// 模板渲染器Builder defaultTemplateRenderer(TemplateRenderer templateRenderer);// 工具相关Builder defaultToolNames(String... toolNames);Builder defaultTools(Object... toolObjects);Builder defaultToolCallbacks(ToolCallback... toolCallbacks);// 克隆当前BuilderBuilder clone();// 构建最终对象ChatClient build();
}

使用示例:

ChatClient chatClient = ChatClient.builder(chatModel).defaultSystem("你是一个专业的技术顾问").defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.3).build()).defaultAdvisors(new LoggingAdvisor(),new SafeGuardAdvisor()).build();

2. 责任链模式的Advisor实现

Advisor的执行巧妙地运用了责任链模式,每个Advisor都是链上的一个节点,它既可以处理请求,也可以将请求传递给链上的下一个节点。

public interface CallAdvisorChain {ChatClientResponse nextCall(ChatClientRequest request);
}class DefaultCallAdvisorChain implements CallAdvisorChain {private final List<CallAdvisor> advisors;private final int index;@Overridepublic ChatClientResponse nextCall(ChatClientRequest request) {if (index >= advisors.size()) {throw new IllegalStateException("No more advisors in chain");}CallAdvisor advisor = advisors.get(index);CallAdvisorChain nextChain = new DefaultCallAdvisorChain(advisors, index + 1);// 每个Advisor都可以决定是否继续执行链return advisor.adviseCall(request, nextChain);}
}

3. 模板方法模式的应用

BaseAdvisor接口(我们将在后续章节深入探讨)则提供了模板方法的实现,定义了beforeafter两个钩子方法,让开发者可以轻松地在AI调用的前后织入自定义逻辑。

public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {@Overridedefault ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {// 模板方法:before -> 执行链 -> afterChatClientRequest processedRequest = before(request, chain);ChatClientResponse response = chain.nextCall(processedRequest);return after(response, chain);}// 子类实现具体的前置和后置逻辑ChatClientRequest before(ChatClientRequest request, AdvisorChain chain);ChatClientResponse after(ChatClientResponse response, AdvisorChain chain);
}

流式处理的优雅实现

流式处理是现代AI应用的标配,ChatClient通过与Project Reactor的深度集成,提供了非常优雅的实现:

@Override
public Flux<String> content() {return executeStreamAdvisorChain(request).map(response -> response.getResult().getOutput().getContent()).filter(Objects::nonNull);
}private Flux<ChatClientResponse> executeStreamAdvisorChain(ChatClientRequest request) {// 构建流式Advisor链List<StreamAdvisor> streamAdvisors = request.getAdvisors().stream().filter(StreamAdvisor.class::isInstance).map(StreamAdvisor.class::cast).sorted(AnnotationAwareOrderComparator.INSTANCE).toList();// 添加默认的流式调用AdvisorList<StreamAdvisor> allAdvisors = new ArrayList<>(streamAdvisors);allAdvisors.add(new ChatModelStreamAdvisor(chatModel));// 执行流式链StreamAdvisorChain chain = new DefaultStreamAdvisorChain(allAdvisors);return chain.nextStream(request);
}

使用示例:

// 流式输出,逐字显示
chatClient.prompt("写一首关于Spring的诗").stream().content().subscribe(System.out::print);// 或者收集完整响应
List<String> chunks = chatClient.prompt("解释量子计算").stream().content().collectList().block();

结构化输出的魔法

ChatClient最令人惊艳的功能之一,就是能将AI的非结构化文本输出,直接转换为类型安全的Java对象,这极大地简化了后续的数据处理。

// 定义输出结构
public record WeatherInfo(String city,double temperature,String condition,List<String> forecast
) {}// 直接获取结构化对象
WeatherInfo weather = chatClient.prompt("北京今天的天气如何?请以JSON格式返回").call().entity(WeatherInfo.class);System.out.println("城市:" + weather.city());
System.out.println("温度:" + weather.temperature() + "°C");

这个“魔法”的背后,是Spring AI内置的StructuredOutputConverter在默默工作,它会智能地在Prompt中添加指令,引导AI输出指定的JSON格式,并自动完成反序列化。

@Override
public <T> T entity(Class<T> type) {ChatClientResponse response = chatClientResponse();// 使用Jackson进行JSON转换StructuredOutputConverter<T> converter = new BeanOutputConverter<>(type);String content = response.getResult().getOutput().getContent();return converter.convert(content);
}

最佳实践与使用技巧

1. 合理使用Builder预配置

在真实项目中,你可能会针对不同业务场景使用不同的AI配置。通过@Bean@Qualifier,你可以预先配置好多个ChatClient实例,供不同服务注入使用。

// 为不同场景创建专门的ChatClient
@Configuration
public class ChatClientConfig {@Bean@Qualifier("technical")public ChatClient technicalChatClient(ChatModel chatModel) {return ChatClient.builder(chatModel).defaultSystem("你是一个资深的技术专家").defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.3)  // 技术问答需要更准确.build()).build();}@Bean@Qualifier("creative")public ChatClient creativeChatClient(ChatModel chatModel) {return ChatClient.builder(chatModel).defaultSystem("你是一个富有创意的作家").defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.9)  // 创意写作需要更多随机性.build()).build();}
}

2. 善用Advisor增强功能

AdvisorChatClient的“超级外挂”。你可以自定义Advisor来实现各种横切关注点,比如下面这个用于控制Token成本的例子:

// 自定义Advisor
public class CostControlAdvisor implements BaseAdvisor {private final TokenCounter tokenCounter;private final int maxTokensPerRequest;@Overridepublic ChatClientRequest before(ChatClientRequest request, AdvisorChain chain) {// 检查token数量int estimatedTokens = tokenCounter.estimate(request.getPrompt());if (estimatedTokens > maxTokensPerRequest) {throw new IllegalArgumentException("Request too large: " + estimatedTokens);}return request;}@Overridepublic ChatClientResponse after(ChatClientResponse response, AdvisorChain chain) {// 记录实际使用的tokenUsage usage = response.getMetadata().getUsage();tokenCounter.record(usage.getTotalTokens());return response;}
}

3. 错误处理和重试

结合Advisor和Spring Retry,可以轻松实现健壮的错误处理和重试机制。

// 带重试的调用
String response = chatClient.prompt("复杂的技术问题").advisors(new RetryAdvisor(3, Duration.ofSeconds(2))) // 配置重试3次,间隔2秒.call().content();// 推荐使用Spring AI内置的异常体系进行捕获
try {String result = chatClient.prompt("可能失败的请求").call().content();
} catch (ChatClientException e) {log.error("AI调用失败,根本原因: ", e.getRootCause());return "抱歉,AI服务暂时不可用,请稍后再试。";
}

小结

ChatClient API是Spring AI最亮眼的“门面”之一,它完美地践行了Spring生态的设计哲学:

  1. 流式接口:让API调用变得直观和优雅
  2. Builder模式:提供了灵活的配置方式
  3. Advisor机制:通过AOP和责任链模式,提供了强大的、非侵入式的横切关注点处理能力。
  4. 响应式支持:与Project Reactor无缝集成,天然支持流式处理和背压控制。
  5. 结构化输出:将AI的自然语言输出直接转换为Java对象,打通了AI与业务逻辑的“最后一公里”。

这套精心设计的API不仅极大地降低了AI应用的开发门槛,也为构建复杂的企业级场景提供了坚实、可扩展的基础。它就像是AI世界的WebClient,让Java开发者能够用最熟悉、最舒适的方式,驾驭强大的生成式AI。

下一章,我们将深入向量存储的世界,探索Spring AI是如何统一五花八门的向量数据库的。这将是构建RAG应用的基石,也是企业AI应用的核心组件,敬请期待!


思考题:如果让你设计一个AI调用的流式API,你会如何平衡易用性和灵活性?ChatClient的设计给了你什么启发?

http://www.dtcms.com/a/348970.html

相关文章:

  • R60ABD1 串口通信实现
  • 在 Ubuntu 24.04 或 22.04 LTS 服务器上安装、配置和使用 Fail2ban
  • 【Qwen Image】蒸馏版与非蒸馏版 评测小结
  • 第3篇:配置管理的艺术 - 让框架更灵活
  • 多线程下单例如何保证
  • [身份验证脚手架] 前端认证与个人资料界面
  • 2025.8.18-2025.8.24第34周:有内耗有挣扎
  • Spring Cloud 快速通关之Sentinel
  • 遥感机器学习入门实战教程|Sklearn案例⑩:降维与分解(decomposition 模块)
  • [e3nn] 等变神经网络 | 线性层o3.Linear | 非线性nn.Gate
  • 动态规划--编译距离
  • AI代码生成器全面评测:六个月、500小时测试揭示最强开发助手
  • Redis 高可用篇
  • 51单片机-实现定时器模块教程
  • GaussDB 数据库架构师修炼(十八) SQL引擎-统计信息
  • 用 WideSearch 思路打造「零幻觉、全覆盖」的多 Agent 信息收集器
  • SRE 系列(四)| MTTI 与 On-Call:高效故障响应之道
  • C++标准库算法:从零基础到精通
  • Go语言 Hello World 实例
  • 数据标注的质检环节有多少种
  • 单表查询-分析函数的应用
  • 智能体之推理引擎(3)
  • 记一次使用 C++ 实现多种扑克牌逻辑
  • ptrade `get_fundamentals` - 获取财务数据
  • 58 C++ 现代C++编程艺术7-模板友元
  • VC2022连接mysql
  • 微服务-21.网关路由-路由属性
  • 2025年KBS SCI1区TOP,新颖奖励与ε-贪婪衰减Q-learning算法+局部移动机器人路径规划,深度解析+性能实测
  • AI基础学习周报十
  • AI产品经理面试宝典第74天:技术边界与商业闭环的面试问题与答法