原文链接地址# SpringAI(GA):ChatClient调用链路解读
教程说明
说明:本教程将采用2025年5月20日正式的GA版,给出如下内容
- 核心功能模块的快速上手教程
- 核心功能模块的源码级解读
- Spring ai alibaba增强的快速上手教程 + 源码级解读
版本:JDK21 + SpringBoot3.4.5 + SpringAI 1.0.0 + SpringAI Alibaba最新
将陆续完成如下章节教程。本章是第一章(chat初体验)下的其中一部分源码解读—ChatClient解读
- 目前已完成第一、二章源码解读部分
微信推文往届解读可参考:
获取更好的观赏体验,可付费获取飞书云文档Spring AI最新教程权限,目前39.9,随着内容不断完善,会逐步涨价。
注:M6版快速上手教程+源码解读飞书云文档已免费提供
[!TIP] ChatClient 端设置 advisors、ChatOptions、用户提示信息、系统提示信息、工具等信息,构建 DefaultChatClient.DefaultChatClientRequestSpec,再利用 DefaultChatClientUtils 将其转换为 ChatClientRequest
AdvisorChain 链调用一系列的增强器Advisor 基础,每个增强器输入是 ChatClientRequest,输出 ChatClientResponse(其中必定会用到的是 ChatModelCallAdvisor 或 ChatModelStreamAdvisor)
ChatClient
类的说明:面向对话式 AI 模型的客户端接口,提供了系列的 API 与 AI 会话模型交互,该接口封装了请求构建、调用、响应处理等流畅,支持同步、流式调用
方法说明
| 方法名称 | 描述 |
| create(静态方法) | 由ChatModel、观测信息等创建 ChatClient 实例 |
| builder(静态方法) | 由ChatModel、观测信息等创建 ChatClient.Builder实例 |
| mutate | 复制当前客户端配置,生成新的ChatClient.Builder实例 |
| prompt | 构建ChatClientRequestSpec实例 |
内部接口类说明
| 接口类 | 方法名称 | 描述 |
| Builder (全局的ChatClient配置) | defaultAdvisors | 设置advisors |
| defaultOptions | 设置ChatOptions | |
| defaultUser | 设置用户提示信息 | |
| defaultSystem | 设置系统提示信息 | |
| defaultTemplateRenderer | 设置模版渲染器,用于处理字符串的占位符 | |
| defaultToolNames | 根据工具名称获取工具配置 | |
| defaultTools | 根据实例获取工具配置 | |
| defaultToolCallbacks | 根据ToolCallback获取工具配置 | |
| defaultToolContext | 工具的上下文 | |
| clone | 复制当前客户端配置,生成新的ChatClient.Builder实例 | |
| build | 构建最终的ChatClient实例 | |
| ChatClientRequestSpec (当前的ChatClient配置) | advisors | 设置advisors |
| options | 设置ChatOptions | |
| user | 设置用户提示信息 | |
| system | 设置系统提示信息 | |
| templateRenderer | 设置模版渲染器,用于处理字符串的占位符 | |
| toolNames | 根据工具名称获取工具配置 | |
| tools | 根据实例获取工具配置 | |
| toolCallbacks | 根据ToolCallback获取工具配置 | |
| toolContext | 工具的上下文 | |
| mutate | 复制当前客户端配置,生成新的ChatClient.Builder实例 | |
| messages | 添加Message | |
| call | 同步调用 | |
| stream | 流式调用 | |
| PromptUserSpec (用户提示信息的构建规范) | text | 设置用户文本内容 |
| param | 设置参数 | |
| params | 设置参数 | |
| media | 设置多媒体内容(如图片) | |
| PromptSystemSpec (系统提示信息的构建规范) | text | 设置系统指令 |
| param | 设置参数 | |
| params | 设置参数 | |
| AdvisorSpec (设置增强器) | param | 设置增强器中会用到的一些参数配置 |
| advisors | 添加增强器 | |
| CallResponseSpec | entity | 将响应体转换为指定类型 |
| chatClientResponse | 原始响应对象+请求时的上下文内容 | |
| chatResponse | 原始的响应对象 | |
| content | 响应的文本内容 | |
| responseEntity | 获取封装了响应头和body的对象 | |
| chatClientResponse | 流式的原始响应对象+请求时的上下文内容 | |
| StreamResponseSpec | chatResponse | 流式的原始的响应对象 |
| content | 流式的响应的文本内容 |
public interface ChatClient {
static ChatClient create(ChatModel chatModel) {
return create(chatModel, ObservationRegistry.NOOP);
}
static ChatClient create(ChatModel chatModel, ObservationRegistry observationRegistry) {
return create(chatModel, observationRegistry, (ChatClientObservationConvention)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();
}
static Builder builder(ChatModel chatModel) {
return builder(chatModel, ObservationRegistry.NOOP, (ChatClientObservationConvention)null);
}
static Builder builder(ChatModel chatModel, ObservationRegistry observationRegistry, @Nullable ChatClientObservationConvention customObservationConvention) {
Assert.notNull(chatModel, "chatModel cannot be null");
Assert.notNull(observationRegistry, "observationRegistry cannot be null");
return new DefaultChatClientBuilder(chatModel, observationRegistry, customObservationConvention);
}
ChatClientRequestSpec prompt();
ChatClientRequestSpec prompt(String content);
ChatClientRequestSpec prompt(Prompt prompt);
Builder mutate();
public interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}
public interface Builder {
Builder defaultAdvisors(Advisor... advisor);
Builder defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer);
Builder defaultAdvisors(List<Advisor> advisors);
Builder defaultOptions(ChatOptions chatOptions);
Builder defaultUser(String text);
Builder defaultUser(Resource text, Charset charset);
Builder defaultUser(Resource text);
Builder defaultUser(Consumer<PromptUserSpec> userSpecConsumer);
Builder defaultSystem(String text);
Builder defaultSystem(Resource text, Charset charset);
Builder defaultSystem(Resource text);
Builder defaultSystem(Consumer<PromptSystemSpec> systemSpecConsumer);
Builder defaultTemplateRenderer(TemplateRenderer templateRenderer);
Builder defaultToolNames(String... toolNames);
Builder defaultTools(Object... toolObjects);
Builder defaultToolCallbacks(ToolCallback... toolCallbacks);
Builder defaultToolCallbacks(List<ToolCallback> toolCallbacks);
Builder defaultToolCallbacks(ToolCallbackProvider... toolCallbackProviders);
Builder defaultToolContext(Map<String, Object> toolContext);
Builder clone();
ChatClient build();
}
public interface CallPromptResponseSpec {
String content();
List<String> contents();
ChatResponse chatResponse();
}
public interface CallResponseSpec {
@Nullable
<T> T entity(ParameterizedTypeReference<T> type);
@Nullable
<T> T entity(StructuredOutputConverter<T> structuredOutputConverter);
@Nullable
<T> T entity(Class<T> type);
ChatClientResponse chatClientResponse();
@Nullable
ChatResponse chatResponse();
@Nullable
String content();
<T> ResponseEntity<ChatResponse, T> responseEntity(Class<T> type);
<T> ResponseEntity<ChatResponse, T> responseEntity(ParameterizedTypeReference<T> type);
<T> ResponseEntity<ChatResponse, T> responseEntity(StructuredOutputConverter<T> structuredOutputConverter);
}
public interface ChatClientRequestSpec {
Builder mutate();
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 toolCallbacks(List<ToolCallback> toolCallbacks);
ChatClientRequestSpec toolCallbacks(ToolCallbackProvider... toolCallbackProviders);
ChatClientRequestSpec toolContext(Map<String, Object> toolContext);
ChatClientRequestSpec system(String text);
ChatClientRequestSpec system(Resource textResource, Charset charset);
ChatClientRequestSpec system(Resource text);
ChatClientRequestSpec system(Consumer<PromptSystemSpec> consumer);
ChatClientRequestSpec user(String text);
ChatClientRequestSpec user(Resource text, Charset charset);
ChatClientRequestSpec user(Resource text);
ChatClientRequestSpec user(Consumer<PromptUserSpec> consumer);
ChatClientRequestSpec templateRenderer(TemplateRenderer templateRenderer);
CallResponseSpec call();
StreamResponseSpec stream();
}
public interface PromptSystemSpec {
PromptSystemSpec text(String text);
PromptSystemSpec text(Resource text, Charset charset);
PromptSystemSpec text(Resource text);
PromptSystemSpec params(Map<String, Object> p);
PromptSystemSpec param(String k, Object v);
}
public interface PromptUserSpec {
PromptUserSpec text(String text);
PromptUserSpec text(Resource text, Charset charset);
PromptUserSpec text(Resource text);
PromptUserSpec params(Map<String, Object> p);
PromptUserSpec param(String k, Object v);
PromptUserSpec media(Media... media);
PromptUserSpec media(MimeType mimeType, URL url);
PromptUserSpec media(MimeType mimeType, Resource resource);
}
public interface StreamPromptResponseSpec {
Flux<ChatResponse> chatResponse();
Flux<String> content();
}
public interface StreamResponseSpec {
Flux<ChatClientResponse> chatClientResponse();
Flux<ChatResponse> chatResponse();
Flux<String> content();
}
}
DefaultChatClient
ChatClient 接口的默认实现类,用于构建和执行与 AI 聊天模型交互的请求
- 内部类 DefaultChatClientRequestSpec 实现了 ChatClient.ChatClientRequestSpec:新增 ChatModelCallAdvisor
public static class DefaultChatClientRequestSpec implements ChatClient.ChatClientRequestSpec {
private BaseAdvisorChain buildAdvisorChain() {
this.advisors.add(ChatModelCallAdvisor.builder().chatModel(this.chatModel).build());
this.advisors.add(ChatModelStreamAdvisor.builder().chatModel(this.chatModel).build());
return DefaultAroundAdvisorChain.builder(this.observationRegistry).pushAll(this.advisors).templateRenderer(this.templateRenderer).build();
}
}
- 内部类 DefaultPromptSystemSpec 实现 ChatClient.PromptSystemSpec:设置用户文本内容、参数
- 内部类 DefaultPromptSystemSpec 实现 ChatClient.PromptSystemSpec:设置系统文本内容、参数
- 内部类 DefaultAdvisorSpec 实现 ChatClient.AdvisorSpec:设置 Advisor,及其 advisor 中用到的参数
- 内部类 DefaultCallResponseSpec 实现 ChatClient.CallResponseSpec:通过 doGetObservableChatClientResponse 方法发起请求,调用一系列的 BaseAdvisorChain
public static class DefaultCallResponseSpec implements ChatClient.CallResponseSpec {
private final ChatClientRequest request;
private final BaseAdvisorChain advisorChain;
private final ObservationRegistry observationRegistry;
private final ChatClientObservationConvention observationConvention;
private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest, @Nullable String outputFormat) {
if (outputFormat != null) {
chatClientRequest.context().put(ChatClientAttributes.OUTPUTFORMAT.getKey(), outputFormat);
}
ChatClientObservationContext observationContext = ChatClientObservationContext.builder().request(chatClientRequest).advisors(this.advisorChain.getCallAdvisors()).stream(false).format(outputFormat).build();
Observation observation = ChatClientObservationDocumentation.AICHATCLIENT.observation(this.observationConvention, DefaultChatClient.DEFAULTCHATCLIENTOBSERVATIONCONVENTION, () -> observationContext, this.observationRegistry);
ChatClientResponse chatClientResponse = (ChatClientResponse)observation.observe(() -> this.advisorChain.nextCall(chatClientRequest));
return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();
}
}
- 内部类 DefaultStreamResponseSpec 实现 ChatClient.StreamResponseSpec:通过 doGetObservableFluxChatResponse 方法发起请求,调用一系列的 BaseAdvisorChain
public static class DefaultStreamResponseSpec implements ChatClient.StreamResponseSpec {
private final ChatClientRequest request;
private final BaseAdvisorChain advisorChain;
private final ObservationRegistry observationRegistry;
private final ChatClientObservationConvention observationConvention;
private Flux<ChatClientResponse> doGetObservableFluxChatResponse(ChatClientRequest chatClientRequest) {
return Flux.deferContextual((contextView) -> {
ChatClientObservationContext observationContext = ChatClientObservationContext.builder().request(chatClientRequest).advisors(this.advisorChain.getStreamAdvisors()).stream(true).build();
Observation observation = ChatClientObservationDocumentation.AICHATCLIENT.observation(this.observationConvention, DefaultChatClient.DEFAULTCHATCLIENTOBSERVATIONCONVENTION, () -> observationContext, this.observationRegistry);
observation.parentObservation((Observation)contextView.getOrDefault("micrometer.observation", (Object)null)).start();
Flux var10000 = this.advisorChain.nextStream(chatClientRequest);
Objects.requireNonNull(observation);
return var10000.doOnError(observation::error).doFinally((s) -> observation.stop()).contextWrite((ctx) -> ctx.put("micrometer.observation", observation));
});
}
}
完整代码如下
package org.springframework.ai.chat.client;
public class DefaultChatClient implements ChatClient {
private static final ChatClientObservationConvention DEFAULTCHATCLIENTOBSERVATIONCONVENTION = new DefaultChatClientObservationConvention();
private static final TemplateRenderer DEFAULTTEMPLATERENDERER = StTemplateRenderer.builder().build();
private final DefaultChatClientRequestSpec defaultChatClientRequest;
public DefaultChatClient(DefaultChatClientRequestSpec defaultChatClientRequest) {
Assert.notNull(defaultChatClientRequest, "defaultChatClientRequest cannot be null");
this.defaultChatClientRequest = defaultChatClientRequest;
}
public ChatClient.ChatClientRequestSpec prompt() {
return new DefaultChatClientRequestSpec(this.defaultChatClientRequest);
}
public ChatClient.ChatClientRequestSpec prompt(String content) {
Assert.hasText(content, "content cannot be null or empty");
return this.prompt(new Prompt(content));
}
public ChatClient.ChatClientRequestSpec prompt(Prompt prompt) {
Assert.notNull(prompt, "prompt cannot be null");
DefaultChatClientRequestSpec spec = new DefaultChatClientRequestSpec(this.defaultChatClientRequest);
if (prompt.getOptions() != null) {
spec.options(prompt.getOptions());
}
if (prompt.getInstructions() != null) {
spec.messages(prompt.getInstructions());
}
return spec;
}
public ChatClient.Builder mutate() {
return this.defaultChatClientRequest.mutate();
}
public static class DefaultPromptUserSpec implements ChatClient.PromptUserSpec {
private final Map<String, Object> params = new HashMap();
private final List<Media> media = new ArrayList();
@Nullable
private String text;
public ChatClient.PromptUserSpec media(Media... media) {
Assert.notNull(media, "media cannot be null");
Assert.noNullElements(media, "media cannot contain null elements");
this.media.addAll(Arrays.asList(media));
return this;
}
public ChatClient.PromptUserSpec media(MimeType mimeType, URL url) {
Assert.notNull(mimeType, "mimeType cannot be null");
Assert.notNull(url, "url cannot be null");
try {
this.media.add(Media.builder().mimeType(mimeType).data(url.toURI()).build());
return this;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public ChatClient.PromptUserSpec media(MimeType mimeType, Resource resource) {
Assert.notNull(mimeType, "mimeType cannot be null");
Assert.notNull(resource, "resource cannot be null");
this.media.add(Media.builder().mimeType(mimeType).data(resource).build());
return this;
}
public ChatClient.PromptUserSpec text(String text) {
Assert.hasText(text, "text cannot be null or empty");
this.text = text;
return this;
}
public ChatClient.PromptUserSpec text(Resource text, Charset charset) {
Assert.notNull(text, "text cannot be null");
Assert.notNull(charset, "charset cannot be null");
try {
this.text(text.getContentAsString(charset));
return this;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public ChatClient.PromptUserSpec text(Resource text) {
Assert.notNull(text, "text cannot be null");
this.text(text, Charset.defaultCharset());
return this;
}
public ChatClient.PromptUserSpec param(String key, Object value) {
Assert.hasText(key, "key cannot be null or empty");
Assert.notNull(value, "value cannot be null");
this.params.put(key, value);
return this;
}
public ChatClient.PromptUserSpec params(Map<String, Object> params) {
Assert.notNull(params, "params cannot be null");
Assert.noNullElements(params.keySet(), "param keys cannot contain null elements");
Assert.noNullElements(params.values(), "param values cannot contain null elements");
this.params.putAll(params);
return this;
}
@Nullable
protected String text() {
return this.text;
}
protected Map<String, Object> params() {
return this.params;
}
protected List<Media> media() {
return this.media;
}
}
public static class DefaultPromptSystemSpec implements ChatClient.PromptSystemSpec {
private final Map<String, Object> params = new HashMap();
@Nullable
private String text;
public ChatClient.PromptSystemSpec text(String text) {
Assert.hasText(text, "text cannot be null or empty");
this.text = text;
return this;
}
public ChatClient.PromptSystemSpec text(Resource text, Charset charset) {
Assert.notNull(text, "text cannot be null");
Assert.notNull(charset, "charset cannot be null");
try {
this.text(text.getContentAsString(charset));
return this;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public ChatClient.PromptSystemSpec text(Resource text) {
Assert.notNull(text, "text cannot be null");
this.text(text, Charset.defaultCharset());
return this;
}
public ChatClient.PromptSystemSpec param(String key, Object value) {
Assert.hasText(key, "key cannot be null or empty");
Assert.notNull(value, "value cannot be null");
this.params.put(key, value);
return this;
}
public ChatClient.PromptSystemSpec params(Map<String, Object> params) {
Assert.notNull(params, "params cannot be null");
Assert.noNullElements(params.keySet(), "param keys cannot contain null elements");
Assert.noNullElements(params.values(), "param values cannot contain null elements");
this.params.putAll(params);
return this;
}
@Nullable
protected String text() {
return this.text;
}
protected Map<String, Object> params() {
return this.params;
}
}
public static class DefaultAdvisorSpec implements ChatClient.AdvisorSpec {
private final List<Advisor> advisors = new ArrayList();
private final Map<String, Object> params = new HashMap();
public ChatClient.AdvisorSpec param(String key, Object value) {
Assert.hasText(key, "key cannot be null or empty");
Assert.notNull(value, "value cannot be null");
this.params.put(key, value);
return this;
}
public ChatClient.AdvisorSpec params(Map<String, Object> params) {
Assert.notNull(params, "params cannot be null");
Assert.noNullElements(params.keySet(), "param keys cannot contain null elements");
Assert.noNullElements(params.values(), "param values cannot contain null elements");
this.params.putAll(params);
return this;
}
public ChatClient.AdvisorSpec advisors(Advisor... advisors) {
Assert.notNull(advisors, "advisors cannot be null");
Assert.noNullElements(advisors, "advisors cannot contain null elements");
this.advisors.addAll(List.of(advisors));
return this;
}
public ChatClient.AdvisorSpec advisors(List<Advisor> advisors) {
Assert.notNull(advisors, "advisors cannot be null");
Assert.noNullElements(advisors, "advisors cannot contain null elements");
this.advisors.addAll(advisors);
return this;
}
public List<Advisor> getAdvisors() {
return this.advisors;
}
public Map<String, Object> getParams() {
return this.params;
}
}
public static class DefaultCallResponseSpec implements ChatClient.CallResponseSpec {
private final ChatClientRequest request;
private final BaseAdvisorChain advisorChain;
private final ObservationRegistry observationRegistry;
private final ChatClientObservationConvention observationConvention;
public DefaultCallResponseSpec(ChatClientRequest chatClientRequest, BaseAdvisorChain advisorChain, ObservationRegistry observationRegistry, ChatClientObservationConvention observationConvention) {
Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");
Assert.notNull(advisorChain, "advisorChain cannot be null");
Assert.notNull(observationRegistry, "observationRegistry cannot be null");
Assert.notNull(observationConvention, "observationConvention cannot be null");
this.request = chatClientRequest;
this.advisorChain = advisorChain;
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
}
public <T> ResponseEntity<ChatResponse, T> responseEntity(Class<T> type) {
Assert.notNull(type, "type cannot be null");
return this.doResponseEntity(new BeanOutputConverter(type));
}
public <T> ResponseEntity<ChatResponse, T> responseEntity(ParameterizedTypeReference<T> type) {
Assert.notNull(type, "type cannot be null");
return this.doResponseEntity(new BeanOutputConverter(type));
}
public <T> ResponseEntity<ChatResponse, T> responseEntity(StructuredOutputConverter<T> structuredOutputConverter) {
Assert.notNull(structuredOutputConverter, "structuredOutputConverter cannot be null");
return this.doResponseEntity(structuredOutputConverter);
}
protected <T> ResponseEntity<ChatResponse, T> doResponseEntity(StructuredOutputConverter<T> outputConverter) {
Assert.notNull(outputConverter, "structuredOutputConverter cannot be null");
ChatResponse chatResponse = this.doGetObservableChatClientResponse(this.request, outputConverter.getFormat()).chatResponse();
String responseContent = getContentFromChatResponse(chatResponse);
if (responseContent == null) {
return new ResponseEntity(chatResponse, (Object)null);
} else {
T entity = (T)outputConverter.convert(responseContent);
return new ResponseEntity(chatResponse, entity);
}
}
@Nullable
public <T> T entity(ParameterizedTypeReference<T> type) {
Assert.notNull(type, "type cannot be null");
return (T)this.doSingleWithBeanOutputConverter(new BeanOutputConverter(type));
}
@Nullable
public <T> T entity(StructuredOutputConverter<T> structuredOutputConverter) {
Assert.notNull(structuredOutputConverter, "structuredOutputConverter cannot be null");
return (T)this.doSingleWithBeanOutputConverter(structuredOutputConverter);
}
@Nullable
public <T> T entity(Class<T> type) {
Assert.notNull(type, "type cannot be null");
BeanOutputConverter<T> outputConverter = new BeanOutputConverter(type);
return (T)this.doSingleWithBeanOutputConverter(outputConverter);
}
@Nullable
private <T> T doSingleWithBeanOutputConverter(StructuredOutputConverter<T> outputConverter) {
ChatResponse chatResponse = this.doGetObservableChatClientResponse(this.request, outputConverter.getFormat()).chatResponse();
String stringResponse = getContentFromChatResponse(chatResponse);
return (T)(stringResponse == null ? null : outputConverter.convert(stringResponse));
}
public ChatClientResponse chatClientResponse() {
return this.doGetObservableChatClientResponse(this.request);
}
@Nullable
public ChatResponse chatResponse() {
return this.doGetObservableChatClientResponse(this.request).chatResponse();
}
@Nullable
public String content() {
ChatResponse chatResponse = this.doGetObservableChatClientResponse(this.request).chatResponse();
return getContentFromChatResponse(chatResponse);
}
private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest) {
return this.doGetObservableChatClientResponse(chatClientRequest, (String)null);
}
private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest, @Nullable String outputFormat) {
if (outputFormat != null) {
chatClientRequest.context().put(ChatClientAttributes.OUTPUTFORMAT.getKey(), outputFormat);
}
ChatClientObservationContext observationContext = ChatClientObservationContext.builder().request(chatClientRequest).advisors(this.advisorChain.getCallAdvisors()).stream(false).format(outputFormat).build();
Observation observation = ChatClientObservationDocumentation.AICHATCLIENT.observation(this.observationConvention, DefaultChatClient.DEFAULTCHATCLIENTOBSERVATIONCONVENTION, () -> observationContext, this.observationRegistry);
ChatClientResponse chatClientResponse = (ChatClientResponse)observation.observe(() -> this.advisorChain.nextCall(chatClientRequest));
return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();
}
@Nullable
private static String getContentFromChatResponse(@Nullable ChatResponse chatResponse) {
return (String)Optional.ofNullable(chatResponse).map(ChatResponse::getResult).map(Generation::getOutput).map(AbstractMessage::getText).orElse((Object)null);
}
}
public static class DefaultStreamResponseSpec implements ChatClient.StreamResponseSpec {
private final ChatClientRequest request;
private final BaseAdvisorChain advisorChain;
private final ObservationRegistry observationRegistry;
private final ChatClientObservationConvention observationConvention;
public DefaultStreamResponseSpec(ChatClientRequest chatClientRequest, BaseAdvisorChain advisorChain, ObservationRegistry observationRegistry, ChatClientObservationConvention observationConvention) {
Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");
Assert.notNull(advisorChain, "advisorChain cannot be null");
Assert.notNull(observationRegistry, "observationRegistry cannot be null");
Assert.notNull(observationConvention, "observationConvention cannot be null");
this.request = chatClientRequest;
this.advisorChain = advisorChain;
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention;
}
private Flux<ChatClientResponse> doGetObservableFluxChatResponse(ChatClientRequest chatClientRequest) {
return Flux.deferContextual((contextView) -> {
ChatClientObservationContext observationContext = ChatClientObservationContext.builder().request(chatClientRequest).advisors(this.advisorChain.getStreamAdvisors()).stream(true).build();
Observation observation = ChatClientObservationDocumentation.AICHATCLIENT.observation(this.observationConvention, DefaultChatClient.DEFAULTCHATCLIENTOBSERVATIONCONVENTION, () -> observationContext, this.observationRegistry);
observation.parentObservation((Observation)contextView.getOrDefault("micrometer.observation", (Object)null)).start();
Flux var10000 = this.advisorChain.nextStream(chatClientRequest);
Objects.requireNonNull(observation);
return var10000.doOnError(observation::error).doFinally((s) -> observation.stop()).contextWrite((ctx) -> ctx.put("micrometer.observation", observation));
});
}
public Flux<ChatClientResponse> chatClientResponse() {
return this.doGetObservableFluxChatResponse(this.request);
}
public Flux<ChatResponse> chatResponse() {
return this.doGetObservableFluxChatResponse(this.request).mapNotNull(ChatClientResponse::chatResponse);
}
public Flux<String> content() {
return this.doGetObservableFluxChatResponse(this.request).mapNotNull(ChatClientResponse::chatResponse).map((r) -> r.getResult() != null && r.getResult().getOutput() != null && r.getResult().getOutput().getText() != null ? r.getResult().getOutput().getText() : "").filter(StringUtils::hasLength);
}
}
public static class DefaultChatClientRequestSpec implements ChatClient.ChatClientRequestSpec {
private final ObservationRegistry observationRegistry;
private final ChatClientObservationConvention observationConvention;
private final ChatModel chatModel;
private final List<Media> media;
private final List<String> toolNames;
private final List<ToolCallback> toolCallbacks;
private final List<Message> messages;
private final Map<String, Object> userParams;
private final Map<String, Object> systemParams;
private final List<Advisor> advisors;
private final Map<String, Object> advisorParams;
private final Map<String, Object> toolContext;
private TemplateRenderer templateRenderer;
@Nullable
private String userText;
@Nullable
private String systemText;
@Nullable
private ChatOptions chatOptions;
DefaultChatClientRequestSpec(DefaultChatClientRequestSpec ccr) {
this(ccr.chatModel, ccr.userText, ccr.userParams, ccr.systemText, ccr.systemParams, ccr.toolCallbacks, ccr.messages, ccr.toolNames, ccr.media, ccr.chatOptions, ccr.advisors, ccr.advisorParams, ccr.observationRegistry, ccr.observationConvention, ccr.toolContext, ccr.templateRenderer);
}
public DefaultChatClientRequestSpec(ChatModel chatModel, @Nullable String userText, Map<String, Object> userParams, @Nullable String systemText, Map<String, Object> systemParams, List<ToolCallback> toolCallbacks, List<Message> messages, List<String> toolNames, List<Media> media, @Nullable ChatOptions chatOptions, List<Advisor> advisors, Map<String, Object> advisorParams, ObservationRegistry observationRegistry, @Nullable ChatClientObservationConvention observationConvention, Map<String, Object> toolContext, @Nullable TemplateRenderer templateRenderer) {
this.media = new ArrayList();
this.toolNames = new ArrayList();
this.toolCallbacks = new ArrayList();
this.messages = new ArrayList();
this.userParams = new HashMap();
this.systemParams = new HashMap();
this.advisors = new ArrayList();
this.advisorParams = new HashMap();
this.toolContext = new HashMap();
Assert.notNull(chatModel, "chatModel cannot be null");
Assert.notNull(userParams, "userParams cannot be null");
Assert.notNull(systemParams, "systemParams cannot be null");
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
Assert.notNull(messages, "messages cannot be null");
Assert.notNull(toolNames, "toolNames cannot be null");
Assert.notNull(media, "media cannot be null");
Assert.notNull(advisors, "advisors cannot be null");
Assert.notNull(advisorParams, "advisorParams cannot be null");
Assert.notNull(observationRegistry, "observationRegistry cannot be null");
Assert.notNull(toolContext, "toolContext cannot be null");
this.chatModel = chatModel;
this.chatOptions = chatOptions != null ? chatOptions.copy() : (chatModel.getDefaultOptions() != null ? chatModel.getDefaultOptions().copy() : null);
this.userText = userText;
this.userParams.putAll(userParams);
this.systemText = systemText;
this.systemParams.putAll(systemParams);
this.toolNames.addAll(toolNames);
this.toolCallbacks.addAll(toolCallbacks);
this.messages.addAll(messages);
this.media.addAll(media);
this.advisors.addAll(advisors);
this.advisorParams.putAll(advisorParams);
this.observationRegistry = observationRegistry;
this.observationConvention = observationConvention != null ? observationConvention : DefaultChatClient.DEFAULTCHATCLIENTOBSERVATIONCONVENTION;
this.toolContext.putAll(toolContext);
this.templateRenderer = templateRenderer != null ? templateRenderer : DefaultChatClient.DEFAULTTEMPLATERENDERER;
}
@Nullable
public String getUserText() {
return this.userText;
}
public Map<String, Object> getUserParams() {
return this.userParams;
}
@Nullable
public String getSystemText() {
return this.systemText;
}
public Map<String, Object> getSystemParams() {
return this.systemParams;
}
@Nullable
public ChatOptions getChatOptions() {
return this.chatOptions;
}
public List<Advisor> getAdvisors() {
return this.advisors;
}
public Map<String, Object> getAdvisorParams() {
return this.advisorParams;
}
public List<Message> getMessages() {
return this.messages;
}
public List<Media> getMedia() {
return this.media;
}
public List<String> getToolNames() {
return this.toolNames;
}
public List<ToolCallback> getToolCallbacks() {
return this.toolCallbacks;
}
public Map<String, Object> getToolContext() {
return this.toolContext;
}
public TemplateRenderer getTemplateRenderer() {
return this.templateRenderer;
}
public ChatClient.Builder mutate() {
DefaultChatClientBuilder builder = (DefaultChatClientBuilder)ChatClient.builder(this.chatModel, this.observationRegistry, this.observationConvention).defaultTemplateRenderer(this.templateRenderer).defaultToolCallbacks(this.toolCallbacks).defaultToolContext(this.toolContext).defaultToolNames(StringUtils.toStringArray(this.toolNames));
if (StringUtils.hasText(this.userText)) {
builder.defaultUser((u) -> u.text(this.userText).params(this.userParams).media((Media[])this.media.toArray(new Media[0])));
}
if (StringUtils.hasText(this.systemText)) {
builder.defaultSystem((s) -> s.text(this.systemText).params(this.systemParams));
}
if (this.chatOptions != null) {
builder.defaultOptions(this.chatOptions);
}
builder.addMessages(this.messages);
return builder;
}
public ChatClient.ChatClientRequestSpec advisors(Consumer<ChatClient.AdvisorSpec> consumer) {
Assert.notNull(consumer, "consumer cannot be null");
DefaultAdvisorSpec advisorSpec = new DefaultAdvisorSpec();
consumer.accept(advisorSpec);
this.advisorParams.putAll(advisorSpec.getParams());
this.advisors.addAll(advisorSpec.getAdvisors());
return this;
}
public ChatClient.ChatClientRequestSpec advisors(Advisor... advisors) {
Assert.notNull(advisors, "advisors cannot be null");
Assert.noNullElements(advisors, "advisors cannot contain null elements");
this.advisors.addAll(Arrays.asList(advisors));
return this;
}
public ChatClient.ChatClientRequestSpec advisors(List<Advisor> advisors) {
Assert.notNull(advisors, "advisors cannot be null");
Assert.noNullElements(advisors, "advisors cannot contain null elements");
this.advisors.addAll(advisors);
return this;
}
public ChatClient.ChatClientRequestSpec messages(Message... messages) {
Assert.notNull(messages, "messages cannot be null");
Assert.noNullElements(messages, "messages cannot contain null elements");
this.messages.addAll(List.of(messages));
return this;
}
public ChatClient.ChatClientRequestSpec messages(List<Message> messages) {
Assert.notNull(messages, "messages cannot be null");
Assert.noNullElements(messages, "messages cannot contain null elements");
this.messages.addAll(messages);
return this;
}
public <T extends ChatOptions> ChatClient.ChatClientRequestSpec options(T options) {
Assert.notNull(options, "options cannot be null");
this.chatOptions = options;
return this;
}
public ChatClient.ChatClientRequestSpec toolNames(String... toolNames) {
Assert.notNull(toolNames, "toolNames cannot be null");
Assert.noNullElements(toolNames, "toolNames cannot contain null elements");
this.toolNames.addAll(List.of(toolNames));
return this;
}
public ChatClient.ChatClientRequestSpec toolCallbacks(ToolCallback... toolCallbacks) {
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
this.toolCallbacks.addAll(List.of(toolCallbacks));
return this;
}
public ChatClient.ChatClientRequestSpec toolCallbacks(List<ToolCallback> toolCallbacks) {
Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
this.toolCallbacks.addAll(toolCallbacks);
return this;
}
public ChatClient.ChatClientRequestSpec tools(Object... toolObjects) {
Assert.notNull(toolObjects, "toolObjects cannot be null");
Assert.noNullElements(toolObjects, "toolObjects cannot contain null elements");
this.toolCallbacks.addAll(Arrays.asList(ToolCallbacks.from(toolObjects)));
return this;
}
public ChatClient.ChatClientRequestSpec toolCallbacks(ToolCallbackProvider... toolCallbackProviders) {
Assert.notNull(toolCallbackProviders, "toolCallbackProviders cannot be null");
Assert.noNullElements(toolCallbackProviders, "toolCallbackProviders cannot contain null elements");
for(ToolCallbackProvider toolCallbackProvider : toolCallbackProviders) {
this.toolCallbacks.addAll(List.of(toolCallbackProvider.getToolCallbacks()));
}
return this;
}
public ChatClient.ChatClientRequestSpec toolContext(Map<String, Object> toolContext) {
Assert.notNull(toolContext, "toolContext cannot be null");
Assert.noNullElements(toolContext.keySet(), "toolContext keys cannot contain null elements");
Assert.noNullElements(toolContext.values(), "toolContext values cannot contain null elements");
this.toolContext.putAll(toolContext);
return this;
}
public ChatClient.ChatClientRequestSpec system(String text) {
Assert.hasText(text, "text cannot be null or empty");
this.systemText = text;
return this;
}
public ChatClient.ChatClientRequestSpec system(Resource text, Charset charset) {
Assert.notNull(text, "text cannot be null");
Assert.notNull(charset, "charset cannot be null");
try {
this.systemText = text.getContentAsString(charset);
return this;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public ChatClient.ChatClientRequestSpec system(Resource text) {
Assert.notNull(text, "text cannot be null");
return this.system(text, Charset.defaultCharset());
}
public ChatClient.ChatClientRequestSpec system(Consumer<ChatClient.PromptSystemSpec> consumer) {
Assert.notNull(consumer, "consumer cannot be null");
DefaultPromptSystemSpec systemSpec = new DefaultPromptSystemSpec();
consumer.accept(systemSpec);
this.systemText = StringUtils.hasText(systemSpec.text()) ? systemSpec.text() : this.systemText;
this.systemParams.putAll(systemSpec.params());
return this;
}
public ChatClient.ChatClientRequestSpec user(String text) {
Assert.hasText(text, "text cannot be null or empty");
this.userText = text;
return this;
}
public ChatClient.ChatClientRequestSpec user(Resource text, Charset charset) {
Assert.notNull(text, "text cannot be null");
Assert.notNull(charset, "charset cannot be null");
try {
this.userText = text.getContentAsString(charset);
return this;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public ChatClient.ChatClientRequestSpec user(Resource text) {
Assert.notNull(text, "text cannot be null");
return this.user(text, Charset.defaultCharset());
}
public ChatClient.ChatClientRequestSpec user(Consumer<ChatClient.PromptUserSpec> consumer) {
Assert.notNull(consumer, "consumer cannot be null");
DefaultPromptUserSpec us = new DefaultPromptUserSpec();
consumer.accept(us);
this.userText = StringUtils.hasText(us.text()) ? us.text() : this.userText;
this.userParams.putAll(us.params());
this.media.addAll(us.media());
return this;
}
public ChatClient.ChatClientRequestSpec templateRenderer(TemplateRenderer templateRenderer) {
Assert.notNull(templateRenderer, "templateRenderer cannot be null");
this.templateRenderer = templateRenderer;
return this;
}
public ChatClient.CallResponseSpec call() {
BaseAdvisorChain advisorChain = this.buildAdvisorChain();
return new DefaultCallResponseSpec(DefaultChatClientUtils.toChatClientRequest(this), advisorChain, this.observationRegistry, this.observationConvention);
}
public ChatClient.StreamResponseSpec stream() {
BaseAdvisorChain advisorChain = this.buildAdvisorChain();
return new DefaultStreamResponseSpec(DefaultChatClientUtils.toChatClientRequest(this), advisorChain, this.observationRegistry, this.observationConvention);
}
private BaseAdvisorChain buildAdvisorChain() {
this.advisors.add(ChatModelCallAdvisor.builder().chatModel(this.chatModel).build());
this.advisors.add(ChatModelStreamAdvisor.builder().chatModel(this.chatModel).build());
return DefaultAroundAdvisorChain.builder(this.observationRegistry).pushAll(this.advisors).templateRenderer(this.templateRenderer).build();
}
}
}
DefaultChatClientUtils
类作用:用来将 DefaultChatClient.DefaultChatClientRequestSpec 转换为 ChatClientRequest
- 处理系统提示
- 处理用户提示
- 处理工具调用选项
package org.springframework.ai.chat.client;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
final class DefaultChatClientUtils {
private DefaultChatClientUtils() {
}
static ChatClientRequest toChatClientRequest(DefaultChatClient.DefaultChatClientRequestSpec inputRequest) {
Assert.notNull(inputRequest, "inputRequest cannot be null");
List<Message> processedMessages = new ArrayList();
String processedSystemText = inputRequest.getSystemText();
if (StringUtils.hasText(processedSystemText)) {
if (!CollectionUtils.isEmpty(inputRequest.getSystemParams())) {
processedSystemText = PromptTemplate.builder().template(processedSystemText).variables(inputRequest.getSystemParams()).renderer(inputRequest.getTemplateRenderer()).build().render();
}
processedMessages.add(new SystemMessage(processedSystemText));
}
if (!CollectionUtils.isEmpty(inputRequest.getMessages())) {
processedMessages.addAll(inputRequest.getMessages());
}
String processedUserText = inputRequest.getUserText();
if (StringUtils.hasText(processedUserText)) {
if (!CollectionUtils.isEmpty(inputRequest.getUserParams())) {
processedUserText = PromptTemplate.builder().template(processedUserText).variables(inputRequest.getUserParams()).renderer(inputRequest.getTemplateRenderer()).build().render();
}
processedMessages.add(UserMessage.builder().text(processedUserText).media(inputRequest.getMedia()).build());
}
ChatOptions processedChatOptions = inputRequest.getChatOptions();
if (processedChatOptions instanceof ToolCallingChatOptions toolCallingChatOptions) {
if (!inputRequest.getToolNames().isEmpty()) {
Set<String> toolNames = ToolCallingChatOptions.mergeToolNames(new HashSet(inputRequest.getToolNames()), toolCallingChatOptions.getToolNames());
toolCallingChatOptions.setToolNames(toolNames);
}
if (!inputRequest.getToolCallbacks().isEmpty()) {
List<ToolCallback> toolCallbacks = ToolCallingChatOptions.mergeToolCallbacks(inputRequest.getToolCallbacks(), toolCallingChatOptions.getToolCallbacks());
ToolCallingChatOptions.validateToolCallbacks(toolCallbacks);
toolCallingChatOptions.setToolCallbacks(toolCallbacks);
}
if (!CollectionUtils.isEmpty(inputRequest.getToolContext())) {
Map<String, Object> toolContext = ToolCallingChatOptions.mergeToolContext(inputRequest.getToolContext(), toolCallingChatOptions.getToolContext());
toolCallingChatOptions.setToolContext(toolContext);
}
}
return ChatClientRequest.builder().prompt(Prompt.builder().messages(processedMessages).chatOptions(processedChatOptions).build()).context(new ConcurrentHashMap(inputRequest.getAdvisorParams())).build();
}
}
AdvisorChain
AdvisorChain 链调用一系列的增强器 Advisor 基础,每个增强器输入是 ChatClientRequest,输出 ChatClientResponse(其中必定会用到的是 ChatModelCallAdvisor 或 ChatModelStreamAdvisor)
- ChatModelCallAdvisor 触发 ChatModel 的 call 方法
- ChatModelStreamAdvisor 触发 ChatModel 的 stream 方法
ChatModel
package org.springframework.ai.chat.model;
import java.util.Arrays;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.Model;
import reactor.core.publisher.Flux;
public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {
default String call(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
Generation generation = this.call(prompt).getResult();
return generation != null ? generation.getOutput().getText() : "";
}
default String call(Message... messages) {
Prompt prompt = new Prompt(Arrays.asList(messages));
Generation generation = this.call(prompt).getResult();
return generation != null ? generation.getOutput().getText() : "";
}
ChatResponse call(Prompt prompt);
default ChatOptions getDefaultOptions() {
return ChatOptions.builder().build();
}
default Flux<ChatResponse> stream(Prompt prompt) {
throw new UnsupportedOperationException("streaming is not supported");
}
}
不同厂商实现各种的 ChaModel,但实现逻辑基本以 OpenAI 作为官方实现
pom 引入对应依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-model-openai</artifactId>
</dependency>
OpenAiChatModel
各字段说明
| 字段名 | 类型 | 描述 |
| defaultOptions | OpenAiChatOptions | 请求参数配置,如temperature、最大 token 数等 |
| retryTemplate | RetryTemplate | 用于执行重试逻辑,适用于网络不稳定或 API 限流等 |
| openAiApi | OpenAiApi | 封装OpenAI官方API的调用接口 |
| observationRegistry | ObservationRegistry | 用于注册和记录观测日志,便于监控和分析调用过程 |
| toolCallingManager | ToolCallingManager | 工具调用管理器,用于解析并执行工具调 |
| toolExecutionEligibilityPredicate | ToolExecutionEligibilityPredicate | 判断是否需要执行工具调用的断言函数 |
| observationConvention | ChatModelObservationConvention | 自定义观测日志格式的约定对象 |
对外暴露的方法
| 方法名 | 描述 |
| call | 发起一次同步请求,返回完整的 ChatResponse,实际调用内部的internalCall方法 |
| internalCall | 1. 构建OpenAI请求对象 2. 创建观测上下文 3. 执行带观测的模型调用 4. 执行OpenAI接口调用 5. 解析模型返回的choices 6. 将每个choice转换为Generation对象,构建完整的 7. 提取限流信息(RateLimit) 8. 计算token使用量 9. 构建最终的ChatResponse并设置上下文 10. 工具调用处理 |
| stream | 发起一次流式请求,返回Flux,实际调用内部的internalStream方法 |
| internalStream | 1. 使用Flux.deferContextual延迟执行,保持上下文一致性 2. 构建OpenAI流式请求对象 3. 发起流式 API 调用,获取 chunk 数据 4. 创建角色映射表,解决 chunk 中 role 缺失问题 5. 创建观测上下文 6. 启动观测操作 7. 将 chunk 转换为 ChatCompletion 标准格式 8. 转换为 ChatResponse 并构建生成内 9. 处理 usage 字段(仅最终 chunk 包含完整 usage) 10. 工具调用处理 11. 聚合消息流并设置响应 |
| getDefaultOptions | 回当前模型使用的默认请求参数,OpenAiChatOptions |
| setObservationConvention | 设置自定义的观测日志格式化规则 |
| mutate | 复制OpenAiChatModel实例 |
OpenAiApi
各字段说明
| 字段名 | 类型 | 描述 |
| baseUrl | String | OpenAI API 的基础 URL,默认为 "https://api.openai.com" |
| apiKey | ApiKey | 认证密钥 |
| headers | MultiValueMap | 自定义 HTTP 请求头,例如用户自定义的身份信息等 |
| completionsPath | String | Chat Completion 接口路径,默认为 /v1/chat/completions |
| embeddingsPath | String | Embedding 接口路径,默认为 /v1/embeddings |
| responseErrorHandler | ResponseErrorHandler | 响应错误处理器,默认处理异常逻辑 |
| restClient | RestClient | 同步请求客户端,用于非流式请求 |
| webClient | WebClient | 异步/响应式请求客户端,用于流式请求 |
| chunkMerger | OpenAiStreamFunctionCallingHelper | 流式函数调用合并器,用于处理多个 chunk 中的 functioncall 数据 |
对外暴露的方法
| 方法名 | 描述 |
| chatCompletionEntity | 发送同步请求获取完整的 Chat Completion 响应 |
| chatCompletionStream | 发起流式请求,接收分块响应(chunk) |
| embeddings | 调用 OpenAI Embedding 接口,生成文本或 token 数组的向量表示 |
内部枚举类说明
| 枚举类 | 描述 |
| ChatModel | 支持的聊天模型 |
| ChatCompletionFinishReason | 模型停止生成的原因 |
| EmbeddingModel | 支持的Embedding模型 |
| OutputModality | 模型输出的范式 |
完整代码如下