工具调用

AI的工具能力

AI的短板

  • AI模型通过训练具备了逻辑推理、数据处理、内容生成等能力,但也存在一些短板。比如以下这些场景
    • 向AI询问今天的天气如何。
    • 让AI制定了旅游计划,让其帮忙预订机票。
  • 因为缺少天气数据的支撑,AI无法给出准确的天气信息。
  • 同样因为缺少预订机票可执行的系统支撑,也无法完成预定。

工具的作用

  • 针对上述两个场景,AI提供梨工具调用的能力来补齐自己的短板。
  • 如果在调用AI时提供了获取天气数据的工具,AI在处理用户的需求时发现需要用到天气数据则会通过工具调用来补充天气数据再进一步处理。
  • 同样如果在与AI交互时提供了预定机票下单的工具,在用户要求帮忙预订机票时,AI则会通过工具调用来完成执行预订。
  • 所以工具的作用主要在于两点
    • 检索数据:AI通过工具调用从外部(应用)系统检索需要数据进行补充,以完成用户的需求。
    • 执行操作:AI通过工具调用来触发外部(应用)系统的执行操作,以完成输入的指令。

注意

  • 虽然工具调用是AI模型的能力,但是实际上调用是由外部(应用)系统来完成调用过程的。
  • 在与AI交互时提供了工具,且AI在处理输入的需求时认为需要调用工具。
  • AI会响应给外部(应用)系统发起工具调用的请求以及提供参数。
  • 在外部(应用)系统完成工具调用后,再通过消息将结果补充给AI。

SpringAI 的工具调用

快速使用工具调用

  • 这里通过一个简单的案例记录一下如何使用工具调用。案例需求如下:

    • 提供一个模拟获取天气的工具(数据检索)。
    • 再提供一个模拟发送高温预警的工具(执行操作)。
    • 然后通过两个调用案例查看工具执行情况
  • 创建模拟工具的类MockWeatherTools,并提供getWeather/sendWarning两个工具方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Slf4j
    public class MockWeatherTools {

    private String mockData = "{\"location\":{\"name\":\"%s\",\"path\":\"中国, %s\"},\"now\":{\"precipitation\":0,\"temperature\":31.5,\"pressure\":1005,\"humidity\":43,\"windDirection\":\"西南风\",\"windDirectionDegree\":216,\"windSpeed\":2.7,\"windScale\":\"微风\",\"feelst\":35}}";

    @Tool(description = "获取一个城市的天气,需要输入城市的名称")
    public Object getWeather(@ToolParam(description = "城市。如湖南长沙") String city) {
    log.info("\n获取天气,城市 -> {}", city);
    return String.format(mockData, city, city);
    }

    @Tool(description = "向用户发送高温预警,需要输出城市和气温")
    public Object sendWarning(@ToolParam(description = "城市。如湖南长沙") String city,
    @ToolParam(description = "气温") double temperature) {
    log.info("\n发送高温预警,城市 -> {}, 气温 -> {}", city, temperature);
    return "发送成功";
    }

    }
  • 调用AI时添加调用工具。这里两个调用案例,第二个提示词中加了要求发送高温预警,添加方法略微不同。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private final ChatClient toolsClient;

    public void toolsExample() {
    String content = toolsClient.prompt()
    .user("上海今天天气如何?")
    // 添加工具对象
    .tools(new MockWeatherTools())
    .call()
    .content();
    log.info("\n[toolsExample] content -> \n{}", content);

    content = toolsClient.prompt()
    // 要求发送高温预警
    .system("气温超过30度须向用户发送高温预警")
    .user("今天北京天气好不好?")
    // 添加工具对象。 其实 .tools() 也是使用的 ToolCallbacks.from()
    .toolCallbacks(ToolCallbacks.from(new MockWeatherTools()))
    .call()
    .content();
    log.info("\n[toolsExample]toolCallbacks content -> \n{}", content);
    }
  • 执行结果。两个案例都调用了获取天气的工具,第二个案例则调用了执行发送高温预警的案例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    2025-07-10T19:05:33.606+08:00  INFO 93554 --- [spring-ai-example] [           main] c.s.a.e.t.one.method.MockWeatherTools    : 
    获取天气,城市 -> 上海
    2025-07-10T19:05:41.406+08:00 INFO 93554 --- [spring-ai-example] [ main] c.s.a.e.t.one.method.MethodToolsExample :
    [toolsExample] content ->
    上海今天的天气情况如下:

    - **温度**: 31.5°C
    - **体感温度**: 35°C
    - **降水**: 0 mm
    - **气压**: 1005 hPa
    - **湿度**: 43%
    - **风向**: 西南风
    - **风速**: 2.7 m/s (微风)

    天气较热,请注意防暑降温!是否需要为您发送高温预警?

    2025-07-10T19:05:45.502+08:00 INFO 93554 --- [spring-ai-example] [ main] c.s.a.e.t.one.method.MockWeatherTools :
    获取天气,城市 -> 北京
    2025-07-10T19:05:54.251+08:00 INFO 93554 --- [spring-ai-example] [ main] c.s.a.e.t.one.method.MockWeatherTools :
    发送高温预警,城市 -> 北京, 气温 -> 31.5
    2025-07-10T19:05:58.646+08:00 INFO 93554 --- [spring-ai-example] [ main] c.s.a.e.t.one.method.MethodToolsExample :
    [toolsExample]toolCallbacks content ->
    已向您发送北京的高温预警,当前气温为31.5°C,请注意防暑降温!

