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

BaseAdvisor
java
public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {
@Override
default ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
//1. do - before call
ChatClientRequest processedChatClientRequest = before(chatClientRequest, callAdvisorChain);
//2. execute call
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(processedChatClientRequest);
//3. do - after call
return 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
java
JdbcChatMemoryRepository chatMemoryRepository = ...; //3
ChatMemory chatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository) //2
.maxMessages(10)
.build();
ChatClient chatClient = ChatClient.builder(openAiChatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) // 1
.build();
BaseChatMemoryAdvisor & MessageChatMemoryAdvisor
java
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.获取converstationId
String conversationId = getConversationId(chatClientRequest.context(), this.defaultConversationId);
// 1. 从chatMemory获取当前conversationId的messages -- todo
List<Message> memoryMessages = this.chatMemory.get(conversationId);
// 2. 将#1和promote中message合并
List<Message> processedMessages = new ArrayList<>(memoryMessages);
processedMessages.addAll(chatClientRequest.prompt().getInstructions());
// 3.更新request中的message
ChatClientRequest 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 至chatMemory
this.chatMemory.add(this.getConversationId(chatClientResponse.context(), this.defaultConversationId),
assistantMessages);
return chatClientResponse;
}
}
ChatMemory & MessageWindowChatMemory
java
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); //process
this.chatMemoryRepository.saveAll(conversationId, processedMessages);
}
private List<Message> process(List<Message> memoryMessages, List<Message> newMessages) {
//合并memoryMessages + newMessages, 若超过maxMessages,则需要按照规则remove历史message
}
}
ChatMemoryRepository & JdbcChatMemoryRepository
java
public final class JdbcChatMemoryRepository implements ChatMemoryRepository {
public List<Message> findByConversationId(String conversationId) {
//执行this.dialect.getSelectMessagesSql()中定义的sql
return this.jdbcTemplate.query(this.dialect.getSelectMessagesSql(), new MessageRowMapper(), new Object[]{conversationId});
}
//findConversationIds(),saveAll(),deleteByConversationId()方法略...
}
JdbcChatMemoryRepositoryDialect & PostgresChatMemoryRepositoryDialect
java
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(略)
java
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去执行request
ChatResponse chatResponse = this.chatModel.call(formattedChatClientRequest.prompt());
return ChatClientResponse.builder()
.chatResponse(chatResponse)
.context(Map.copyOf(formattedChatClientRequest.context()))
.build();
}
}
ChatClient执行advisors全流程
完整实例
java
@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(); //2
log.info("response: " + response);
}
chatClient.prompt()...call()
java
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.advisor
public ChatClientRequestSpec advisors(Consumer<ChatClient.AdvisorSpec> consumer) {
var advisorSpec = new DefaultAdvisorSpec();
consumer.accept(advisorSpec); //添加param -> DefaultAdvisorSpec
this.advisorParams.putAll(advisorSpec.getParams()); // param -> advisorParams
this.advisors.addAll(advisorSpec.getAdvisors());
return this;
}
//2
public CallResponseSpec call() {
//2.1 build advisorChain by using :advisors + ChatModelCallAdvisor , ChatModelStreamAdvisor
BaseAdvisorChain advisorChain = buildAdvisorChain();
//2.2 DefaultChatClientUtils.toChatClientRequest(this)
//2.3 build DefaultCallResponseSpec
return new DefaultCallResponseSpec(DefaultChatClientUtils.toChatClientRequest(this),
advisorChain,this.observationRegistry, this.observationConvention);
}
//2.1
private 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 list
String processedSystemText = inputRequest.getSystemText();
if (StringUtils.hasText(processedSystemText)) {
processedMessages.add(new SystemMessage(processedSystemText));
}
// Messages => In the middle of the list
if (!CollectionUtils.isEmpty(inputRequest.getMessages())) {
processedMessages.addAll(inputRequest.getMessages());
}
// User Text => Last in the list
String 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
java
public class DefaultChatClient implements ChatClient {
public static class DefaultChatClientRequestSpec implements ChatClientRequestSpec {
//3.
public ChatResponse chatResponse() {
return doGetObservableChatClientResponse(this.request).chatResponse(); //3.1
}
//3.1
private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest,
@Nullable String outputFormat) {
//context中添加OUTPUT_FORMAT
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();
var observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation(this.observationConvention,
DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry);
var chatClientResponse = observation.observe(() -> {
//4 advisorChain.nextCall
return this.advisorChain.nextCall(chatClientRequest);
});
return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();
}
}
}
ChatClientObservationDocumentation & ChatClientObservationContext -- 略...
advisorChain.nextCall(chatClientRequest)
java
public class DefaultAroundAdvisorChain implements BaseAdvisorChain {
public ChatClientResponse nextCall(ChatClientRequest chatClientRequest) {
var advisor = this.callAdvisors.pop(); //pop advisor
var 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
java
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();
}
}
示例
java
@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执行呢?
java
@Test
public 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部分前文没有过多解读,在这里我们分析下:
java
final class DefaultChatClientUtils {
static ChatClientRequest toChatClientRequest(DefaultChatClient.DefaultChatClientRequestSpec inputRequest) {
//..... SKIP
//for Tools -- 将request中的 toolnames, callbacks,toolContext 存放到request.chatOptions即ToolCallingChatOptions
ChatOptions processedChatOptions = inputRequest.getChatOptions(); //OpenAiChatOptions
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);
}
}
//将request.chatOptions即ToolCallingChatOptions --> prompt.options
return ChatClientRequest.builder()
.prompt(Prompt.builder().messages(processedMessages).chatOptions(processedChatOptions).build())
.context(new ConcurrentHashMap<>(inputRequest.getAdvisorParams()))
.build();
}
}
Using ToolCallingChatOptions (ChatModelCallAdvisor#call())
ToolCallingChatOptions会在ChatModelCallAdvisor#call())中会被调用.
java
public class OpenAiChatModel implements ChatModel {
public ChatResponse call(Prompt prompt) {
Prompt requestPrompt = buildRequestPrompt(prompt); //1.build new prompt with requestOptions
return this.internalCall(requestPrompt, null);//2
}
//1
Prompt buildRequestPrompt(Prompt prompt) {
// Process runtime options
OpenAiChatOptions 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 options
OpenAiChatOptions requestOptions = ModelOptionsUtils.merge(runtimeOptions, this.defaultOptions,OpenAiChatOptions.class);
// set **options
requestOptions.setHttpHeaders(this.defaultOptions.getHttpHeaders());
requestOptions.setInternalToolExecutionEnabled(this.defaultOptions.getInternalToolExecutionEnabled());
requestOptions.setToolNames(this.defaultOptions.getToolNames());
requestOptions.setToolCallbacks(this.defaultOptions.getToolCallbacks());
requestOptions.setToolContext(this.defaultOptions.getToolContext());
//set
return 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 request
ResponseEntity<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.executeToolCalls
var 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()
java
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()
java
public final class DefaultToolCallingManager implements ToolCallingManager {
//2.3
public 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-map
ToolContext toolContext = buildToolContext(prompt, assistantMessage);
//2.3.2
InternalToolExecutionResult 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.1
private 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-context
toolContextMap = new HashMap<>(toolCallingChatOptions.getToolContext());
//add history messages
toolContextMap.put(ToolContext.TOOL_CALL_HISTORY,
buildConversationHistoryBeforeToolExecution(prompt, assistantMessage));
}
return new ToolContext(toolContextMap);
}
//2.3.2
private InternalToolExecutionResult executeToolCall(Prompt prompt, AssistantMessage assistantMessage,
ToolContext toolContext) {
//2.3.2.1. 加载定义的callbacks
List<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的 inputArguments
final String finalToolInputArguments = toolInputArguments;
//根据toolname找到对应的callback
ToolCallback 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(() -> {
//执行callback
String 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);
}
}