当前位置: 首页 > 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.dtcms.com/a/282177.html

相关文章:

  • 【18】MFC入门到精通——MFC(VS2019)+ OpenCV 显示图片的3种方法
  • Linux Java环境配置
  • xss-labs 1-8关
  • 智芯Z20K11xM MCU开发之TDG触发Adc
  • 新疆兵团第六师新湖农场食记
  • 企业级AI项目未达预期:非结构化数据处理背后有何玄机?
  • es启动问题解决
  • Python 日志轮换处理器的参数详解
  • 元宇宙经济:虚实融合引发经济新变革
  • audiorecord 之 抢占优先级
  • 【世纪龙科技】汽车发动机拆装检修仿真教学软件-仿真精进技能
  • JAVA进阶 项目实战:汽车租聘系统
  • 黄仁勋:早知道雷军会有非凡成功,想买一辆小米汽车
  • 汽车免拆诊断案例 | 2015款进口起亚索兰托L车漏电
  • 自动化框架 Selenium 的使用
  • C++ 异常处理、typeid
  • 霍尔电流传感器在新能源汽车中的应用综述
  • 量子计算实用算法:2025年突破性进展与产业落地全景
  • 汽车功能安全-在系统层面验证TSR实例
  • 【React Native】布局和 Stack 、Slot
  • BNN 技术详解:当神经网络只剩下 +1 和 -1
  • 神经网络常见激活函数 13-Softplus函数
  • 重生之我在打御网杯打半决赛(高职组)
  • FCN语义分割笔记(1)
  • 大语言模型(LLM)训练的教师强制(Teacher Forcing)方法
  • 人工智能之数学基础:神经网络之多样本矩阵参数求导
  • Java线程创建与运行全解析
  • 什么是数据仓库?数据库与数据仓库有什么关系?
  • 消息转换器--通过此工具进行时间转换
  • 7.isaac sim4.2 教程-Core API-数据记录