方法即工具

@Tool注解
  • 上面“快速使用工具调用”的案例就是通过@Tool注解完成的。注解可以通过4个属性进行工具配置。

    • String name():工具名称。若不指定,默认使用方法名称。AI会通过该名称识别并调用工具,所以在与AI交互时提供的工具必须保证名称的唯一性。
    • String description():工具描述,用于引导模型判断何时以及如何调用该工具。若不描述或描述不充分,可能会导致AI不调用或错误调用工具。
    • boolean returnDirect():用于控制工具的调用结果是直接返回给应用系统还是返回给AI模型。默认false,返回给AI模型。
    • Class<? extends ToolCallResultConverter> resultConverter():用于将工具调用结果转换为字符串文本的转换器,必须是ToolCallResultConverter的实现类。
  • 案例中还使用到了@ToolParam来对工具的输入参数进行配置。有两个属性可配置。

    • boolean required():指定参数是否是必填项。默认true必填。
    • String description():参数描述,用于引导模型准确的理解和使用参数。
  • 如何将工具添加至AI调用(ChatClient)中

    • 通过.tools()直接添加工具对象

      1
      2
      3
      4
      5
      6
      toolsClient.prompt()
      .user("上海今天天气如何?")
      // 添加工具对象
      .tools(new MockWeatherTools())
      .call()
      .content();
    • 将工具对象转换为ToolCallback通过.toolCallbacks()添加

      1
      2
      3
      4
      5
      6
      toolsClient.prompt()
      .user("今天北京天气好不好?")
      // 添加工具对象。 其实 .tools() 也是使用的 ToolCallbacks.from()
      .toolCallbacks(ToolCallbacks.from(new MockWeatherTools()))
      .call()
      .content();
    • 其实二者是一样的,.tools()方法里面也是通过ToolCallbacks.from()转换成ToolCallback的。

