SpringAI-工具的特性

工具的生命周期

生命周期

  • SpringAI默认会自动执行AI模型对工具调用的请求,并将工具执行结果返回给AI模型。

  • 整个过程由ChatModel(SpringAI与AI模型交互的核心实现)及ToolCallingManager(工具调用的核心管理器)共同完成。(ChatModel后续会深入学习和记录)

  • 下图是官方的工具执行的生命周期流程图

    SpringAI控制工具执行的生命周期

    1. 当向AI模型提供工具时,SpringAI会将通过ToolDefinition定义的工具信息包含到Prompt中作为消息请求,由ChatModel与AI模型进行交互。
    2. 当AI模型认为需要调用工具时,会响应包含需要调用工具的定义信息及输入参数,SpringAI封装成ChatResponse响应在ChatModel
    3. ChatModel将工具调用的请求(定义信息及输入参数)给到ToolCallingManager
    4. ToolCallingManager通过定义信息识别需要调用的工具,将输入参数发送给工具进行调用执行。
    5. 工具调用结果返回到ToolCallingManager
    6. ToolCallingManager将结果再返回给ChatModel
    7. ChatModel将结果封装成ToolResponseMessage把数据附加给AI模型。
    8. AI模型通过附加数据完成最终处理并响应给ChatModel,并通过 ChatClient 将其返回调用方。
  • 整个过程由SpringAI来控制并完成,SpringAI也提供了将控制交由开发者自行完成,这个后续会记录到。

ToolCallingManager

  • 在生命周期中可以看到工具执行是由ToolCallingManager来完成,SpringAI通过ToolCallingManager管理整个工具调用执行过程的。

  • 下面是ToolCallingManager接口的源码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * Service responsible for managing the tool calling process for a chat model.
    *
    * @author Thomas Vitale
    * @since 1.0.0
    */
    public interface ToolCallingManager {

    /**
    * Resolve the tool definitions from the model's tool calling options.
    */
    List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);

    /**
    * Execute the tool calls requested by the model.
    */
    ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);

    /**
    * Create a default {@link ToolCallingManager} builder.
    */
    static DefaultToolCallingManager.Builder builder() {
    return DefaultToolCallingManager.builder();
    }

    }
    • resolveToolDefinitions方法:解析所有工具的定义,用于ChatModel创建与AI交互请求。

    • executeToolCalls方法:顾名思义,用于执行工具调用。

    • SpringAI默认使用DefaultToolCallingManager为自动配置实现。下面是自动配置的源码。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      @Bean
      @ConditionalOnMissingBean
      ToolCallingManager toolCallingManager(ToolCallbackResolver toolCallbackResolver,
      ToolExecutionExceptionProcessor toolExecutionExceptionProcessor,
      ObjectProvider<ObservationRegistry> observationRegistry,
      ObjectProvider<ToolCallingObservationConvention> observationConvention) {
      var toolCallingManager = ToolCallingManager.builder()
      .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
      .toolCallbackResolver(toolCallbackResolver)
      .toolExecutionExceptionProcessor(toolExecutionExceptionProcessor)
      .build();

      observationConvention.ifAvailable(toolCallingManager::setObservationConvention);

      return toolCallingManager;
      }

控制工具执行

  • SpringAI是允许开发者自行控制工具执行。通过ToolCallingChatOptions设置属性internalToolExecutionEnabledfalse

  • 记录一个控制工具执行的案例。

    • 模拟一个自定义ToolCallingManager来执行工具,实际还是使用DefaultToolCallingManager

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @Slf4j
      public class ExampleToolCallingManager implements ToolCallingManager {

      // 模拟一个自定义的 ToolCallingManager,实际使用的 DefaultToolCallingManager
      private final ToolCallingManager toolCallingManager = ToolCallingManager.builder().build();

      @Override
      public List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions) {
      return toolCallingManager.resolveToolDefinitions(chatOptions);
      }

      @Override
      public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {
      log.info("\nExample tool calling manager!");
      return toolCallingManager.executeToolCalls(prompt, chatResponse);
      }
      }
    • ExampleToolCallingManager注入Bean容器。默认的ToolCallingManager是使用了@ConditionalOnMissingBean注解的,当自定义的注入后默认的就不会再有了。

      1
      2
      3
      4
      5
      6
      7
      @Configuration
      public class ToolCallingManagerConfiguration {
      @Bean
      public ExampleToolCallingManager exampleToolCallingManager(ToolCallingManager toolCallingManager) {
      return new ExampleToolCallingManager();
      }
      }
    • 在生命周期中由ChatModelToolCallingManager共同完成。所以直接使用ChatModel与AI交互

      1
      2
      3
      private final DeepSeekChatModel chatModel;

      private final ExampleToolCallingManager exampleToolCallingManager;
    • 构建 ToolCallingChatOptions ,关闭自动工具执行并添加工具(ChatOptions可以配置一些选项传给ChatModel

      1
      2
      3
      4
      5
      6
      7
      // 传入给 ChatModel 的一些选项
      ToolCallingChatOptions options = ToolCallingChatOptions.builder()
      // 关闭自动控制工具执行
      .internalToolExecutionEnabled(false)
      // 可以通过 ToolCallingChatOptions 添加工具
      .toolCallbacks(ToolCallbacks.from(new MockWeatherTools()))
      .build();
    • 构建Prompt调用AI模型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      Prompt prompt = Prompt.builder()
      // 提示词
      .messages(SystemMessage.builder()
      .text("气温超过30度需要向用户发送高温预警提示")
      .build())
      .messages(UserMessage.builder()
      .text("今天上海热不热?")
      .build())
      .chatOptions(options)
      .build();

      // 调用AI模型
      ChatResponse chatResponse = chatModel.call(prompt);
    • 通过ChatResponse判断是否需要需要执行工具,执行工具并得倒结果。最后打印AI输出的最终响应内容。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // 是否存在工具调用的请求
      while (chatResponse.hasToolCalls()) {
      // 使用模拟的 ToolCallingManager 去执行工具,并得倒工具执行的结果
      ToolExecutionResult toolExecutionResult = exampleToolCallingManager.executeToolCalls(prompt, chatResponse);

      Prompt toolCallPrompt = Prompt.builder()
      // 工具执行的结果封装为消息,并包含此次与AI模型交互的整个上下文消息
      .messages(toolExecutionResult.conversationHistory())
      .chatOptions(options)
      .build();

      // 将工具调用的结果附加给AI模型
      chatResponse = chatModel.call(toolCallPrompt);
      }
      log.info("\n[ControllerToolExecution] response content -> \n{}", chatResponse.getResult().getOutput().getText());
    • 日志打印的输出。通过自定义的ExampleToolCallingManager执行了工具调用。AI并通过附加数据给出来最终响应。

      1
      2
      3
      4
      5
      6
      7
      2025-07-14T14:43:39.431+08:00  INFO 9607 --- [spring-ai-example] [           main] c.s.a.e.t.t.ExampleToolCallingManager    : 
      Example tool calling manager!
      2025-07-14T14:43:39.435+08:00 INFO 9607 --- [spring-ai-example] [ main] c.s.a.e.tools.one.MockWeatherTools :
      获取天气,城市 -> 上海
      2025-07-14T14:43:44.521+08:00 INFO 9607 --- [spring-ai-example] [ main] s.a.e.t.t.ControllerToolExecutionExample :
      [ControllerToolExecution] response content ->
      今天上海的气温是31.5°C,体感温度约为35°C,天气较为炎热。建议注意防暑降温!

异常处理

  • 异常通常要么是捕获要么外抛,捕获较为简单,直接在工具中捕获处理就可以了。

  • 工具往外抛异常要分两种,一种是外抛的SpringAI提供的ToolExecutionException,另一种就是其他异常了,如RuntimeException

  • 如果抛出的是其他异常,比如以RuntimeException为例。

    • 这些异常会陪抛至调用处,比如会抛给ToolCallingManager
    • 如果ToolCallingManager也没有处理就会继续外抛,一直抛至最外层调用处。
  • 如果抛出ToolExecutionException,那么SpringAI会将异常传播至ToolExecutionExceptionProcessor来处理。

    • 下图是异常传播的源码

      异常传播

    • SpringAI默认自动配置DefaultToolExecutionExceptionProcessor作为处理对象。下面是自动配置的源码。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
       @Bean
      @ConditionalOnMissingBean
      ToolExecutionExceptionProcessor toolExecutionExceptionProcessor(ToolCallingProperties properties) {
      ArrayList<Class<? extends RuntimeException>> rethrownExceptions = new ArrayList<>();

      // ClientAuthorizationException is used by Spring Security in oauth2 flows,
      // for example with ServletOAuth2AuthorizedClientExchangeFilterFunction and
      // OAuth2ClientHttpRequestInterceptor.
      Class<? extends RuntimeException> oauth2Exception = getClassOrNull(
      "org.springframework.security.oauth2.client.ClientAuthorizationException");
      if (oauth2Exception != null) {
      rethrownExceptions.add(oauth2Exception);
      }

      return DefaultToolExecutionExceptionProcessor.builder()
      .alwaysThrow(properties.isThrowExceptionOnError())
      .rethrowExceptions(rethrownExceptions)
      .build();
      }
    • DefaultToolExecutionExceptionProcessor中也有两种处理方式

      • 默认是将错误消息当作工具结果附加给AI模型由其处理。
      • 通过配置spring.ai.tools.throw-exception-on-errortrue将异常外抛,则中断工具调用
  • 也可以自定义ToolExecutionExceptionProcessor来处理异常。

    • 比较简单,实现ToolExecutionExceptionProcessor接口即可。下面模拟一个处理:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Slf4j
      public class ExampleToolExecutionExceptionProcessor implements ToolExecutionExceptionProcessor {
      @Override
      public String process(ToolExecutionException e) {
      log.info("\nExample tool execution exception -> {}, ", e.getMessage());
      return ConvertorUtils.toJsonString(Map.of(
      "result", "fail",
      "error", e.getMessage()));
      }
      }
    • 添加自定义的ToolExecutionExceptionProcessorToolCallingManager

      • 如果ToolCallingManager使用的是SringAI自动配置的DefaultToolCallingManager

      • 则直接将自定义的ToolExecutionExceptionProcessor注入到Bean即可,会取代掉自动配置的DefaultToolExecutionExceptionProcessor

      • 如果是自定义的ToolCallingManager,需要自行添加自定义的ToolExecutionExceptionProcessor来使用,否则会不起作用。

      • 比如上面自定义的ExampleToolCallingManager,使用自定义的ExampleToolExecutionExceptionProcessor

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        @Slf4j
        @Component
        public class ExampleToolCallingManager implements ToolCallingManager {

        // 模拟一个自定义的 ToolCallingManager,实际使用的 DefaultToolCallingManager
        private final ToolCallingManager toolCallingManager = ToolCallingManager
        .builder()
        // 添加 ExampleToolExecutionExceptionProcessor 处理 ToolExecutionException
        .toolExecutionExceptionProcessor(new ExampleToolExecutionExceptionProcessor())
        .build();

        @Override
        public List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions) {
        return toolCallingManager.resolveToolDefinitions(chatOptions);
        }

        @Override
        public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse) {
        log.info("\nExample tool calling manager!");
        return toolCallingManager.executeToolCalls(prompt, chatResponse);
        }
        }

