目录
1. Tool Calling 是什么?
2. Tool的 构建规则和调用路线
2.1 构建规则
2.1.1 声明式调用工具
2.1.2 编程式定义工具
2.2 Tool的调用路线
3. 工具规范
3.1 ToolCallback
3.2 MethodToolCallback
3.3 ToolDefinition
以下所有代码:巨蛇神/JavaEE进阶 - 码云 - 开源中国
1. Tool Calling 是什么?
Tool Calling(工具调用) 是大语言模型(LLM) 的四肢!
Tool Calling(工具调用),是AI应用程序中一种常见模式,允许大语言模型(LLM)根据用户请求,智能地选择、调用外部工具(如API、函数等)并获取结果的技术流程,从而增强其功能。
我们一般向AI询问问题,获取答案,但是后面真正的事情还得自己做。例如领导让你定餐厅,你执行顺序会是:
- 打开APP,查看附近推荐的热门餐厅
- 预定餐厅
- 提醒大家
- 等待出发
如果你叫AI帮你预定,它仅能利用现有的知识(不是实时的)给你推荐餐厅,2,3都无法做到,但我们真正想的是,它能根据实时数据推荐餐厅,并且自动预定餐厅,甚至呼叫好车辆。
那么AI怎么从只会动脑,变成智能且联网的决策者?Yes!Tool Calling闪亮登场!
现在许多模型厂商提供的网页模型服务是经过联网操作的,你询问“今天天气”之类的实时信息它是可以给你准确返回的,但是问他“今天或明天某站上海到北京的车次还有哪些”这它是无法办到的,因为它没有配备响应的工具(tool)!
2. Tool的 构建规则和调用路线
2.1 构建规则
2.1.1 声明式调用工具
使用@Tool注解定义的工具就是 “声明式定义工具”。
(ChatClient调用)先看一个工具调用案例,代码之后介绍写和调工具的规则:
//控制器 public class ChatClientController { private ChatClient chatClient; public ChatClientController(ChatClient.Builder builder) { this.chatClient = builder // .defaultTools(new CustomerTool()) //添加默认工具 .build(); } @RequestMapping("/getDateTime") public String getDateTime(String message){ return chatClient.prompt() .user(message) .tools(new DateTimeTool()) //添加工具类,在调用模型之前 .call() .content(); } } //工具类 public class DateTimeTool { //查询时间的工具 @Tool(description = "获取用户时区的当前日期和时间") String getCurrentDateTime() { //返回当前时间 return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString(); } }@Tool注解有四个参数:
- name——工具的名字,不传该参数则默认tool名为函数名,如上述默认名为“getDateTime”
- description——强烈建议写;描述工具是做什么的,什么时候使用它,模型就是靠这个描述选择和调用工具。
- returnDirect——将结果直接返回给调用者,而不将其反馈给模型;默认值为false,经过模型。
- resultConverter——方法返回的数据,默认请况下使用转化成json,但你也可以自定义转化器。
resultConverter的使用需要自定义转化器并实现ToolCallResultConverter(函数式接口),重写convert方法,下面演示:
从此可看出AI会根据@Tool的描述选择符合用户输入内容的工具,接着根据@ToolParam的描述传入对应的实参。
(ChatModel调用)比较底层,套路比较多:
package com.jusheshen.springai.controller; import com.jusheshen.springai.tools.DateTimeTool; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.model.tool.ToolExecutionResult; import org.springframework.ai.support.ToolCallbacks; import org.springframework.ai.tool.ToolCallback; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/model") @RestController public class ChatModelController { private ChatModel chatModel; private ToolCallingManager toolCallingManager = ToolCallingManager.builder().build(); public ChatModelController(ChatModel chatModel) { this.chatModel = chatModel; } @RequestMapping("/getDateTime") public String getDateTime(String message){ ToolCallback[] tools = ToolCallbacks.from(new DateTimeTool()); ChatOptions chatOptions = DeepSeekChatOptions.builder() .toolCallbacks(tools) .build(); Prompt prompt = new Prompt(message,chatOptions); ChatResponse response = chatModel.call(prompt); //使用ChatModel,开发者可以实现当returnDirect=true 时的现象 while (response.hasToolCalls()) { ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response); //看情况实现 if (result.returnDirect()) { // Tool's returnDirect=true — break out without sending back to the model // return result.conversationHistory(); } prompt = new Prompt(result.conversationHistory(), chatOptions); response = chatModel.call(prompt); } String finalAnswer = response.getResult().getOutput().getText(); return finalAnswer; } }2.1.2 编程式定义工具
(ChatClient调用)利用MethodToolCallback 手动构建工具
//通过反射获得工具方法。第一个参数是对象类型,第二是方法名称,后面是方法的形参类型 Method method = ReflectionUtils.findMethod(WeatherTools.class,"getCurrentWeatherByCityName",String.class); MethodToolCallback callback = MethodToolCallback.builder() .toolDefinition(ToolDefinitions.builder(method) .description("根据给定的城市名称, 获取当前城市的天⽓") //工具描述,不写的话默认工具方法名作为描述 // .name() //工具名,不起默认为方法名 // .inputSchema("") //参数输入格式,不写模型自动识别匹配 .build()) .toolMethod(method) //工具 .toolObject(new WeatherTools()) //工具所在对象;若是tool是静态方法不用写 // .toolMetadata() //元信息 .build();调用时添加工具:
@RequestMapping("/getWeather") public String getWeather(String message){ //通过反射获得工具方法 Method method = ReflectionUtils.findMethod(WeatherTools.class,"getCurrentWeatherByCityName",String.class); MethodToolCallback callback = MethodToolCallback.builder() .toolDefinition(ToolDefinitions.builder(method) .description("根据给定的城市名称, 获取当前城市的天⽓") //工具描述,不写的话默认工具方法名作为描述 // .name() //工具名,不起默认为方法名 // .inputSchema("") //参数输入格式,不写模型自动识别匹配 .build()) .toolMethod(method) //工具 .toolObject(new WeatherTools()) //工具所在对象 // .toolMetadata() //元信息 .build(); return chatClient.prompt() .user(message) .tools(callback) .call() .content(); }添加默认工具:
@Configuration public class ToolConfig { @Bean public MethodToolCallback callback(){ //通过反射获得工具方法 Method method = ReflectionUtils.findMethod(WeatherTools.class,"getCurrentWeatherByCityName",String.class); MethodToolCallback callback = MethodToolCallback.builder() .toolDefinition(ToolDefinitions.builder(method) .description("根据给定的城市名称, 获取当前城市的天⽓") //工具描述,不写的话默认工具方法名作为描述 // .name() //工具名,不起默认为方法名 // .inputSchema("") //参数输入格式,不写模型自动识别匹配 .build()) .toolMethod(method) //工具 .toolObject(new WeatherTools()) //工具所在对象 // .toolMetadata() //元信息 .build(); return callback; } } //======================================================================= public ChatClientController(ChatClient.Builder builder,MethodToolCallback toolCallback) { this.chatClient = builder .defaultTools(new CustomerTool(),toolCallback) //添加默认工具 .build(); }(ChatModel调用)与声明式调用的ChatModel调用几乎一模一样:
@Autowired private MethodToolCallback toolCallback; //这句换掉就行了 // ToolCallback[] tools = ToolCallbacks.from(new DateTimeTool()); ChatOptions chatOptions = DeepSeekChatOptions.builder() .toolCallbacks(toolCallback) //注意! .build(); Prompt prompt = new Prompt(message,chatOptions); ChatResponse response = chatModel.call(prompt);2.2 Tool的调用路线
(默认情况下)
- 用户提示词与后端Tool定义 经过 SpringAI框架传递给模型
- 模型整合完数据之后,把数据发给Spring AI 框架
- 框架再把数据给到Tool
- Tool处理完,再发给框架
- 框架给到模型
- 模型把Tool生成的数据经过润色后,再经过Spring AI响应给用户
(自定义的特殊情况)
开发者可在ChatClient的call()或stream()之前使用toolContext()直接传入Map数据给工具(下图红圈部分),这个数据不会经过模型,而直接给工具,也就是上图的第3"步。当然如果没设置为returnDirect=true那么Tool层的数据还是会经过模型,也就是第四、五步。
如果returnDirect=true,则经历下图:
3. 工具规范
3.1 ToolCallback
ToolCallback是一个用于与AI交互的回调接口,允许开发者定义可被AI调用的外部工具。它携带AI模型看到的定义和模型调用它时调用的执行逻辑。
public interface ToolCallback {
/*** 返回该⼯具的元信息描述* ToolDefinition 包含⼯具名称、描述、参数列表等结构化信息,供AI模型理解“什么情况*下该调⽤这个⼯具”以及“需要传什么参数”。* 必须实现,是函数调⽤机制的核⼼部分*/
ToolDefinition getToolDefinition();/*** 提供额外的⾮功能性元数据,主要⽤于框架层⾯的扩展性⽀持,不影响核⼼逻辑。* 使⽤了default⽅法,意味着⼦类可以不重写,默认返回空的元数据对象。*/default ToolMetadata getToolMetadata();
/*** 抽象⽅法,必须由实现类提供具体逻辑。* 接收⼀个字符串输⼊(通常是JSON格式的参数),执⾏业务逻辑,并返回结果字符串。* 结果会传回给AI模型,供其继续推理或⽣成回复。*/
String call(String toolInput);/*** 带上下⽂的调⽤⽅法,也是默认实现。* 如果toolContext有值,则不⽀持,主要⽤于被⼦类重写⽀持上下⽂功能*/
default String call(String toolInput, ToolContext toolContext);
}
3.2 MethodToolCallback
MethodToolCallback是Spring AI框架中ToolCallback的核心实现之一,它用于将普通Java方法包装成AI模型可调用的”工具“。
public final classMethodToolCallbackimplementsToolCallback{//⼯具默认执行的转换器private static finalToolCallResultConverterDEFAULT_RESULT_CONVERTER=newDefaultToolCallResultConverter();//⼯具默认元数据private static finalToolMetadataDEFAULT_TOOL_METADATA=ToolMetadata.builder().build();//⼯具定义,也称为⼯具的元信息:名字、描述、参数列表等,告诉AI “我能干啥”private finalToolDefinition toolDefinition;//⼯具额外元数据(如权限,分类)private finalToolMetadata toolMetadata;//真正要执行的那个Java 方法对象(通过反射获取)private finalMethod toolMethod;//⼯具对象:如果方法不是静态的,需要这个对象来调用(即object.method()@Nullableprivate finalObject toolObject;//⼯具转换器:把方法返回值转成字符串,返回给AI(默认用JSON)private finalToolCallResultConverter toolCallResultConverter;}
3.3 ToolDefinition
ToolDefinition 接口提供AI模型识别工具所需要的信息,包含工具名称、描述及输入模式。每个ToolCallback 都必须提供ToolDefinition示例来定义工具。
public interfaceToolDefinition{/***⼯具名称.在提供给模型的工具集中是唯⼀的*/Stringname();/*** AI模型使用工具描述来确定工具的功能*/Stringdescription();/***⽤于调用工具的参数的模式*/StringinputSchema();}