此页面或文档已过期,请点击链接跳转到新页面。
前往新页面 →工具调用(Tool Calling)
此页面或文档已过期,请点击链接跳转到新页面。
前往新页面 →概述
“工具调用(Tool Calling)”或“函数调用”允许大型语言模型(LLM)在必要时调用一个或多个可用的工具,这些工具通常由开发者定义。工具可以是任何东西:网页搜索、对外部 API 的调用,或特定代码的执行等。LLM 本身不能实际调用工具;相反,它们会在响应中表达调用特定工具的意图(而不是以纯文本回应)。然后,应用程序应该执行这个工具,并报告工具执行的结果给模型。当 LLM 可以访问工具时,它可以在合适的情况下决定调用其中一个工具,这是一个非常强大的功能。
工具调用定义
Spring AI 支持两种工具调用的定义:方法工具 和 函数工具。接下来将以“获取当前时间工具”为例,简单介绍这两种工具定义方法。
其他更丰富的例子可以查看 Spring AI Alibaba Tool Calling Examples。
方法工具
Spring AI 可以定义类的某个方法为工具,在方法上标记 @Tool 注解,在参数上标记 @ToolParam 注解。例如:
public class TimeTools {
@Tool(description = "Get time by zone id") public String getTimeByZoneId(@ToolParam(description = "Time zone id, such as Asia/Shanghai") String zoneId) { 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); }}在调用 ChatClient 时,通过 .tools() 方法传递工具对象,或者在实例化 ChatClient 对象的时候通过 .defalutTools() 方法传递工具对象:
String response = chatClient.prompt("获取北京时间") .tools(new TimeTools()) .call() .content();如果要使用之前编写好的类的方法,不想修改源代码,可以使用 MethodToolCallBack 定义方法工具。
比如,现在有这样的一个类:
public class TimeTools {
public String getTimeByZoneId(String zoneId) { 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(TimeTools.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 TimeTools()) .build();可以使用 JsonSchemaGenerator.generateForMethodInput(method) 方法获取 Input Schema。但如果原方法的参数没有 @ToolParam 或者 @JsonPropertyDescription 注解,则会缺失参数的 description 字段,因此建议可以用该方法生成一个模板,然后填充参数的 description 字段。
在调用 ChatClient 时,通过.toolCallbacks() 传递 MethodToolCallBack 对象,或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 方法传递工具对象:
String response = chatClient.prompt("获取北京时间") .toolCallbacks(toolCallback) .call() .content();当前方法工具不支持以下类型的参数和返回类型:
Optional- 异步类型(
CompletableFuture、Future) - 响应式类型(
Flow、Mono、Flux) - 函数类型(
Function、Supplier、Consumer)
函数工具
开发者可以把任意实现 Function 接口的对象,定义为 Bean ,并通过 .toolNames() 或 .defaultToolNames() 传递给 ChatClient 对象。
例如有这么一个实现了Function 接口的类:
public class TimeFunction implements Function<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) { }
@Override public 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:
@Configurationpublic class TestAutoConfiguration {
@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();开发者也可以不用定义 Bean,直接定义 FunctionToolCallBack 对象,在调用 ChatClient 时通过 .toolCallBacks() 或者在实例化 ChatClient 对象的时候通过 .defalutToolCallBacks() 传递 FunctionToolCallBack 对象:
String response = chatClient.prompt("获取北京时间") .toolCallbacks(FunctionToolCallback .builder("getTimeByZoneId", new TimeFunction()) .description("Get time by zone id") .inputType(TimeFunction.Request.class) .build()) .call() .content();当前函数工具不支持以下类型的参数和返回类型:
- 基本类型
Optional- 集合类型(
List、Map、Array、Set) - 异步类型(
CompletableFuture、Future) - 响应式类型(
Flow、Mono、Flux)
返回值转换
Spring AI 框架中,工具调用的结果会通过 ToolCallResultConverter 进行处理,然后回传给 AI 模型。ToolCallResultConverter 接口提供了将工具调用结果转换为字符串对象的方法。Spring AI 默认使用 DefaultToolCallResultConverter,将返回结果对象使用 Jackson 库转化为 JSON 字符串。ToolCallResultConverter 接口的定义为:
@FunctionalInterfacepublic interface ToolCallResultConverter { /** * Given an Object returned by a tool, convert it to a String compatible with the * given class type. */ String convert(@Nullable Object result, @Nullable Type returnType);}定义方法工具时,可以通过 @Tool 注解的 resultConverter 参数提供 ToolCallResultConverter 的实现类;定义方法工具和函数工具时可以通过 MethodToolCallBack.Builder 和 FunctionToolCallBack.Builder 的 resultConverter() 方法设置ToolCallResultConverter 的实现类。
工具上下文
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();工具调用直接返回
默认情况下,工具调用的返回值会再次回传到 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对象,而不再经过大模型加工处理。