工具规范

工具使用过程

  • 在SpringAI中使用工具,可以将步骤大致总结出以下几点
    • 通过ToolDefinition来定义的基本信息。
    • 将工具的对象实例转换成ToolCallback
    • 定义输入参数的Json Schema
    • 定义工具执行完后的结果转换器
    • 执行过程中SpringAI提供了ToolContext作为工具执行上下文
    • 最后就是是否启用了直接返回returnDirect

ToolDefinition

  • 在定义工具时,不论是一下那种方式,都是通过ToolDefinition来定义的基本信息。

    • 不论是使用@Tool注解

    • 还是ToolDefinition.Builder手动构建。

    • 或者是函数工具时直接通过FunctionToolCallback.Builder进行构建。下图是build()时构建ToolDefinition

      使用的DefaultToolDefinition

  • 通过接口定义的源码可以看到默认是DefaultToolDefinition,SpringAI中也没有其他实现类了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public interface ToolDefinition {

    /**
    * The tool name. Unique within the tool set provided to a model.
    */
    String name();

    /**
    * The tool description, used by the AI model to determine what the tool does.
    */
    String description();

    /**
    * The schema of the parameters used to call the tool.
    */
    String inputSchema();

    /**
    * Create a default {@link ToolDefinition} builder.
    */
    static DefaultToolDefinition.Builder builder() {
    return DefaultToolDefinition.builder();
    }
    }
  • 如果某个模型的工具调用所需要的定义信息更多,可以通过实现该接口来满足SpringAI的工具定义规范。

ToolCallback

  • 不论使用函数工具还是方法工具,都需要将工具的对象实例转换成ToolCallback

    • 函数工具对象实例不论是手动构建还是注入到Bean容器,最后都是转换成FunctionToolCallback
    • 方法工具也是一样,都是转换成MethodToolCallback
  • ToolCallback的两个实现类中存放了工具的定义信息、调用实例、方法等元数据。接口本身提供了.call()方法作为工具调用的规范。

  • 下图可以看到在ToolCallingManager中是通过ToolCallback.call()来完成工具调用的

    ToolCallback工具调用

入参 JSON Schema

  • 工具的输入参数使用的Json Schema来作为规范。
  • SpringAI提供了JsonSchemaGenerator类来支持生成工具输入参数的Json Schema
  • SpringAI还支持以下集中注解来对输入参数进行约束、描述等
    • SpringAI提供的@ToolParam
    • Jackson的@JsonPropertyDescription
    • 复杂对象还可以使用@JsonClassDescription
    • Swagger的@Schema

结果转换器

  • 工具调用结果可以通过ToolCallResultConverter进行特定格式的序列化后再附加给AI模型。

    结果转换

  • SpringAI默认使用的DefaultToolCallResultConverter,通过 Jackson 序列化为JSON字符串附加给AI模型。

    默认的Converter

  • 如果有需要可以实现ToolCallResultConverter自定义序列化过程来满足结果返回的规范。