使用 MethodToolCallback
  • 如果不使用@Tool注解,也可以通过MethodToolCallback来将Java方法构建成工具方法。

  • 使用MethodToolCallback.Builder来构建MethodToolCallback实例。可以构建的属性如下:

    • ToolDefinition toolDefinition:用来定义工具的名称、描述,以及输入参数的Schema。可以使用ToolDefinition.Builder来构建。可构建的属性:
      • String name:工具名称。
      • String description:工具描述。
      • String inputSchema:输入参数的Json Schema。必须输入,否则会无法构建。可以使用JsonSchemaGenerator来生成。
    • ToolMetadata toolMetadata:工具的其他配置。可以使用ToolMetadata.Builder来构建。属性有:
      • returnDirect:控制工具的调用结果是直接返回给应用系统还是返回给AI模型。
    • Method toolMethod:工具方法的Java Method 实例,用于调用工具。
      • 实例方法或静态方法都可以。
      • 所有可见性(public、protected、package-private 或 private)的方法都可以。
      • 输入参数由SpringAI自动创建Json Schema,所以使用@ToolParam@JsonProperty@Schema都可以对参数进行配置描述。
    • Object toolObject:包含工具方法的对象实例,用于调用工具。
      • 如果是静态方法,则无需该参数。
      • 可以返回void
      • 若有返回值,则返回类型必须是可序列化的。
    • ToolCallResultConverter toolCallResultConverter:将工具调用结果转换为字符串文本的转换器。可以实现ToolCallResultConverter自定义转换器。
  • 使用案例。

    • 创建一个模拟获取天气工具。用protected的静态方法。使用@JsonPropertyDescription对参数描述。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Slf4j
      public class ManualMockWeatherTools {

      private static String mockResultStr = "{\"location\":{\"name\":\"%s\",\"path\":\"中国, %s\"},\"now\":{\"precipitation\":0,\"temperature\":31.5,\"pressure\":1005,\"humidity\":43,\"windDirection\":\"西南风\",\"windDirectionDegree\":216,\"windSpeed\":2.7,\"windScale\":\"微风\",\"feelst\":23.1}}";

      protected static Object getWeather(@JsonPropertyDescription("城市") String city) {
      log.info("\n[Manual] 获取天气,城市 -> {}", city);
      return String.format(mockResultStr, city, city);
      }
      }
    • 获取工具方法的Java Method 实例

      1
      2
      // ReflectionUtils 是Spring的工具类
      Method method = ReflectionUtils.findMethod(ManualMockWeatherTools.class, "getWeather", String.class);
    • 构建ToolDefinition

      1
      2
      3
      4
      5
      6
      ToolDefinition definition = ToolDefinition.builder()
      .name("getWeather")
      .description("获取天气。")
      // JsonSchemaGenerator 是SpringAI提供的工具类
      .inputSchema(JsonSchemaGenerator.generateForMethodInput(method))
      .build();
    • 构建ToolMetadata。如果是返回给AI的工具,无需构建。所这里是注释的。

      1
      2
      3
      //        ToolMetadata metadata = ToolMetadata.builder()
      // .returnDirect(false)
      // .build();
    • 构建MethodToolCallback

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      MethodToolCallback toolCallback = MethodToolCallback.builder()
      .toolDefinition(definition)
      .toolMethod(method)
      //元数据信息,目前只有是否直接返回设置
      //.toolMetadata(metadata)
      // 静态方法可以不传Object
      //.toolObject(new ManualMockWeatherTools())
      // 结果转换器,用默认的。
      //.toolCallResultConverter(new DefaultToolCallResultConverter())
      .build();
    • ChatClient添加工具。使用.toolCallbacks()添加

      1
      2
      3
      4
      5
      6
      String content = toolsClient.prompt()
      .user("长沙天气如何?")
      .toolCallbacks(toolCallback)
      .call()
      .content();
      log.info("\n[manualToolsExample] toolCallbacks content -> \n{}", content);
    • 执行结果。正常调用工具。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      2025-07-11T14:27:55.547+08:00  INFO 98380 --- [spring-ai-example] [           main] c.s.a.e.t.o.m.ManualMockWeatherTools     : 
      [Manual] 获取天气,城市 -> 长沙
      2025-07-11T14:28:03.730+08:00 INFO 98380 --- [spring-ai-example] [ main] c.s.a.e.t.one.method.MethodToolsExample :
      [manualToolsExample] toolCallbacks content ->
      长沙当前的天气情况如下:

      - **温度**: 31.5°C
      - **体感温度**: 23.1°C
      - **降水**: 无降水
      - **气压**: 1005 hPa
      - **湿度**: 43%
      - **风向**: 西南风
      - **风速**: 2.7 m/s (微风)
  • 使用SpringAI提供的简便方法简化一下构建过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Method method = ReflectionUtils.findMethod(ManualMockWeatherTools.class, "getWeather", String.class);

    MethodToolCallback toolCallback = MethodToolCallback.builder()
    // 使用 ToolDefinitions.from
    .toolDefinition(ToolDefinitions.from(method))
    // 使用 ToolMetadata.from
    .toolMetadata(ToolMetadata.from(method))
    .toolMethod(method)
    //.toolObject( new ManualMockWeatherTools())
    //.toolCallResultConverter(new DefaultToolCallResultConverter())
    .build();

总结

  • 方法即工具其实就是将我们常用的Java方法构建成工具方法。

  • 方法即工具也存在一些限制,工具方法的参数或返回不支持下面这些类型

    • Optional
    • 异步类型(如 CompletableFutureFuture
    • 响应式类型 (如 FlowMonoFlux
    • 函数式类型(如 FunctionSupplierConsumer
  • 使用@Tool注解与使用MethodToolCallback有什么区别嘛?仅编写方式上的区别,本质没有什么区别。

  • 从远源码可以看到@Tool注解也是使用MethodToolCallback.Builder来构建的MethodToolCallback实例。

    MethodToolCallback

最后

  • 工具调用的思想整体类似回调函数的思想,把工具方法传入由AI决定是否调用。
  • 下一篇继续学习记录函数式工具的使用。
  • 所有案例的源码,都会提交在GitHub上。包:com.spring.ai.example.tools.one