SpringAI框架中的ChatClient 的 call / stream 调用流程(从源码上出发带你从0到1了解)
在构建chatClient时,我们可以给chatClient传入一些Advisor。
构建Advisors
比如下面,我传入了两个 Advisor,第一个是 SimpleLoggerAdvisor,第二个是 RelAdvisor。 如果不指定order的大小,它默认会按照传入的顺序去执行Advisor。
chatClient = ChatClient.builder(dashScopeChatModel)
// 实现 Logger 的 Advisor
.defaultAdvisors(
new SimpleLoggerAdvisor(),
new RelAdvisor()
).defaultSystem("请用英文回答问题")
// 设置 ChatClient 中 ChatModel 的 Options 参数
.defaultOptions(
DashScopeChatOptions.builder()
.temperature(0.7)
.build()
)
.build();
public ChatClient.Builder defaultAdvisors(Advisor... advisors) {
this.defaultRequest.advisors(advisors);
return this;
}
可以看到这里面有一个addAll方法,就是按顺序把advisor添加到一个list里面,最终取出来也是一个一个取的。
private final List<Advisor> advisors;
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;
}
然后可以去调用 chatClient 的 call 或 stream 方法去对话。
call() 方法
buildAdvisorChain()
call方法里面有一个 buildAdvisorChain() 方法,其实就是把多个 Advisor编成一个链,就像是责任链模式,一个一个地去执行。
public ChatClient.CallResponseSpec call() {
BaseAdvisorChain advisorChain = this.buildAdvisorChain();
return new DefaultCallResponseSpec(DefaultChatClientUtils.toChatClientRequest(this), advisorChain, this.observationRegistry, this.chatClientObservationConvention);
}
再进去里面发现,它最终还会为 advisors集合添加两个Advisor,分别是 ChatModelCallAdvisor 和 ChatModelStreamAdvisor 。 主要是做 同步对话 和 流式对话中会使用到。
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).observationConvention(this.advisorObservationConvention).pushAll(this.advisors).build();
}
再点击进去 pushAll(this.advisors) 方法里面可以看到:它会过滤出 advisors 里面的 CallAdvisor 和 StreamAdvisor 的类型,存储到一个List里面。然后在遍历这些List,把它添加到一个栈里面,这也就是为啥Advisor有一定先后执行顺序的原因,栈是先进后出的数据结构。
private final Deque<CallAdvisor> callAdvisors;
private final Deque<StreamAdvisor> streamAdvisors;
public Builder pushAll(List<? extends Advisor> advisors) {
Assert.notNull(advisors, "the advisors must be non-null");
Assert.noNullElements(advisors, "the advisors must not contain null elements");
if (!CollectionUtils.isEmpty(advisors)) {
List<CallAdvisor> callAroundAdvisorList = advisors.stream().filter((a) -> a instanceof CallAdvisor).map((a) -> (CallAdvisor)a).toList();
if (!CollectionUtils.isEmpty(callAroundAdvisorList)) {
Deque var10001 = this.callAdvisors;
Objects.requireNonNull(var10001);
callAroundAdvisorList.forEach(var10001::push);
}
List<StreamAdvisor> streamAroundAdvisorList = advisors.stream().filter((a) -> a instanceof StreamAdvisor).map((a) -> (StreamAdvisor)a).toList();
if (!CollectionUtils.isEmpty(streamAroundAdvisorList)) {
// 栈
Deque var4 = this.streamAdvisors;
Objects.requireNonNull(var4);
// 添加到栈里面
streamAroundAdvisorList.forEach(var4::push);
}
this.reOrder();
}
return this;
}
在 pushAll() 的最后还有一个 reOrder() 方法,这个方法主要作用是:按照每一个Advisor内部的 order进行排序,order的数值越小,优先级越高,也就是先入栈,出的时候就第一个出。优先级越高,再栈的最顶端,因为pop出栈的时候就是第一个出的。
private final Deque<CallAdvisor> callAdvisors;
private final Deque<StreamAdvisor> streamAdvisors;
private void reOrder() {
ArrayList<CallAdvisor> callAdvisors = new ArrayList(this.callAdvisors);
// 先把 callAdvisors 按照内部的 order进行排序
OrderComparator.sort(callAdvisors);
// 先清空栈
this.callAdvisors.clear();
Deque var10001 = this.callAdvisors;
Objects.requireNonNull(var10001);
// 再重新把排序后的 advisor入队,保证执行先后的顺序
callAdvisors.forEach(var10001::addLast);
ArrayList<StreamAdvisor> streamAdvisors = new ArrayList(this.streamAdvisors);
OrderComparator.sort(streamAdvisors);
this.streamAdvisors.clear();
var10001 = this.streamAdvisors;
Objects.requireNonNull(var10001);
streamAdvisors.forEach(var10001::addLast);
}
以上就是 call() 方法的 buildAdvisorChain() 的过程,
public ChatClient.CallResponseSpec call() {
BaseAdvisorChain advisorChain = this.buildAdvisorChain();
return new DefaultCallResponseSpec(DefaultChatClientUtils.toChatClientRequest(this), advisorChain, this.observationRegistry, this.chatClientObservationConvention);
}
DefaultChatClientUtils.toChatClientRequest(this)
然后就是 DefaultChatClientUtils.toChatClientRequest(this) ,这个方法是核心方法,构建一个 ChatClientRequest对象,后续与LLM交互都是用这个玩意。
final class DefaultChatClientUtils {
private DefaultChatClientUtils() {
}
static ChatClientRequest toChatClientRequest(DefaultChatClient.DefaultChatClientRequestSpec inputRequest) {
Assert.notNull(inputRequest, "inputRequest cannot be null");
// 存储历史对话消息列表,最终会给到LLM
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 添加一个【系统消息】SystemMessage
processedMessages.add(SystemMessage.builder().text(processedSystemText).metadata(inputRequest.getSystemMetadata()).build());
}
// 如果 message() 不为空也会添加到 processedMessages
if (!CollectionUtils.isEmpty(inputRequest.getMessages())) {
processedMessages.addAll(inputRequest.getMessages());
}
// 看看用户的 Query是否为空, 不为空就也追加到 processedMessages
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()).metadata(inputRequest.getUserMetadata()).build());
}
ChatOptions processedChatOptions = inputRequest.getChatOptions();
// 如果存在工具
if (!inputRequest.getToolNames().isEmpty() || !inputRequest.getToolCallbacks().isEmpty() || !inputRequest.getToolCallbackProviders().isEmpty() || !CollectionUtils.isEmpty(inputRequest.getToolContext())) {
if (processedChatOptions == null) {
processedChatOptions = new DefaultToolCallingChatOptions();
} else if (processedChatOptions instanceof DefaultChatOptions) {
DefaultChatOptions defaultChatOptions = (DefaultChatOptions)processedChatOptions;
processedChatOptions = (ChatOptions)ModelOptionsUtils.copyToTarget(defaultChatOptions, ChatOptions.class, DefaultToolCallingChatOptions.class);
}
}
// 如果 processedChatOptions是ToolCallingChatOptions类型
if (processedChatOptions instanceof ToolCallingChatOptions toolCallingChatOptions) {
// 如果工具不为空
if (!inputRequest.getToolNames().isEmpty()) {
// 合并所有的工具
Set<String> toolNames = ToolCallingChatOptions.mergeToolNames(new HashSet(inputRequest.getToolNames()), toolCallingChatOptions.getToolNames());
// 把工具名称的集合传入 toolCallingChatOptions
toolCallingChatOptions.setToolNames(toolNames);
}
// 获取toolCallBack
List<ToolCallback> allToolCallbacks = new ArrayList(inputRequest.getToolCallbacks());
for(ToolCallbackProvider provider : inputRequest.getToolCallbackProviders()) {
allToolCallbacks.addAll(List.of(provider.getToolCallbacks()));
}
if (!allToolCallbacks.isEmpty()) {
List<ToolCallback> toolCallbacks = ToolCallingChatOptions.mergeToolCallbacks(allToolCallbacks, 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);
}
}
// 返回一个 ChatClientRequest 具体对象
return ChatClientRequest.builder().prompt(Prompt.builder().messages(processedMessages).chatOptions(processedChatOptions).build()).context(new ConcurrentHashMap(inputRequest.getAdvisorParams())).build();
}
}
最后 return的时候有一个 context属性 .context(new ConcurrentHashMap(inputRequest.getAdvisorParams()))
这个主要是支持为chatClient传入一些自定义的一些参数,这些参数会伴随一个 请求 到 响应的生命周期,可以在各个阶段中这个param一路传递过去,每个节点需要用到的时候就可以取出来。
在构建chatClient的时候就可以传入一些自定义属性,是kv结构的,其实最终就会被 DefaultChatClientUtils.toChatClientRequest() 方法里被赋值到返回的 ChatClientRequest对象里面的context属性里,也可以通过 ChatClientRequest对象拿到context里面的一些自定义属性。
this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(spec -> {
spec.params(Map.of("name", "刘文灿")); // 自定义属性
})
.build();
可以看到defaultAdvisors()里面,最终会把 advisorSpec的params 传入 advisorParams Map里面,最终构建 ChatClientRequest的时候就可以拿到他赋值给 ChatClientRequest对象。
private final Map<String, Object> advisorParams;
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;
}
chatClient的content方法(核心)
点进去 content方法里面,可以看到 doGetObservableChatClientResponse
@Nullable
public String content() {
ChatResponse chatResponse = this.doGetObservableChatClientResponse(this.request).chatResponse();
return getContentFromChatResponse(chatResponse);
}
接着进去 this.advisorChain.nextCall
private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest, @Nullable String outputFormat) {
if (outputFormat != null) {
chatClientRequest.context().put(ChatClientAttributes.OUTPUT_FORMAT.getKey(), outputFormat);
}
ChatClientObservationContext observationContext = ChatClientObservationContext.builder().request(chatClientRequest).advisors(this.advisorChain.getCallAdvisors()).stream(false).format(outputFormat).build();
Observation observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation(this.observationConvention, DefaultChatClient.DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);
ChatClientResponse chatClientResponse = (ChatClientResponse)observation.observe(() -> {
// 开始调用第一个advisor,里面是递归逻辑。
ChatClientResponse response = this.advisorChain.nextCall(chatClientRequest);
observationContext.setResponse(response);
return response;
});
// 最终执行完所有的advisor后,把最终的response返回出去。
return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();
}
点进去,还可以看到一个 advisor.adviseCall
public ChatClientResponse nextCall(ChatClientRequest chatClientRequest) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
if (this.callAdvisors.isEmpty()) {
throw new IllegalStateException("No CallAdvisors available to execute");
} else {
// 将存储Advisor的栈结构,进行pop,也就拿出优先级较高的那一个advisor去执行
CallAdvisor advisor = (CallAdvisor)this.callAdvisors.pop();
AdvisorObservationContext observationContext = AdvisorObservationContext.builder().advisorName(advisor.getName()).chatClientRequest(chatClientRequest).order(advisor.getOrder()).build();
return (ChatClientResponse)AdvisorObservationDocumentation.AI_ADVISOR.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).observe(() -> {
// 调用对应advisor的adviseCall方法, 去执行真正的逻辑
ChatClientResponse chatClientResponse = advisor.adviseCall(chatClientRequest, this);
observationContext.setChatClientResponse(chatClientResponse);
return chatClientResponse;
});
}
}
我当前的编排第一个是 SimpleLoggerAdvisor,所以我先点击去对应的方法看看实现逻辑。可以看到它是一个递归调用,假如说我编排了多个advisor。
A -> B -> C -> ChatModelCallAdvisor
SpringAI框架会先去执行 A,然后在A里面递归去执行下一个B,B里面又会递归执行下一个C,C又会递归执行下一个 ChatModelCallAdvisor,最后这个也就是与LLM真正交互的Advisor,最后交互完后把结果层层往上一层返回,最终就输出一个完整 chatClientResponse 回去。
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
// 先打印请求前日志
this.logRequest(chatClientRequest);
// 再递归调用下一个advisor
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
// 最后再把结果返回出去
this.logResponse(chatClientResponse);
return chatClientResponse;
}
真正与LLM交互的是最终的Advisor:ChatModelCallAdvisor
继续点击进去,找到 ChatModelCallAdvisor的实现类,找到其中的adviseCall方法。
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");
ChatClientRequest formattedChatClientRequest = augmentWithFormatInstructions(chatClientRequest);
ChatResponse chatResponse = this.chatModel.call(formattedChatClientRequest.prompt());
return ChatClientResponse.builder().chatResponse(chatResponse).context(Map.copyOf(formattedChatClientRequest.context())).build();
}
其中核心的就是 this.chatModel.call()方法,里面有一个 internalCall,这是最关键的实现,下面会讲。
public ChatResponse call(Prompt prompt) {
Prompt requestPrompt = this.buildRequestPrompt(prompt);
// 内部调用
return this.internalCall(requestPrompt, (ChatResponse)null);
}
internalCall方法【最核心】真正与LLM对话的方法,里面的流程都在注释描述了。
public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
OpenAiApi.ChatCompletionRequest request = this.createRequest(prompt, false);
ChatModelObservationContext observationContext = ChatModelObservationContext.builder().prompt(prompt).provider(OpenAiApiConstants.PROVIDER_NAME).build();
// 调用 LLM,并获取其中的响应
ChatResponse response = (ChatResponse)ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).observe(() -> {
ResponseEntity<OpenAiApi.ChatCompletion> completionEntity = (ResponseEntity)this.retryTemplate.execute((ctx) -> this.openAiApi.chatCompletionEntity(request, this.getAdditionalHttpHeaders(prompt)));
OpenAiApi.ChatCompletion chatCompletion = (OpenAiApi.ChatCompletion)completionEntity.getBody();
if (chatCompletion == null) {
logger.warn("No chat completion returned for prompt: {}", prompt);
return new ChatResponse(List.of());
} else {
List<OpenAiApi.ChatCompletion.Choice> choices = chatCompletion.choices();
if (choices == null) {
logger.warn("No choices returned for prompt: {}", prompt);
return new ChatResponse(List.of());
} else {
List<Generation> generations = choices.stream().map((choice) -> {
Map<String, Object> metadata = Map.of("id", chatCompletion.id() != null ? chatCompletion.id() : "", "role", choice.message().role() != null ? choice.message().role().name() : "", "index", choice.index() != null ? choice.index() : 0, "finishReason", this.getFinishReasonJson(choice.finishReason()), "refusal", StringUtils.hasText(choice.message().refusal()) ? choice.message().refusal() : "", "annotations", choice.message().annotations() != null ? choice.message().annotations() : List.of(Map.of()));
return this.buildGeneration(choice, metadata, request);
}).toList();
RateLimit rateLimit = OpenAiResponseHeaderExtractor.extractAiResponseHeaders(completionEntity);
OpenAiApi.Usage usage = chatCompletion.usage();
Usage currentChatResponseUsage = (Usage)(usage != null ? this.getDefaultUsage(usage) : new EmptyUsage());
Usage accumulatedUsage = UsageCalculator.getCumulativeUsage(currentChatResponseUsage, previousChatResponse);
ChatResponse chatResponse = new ChatResponse(generations, this.from(chatCompletion, rateLimit, accumulatedUsage));
observationContext.setResponse(chatResponse);
return chatResponse;
}
}
});
// 根据LLM的响应去判断是否需要工具调用
// 其中判断我们应用层是否开启了工具自动调用 和 判断toolCallback是否为空
if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) {
// 调用工具
ToolExecutionResult toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);
// 判断 returnDirect 是否需要直接返回工具调用后的结果
// 1. 不需要:直接返回原始工具执行后的结果
// 2. 需要:再次把对话历史上下文 和 工具执行回来的结果交给LLM再一次润色后再返回【增多一次LLM的调用】
return toolExecutionResult.returnDirect() ? ChatResponse.builder().from(response).generations(ToolExecutionResult.buildGenerations(toolExecutionResult)).build() : this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()), response);
} else {
// 不需要工具调用直接返回LLM执行回来的结果
return response;
}
}
在我们使用SpringAi框架去手搓实现一个 Agent的时候,是要关闭SpringAI框架层面的自动工具调用,是否需要调用工具将由我们自己应用层去实现。
在构建chatClient的时候可以指定一个chatOptions,chatOptions里面可以指定一个 .internalToolExecutionEnabled(false) ,代表关闭框架层工具自动调用。
private void initChatClient() {
try {
OpenAiChatOptions openAiChatOptions = OpenAiChatOptions.builder()
.streamUsage(true) // 这个开启的时候,在流式输出最后一个chunk里面会输出usage信息
.toolCallbacks(tools)
.internalToolExecutionEnabled(false)
.build();
ChatClient.Builder builder = ChatClient.builder(chatModel);
if (!CollectionUtils.isEmpty(advisors)) {
builder.defaultAdvisors(advisors);
}
this.chatClient = builder.defaultOptions(openAiChatOptions)
.defaultToolCallbacks(tools)
.build();
} catch (Exception e) {
throw new RuntimeException("ChatClient 初始化失败:" + e.getMessage(), e);
}
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)