官网地址:https://docs.spring.io/spring-ai/reference/api/tools.html
1、描述
工具调用功能可以让LLM与API或者工具交互,增强其能力,SpringAI提供便捷的API来定义工具、解析模型的工具调用请求并执行工具调用。
工具主要用于:
- 信息检索。这种类别的工具可用于从外部(数据库、Web服务、文件系统或者Web搜索引擎等)检索信息,目的是增强模型的知识,可以获得最新的知识或者查询数据库特定的数据。
- 执行操作。这种类别的工具可用于在软件系统中执行操作,例如发送邮件、在数据库中创建新纪录、提交表单或者触发工作流,目的是可以自动化需要人工干预或者显示编程的任务,比如查询订单状态信息、填写表单或者在代码生成场景中生成模板代码等。
注意(安全考虑):
LLM定位是一个纯粹的“决策和规划的大脑”,所有涉及与外部交互都由我们自己控制,比如网络请求、数据库查询或者文件操作等,LLM 和工具执行环境隔离,可以从根本上解决 LLM 的行为失控,从而构建安全可靠的 AI 应用。
例如:我们提供一个工具,用来获取城市的天气情况,当我们提供了@Tool 的注解的方法,LLM 只能发现方法getWeather,以及参数 city,以及返回参数,它无法直接创建 HTTP 请求直接调用weatherApiClient。代码如下:
@Tool(description = "根据城市名称获取天气")public String getWeather(String city) {// 模型只能“请求”调用 getWeather("上海")// 实际的 HTTP 调用发生在这里,完全由你的代码控制return weatherApiClient.fetchFromSomeAPI(city);}
SpringAI 支持工具调用的组件与流程

2、声明式:@Tool
通过注解@Tool 将方法转换为工具,例如:
@Tool(description = "获取当前时间")String getCurrentTime() {return DateTime.now().toMsStr();}
@Tool 关键信息:
- name:工具的名称。如果没有设置,则使用方法名称,LLM 使用此名称在调用工具时识别它,在同一类中不允许有两个同名的工具,名称在特定聊天可用的所有工具中必须是唯一的,建议设置为全局唯一。
- description:工具的描述。LLM 使用描述来理解何时及如何调用工具,如果没有设置,方法名称将作为工具描述,但是强烈建议设置工具描述,这个对LLM 理解工具的作用和如何使用很重要,如果工具描述不准确,会导致 LLM 使用不到该工具或者使用使用不正确。
- returnDirect:工具结果是直接返回给客户端还是传回模型,默认 false,默认传递回模型
- resultConverter:用于将工具调用结果转换为 String 对象以发送会 AI 模型的ToolCallResultConverter实现。
@ ToolParam关键信息
- description:参数的描述,模型可以使用它更好地理解如何使用它。例如,参数应该采用什么格式,允许什么值等等。
- required:参数是必需还是可选。默认情况下,所有参数都被认为是必需的。
3、将工具添加到ChatClient
3.1、添加工具:
ChatClient.create(chatModel).prompt("What day is tomorrow?").tools(new DateTimeTools()).call().content();
3.2、添加默认工具:
@Beanpublic ChatClient chatClient(OllamaChatModel ollamaChatModel) {return ChatClient.builder(ollamaChatModel).defaultTools(new DateTimeTools()).build();}
在底层,ChatClient 将从工具类实例中的每个 @Tool 注解方法生成一个 ToolCallback 并将其传递给模型。如果您希望自己生成 ToolCallback,可以使用 ToolCallbacks 实用程序类。
如果同时设置了运行行工具和默认工具,默认工具会被覆盖。
3.3、方法工具限制
目前不支持以下类型作为用作工具的方法的参数或返回类型:
- Optional
- 异步类型(例如 CompletableFuture、Future)
- 响应式类型(例如 Flow、Mono、Flux)
- 函数式类型(例如 Function、Supplier、Consumer)。
函数式类型通过基于函数的工具规范方法受支持
4、函数作为工具
Spring AI 提供内置支持,用于从函数指定工具,无论是使用低级 FunctionToolCallback 实现以编程方式实现,还是作为在运行时解析的 @Bean 动态实现。
4.1、FunctionToolCallback
可以通过编程方式构建 FunctionToolCallback 将函数类型(Function、Supplier、Consumer 或 BiFunction)转换为工具。
public class WeatherService implements Function<WeatherRequest, WeatherResponse> {public WeatherResponse apply(WeatherRequest request) {return new WeatherResponse(30.0, Unit.C);}
}public enum Unit { C, F }
public record WeatherRequest(String location, Unit unit) {}
public record WeatherResponse(double temp, Unit unit) {}@Beanpublic ChatClient chatClient(OllamaChatModel ollamaChatModel) {FunctionToolCallback<WeatherRequest, WeathResponse> functionToolCallback = FunctionToolCallback.builder("当前天气", new WeatherService()).description("获取当前地区的天气").inputType(WeatherRequest.class).build();return ChatClient.builder(ollamaChatModel).defaultToolCallbacks(functionToolCallback).build();}
FunctionToolCallback.Builder 构建 FunctionToolCallback 实例并提供有关工具的关键信息:
- name:工具的名称。AI 模型使用此名称在调用工具时识别它。因此,同一上下文中不允许有两个同名的工具。名称在特定聊天请求可用的所有工具中必须是唯一的。必需。
- toolFunction:表示工具方法的函数对象(Function、Supplier、Consumer 或 BiFunction)。必需。
- description:工具的描述,模型可以使用它来理解何时以及如何调用工具。如果未提供,方法名称将用作工具描述。但是,强烈建议提供详细描述,因为这对模型理解工具的目的和如何使用至关重要。未能提供好的描述可能导致模型在应该使用工具时没有使用,或者使用不正确。
- inputType:函数输入的类型。必需。
- inputSchema:工具输入参数的 JSON 模式。如果未提供,模式将根据 inputType 自动生成。您可以使用 @ToolParam 注解提供有关输入参数的额外信息,例如描述或参数是必需还是可选。默认情况下,所有输入参数都被认为是必需的。有关更多详细信息,请参阅JSON 模式。
- toolMetadata:定义附加设置的 ToolMetadata 实例,例如结果是否应直接返回给客户端,以及要使用的结果转换器。可以使用 ToolMetadata.Builder 类构建它。
- toolCallResultConverter:用于将工具调用结果转换为 String 对象以发送回 AI 模型的 ToolCallResultConverter 实例。如果未提供,将使用默认转换器(DefaultToolCallResultConverter)。
函数输入和输出可以是 Void 或 POJO。输入和输出 POJO 必须是可序列化的,因为结果将被序列化并发送回模型。函数以及输入和输出类型必须是公共的。
5、ToolContext
Spring AI 支持通过 ToolContext API 将额外的上下文信息传递给工具。此功能允许您提供额外的、用户提供的数据,这些数据可以在工具执行期间与 AI 模型传递的工具参数一起使用。
@Component
public class DateTimeTools {@Tool(description = "获取当前时间")String getCurrentTime(ToolContext toolContext) {String conversationId = toolContext.getContext().get(ChatMemory.CONVERSATION_ID).toString();System.out.println("调用该工具的对话 ID 为:" + conversationId);return DateTime.now().toMsStr();}
}@PostMapping("chat")public Flux<String> chat(@RequestBody ChatReq chatReq) {return chatClient.prompt(chatReq.getMessage()).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatReq.getMemoryId())).toolContext(Map.of(ChatMemory.CONVERSATION_ID, chatReq.getMemoryId())).stream().content();}
结果输出: