Spring AI 框架在升级,Function Calling 废弃,被 Tool Calling 取代

4,490 阅读9分钟

在最近发布的 Spring AI 1.0.0.M6 版本中,其中一个重大变化是 Function Calling 被废弃,被 Tool Calling 取代。本文就重点分析 Tool Calling 的新特性,如何将 Function Calling 迁移到 Tool Calling 实现。

为什么废弃 Function Calling

  • 更好的改进和扩展 Spring AI 中工具调用功能。
  • 新的 API 从 functions 改为 tools 术语,与行业术语保持一致性。

除以上两点之外,在设计上也做了一定的优化,主要体现在;

  • 新的 API 在工具的定义和实现之间提供了更好的分离
  • 工具定义可以在不同的实现中重复使用
  • 在使用上,简化了构造器模式,更好的支持基于方法的工具,并改进了错误处理。

两者在本质上概念上没有区别,都是为增强大模型的能力,在底层的实现原理上也是一致的,只是对外提供的概念稍微变动一下。

官方建议尽快将 Function Calling 相关使用的 API 迁移到 Tool Calling API。Function Calling API 在后续的版本中会被移除。

Function Calling 与 Tool Calling 关键变化

  1. FunctionCallback -> ToolCallback
  2. FunctionCallback.builder().function() -> FunctionToolCallback.builder()
  3. FunctionCallback.builder().method() -> MethodToolCallback.builder()
  4. FunctionCallbackOptions -> ToolCallingChatOptions
  5. ChatClient.builder().defaultFunctions -> ChatClient.builder().defaultTools()
  6. ChatClient.functions() -> ChatClient.tools()
  7. FunctionCallingOptions.builder().functions() -> ToolCallbackChatOptions.builder().toolNames()
  8. FunctionCallingOptions.builder().functionCallbacks() -> ToolCallbackChatOptions.builder().toolCallbacks()

基本上是将 Function 替换为 Tool,再者就是为了方便方法的使用做了一些优化。对于如何迁移参考官方文档

Tool Calling ?

工具调用(也称为函数调用)是 AI 应用程序中的一种常见模式,允许模型与一组 API 或者工具进行交互,从而增强其功能。

主要使用的场景;

  1. 信息检索(Information Retrieval)。

    此类工具可用于从外部资源(如数据库、Web服务、文件系统或者 WEB 搜索引擎)检索信息。

    目标:增强模型的知识,使其能够回答其其它方式不能回答的问题。例如,工具用于检索给定的位置天气、检索最新的新闻文章或者查询数据库。

  2. 采取行动(Taking Action)

    此类工具可用于在软件系统中的执行操作(如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流)。

    目标:自动执行原本需要人工干预或者显式编程的任务。例如,与机器人交互生成待办事项、创建会议安排等。

再次强调: 通常我们认为工具调用是模型功能,但实际上由客户端应用程序提供工具调用逻辑,模型只能请求工具调用并提供输入参数,模型本身不执行工具调用。

Tool Calling 示例

将从两个简单的工具:一个用于信息检索,一个用于采取行动 两个示例演示 Spring AI Tool Calling。

信息检索

AI 模型无法访问实时信息。模型无法回答任何假设了解信息(如当前日期或天气预报)的问题。所以我们可以通过提供一个检索信息的工具,让模型在需要访问实时信息时调用此工具。

使用 ollama qwen2.5 大模型实现查询日期,比如明天年月日?

1. 定义工具

package org.ivy.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

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();
    }

2. 工具使用

package org.ivy.controller;

import org.ivy.tools.DateTimeTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ToolsController {
    private final OllamaChatModel ollamaChatModel;

    public ToolsController(OllamaChatModel ollamaChatModel) {
        this.ollamaChatModel = ollamaChatModel;
    }

    @GetMapping("/search-tool")
    public String get(@RequestParam(value = "prompt", required = false) String prompt) {
        return ChatClient.create(ollamaChatModel)
                .prompt(prompt)
                .tools(new DateTimeTools())
                .call()
                .content();
    }
}

3.测试

image.png

执行功能

我们将定义第二个工具,用于在特定时间设置闹钟。在第一个工具的基础上,先获取当前时间,在当前时间的基础上设置闹钟。

1. 工具定义

