当前位置: 首页 > news >正文

spring-ai之工具调用(Tool Calling)

1、基于spring-ai 1.0.0 和spring-ai-alibaba 1.0.0.2

概述

“工具调用(Tool Calling)”或“函数调用”允许大型语言模型(LLM)在必要时调用一个或多个可用的工具,这些工具通常由开发者定义。工具可以是任何东西:网页搜索、对外部 API 的调用,或特定代码的执行等。LLM 本身不能实际调用工具;相反,它们会在响应中表达调用特定工具的意图(而不是以纯文本回应)。然后,应用程序应该执行这个工具,并报告工具执行的结果给模型。当 LLM 可以访问工具时,它可以在合适的情况下决定调用其中一个工具,这是一个非常强大的功能。

工具调用定义

Spring AI 支持两种工具调用的定义:方法工具 和 函数工具。接下来将以“获取当前时间工具”为例,简单介绍这两种工具定义方法。

方法工具

Spring AI 可以定义类的某个方法为工具,在方法上标记 @Tool 注解,在参数上标记 @ToolParam 注解。例如:

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.toolcall.component;import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;public class TimeTools {/*** Spring AI 可以定义类的某个方法为工具,在方法上标记 @Tool 注解,* 在参数上标记 @ToolParam 注解** @param timeZoneId* @return*/@Tool(description = "Get the time of a specified city.")public String getCityTime(@ToolParam(description = "Time zone id, such as Asia/Shanghai")String timeZoneId) {
//        当前方法工具不支持以下类型的参数和返回类型:
//
//        Optional
//        异步类型(CompletableFuture、Future)
//        响应式类型(Flow、Mono、Flux)
//        函数类型(Function、Supplier、Consumer)ZoneId zid = ZoneId.of(timeZoneId);ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");return zonedDateTime.format(formatter);}
}

在调用 ChatClient 时,通过 .tools() 方法传递工具对象,或者在实例化 ChatClient 对象的时候通过 .defalutTools() 方法传递工具对象:

String response = chatClient.prompt("获取北京时间").tools(new TimeTools()).call().content();

实例化工具

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.toolcall.config;import com.alibaba.cloud.ai.toolcall.component.AddressInformationTools;
import com.alibaba.cloud.ai.toolcall.component.TimeTools;
import com.alibaba.cloud.ai.toolcalling.baidumap.BaiduMapSearchInfoService;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ToolCallAutoConfiguration {@Beanpublic TimeTools timeTools() {return new TimeTools();}@Beanpublic AddressInformationTools addressInformationTools(BaiduMapSearchInfoService service) {return new AddressInformationTools(service);}@Beanpublic ChatClient chatClient(ChatModel chatModel) {return ChatClient.builder(chatModel).defaultAdvisors(new SimpleLoggerAdvisor()).build();}}

请求controller

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.toolcall.controller;import com.alibaba.cloud.ai.toolcall.component.TimeTools;
import com.alibaba.cloud.ai.toolcall.component.TimeToolsMethodToolCallBack;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.lang.reflect.Method;@RestController
public class TimeController {private final ChatClient dashScopeChatClient;private final TimeTools timeTools;public TimeController(ChatClient chatClient, TimeTools timeTools) {this.dashScopeChatClient = chatClient;this.timeTools = timeTools;}/*** No Tool*/@GetMapping("/time/chat")public String simpleChat(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {return dashScopeChatClient.prompt(query).call().content();}/*** Methods as Tools*/@GetMapping("/time/chat-tool-method")public String chatWithTimeFunction(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过 .tools() 方法传递工具对象
//        ,或者在实例化 ChatClient 对象的时候通过 .defalutTools() 方法传递工具对象:return dashScopeChatClient.prompt(query).tools(timeTools).call().content();}/*** Methods as Tools*/@GetMapping("/time/timeToolsMethodToolCallBack")public String timeToolsMethodToolCallBack(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
//        通过 MethodToolCallBack.Builder 定义方法工具:String inputSchema = """{"$schema" : "https://json-schema.org/draft/2020-12/schema","type" : "object","properties" : {"zoneId" : {"type" : "string","description" : "Time zone id, such as Asia/Shanghai"}},"required" : [ "zoneId" ],"additionalProperties" : false}""";Method method = ReflectionUtils.findMethod(TimeToolsMethodToolCallBack.class, "getTimeByZoneId", String.class);if (method == null) {throw new RuntimeException("Method not found");}MethodToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(ToolDefinition.builder().description("Get time by zone id").name("getTimeByZoneId").inputSchema(inputSchema).build()).toolMethod(method).toolObject(new TimeToolsMethodToolCallBack()).build();
//        可以使用 JsonSchemaGenerator.generateForMethodInput(method) 方法获取 Input Schema。
//        但如果原方法的参数没有 @ToolParam 或者 @JsonPropertyDescription 注解,则会缺失参数的 description 字段,
//        因此建议可以用该方法生成一个模板,然后填充参数的 description 字段。
//
//        在调用 ChatClient 时,通过.toolCallbacks() 传递 MethodToolCallBack 对象,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 方法传递工具对象:return dashScopeChatClient.prompt(query).toolCallbacks(toolCallback).call().content();}}

如果要使用之前编写好的类的方法,不想修改源代码,可以使用 MethodToolCallBack 定义方法工具。

比如,现在有这样的一个类:

package com.alibaba.cloud.ai.toolcall.component;import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;/*** 如果要使用之前编写好的类的方法,不想修改源代码,* 可以使用 MethodToolCallBack 定义方法工具。** 比如,现在有这样的一个类*/
public class TimeToolsMethodToolCallBack {public String getTimeByZoneId(String zoneId) {
//        当前方法工具不支持以下类型的参数和返回类型:
//
//        Optional
//        异步类型(CompletableFuture、Future)
//        响应式类型(Flow、Mono、Flux)
//        函数类型(Function、Supplier、Consumer)ZoneId zid = ZoneId.of(zoneId);ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");return zonedDateTime.format(formatter);}
}

通过 MethodToolCallBack.Builder 定义方法工具:

    String inputSchema = """{"$schema" : "https://json-schema.org/draft/2020-12/schema","type" : "object","properties" : {"zoneId" : {"type" : "string","description" : "Time zone id, such as Asia/Shanghai"}},"required" : [ "zoneId" ],"additionalProperties" : false}""";Method method = ReflectionUtils.findMethod(TimeToolsMethodToolCallBack.class, "getTimeByZoneId", String.class);if (method == null) {throw new RuntimeException("Method not found");}MethodToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(ToolDefinition.builder().description("Get time by zone id").name("getTimeByZoneId").inputSchema(inputSchema).build()).toolMethod(method).toolObject(new TimeToolsMethodToolCallBack()).build();

可以使用 JsonSchemaGenerator.generateForMethodInput(method) 方法获取 Input Schema。但如果原方法的参数没有 @ToolParam 或者 @JsonPropertyDescription 注解,则会缺失参数的 description 字段,因此建议可以用该方法生成一个模板,然后填充参数的 description 字段。

在调用 ChatClient 时,通过.toolCallbacks() 传递 MethodToolCallBack 对象,或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 方法传递工具对象:

String response = chatClient.prompt("获取北京时间").toolCallbacks(toolCallback).call().content();

函数工具

开发者可以把任意实现 Function 接口的对象,定义为 Bean ,并通过 .toolNames() 或 .defaultToolNames() 传递给 ChatClient 对象。

例如有这么一个实现了Function 接口的类:

package com.alibaba.cloud.ai.toolcall.function;import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Function;public class TimeFunction implementsFunction<TimeFunction.Request, TimeFunction.Response> {@JsonClassDescription("Request to get time by zone id")public record Request(@JsonProperty(required = true, value = "zoneId")@JsonPropertyDescription("Time zone id, such as Asia/Shanghai") String zoneId) {}@JsonClassDescription("Response to get time by zone id")public record Response(@JsonPropertyDescription("time") String time) {}@Overridepublic Response apply(Request request) {ZoneId zid = ZoneId.of(request.zoneId());ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");return new Response(zonedDateTime.format(formatter));}
}

将该类的对象定义为 Bean:

package com.alibaba.cloud.ai.toolcall.config;import com.alibaba.cloud.ai.toolcall.function.TimeFunction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;@Configuration
public class TimeFunctionAutoConfiguration {@Bean@Description("Get time by zone id")public TimeFunction getTimeByZoneId() {return new TimeFunction();}
}

在调用 ChatClient 时,通过.toolNames() 传递函数工具的 Bean 名称,或者在实例化 ChatClient 对象的时候通过 .defalutToolNames() 方法传递函数工具:

String response = chatClient.prompt("获取北京时间").toolNames("getTimeByZoneId").call().content();

controller代码

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.toolcall.controller;import com.alibaba.cloud.ai.toolcall.component.TimeTools;
import com.alibaba.cloud.ai.toolcall.component.TimeToolsMethodToolCallBack;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.lang.reflect.Method;@RestController
public class TimeController {private final ChatClient dashScopeChatClient;private final TimeTools timeTools;public TimeController(ChatClient chatClient, TimeTools timeTools) {this.dashScopeChatClient = chatClient;this.timeTools = timeTools;}/*** No Tool*/@GetMapping("/time/chat")public String simpleChat(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {return dashScopeChatClient.prompt(query).call().content();}/*** Methods as Tools*/@GetMapping("/time/chat-tool-method")public String chatWithTimeFunction(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过 .tools() 方法传递工具对象
//        ,或者在实例化 ChatClient 对象的时候通过 .defalutTools() 方法传递工具对象:return dashScopeChatClient.prompt(query).tools(timeTools).call().content();}/*** Methods as Tools*/@GetMapping("/time/timeToolsMethodToolCallBack")public String timeToolsMethodToolCallBack(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
//        通过 MethodToolCallBack.Builder 定义方法工具:String inputSchema = """{"$schema" : "https://json-schema.org/draft/2020-12/schema","type" : "object","properties" : {"zoneId" : {"type" : "string","description" : "Time zone id, such as Asia/Shanghai"}},"required" : [ "zoneId" ],"additionalProperties" : false}""";Method method = ReflectionUtils.findMethod(TimeToolsMethodToolCallBack.class, "getTimeByZoneId", String.class);if (method == null) {throw new RuntimeException("Method not found");}MethodToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(ToolDefinition.builder().description("Get time by zone id").name("getTimeByZoneId").inputSchema(inputSchema).build()).toolMethod(method).toolObject(new TimeToolsMethodToolCallBack()).build();
//        可以使用 JsonSchemaGenerator.generateForMethodInput(method) 方法获取 Input Schema。
//        但如果原方法的参数没有 @ToolParam 或者 @JsonPropertyDescription 注解,则会缺失参数的 description 字段,
//        因此建议可以用该方法生成一个模板,然后填充参数的 description 字段。
//
//        在调用 ChatClient 时,通过.toolCallbacks() 传递 MethodToolCallBack 对象,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 方法传递工具对象:return dashScopeChatClient.prompt(query).toolCallbacks(toolCallback).call().content();}@GetMapping("/time/function")public String function(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过.toolNames() 传递函数工具的 Bean 名称,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolNames() 方法传递函数工具:return dashScopeChatClient.prompt(query).toolNames("getTimeByZoneId").call().content();}}

开发者也可以不用定义 Bean,直接定义 FunctionToolCallBack 对象,在调用 ChatClient 时通过 .toolCallBacks() 或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 传递 FunctionToolCallBack 对象:

示例如下 TimeFunction2 

package com.alibaba.cloud.ai.toolcall.function;import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Function;public class TimeFunction2 implementsFunction<TimeFunction2.Request, TimeFunction2.Response> {@JsonClassDescription("Request to get time by zone id")public record Request(@JsonProperty(required = true, value = "zoneId")@JsonPropertyDescription("Time zone id, such as Asia/Shanghai") String zoneId) {}@JsonClassDescription("Response to get time by zone id")public record Response(@JsonPropertyDescription("time") String time) {}@Overridepublic Response apply(Request request) {ZoneId zid = ZoneId.of(request.zoneId());ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");return new Response(zonedDateTime.format(formatter));}
}

    @GetMapping("/time/function2")public String function2(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        开发者也可以不用定义 Bean,直接定义 FunctionToolCallBack 对象,在调用 ChatClient 时通过 .toolCallBacks() 
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 传递 FunctionToolCallBack 对象:String response = dashScopeChatClient.prompt(query).toolCallbacks(FunctionToolCallback.builder("getTimeByZoneId", new TimeFunction2()).description("Get time by zone id").inputType(TimeFunction2.Request.class).build()).call().content();return response;}

请求controller

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.toolcall.controller;import com.alibaba.cloud.ai.toolcall.component.TimeTools;
import com.alibaba.cloud.ai.toolcall.component.TimeToolsMethodToolCallBack;
import com.alibaba.cloud.ai.toolcall.function.TimeFunction2;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.lang.reflect.Method;@RestController
public class TimeController {private final ChatClient dashScopeChatClient;private final TimeTools timeTools;public TimeController(ChatClient chatClient, TimeTools timeTools) {this.dashScopeChatClient = chatClient;this.timeTools = timeTools;}/*** No Tool*/@GetMapping("/time/chat")public String simpleChat(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {return dashScopeChatClient.prompt(query).call().content();}/*** Methods as Tools*/@GetMapping("/time/chat-tool-method")public String chatWithTimeFunction(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过 .tools() 方法传递工具对象
//        ,或者在实例化 ChatClient 对象的时候通过 .defalutTools() 方法传递工具对象:return dashScopeChatClient.prompt(query).tools(timeTools).call().content();}/*** Methods as Tools*/@GetMapping("/time/timeToolsMethodToolCallBack")public String timeToolsMethodToolCallBack(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
//        通过 MethodToolCallBack.Builder 定义方法工具:String inputSchema = """{"$schema" : "https://json-schema.org/draft/2020-12/schema","type" : "object","properties" : {"zoneId" : {"type" : "string","description" : "Time zone id, such as Asia/Shanghai"}},"required" : [ "zoneId" ],"additionalProperties" : false}""";Method method = ReflectionUtils.findMethod(TimeToolsMethodToolCallBack.class, "getTimeByZoneId", String.class);if (method == null) {throw new RuntimeException("Method not found");}MethodToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(ToolDefinition.builder().description("Get time by zone id").name("getTimeByZoneId").inputSchema(inputSchema).build()).toolMethod(method).toolObject(new TimeToolsMethodToolCallBack()).build();
//        可以使用 JsonSchemaGenerator.generateForMethodInput(method) 方法获取 Input Schema。
//        但如果原方法的参数没有 @ToolParam 或者 @JsonPropertyDescription 注解,则会缺失参数的 description 字段,
//        因此建议可以用该方法生成一个模板,然后填充参数的 description 字段。
//
//        在调用 ChatClient 时,通过.toolCallbacks() 传递 MethodToolCallBack 对象,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 方法传递工具对象:return dashScopeChatClient.prompt(query).toolCallbacks(toolCallback).call().content();}@GetMapping("/time/function")public String function(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过.toolNames() 传递函数工具的 Bean 名称,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolNames() 方法传递函数工具:return dashScopeChatClient.prompt(query).toolNames("getTimeByZoneId").call().content();}@GetMapping("/time/function2")public String function2(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        开发者也可以不用定义 Bean,直接定义 FunctionToolCallBack 对象,在调用 ChatClient 时通过 .toolCallBacks()
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 传递 FunctionToolCallBack 对象:String response = dashScopeChatClient.prompt(query).toolCallbacks(FunctionToolCallback.builder("getTimeByZoneId", new TimeFunction2()).description("Get time by zone id").inputType(TimeFunction2.Request.class).build()).call().content();return response;}}

pom

<?xml version="1.0" encoding="UTF-8"?><!--Copyright 2023-2024 the original author or authors.Licensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License athttps://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.
--><project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://maven.apache.org/POM/4.0.0"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-examples</artifactId><version>${revision}</version><relativePath>../pom.xml</relativePath></parent><version>${revision}</version><artifactId>spring-ai-alibaba-tool-calling-demo</artifactId><description>Spring AI Alibaba Tool Calling Example</description><name>Spring AI Alibaba Tool Calling Examples</name><properties><junit-jupiter.version>5.10.0</junit-jupiter.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-tool-calling-baidutranslate</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-tool-calling-weather</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-tool-calling-baidumap</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-tool-calling-time</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-tool-calling-githubtoolkit</artifactId></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>${junit-jupiter.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-deploy-plugin</artifactId><version>${maven-deploy-plugin.version}</version><configuration><skip>true</skip></configuration></plugin></plugins></build></project>

工具上下文

Spring AI 支持通过 ToolContext API 向工具传递额外的上下文信息。该特性允许提供补充数据,比如用户身份信息。这些数据将与 AI 模型传递的工具参数结合使用。

public class UserInfoTools {@Tool(description = "get current user name")public String getUserName(ToolContext context) {String userId = context.getContext().get("userId").toString();if (!StringUtils.hasText(userId)) {return "null";}// 模拟数据return userId + "user";}
}

在调用 ChatClient 时,通过 .toolContext() 方法传递工具上下文:

String response = chatClient.prompt("获取我的用户名").tools(new UserInfoTools()).toolContext(Map.of("userId", "12345")).call().content();

controller

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.toolcall.controller;import com.alibaba.cloud.ai.toolcall.component.TimeTools;
import com.alibaba.cloud.ai.toolcall.component.TimeToolsMethodToolCallBack;
import com.alibaba.cloud.ai.toolcall.component.UserInfoTools;
import com.alibaba.cloud.ai.toolcall.function.TimeFunction2;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.lang.reflect.Method;
import java.util.Map;@RestController
public class TimeController {private final ChatClient dashScopeChatClient;private final TimeTools timeTools;public TimeController(ChatClient chatClient, TimeTools timeTools) {this.dashScopeChatClient = chatClient;this.timeTools = timeTools;}/*** No Tool*/@GetMapping("/time/chat")public String simpleChat(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {return dashScopeChatClient.prompt(query).call().content();}/*** Methods as Tools*/@GetMapping("/time/chat-tool-method")public String chatWithTimeFunction(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过 .tools() 方法传递工具对象
//        ,或者在实例化 ChatClient 对象的时候通过 .defalutTools() 方法传递工具对象:return dashScopeChatClient.prompt(query).tools(timeTools).call().content();}/*** Methods as Tools*/@GetMapping("/time/timeToolsMethodToolCallBack")public String timeToolsMethodToolCallBack(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
//        通过 MethodToolCallBack.Builder 定义方法工具:String inputSchema = """{"$schema" : "https://json-schema.org/draft/2020-12/schema","type" : "object","properties" : {"zoneId" : {"type" : "string","description" : "Time zone id, such as Asia/Shanghai"}},"required" : [ "zoneId" ],"additionalProperties" : false}""";Method method = ReflectionUtils.findMethod(TimeToolsMethodToolCallBack.class, "getTimeByZoneId", String.class);if (method == null) {throw new RuntimeException("Method not found");}MethodToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(ToolDefinition.builder().description("Get time by zone id").name("getTimeByZoneId").inputSchema(inputSchema).build()).toolMethod(method).toolObject(new TimeToolsMethodToolCallBack()).build();
//        可以使用 JsonSchemaGenerator.generateForMethodInput(method) 方法获取 Input Schema。
//        但如果原方法的参数没有 @ToolParam 或者 @JsonPropertyDescription 注解,则会缺失参数的 description 字段,
//        因此建议可以用该方法生成一个模板,然后填充参数的 description 字段。
//
//        在调用 ChatClient 时,通过.toolCallbacks() 传递 MethodToolCallBack 对象,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 方法传递工具对象:return dashScopeChatClient.prompt(query).toolCallbacks(toolCallback).call().content();}@GetMapping("/time/function")public String function(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过.toolNames() 传递函数工具的 Bean 名称,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolNames() 方法传递函数工具:return dashScopeChatClient.prompt(query).toolNames("getTimeByZoneId").call().content();}@GetMapping("/time/function2")public String function2(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        开发者也可以不用定义 Bean,直接定义 FunctionToolCallBack 对象,在调用 ChatClient 时通过 .toolCallBacks()
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 传递 FunctionToolCallBack 对象:String response = dashScopeChatClient.prompt(query).toolCallbacks(FunctionToolCallback.builder("getTimeByZoneId", new TimeFunction2()).description("Get time by zone id").inputType(TimeFunction2.Request.class).build()).call().content();return response;}@GetMapping("/time/user")public String user() {String response = dashScopeChatClient.prompt("获取我的用户名").tools(new UserInfoTools()).toolContext(Map.of("userId", "12345")).call().content();return response;}}

工具调用直接返回

默认情况下,工具调用的返回值会再次回传到 AI 模型进一步处理。但在一些场景中需要将结果直接返回给调用方而非模型,比如数据搜索。

定义方法工具时,可以通过 @Tool 注解的 returnDirect 参数置 true 来启动直接返回;定义方法工具和函数工具时需要通过 ToolMetadata 对象传递到 MethodToolCallBack.Builder 和 FunctionToolCallBack.Builder中。

以工具调用定义中的 TimeFunction 为例,演示代码:

String response = chatClient.prompt("获取北京时间").toolCallbacks(FunctionToolCallback.builder("getTimeByZoneId", new TimeFunction()).toolMetadata(ToolMetadata.builder().returnDirect(true).build()).description("Get time by zone id").inputType(TimeFunction.Request.class).build()).call().content();

调用这段代码将直接返回 TimeFunction 返回的JSON对象,而不再经过大模型加工处理。

controller代码

/** Copyright 2024-2025 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package com.alibaba.cloud.ai.toolcall.controller;import com.alibaba.cloud.ai.toolcall.component.TimeTools;
import com.alibaba.cloud.ai.toolcall.component.TimeToolsMethodToolCallBack;
import com.alibaba.cloud.ai.toolcall.component.UserInfoTools;
import com.alibaba.cloud.ai.toolcall.function.TimeFunction;
import com.alibaba.cloud.ai.toolcall.function.TimeFunction2;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.function.FunctionToolCallback;
import org.springframework.ai.tool.metadata.ToolMetadata;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.lang.reflect.Method;
import java.util.Map;@RestController
public class TimeController {private final ChatClient dashScopeChatClient;private final TimeTools timeTools;public TimeController(ChatClient chatClient, TimeTools timeTools) {this.dashScopeChatClient = chatClient;this.timeTools = timeTools;}/*** No Tool*/@GetMapping("/time/chat")public String simpleChat(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {return dashScopeChatClient.prompt(query).call().content();}/*** Methods as Tools*/@GetMapping("/time/chat-tool-method")public String chatWithTimeFunction(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过 .tools() 方法传递工具对象
//        ,或者在实例化 ChatClient 对象的时候通过 .defalutTools() 方法传递工具对象:return dashScopeChatClient.prompt(query).tools(timeTools).call().content();}/*** Methods as Tools*/@GetMapping("/time/timeToolsMethodToolCallBack")public String timeToolsMethodToolCallBack(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
//        通过 MethodToolCallBack.Builder 定义方法工具:String inputSchema = """{"$schema" : "https://json-schema.org/draft/2020-12/schema","type" : "object","properties" : {"zoneId" : {"type" : "string","description" : "Time zone id, such as Asia/Shanghai"}},"required" : [ "zoneId" ],"additionalProperties" : false}""";Method method = ReflectionUtils.findMethod(TimeToolsMethodToolCallBack.class, "getTimeByZoneId", String.class);if (method == null) {throw new RuntimeException("Method not found");}MethodToolCallback toolCallback = MethodToolCallback.builder().toolDefinition(ToolDefinition.builder().description("Get time by zone id").name("getTimeByZoneId").inputSchema(inputSchema).build()).toolMethod(method).toolObject(new TimeToolsMethodToolCallBack()).build();
//        可以使用 JsonSchemaGenerator.generateForMethodInput(method) 方法获取 Input Schema。
//        但如果原方法的参数没有 @ToolParam 或者 @JsonPropertyDescription 注解,则会缺失参数的 description 字段,
//        因此建议可以用该方法生成一个模板,然后填充参数的 description 字段。
//
//        在调用 ChatClient 时,通过.toolCallbacks() 传递 MethodToolCallBack 对象,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 方法传递工具对象:return dashScopeChatClient.prompt(query).toolCallbacks(toolCallback).call().content();}@GetMapping("/time/function")public String function(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        在调用 ChatClient 时,通过.toolNames() 传递函数工具的 Bean 名称,
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolNames() 方法传递函数工具:return dashScopeChatClient.prompt(query).toolNames("getTimeByZoneId").call().content();}@GetMapping("/time/function2")public String function2(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {//        开发者也可以不用定义 Bean,直接定义 FunctionToolCallBack 对象,在调用 ChatClient 时通过 .toolCallBacks()
//        或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 传递 FunctionToolCallBack 对象:String response = dashScopeChatClient.prompt(query).toolCallbacks(FunctionToolCallback.builder("getTimeByZoneId", new TimeFunction2()).description("Get time by zone id").inputType(TimeFunction2.Request.class).build()).call().content();return response;}@GetMapping("/time/user")public String user() {String response = dashScopeChatClient.prompt("获取我的用户名").tools(new UserInfoTools()).toolContext(Map.of("userId", "12345")).call().content();return response;}@GetMapping("/time/function3")public String function3() {String response = dashScopeChatClient.prompt("获取北京时间").toolCallbacks(FunctionToolCallback.builder("getTimeByZoneId", new TimeFunction()).toolMetadata(ToolMetadata.builder().returnDirect(true).build()).description("Get time by zone id").inputType(TimeFunction.Request.class).build()).call().content();return response;}}

 

http://www.lryc.cn/news/590753.html

相关文章:

  • TCP 拥塞控制算法 —— 慢启动(Slow Start)笔记
  • 能行为监测算法:低成本下的高效管理
  • AlpineLinux的用户管理
  • 同态加密赋能大模型医疗文本分析:可验证延迟压缩的融合之道
  • MPPT电路设计
  • LVS集群调度器
  • 解决容器dns问题
  • LVS四种模式及部署NAT、DR模式集群
  • Liunx-Lvs配置项目练习
  • Python函数全解析
  • 横向移动(中)
  • 使用YOLOv11实现水果类别检测:从数据到模型训练的全过程
  • 每日钉钉API探索:getAuthCode实现免登授权
  • 测试工作中的质量门禁管理
  • Maven入门指南:生命周期、阶段和执行顺序详解
  • 基于FPGA的IIC控制EEPROM读写(1)
  • 项目流程管理系统使用建议:推荐13款
  • 华为OD机试_2025 B卷_完美走位(Python,100分)(附详细解题思路)
  • ES组合使用must与should时的注意事项
  • 【LeetCode刷题指南特别篇】--移除链表元素,调试技巧,链表分割
  • Linux4:线程
  • TRAE + Milvus MCP:用自然语言 0 门槛玩转向量数据库
  • OpenVela之 Arch Timer 驱动框架使用指南
  • UltraISO编辑ISO文件
  • Karate(Java)接口自动化测试框架
  • 二刷 黑马点评 分布式锁-redission
  • 基于Canal实现MySQL数据库数据同步
  • Alamofire 网络请求全流解析,通俗易懂
  • ai 编程工具,简单总结
  • Python脚本批量修复文件时间戳,根据文件名或拍摄日期