spring-ai advisors 使用与源码分析
advisors
spring-aiv1.0.3
为什么要用 Advisors?
Spring AI Advisors API提供了一种灵活而强大的方式来拦截、修改和增强Spring应用程序中AI驱动的交互。通过利用Advisors API,开发人员可以创建更复杂、可重用和可维护的AI组件。
常见的使用场景
- 统一拦截:把
日志、memory、tool调用、检索、审计等横切逻辑从业务中剥离。 - 解放双手:原生 OpenAI 的
tool_calls需要你手写循环(解析→调用→回补→再问);Advisor 帮你自动闭环。
源码分析
核心接口

BaseAdvisor
public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {@Overridedefault ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {//1. do - before callChatClientRequest processedChatClientRequest = before(chatClientRequest, callAdvisorChain);//2. execute callChatClientResponse chatClientResponse = callAdvisorChain.nextCall(processedChatClientRequest);//3. do - after callreturn after(chatClientResponse, callAdvisorChain);}default Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,StreamAdvisorChain streamAdvisorChain) {/*略...*/}ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain);ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain);
}
自定义MessageChatMemoryAdvisor - 源码分析
定义JDBC ChatMemory
JdbcChatMemoryRepository chatMemoryRepository = ...; //3ChatMemory chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository) //2.maxMessages(10).build();ChatClient chatClient = ChatClient.builder(openAiChatModel).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) // 1.build();
BaseChatMemoryAdvisor & MessageChatMemoryAdvisor
public interface BaseChatMemoryAdvisor extends BaseAdvisor {default String getConversationId(Map<String, Object> context, String defaultConversationId) {//ChatMemory.CONVERSATION_ID = "chat_memory_conversation_id" return context.containsKey(ChatMemory.CONVERSATION_ID) ? context.get(ChatMemory.CONVERSATION_ID).toString(): defaultConversationId;}}public final class MessageChatMemoryAdvisor implements BaseChatMemoryAdvisor {//constructor 注入private final ChatMemory chatMemory;public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {//0.获取converstationIdString conversationId = getConversationId(chatClientRequest.context(), this.defaultConversationId);// 1. 从chatMemory获取当前conversationId的messages -- todoList<Message> memoryMessages = this.chatMemory.get(conversationId);// 2. 将#1和promote中message合并List<Message> processedMessages = new ArrayList<>(memoryMessages);processedMessages.addAll(chatClientRequest.prompt().getInstructions());// 3.更新request中的messageChatClientRequest processedChatClientRequest = chatClientRequest.mutate().prompt(chatClientRequest.prompt().mutate().messages(processedMessages).build()).build();// 4. Add the new user message to the conversation memory.UserMessage userMessage = processedChatClientRequest.prompt().getUserMessage();this.chatMemory.add(conversationId, userMessage);return processedChatClientRequest;}public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {List<Message> assistantMessages = new ArrayList<>();if (chatClientResponse.chatResponse() != null) {assistantMessages = chatClientResponse.chatResponse().getResults().stream().map(g -> (Message) g.getOutput()).toList();}//添加message 至chatMemorythis.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId),assistantMessages);return chatClientResponse;}
}
ChatMemory & MessageWindowChatMemory
public interface ChatMemory {String CONVERSATION_ID = "chat_memory_conversation_id";void add(String conversationId, List<Message> messages);List<Message> get(String conversationId);void clear(String conversationId);
}public final class MessageWindowChatMemory implements ChatMemory {private final ChatMemoryRepository chatMemoryRepository;private final int maxMessages;//单个conversationId,最大的messages 条数public List<Message> get(String conversationId) {return this.chatMemoryRepository.findByConversationId(conversationId);}public void clear(String conversationId) {this.chatMemoryRepository.deleteByConversationId(conversationId);}public void add(String conversationId, List<Message> messages) {List<Message> memoryMessages = this.chatMemoryRepository.findByConversationId(conversationId);List<Message> processedMessages = process(memoryMessages, messages); //processthis.chatMemoryRepository.saveAll(conversationId, processedMessages);}private List<Message> process(List<Message> memoryMessages, List<Message> newMessages) {//合并memoryMessages + newMessages, 若超过maxMessages,则需要按照规则remove历史message}}
ChatMemoryRepository & JdbcChatMemoryRepository
public final class JdbcChatMemoryRepository implements ChatMemoryRepository {public List<Message> findByConversationId(String conversationId) {//执行this.dialect.getSelectMessagesSql()中定义的sqlreturn this.jdbcTemplate.query(this.dialect.getSelectMessagesSql(), new MessageRowMapper(), new Object[]{conversationId});}//findConversationIds(),saveAll(),deleteByConversationId()方法略...
}
JdbcChatMemoryRepositoryDialect & PostgresChatMemoryRepositoryDialect
public interface JdbcChatMemoryRepositoryDialect {String getSelectMessagesSql();String getInsertMessageSql();String getSelectConversationIdsSql();String getDeleteMessagesSql();}public class PostgresChatMemoryRepositoryDialect implements JdbcChatMemoryRepositoryDialect {public String getSelectMessagesSql() {return "SELECT content, type FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ? ORDER BY \"timestamp\"";}public String getInsertMessageSql() {return "INSERT INTO SPRING_AI_CHAT_MEMORY (conversation_id, content, type, \"timestamp\") VALUES (?, ?, ?, ?)";}public String getSelectConversationIdsSql() {return "SELECT DISTINCT conversation_id FROM SPRING_AI_CHAT_MEMORY";}public String getDeleteMessagesSql() {return "DELETE FROM SPRING_AI_CHAT_MEMORY WHERE conversation_id = ?";}
}
ChatModelCallAdvisor & ChatModelStreamAdvisor(略)
public final class ChatModelCallAdvisor implements CallAdvisor {public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {Assert.notNull(chatClientRequest, "the chatClientRequest cannot be null");ChatClientRequest formattedChatClientRequest = augmentWithFormatInstructions(chatClientRequest);//调用chat-model去执行requestChatResponse chatResponse = this.chatModel.call(formattedChatClientRequest.prompt());return ChatClientResponse.builder().chatResponse(chatResponse).context(Map.copyOf(formattedChatClientRequest.context())).build();}
}
ChatClient执行advisors全流程
完整实例
@Test
public void test_jdbc() {ChatMemory chatMemory = MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository).maxMessages(10).build();ChatClient chatClient = ChatClient.builder(openAiChatModel).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).build();String conversationId = "007";ChatResponse response = chatClient.prompt().user("who am i?").advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)) //在context中定义CONVERSATION_ID.call() // 1.chatResponse(); //2log.info("response: " + response);}
chatClient.prompt()…call()
public class DefaultChatClient implements ChatClient {public static class DefaultChatClientRequestSpec implements ChatClientRequestSpec {private final List<Advisor> advisors = new ArrayList<>();private final Map<String, Object> advisorParams = new HashMap<>();//1.advisorpublic ChatClientRequestSpec advisors(Consumer<ChatClient.AdvisorSpec> consumer) {var advisorSpec = new DefaultAdvisorSpec();consumer.accept(advisorSpec); //添加param -> DefaultAdvisorSpecthis.advisorParams.putAll(advisorSpec.getParams()); // param -> advisorParamsthis.advisors.addAll(advisorSpec.getAdvisors());return this;}//2public CallResponseSpec call() {//2.1 build advisorChain by using :advisors + ChatModelCallAdvisor , ChatModelStreamAdvisorBaseAdvisorChain advisorChain = buildAdvisorChain(); //2.2 DefaultChatClientUtils.toChatClientRequest(this)//2.3 build DefaultCallResponseSpecreturn new DefaultCallResponseSpec(DefaultChatClientUtils.toChatClientRequest(this), advisorChain,this.observationRegistry, this.observationConvention);}//2.1private BaseAdvisorChain buildAdvisorChain() {// At the stack bottom add the model call advisors.// They play the role of the last advisors in the advisor chain.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();}}
}//2.2
final class DefaultChatClientUtils {static ChatClientRequest toChatClientRequest(DefaultChatClient.DefaultChatClientRequestSpec inputRequest) {List<Message> processedMessages = new ArrayList<>();// System Text => First in the listString processedSystemText = inputRequest.getSystemText();if (StringUtils.hasText(processedSystemText)) {processedMessages.add(new SystemMessage(processedSystemText));}// Messages => In the middle of the listif (!CollectionUtils.isEmpty(inputRequest.getMessages())) {processedMessages.addAll(inputRequest.getMessages());}// User Text => Last in the listString processedUserText = inputRequest.getUserText();if (StringUtils.hasText(processedUserText)) {processedMessages.add(UserMessage.builder().text(processedUserText).media(inputRequest.getMedia()).build());}//tools 相关.. SKIP....return ChatClientRequest.builder().prompt(Prompt.builder().messages(processedMessages).chatOptions(processedChatOptions).build()).context(new ConcurrentHashMap<>(inputRequest.getAdvisorParams())) //将avisorParams 放至 contxt.build();}}
chatClient.prompt()…call().chatResponse
public class DefaultChatClient implements ChatClient {public static class DefaultChatClientRequestSpec implements ChatClientRequestSpec {//3.public ChatResponse chatResponse() {return doGetObservableChatClientResponse(this.request).chatResponse(); //3.1}//3.1private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest,@Nullable String outputFormat) {//context中添加OUTPUT_FORMATif (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();var observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation(this.observationConvention,DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);var chatClientResponse = observation.observe(() -> {//4 advisorChain.nextCallreturn this.advisorChain.nextCall(chatClientRequest);});return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();}}
}
ChatClientObservationDocumentation & ChatClientObservationContext – 略…
advisorChain.nextCall(chatClientRequest)
public class DefaultAroundAdvisorChain implements BaseAdvisorChain {public ChatClientResponse nextCall(ChatClientRequest chatClientRequest) {var advisor = this.callAdvisors.pop(); //pop advisorvar observationContext = AdvisorObservationContext.builder().advisorName(advisor.getName()).chatClientRequest(chatClientRequest).order(advisor.getOrder()).build();return AdvisorObservationDocumentation.AI_ADVISOR.observation(null, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry).observe(() -> advisor.adviseCall(chatClientRequest, this)); //advisor.adviseCall().}}
Tools源码分析
完整实例
定义Tool
public class DateTimeTools {@Tool(description = "Get the current date and time in the user's timezone")String getCurrentDateTime() {return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();}
}
示例
@Test
public void test_tools() {String response = ChatClient.create(openAiChatModel).prompt("What day is tomorrow?").tools(new DateTimeTools()).call().content();System.out.printf("response: %s%n", response); //response: Today is October 30, 2025. Therefore, tomorrow will be October 31, 2025. If you need to know the day of the week for tomorrow, please let me know!}
manual执行tool
上述例子中,tool不需要人工的接入,会被自动的调用,如何设置为manual执行呢?
@Testpublic void test_tools_manual() {ChatOptions opts = ToolCallingChatOptions.builder().toolCallbacks(ToolCallbacks.from(new DateTimeTools())).internalToolExecutionEnabled(false).build();ChatResponse response = ChatClient.create(openAiChatModel).prompt("What day is tomorrow?").options(opts).tools(new DateTimeTools()).call().chatResponse();System.out.println(response.getResults().get(0).getOutput());}
输出
AssistantMessage [messageType=ASSISTANT, toolCalls=[ToolCall[id=call_dVZAUN1LsodBQYhLjjb8MjYB, type=function, name=getCurrentDateTime, arguments={}]], textContent=null, metadata={role=ASSISTANT, messageType=ASSISTANT, finishReason=TOOL_CALLS, refusal=, index=0, annotations=[], id=chatcmpl-CWJFVxCDmBjGAupsZ6QPghBUS3FGP}]
这个才是我们所期待的输出,spring-ai什么样的机制让ToolCall自动执行了呢?
ToolCallingChatOptions
类图

SetUp ToolCallingChatOptions
上文我们已经分析过DefaultChatClientUtils.toChatClientRequest(),其中tool部分前文没有过多解读,在这里我们分析下:
final class DefaultChatClientUtils {static ChatClientRequest toChatClientRequest(DefaultChatClient.DefaultChatClientRequestSpec inputRequest) {//..... SKIP//for Tools -- 将request中的 toolnames, callbacks,toolContext 存放到request.chatOptions即ToolCallingChatOptionsChatOptions processedChatOptions = inputRequest.getChatOptions(); //OpenAiChatOptionsif (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);}}//将request.chatOptions即ToolCallingChatOptions --> prompt.optionsreturn ChatClientRequest.builder().prompt(Prompt.builder().messages(processedMessages).chatOptions(processedChatOptions).build()).context(new ConcurrentHashMap<>(inputRequest.getAdvisorParams())).build();}
}
Using ToolCallingChatOptions (ChatModelCallAdvisor#call())
ToolCallingChatOptions会在ChatModelCallAdvisor#call())中会被调用.
public class OpenAiChatModel implements ChatModel {public ChatResponse call(Prompt prompt) {Prompt requestPrompt = buildRequestPrompt(prompt); //1.build new prompt with requestOptionsreturn this.internalCall(requestPrompt, null);//2}//1Prompt buildRequestPrompt(Prompt prompt) {// Process runtime optionsOpenAiChatOptions runtimeOptions = null;if (prompt.getOptions() != null) {if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions) {runtimeOptions = ModelOptionsUtils.copyToTarget(toolCallingChatOptions, ToolCallingChatOptions.class,OpenAiChatOptions.class);}else {runtimeOptions = ModelOptionsUtils.copyToTarget(prompt.getOptions(), ChatOptions.class,OpenAiChatOptions.class);}}// Define request options by merging runtime options and default optionsOpenAiChatOptions requestOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions,OpenAiChatOptions.class);// set **optionsrequestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders());requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled());requestOptions.setToolNames(this.defaultOptions.getToolNames());requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks());requestOptions.setToolContext(this.defaultOptions.getToolContext());//setreturn new Prompt(prompt.getInstructions(), requestOptions);}//2.public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {ChatCompletionRequest request = createRequest(prompt, false);ChatModelObservationContext observationContext = ChatModelObservationContext.builder().prompt(prompt).provider(OpenAiApiConstants.PROVIDER_NAME).build();//2.1 call open ai ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,this.observationRegistry).observe(() -> {//open-ai do requestResponseEntity<ChatCompletion> completionEntity = this.retryTemplate.execute(ctx -> this.openAiApi.chatCompletionEntity(request, getAdditionalHttpHeaders(prompt)));var chatCompletion = completionEntity.getBody();List<Choice> choices = chatCompletion.choices();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", 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 buildGeneration(choice, metadata, request);}).toList();// Current usage,rateLimit,....ChatResponse chatResponse = new ChatResponse(generations,from(chatCompletion, rateLimit, accumulatedUsage));return chatResponse;});/*** 2.2 DefaultToolExecutionEligibilityPredicate* - isInternalToolExecutionEnabled is true* - and response.hasToolCalls*/ if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) {//2.3 DefaultToolCallingManager.executeToolCallsvar toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response);if (toolExecutionResult.returnDirect()) {// Return tool execution result directly to the client.return ChatResponse.builder().from(response).generations(ToolExecutionResult.buildGenerations(toolExecutionResult)).build();}else {// Send the tool execution result back to the model.return this.internalCall(new Prompt(toolExecutionResult.conversationHistory(), prompt.getOptions()),response);}}return response;}}
DefaultToolExecutionEligibilityPredicate.isToolExecutionRequired()
public interface ToolExecutionEligibilityPredicate extends BiPredicate<ChatOptions, ChatResponse> {default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse) {return test(promptOptions, chatResponse);//sub-class}
}public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {return ToolCallingChatOptions.isInternalToolExecutionEnabled(promptOptions) && chatResponse != null&& chatResponse.hasToolCalls();}
}
DefaultToolCallingManager.executeToolCalls()
public final class DefaultToolCallingManager implements ToolCallingManager {//2.3public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {Optional<Generation> toolCallGeneration = chatResponse.getResults().stream().filter(g -> !CollectionUtils.isEmpty(g.getOutput().getToolCalls())).findFirst();AssistantMessage assistantMessage = toolCallGeneration.get().getOutput();//2.3.1 build tool-context-mapToolContext toolContext = buildToolContext(prompt, assistantMessage);//2.3.2InternalToolExecutionResult internalToolExecutionResult = executeToolCall(prompt, assistantMessage,toolContext);List<Message> conversationHistory = buildConversationHistoryAfterToolExecution(prompt.getInstructions(),assistantMessage, internalToolExecutionResult.toolResponseMessage());return ToolExecutionResult.builder().conversationHistory(conversationHistory).returnDirect(internalToolExecutionResult.returnDirect()).build();}//2.3.1private static ToolContext buildToolContext(Prompt prompt, AssistantMessage assistantMessage) {Map<String, Object> toolContextMap = Map.of();if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions&& !CollectionUtils.isEmpty(toolCallingChatOptions.getToolContext())) {//init with tool-contexttoolContextMap = new HashMap<>(toolCallingChatOptions.getToolContext());//add history messagestoolContextMap.put(ToolContext.TOOL_CALL_HISTORY,buildConversationHistoryBeforeToolExecution(prompt, assistantMessage));}return new ToolContext(toolContextMap);}//2.3.2private InternalToolExecutionResult executeToolCall(Prompt prompt, AssistantMessage assistantMessage,ToolContext toolContext) {//2.3.2.1. 加载定义的callbacksList<ToolCallback> toolCallbacks = toolCallingChatOptions.getToolCallbacks();List<ToolResponseMessage.ToolResponse> toolResponses = new ArrayList<>();Boolean returnDirect = null;for (AssistantMessage.ToolCall toolCall : assistantMessage.getToolCalls()) {String toolName = toolCall.name();String toolInputArguments = toolCall.arguments();//构造tool的 inputArgumentsfinal String finalToolInputArguments = toolInputArguments;//根据toolname找到对应的callbackToolCallback toolCallback = toolCallbacks.stream().filter(tool -> toolName.equals(tool.getToolDefinition().name())).findFirst().orElseGet(() -> this.toolCallbackResolver.resolve(toolName));// returnDirect是在Callback.ToolMetadata中定义的,用来表明无论response是什么,都直接返回returnDirect = returnDirect && toolCallback.getToolMetadata().returnDirect(); String toolCallResult = ToolCallingObservationDocumentation.TOOL_CALL.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,this.observationRegistry).observe(() -> {//执行callbackString toolResult = toolCallback.call(finalToolInputArguments, toolContext);return toolResult;});toolResponses.add(new ToolResponseMessage.ToolResponse(toolCall.id(), toolName,toolCallResult != null ? toolCallResult : ""));}return new InternalToolExecutionResult(new ToolResponseMessage(toolResponses, Map.of()), returnDirect);}}