package org.ivy.tools;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.context.i18n.LocaleContextHolder;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

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();
    }

    @Tool(description = "Set a user alarm for the given time, provided in ISO-8601 format")
    LocalDateTime setAlarm(String time) {
        LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
        System.out.println("Alarm set for " + alarmTime);
        return alarmTime;
    }
}

2. 测试

image.png

Tool Calling 原理

image.png

  1. 当想让某个工具可供大模型使用时,我们会在 chat 请求中包含工具的定义(工具名称、工具参数、工具的描述)
  2. 当模型决定调用工具时,大模型会返回一个响应,其中包括工具名称和输入的参数。
  3. 应用程序负责使用工具名称来识别并使用提供的输入参数执行工具。
  4. 工具调用的结果由应用程序处理。
  5. 应用程序将工具调用结果发送给大模型。
  6. 大模型使用工具调用结果作为附加上下文生成最终响应。

Tool Calling 源码

方法即工具(Methods as Tools)

Spring AI 内置两种指定方法为工具的方法声明式和编程式

声明式定义工具
@Tool 定义工具
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Tool {

    /**
     * 指定工具的名称,如果不指定则默认使用方法名称。
     * 注意:方法名称的唯一性
     */
    String name() default "";

    /**
     * 工具的描述,模型可以使用它来了解何时以及如何调用工具,如果不指定则使用方法名。
     * 强烈建议:详细,清晰的描述工具的功能,这对于工具的使用至关重要,直接影响大模型的
     * 使用效果。
     */
    String description() default "";

    /**
     * 指定工具执行的结果是直接返回,还是要发给大模型,默认发送给大模型
     */
    boolean returnDirect() default false;

    /**
     * 指定工具执行结果转换器。Spring AI 内置一个默认转换为String,如果有特殊业务
     * 需求可自行实现。
     */
    Class<? extends ToolCallResultConverter> resultConverter() default DefaultToolCallResultConverter.class;

}

对于 ToolCallResultConverter 接口

@FunctionalInterface
public interface ToolCallResultConverter {

    // 将工具执行返回结果转换为 JSON 字符串
    String convert(@Nullable Object result, @Nullable Type returnType);

}

默认实现类型 DefaultToolCallResultConverter

@ToolParam 定义工具参数
@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToolParam {

    /**
     * 指定参数是否为必填,默认必填
     */
    boolean required() default true;

    /**
     * 工具参数的描述,模型通过描述可以更好的理解参数的作用
     * 强烈建议:清晰、明确、易懂
     */
    String description() default "";

}

其他注解:

  1. @Nullable:指定参数可选。
  2. @Schema (Swagger) -> json schema
  3. @JsonProperty (jackson) -> json schema
编程式定义工具
ToolCallback 源码
public interface ToolCallback extends FunctionCallback {

    /**
     * 工具定义,主要作用AI大模型根据工具定义,决定何时,如何使用工具
     */
    ToolDefinition getToolDefinition();

    /**
     * 定义工具其它设置,主要用于如何处理工具。
     */
    default ToolMetadata getToolMetadata() {
       return ToolMetadata.builder().build();
    }

    /**
     * 根据给定的输入参数并将工具执行结果发送给大模型。
     */
    String call(String toolInput);
    
    default String call(String toolInput, @Nullable ToolContext tooContext) {
       if (tooContext != null && !tooContext.getContext().isEmpty()) {
          throw new UnsupportedOperationException("Tool context is not supported!");
       }
       return call(toolInput);
    }
}
MethodToolCallback 源码
public class MethodToolCallback implements ToolCallback {
    // 工具默认执行转换器
    private static final ToolCallResultConverter DEFAULT_RESULT_CONVERTER = new DefaultToolCallResultConverter();
    // 工具默认元数据
    private static final ToolMetadata DEFAULT_TOOL_METADATA = ToolMetadata.builder().build();
    // 工具定义
    private final ToolDefinition toolDefinition;
    // 工具元数据
    private final ToolMetadata toolMetadata;
    // 工具方法
    private final Method toolMethod;
    // 工具对象
    @Nullable
    private final Object toolObject;
}

其中的 ToolDefinition 比较重要:

  • name 工具名称
  • description 工具描述
  • inputSchema 工具输入参数的 json schema,如果未提供,则根据方法参数自动生成shema。
方法定义规则及限制