ToolContext

  • SpringAI提供了ToolContext作为向工具传递额外的上下文信息。可以通过其将额外的数据传递至工具内。

  • 使用方式

    • 定义工具方法的时候,参数中加一个ToolContext。如下:

      1
      2
      3
      4
      5
      6
      @Tool(description = "获取一个位置的海拔,需要提供一个位置")
      public Object getAltitude(@ToolParam(description = "位置。如长沙岳麓山") String location, ToolContext context) {
      log.info("\n从上下文中获取海拔,位置 -> {}", location);
      Object altitude = context.getContext().get("altitude");
      return Map.of("location", location, "altitude", altitude, "unit" , "千米");
      }
    • 在添加工具的时候也添加一下ToolContext。如果定义工具中有ToolContext,而未添加上下文则会报错。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public void toolsContextExample() {
      String content = toolsClient.prompt()
      .user("上海海拔高不高嘛?")
      .tools(new MockWeatherTools())
      // 通过上下文设置返回的高度
      .toolContext(Map.of("altitude", 18.8))
      .call()
      .content();
      log.info("\n[toolsExample] content -> \n{}", content);
      }
    • 执行结果

      1
      2
      3
      4
      5
      6
      7
      2025-07-14T20:02:45.815+08:00  INFO 16535 --- [spring-ai-example] [           main] c.s.a.e.t.t.ExampleToolCallingManager    : 
      Example tool calling manager!
      2025-07-14T20:02:45.823+08:00 INFO 16535 --- [spring-ai-example] [ main] c.s.a.e.tools.one.MockWeatherTools :
      从上下文中获取海拔,位置 -> 上海
      2025-07-14T20:02:50.598+08:00 INFO 16535 --- [spring-ai-example] [ main] c.s.a.e.tools.one.MethodToolsExample :
      [toolsExample] content ->
      上海的海拔大约是18.8千米,相对来说是比较高的。
  • 下图是官方给出的ToolContext在工具生命周期中的流程图。在第三步调用工具的时候将ToolContext传入给工具。

    提供额外的上下文信息到工具

  • 源码中也很容易发现是在ToolCallingManager中调用工具时传入ToolContext

    传入ToolContext

直接返回

  • SpringAI默认情况下,工具调用结果将作为附加数据补充给模型,随后模型可利用该结果继续对话。

  • 当将returnDirect属性设置为true时,可以使工具调用结果直接返回给调用方,而非AI模型。

  • 下面是官方给出的直接返回时工具的生命周期流程图

    直接返回工具的结果给调用方

    • 前面4个步骤,基本是保持一直的。只有第一步在添加工具时将returnDirect属性设置为true
    • 在工具调用结果返回时,直接返回调用方,而非传回模型。
  • 下面是源码中判断returnDirect是直接返回ChatResponse还是继续调用AI。

    直接返回ChatResponse

总结

  • 工具的生命周期主要由ChatModelToolCallingManager来完成。
    • ChatModel负责与AI交互并通知ToolCallingManager
    • ToolCallingManager负责执行工具以及返回结果
  • SpringAI默认自动配置DefaultToolCallingManager,需要自定义则实现ToolCallingManager接口即可。
  • 在工具的生命周期中可以通过SpringAI默认会自动控制执行工具调用。也允许由开发者自行控制。
    • 设置属性internalToolExecutionEnabledfalse关闭自动控制。
    • 自行使用ChatModel与AI通信,返回的ChatResponse中如果AI决定需要调用工具。
    • 开发者通过ToolCallingManager(可以使用SpringAI默认的,也可以自行定义)来执行工具。
  • 工具如果抛出ToolExecutionException,SpringAI会将异常传播至ToolExecutionExceptionProcessor来处理。
  • 按照SpringAI提供的以下几个规范,可自行扩展工具相关开发。
    • 工具定义接口:ToolDefinition
    • 工具执行接口:ToolCallback
    • 输入参数规范使用的Json Schema
    • 结果转换器接口:ToolCallResultConverter
    • 工具执行上下文:ToolContext
    • 直接返回:returnDirect

最后

  • 工具调用算是AI应用开发的重要核心把,目前仅有的了解,好像AI Agent的实现主要核心之一也是工具调用。还有MCP的核心好像也是工具。
  • 在SpringAI的工具调用中不仅仅提供了各种快速开发工具与AI进行交互的能力,还允许开发者自行实现控制工具执行的过程,同时还提供了相关规范由开发者遵循来扩展工具的开发。
  • 模拟工具的使用单独写了个案例提交在Github上,包:com.spring.ai.example.tools.four,但是不单独记录了。
  • 本篇记录的案例也提交在Github上了,包:com.spring.ai.example.tools.three