章
目
录
在最新发布的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和Tool Calling本质概念相同,底层实现原理也一致,只是对外提供的概念和部分API有所变动。官方建议开发者尽快将Function Calling相关API迁移到Tool Calling,因为在后续版本中,Function Calling API会被彻底移除。
二、Function Calling与Tool Calling的关键变化
这两者的变化主要体现在API的调整上,简单来说,就是把“Function”替换成“Tool”,同时为了方便方法使用还做了一些优化。具体如下:
- 接口与构建器变化:
FunctionCallback
变为ToolCallback
;FunctionCallback.builder().function()
改为FunctionToolCallback.builder()
;FunctionCallback.builder().method()
改为MethodToolCallback.builder()
。 - 选项类变化:
FunctionCallingOptions
相关构建器方法变化,如FunctionCallingOptions.builder().functions()
变为ToolCallbackChatOptions.builder().toolNames()
;FunctionCallingOptions.builder().functionCallbacks()
变为ToolCallbackChatOptions.builder().toolCallbacks()
。 - 聊天客户端变化:
ChatClient.builder().defaultFunctions
变为ChatClient.builder().defaultTools()
;ChatClient.functions()
变为ChatClient.tools()
。
若要进行迁移操作,可以参考官方文档获取更详细的指导。
三、深入了解Tool Calling
(一)什么是Tool Calling
Tool Calling(工具调用),也可以理解为函数调用,是AI应用程序中常用的一种模式。借助它,模型能够与一组API或者工具进行交互,进而增强自身的功能。
(二)Tool Calling的应用场景
- 信息检索:AI模型通常无法直接获取实时信息,比如当被问到当前日期、天气预报这类需要实时数据的问题时,模型往往无法回答。这时,就可以借助信息检索工具,让模型在需要时调用该工具,从外部资源(像数据库、Web服务、文件系统或者搜索引擎)获取相关信息。这样一来,模型的知识储备得到扩充,就能回答原本无法回答的问题了。比如利用工具查询指定位置的天气、检索最新的新闻文章,或者查询数据库等。
- 执行操作:这类工具主要用于在软件系统中执行各种操作,例如发送电子邮件、在数据库中创建新记录、提交表单,甚至触发工作流等。其目的是自动完成那些原本需要人工干预,或者必须通过显式编程才能实现的任务。举个例子,与机器人交互生成待办事项、创建会议安排等。
需要注意的是,虽然工具调用看起来像是模型自身具备的功能,但实际上,工具调用逻辑是由客户端应用程序提供的。模型只能请求调用工具并提供输入参数,真正执行工具调用的并不是模型本身。
四、Tool Calling示例演示
接下来,通过两个简单示例,带大家了解Spring AI中Tool Calling的具体用法。
(一)信息检索示例
以使用ollama qwen2.5大模型查询日期为例,比如想知道明天的年月日,具体实现步骤如下:
- 定义工具
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属性用于描述工具功能,方便模型理解何时调用该工具
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
// 获取当前日期和时间,并根据用户所在时区进行格式化
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
- 工具使用
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 {
// 注入OllamaChatModel实例,用于与ollama qwen2.5大模型进行交互
private final OllamaChatModel ollamaChatModel;
public ToolsController(OllamaChatModel ollamaChatModel) {
this.ollamaChatModel = ollamaChatModel;
}
// 处理GET请求,根据用户提供的prompt调用工具并返回结果
@GetMapping("/search-tool")
public String get(@RequestParam(value = "prompt", required = false) String prompt) {
return ChatClient.create(ollamaChatModel)
.prompt(prompt)
// 将DateTimeTools工具添加到聊天客户端,使模型可以调用
.tools(new DateTimeTools())
.call()
// 获取模型生成的最终响应内容
.content();
}
}
- 测试:访问
http://localhost:8808/search-tool
,在请求参数中传入相应的prompt,就可以看到模型调用工具后返回的日期信息。
(二)执行功能示例
接下来定义一个用于设置闹钟的工具,这个工具基于前面获取当前时间的工具,在当前时间基础上设置闹钟。
- 工具定义
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) {
// 将传入的时间字符串按照ISO-8601格式解析为LocalDateTime对象
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
// 打印设置的闹钟时间
System.out.println("Alarm set for " + alarmTime);
return alarmTime;
}
}
- 测试:访问
http://localhost:8808/search-tool?prompt=Can you set an alarm 10 minutes from now?
,可以看到模型调用工具后返回的设置闹钟结果。
五、Tool Calling的原理剖析
当我们希望某个工具能被大模型使用时,需要在聊天请求中包含工具的定义信息,这些信息包括工具名称、工具参数以及工具描述。
大模型在处理请求过程中,如果决定调用工具,会返回一个包含工具名称和输入参数的响应。
接下来,应用程序会根据工具名称识别要调用的工具,并使用提供的输入参数执行该工具。执行完成后,应用程序负责处理工具调用的结果。
处理完结果后,应用程序再将工具调用结果发送给大模型。大模型会把这个结果作为额外的上下文信息,生成最终的响应内容返回给用户。
六、Tool Calling的源码解读
(一)方法即工具(Methods as Tools)
在Spring AI中,有两种方式可以将方法指定为工具,分别是声明式和编程式。
- 声明式定义工具
- @Tool注解:用于标记方法为工具,它有多个属性。
name
属性用于指定工具名称,如果不设置,则默认使用方法名,要注意方法名的唯一性;description
属性非常重要,用于详细描述工具功能,模型会根据这个描述来判断何时以及如何调用工具;returnDirect
属性用于指定工具执行结果是直接返回给调用方,还是先发送给大模型,默认是发送给大模型;resultConverter
属性用于指定工具执行结果的转换器,Spring AI内置了默认的转换为String的转换器,如果有特殊业务需求,开发者可以自行实现。 - @ToolParam注解:用于定义工具参数。
required
属性指定参数是否必填,默认是必填;description
属性用于描述参数作用,让模型更好地理解参数含义。此外,还有@Nullable
注解用于指定参数可选,@Schema
(Swagger)和@JsonProperty
(jackson)都与json schema相关。
- @Tool注解:用于标记方法为工具,它有多个属性。
- 编程式定义工具
- ToolCallback接口:定义了工具的基本行为。
getToolDefinition
方法用于获取工具定义,这是AI大模型决定何时、如何使用工具的依据;getToolMetadata
方法用于获取工具的其他设置,默认返回一个空的工具元数据;call
方法用于执行工具,并将结果发送给大模型。 - MethodToolCallback类:实现了
ToolCallback
接口,它有多个重要成员变量,包括工具定义、工具元数据、工具方法以及工具对象。其中,ToolDefinition
比较关键,包含工具名称、描述以及输入参数的json schema,如果未提供json schema,会根据方法参数自动生成。
- ToolCallback接口:定义了工具的基本行为。
(二)函数即工具(Function as Tools)
除了通过方法定义工具外,Spring AI还支持将函数类型(如Function
、Supplier
、Consumer
、BiFunction
)转换为工具,通过构建FunctionToolCallback
来实现。同样,在定义工具时,需要定义好ToolDefinition
。不过,函数工具在返回值及参数方面有一些限制,不支持基本类型、集合类型、异步类型以及响应式类型。
(三)Tool Specification(工具规范)
- 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向工具传递额外的上下文信息。这一功能允许开发者提供更多用户信息,这些信息可以和工具参数一起使用。例如:
class CustomerTools {
// 使用@Tool注解定义工具,该工具用于检索客户信息
@Tool(description = "Retrieve customer information")
Customer getCustomerInfo(Long id, ToolContext toolContext) {
// 根据传入的客户ID和租户ID从数据库中获取客户信息
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())
// 设置额外的上下文信息,这里是租户ID
.toolContext(Map.of("tenantId", "acme"))
.call()
.content();
System.out.println(response);
- Return Direct:默认情况下,工具调用结果会发送给大模型,由模型继续生成对话。但在某些业务场景下,可能需要将结果直接返回给调用方,而不经过大模型。这就需要在定义工具时,根据实际需求指定
returnDirect=true
。 - Tool Execution:工具的执行过程就是使用提供的输入参数和工具名称调用工具,并返回结果的过程。这一过程由
ToolCallingManager
接口负责管理,该接口主要包含两个方法:resolveToolDefinitions
用于解析工具定义;executeToolCalls
用于执行工具调用并返回结果。其默认实现类是DefaultToolCallingManager
。
七、总结
本文对Spring AI框架中Tool Calling的相关内容进行了较为全面的介绍,涵盖了它取代Function Calling的原因、自身的新特性、具体的使用示例、原理以及源码解读等方面。不过,像ToolContext
、工具执行过程中的异常处理等内容,本文并未深入涉及,这些部分同样重要,感兴趣的读者可以参考官方文档或者github源码进一步学习。