规则:

  1. static 方法 或者 实例方法
  2. 可见性,public、protected、package-private 或 private 方法
  3. 方法的参数个数任意、参数的类型基本类型、POJOs、枚举、列表、数组、Map等等。
  4. 方法返回值,可以为void或者其他任意类型,但是必须可序列化

限制:方法的参数和返回值不支持如下类型;

  1. Optional
  2. 异步类型 (如: CompletableFuture、 Future)
  3. 响应式类型 (如: Flow、Flux、 Mono)
  4. 函数类型 (如: Function、Supplier、 Consumer)

函数即工具(Function as Tools)

除可以通过方法定义工具外,还支持以编程式构建 FunctionToolCallback。将函数类型 Function、 Supplier、Consumer、BiFunction 转换为工具。

FunctionToolCallback 源码
public class FunctionToolCallback<I, O> implements ToolCallback {

    private static final ToolCallResultConverter DEFAULT_RESULT_CONVERTER = new DefaultToolCallResultConverter();

    private static final ToolMetadata DEFAULT_TOOL_METADATA = ToolMetadata.builder().build();

    private final ToolDefinition toolDefinition;

    private final ToolMetadata toolMetadata;

    private final Type toolInputType;

    private final BiFunction<I, ToolContext, O> toolFunction;

    private final ToolCallResultConverter toolCallResultConverter;
}

其中 ToolDefinition 工具的定义需要定义好,这里就不多说了。

函数工具定义规则及限制

限制:函数返回值及参数不支持如下;

  1. 基本类型
  2. 集合类型 (List、Map、Set、Array)
  3. 异步类型 (CompletableFuture、Future)
  4. 响应式类型 (Flow、Mono、Flux)

Tool Specification

在 Spring AI 中,工具通过 ToolCallback 接口创建,前两小节介绍了 Spring AI 提供的内置的支持方法以及函数定义工具。本小节将深入探讨工具规范以及如何自定义和扩展支持更多使用场景。

Tool Callback

ToolCallback 接口提供了定义可由 AI 模型调用的工具的方法,包括定义和执行逻辑。 Spring AI 内置两种实现。

  • MethodToolCallback
  • FunctionToolCallback

Tool Definition

ToolDefinition 接口提供了可由 AI 模型解析的工具定义方法。包括工具名称、描述和输入的Json schema。每个 ToolCallback 必须提供一个 ToolDefinition 实例。

ToolDefinition toolDefinition = ToolDefinition.builder()
    .name("currentWeather")
    .description("Get the weather in location")
    .inputSchema("""
        {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string"
                },
                "unit": {
                    "type": "string",
                    "enum": ["C", "F"]
                }
            },
            "required": ["location", "unit"]
        }
    """)
    .build();

Tool Context

Spring AI 支持通过 ToolContext API 将额外的上下文信息传递给工具。此功能允许提供额外的用户提供的信息的数据,这些数据可与工具参数一起使用。

image.png

如何使用

class CustomerTools {

    @Tool(description = "Retrieve customer information")
    Customer getCustomerInfo(Long id, ToolContext toolContext) {
        return customerRepository.findById(id, toolContext.get("tenantId"));
    }

}

ChatModel chatModel = ...

String response = ChatClient.create(chatModel)
        .prompt("Tell me more about the customer with ID 42")
        .tools(new CustomerTools())
        .toolContext(Map.of("tenantId", "acme"))
        .call()
        .content();

System.out.println(response);

Return Direct

默认情况下,工具调用的结果作为响应发送给大模型,模型可以使用结果继续对话。但是在某些情况下,需要将结果直接返回给调用方,而无需发送给大模型。

image.png

这需要根据自身的业务场景判断并在定义工具时指定。

Tool Execution

工具的执行是使用提供的输入参数、工具名称调用工具返回结果的过程。工具执行由 ToolCallingManager 接口处理。该接口负责管理工具执行生命周期。

public interface ToolCallingManager {

	/**
	 * 解析工具定义
	 */
	List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);

	/**
	 * 工具执行,并返回结果
	 */
	ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);

}

默认实现类:DefaultToolCallingManager

文末总结

本文主要对 Spring AI Tool Calling 相关内容进行了详细说明,但并未完全,详细内容可以参考官方文档或者github源码。对于 ToolContext、Tool Execution 以及异常处理等本文并未涉及到,但也是非常重要的,大家自行学习吧。。。。