7-Tool Calling

1 核心概念和作用

定义:允许 AI 模型调用外部工具(如 API、数据库查询),扩展模型能力,实现信息检索和操作执行。

两大应用场景:

  • 信息检索:获取实时数据(如天气、新闻),支持 RAG 场景。
  • 操作执行:自动化任务(如发送邮件、创建数据库记录)。

安全机制:模型仅能请求工具调用,实际执行由应用负责,模型无法直接访问 API。

2 快速开始

信息检索工具(获取当前时间)

先定义一个自定义的工具类,提供获取当前时间的方法

1
2
3
4
5
6
7
public class DateTimeTools {

@Tool(description = "获取用户时区的当前日期和时间")
String getCurrentDateTime(){
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
}
1
2
3
4
5
6
7
8
9
@Test
void testTools1(){
ChatClient chatClient = ChatClient.builder(chatModel).build();
String prizzaOrder = chatClient.prompt("明天星期几")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(pizzaOrder);
}

效果

操作执行工具(设置闹钟)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DateTimeTools {

@Tool(description = "获取用户时区的当前日期和时间")
String getCurrentDateTime(){
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}

@Tool(description = "设置指定时间的闹钟,时间格式为 ISO-8601")
void setAlarm(@ToolParam(description = "ISO-8601 格式时间") String time) {
LocalDateTime alarmTime = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME);
System.out.println("闹钟设置为: " + alarmTime);
}
}
1
2
3
4
5
6
7
8
9
@Test
void testTools2() {
ChatClient chatClient = ChatClient.builder(chatModel).build();
String pizzaOrder = chatClient.prompt("1分钟后设置一个闹钟")
.tools(new DateTimeTools())
.call()
.content();
System.out.println(pizzaOrder);
}

效果

3 工具定义方式

3.1 基于方法的工具

声明式定义(@Tool 注解)

注解参数:

  • name:工具名称(默认方法名)。
  • description:工具描述(关键,影响模型调用决策)。
  • returnDirect:是否直接返回结果给客户端(默认 false,返回给模型)。

参数注解:

  • @ToolParam(description = “参数说明”, required = false):设置参数描述和必填性。
1
2
3
4
5
6
7
public class Tools {
@Tool(description = "计算两数之和")
int add(@ToolParam(description = "第一个加数") int a, @ToolParam(description = "第二个加数") int b) {
System.out.println("add方法执行了...");
return a + b;
}
}
1
2
3
4
5
6
7
8
9
@Test
void testTools2() {
ChatClient chatClient = ChatClient.builder(chatModel).build();
String content = chatClient.prompt("计算出100和1000两个数相加的结果")
.tools(new Tools())
.call()
.content();
System.out.println(content);
}

编程式定义

1
2
3
4
5
6
7
8
9
10
11
12
public class Tools {
@Tool(description = "计算两数之和")
int add(@ToolParam(description = "第一个加数") int a, @ToolParam(description = "第二个加数") int b) {
System.out.println("add方法执行了...");
return a + b;
}

int add1( int a, int b) {
System.out.println("add1方法执行了...");
return a + b;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
void testTools3(){
// 编程式定义MethodToolCallback
Method method = ReflectionUtils.findMethod(Tools.class, "add1", int.class, int.class);
ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder(method)
.description("计算两数之和")
.build())
.toolMethod(method)
.toolObject(new Tools())
.build();
ChatClient chatClient = ChatClient.builder(chatModel).build();
String content = chatClient.prompt("计算出100和1000两个数相加的结果")
.toolCallbacks(toolCallback)
.call()
.content();
System.out.println(content);
}

3.2 基于函数的工具

编程式定义(FunctionToolCallback)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 函数接口
interface WeatherService extends Function<WeatherRequest, WeatherResponse> {
@Override
WeatherResponse apply(WeatherRequest request);
}
record WeatherRequest(String location, DurationFormat.Unit unit) {}
record WeatherResponse(double temp, DurationFormat.Unit unit) {}

@Test
void testTools4(){
// 创建工具
ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather",new WeatherService(){
@Override
public WeatherResponse apply(WeatherRequest request) {
return new WeatherResponse(25.5,request.unit());
}
}).description("获取指定地点的天气")
.inputType(WeatherRequest.class)
.build();
ChatClient chatClient = ChatClient.builder(chatModel).build();
String content = chatClient.prompt("今天长沙的天气怎么样?")
.toolCallbacks(toolCallback)
.call()
.content();
System.out.println(content);
}

动态定义(@Bean 注解)

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ToolConfig {
public static final String WEATHER_TOOL = "currentWeather";

@Bean(WEATHER_TOOL)
@Description("获取指定地点的天气")
Function<WeatherRequest, WeatherResponse> currentWeather() {
return request -> new WeatherResponse(30.0, request.unit());
}
}
record WeatherRequest(String location, DurationFormat.Unit unit) {}
record WeatherResponse(double temp, DurationFormat.Unit unit) {}
1
2
3
4
5
6
7
8
9
10
@Test
void testTools5() throws Exception{
ChatClient chatClient = ChatClient.builder(chatModel).build();
String content = chatClient.prompt("今天长沙的天气怎么样?")
.toolNames(ToolConfig.WEATHER_TOOL)
.call()
.content();
System.out.println(content);
}

4 核心组件与接口

ToolCallback 接口:工具的核心接口,定义执行逻辑。

1
2
3
4
5
public interface ToolCallback {
ToolDefinition getToolDefinition(); // 工具定义(名称、描述、输入 schema)
ToolMetadata getToolMetadata(); // 工具元数据(如是否直接返回结果)
String call(String toolInput);
}

ToolDefinition 接口:工具元数据,供模型理解调用方式。

1
2
3
4
5
public interface ToolDefinition {
String name(); // 工具名称
String description(); // 工具描述
String inputSchema(); // 输入参数的 JSON Schema
}

ToolCallingManager:工具执行管理器,处理工具调用流程。

1
2
3
4
5
6
7
// 自定义工具执行管理器
@Bean
ToolCallingManager toolCallingManager() {
return ToolCallingManager.builder()
.exceptionProcessor(exception -> "工具调用失败: " + exception.getMessage())
.build();
}

5 工具执行流程

框架控制执行(默认)

  1. 模型返回工具调用请求(含工具名和参数)。
  2. ChatModel 调用 ToolCallingManager 执行工具。
  3. 工具结果返回给模型,模型生成最终响应。

用户控制执行:手动处理工具调用循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test
void testTools6(){
// 创建工具
ToolCallback toolCallback = FunctionToolCallback.builder("currentWeather",new WeatherService(){
@Override
public WeatherResponse apply(WeatherRequest request) {
return new WeatherResponse(25.5,request.unit());
}
}).description("获取指定地点的天气")
.inputType(WeatherRequest.class)
.build();
ChatOptions options = ToolCallingChatOptions.builder()
.toolCallbacks(toolCallback)
.internalToolExecutionEnabled(false) // 禁用自动执行
.build();
Prompt prompt = new Prompt("北京今天的天气怎么样?适合做什么活动呢", options);
ChatResponse response = chatModel.call(prompt);
System.out.println(response.toString());
ToolCallingManager manager = ToolCallingManager.builder().build();
while (response.hasToolCalls()) {
ToolExecutionResult result = manager.executeToolCalls(prompt, response);
prompt = new Prompt(result.conversationHistory(), options);
response = chatModel.call(prompt);
System.out.println(response.getResult().getOutput().getText());
}
}

6 工具结果处理

直接返回结果(returnDirect = true)

1
2
3
4
5
@Tool(description = "获取用户信息", returnDirect = true)
UserInfo getUser(@ToolParam(description = "用户ID") Long id) {
return new UserInfo(id,"李四");
}
// 结果直接返回给客户端,不经过模型

自定义结果转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 自定义转换器
public class CustomConverter implements ToolCallResultConverter {
@Override
public String convert(Object result, Type returnType) {
if (result instanceof UserInfo) {
return "用户: " + ((UserInfo) result).getName();
}
return String.valueOf(result);
}
}

@Tool(description = "获取用户信息", returnDirect = true,resultConverter = CustomConverter.class)
UserInfo getUser(@ToolParam(description = "用户ID") Long id) {
return new UserInfo(id,"李四");
}

7 工具上下文与异常处理

工具上下文(ToolContext):传递额外参数(如租户 ID)。

1
2
3
4
5
6
7
8
9
10
11
12
ChatClient.create(chatModel)
.prompt("查询客户42的信息")
.tools(new CustomerTools())
.toolContext(Map.of("tenantId", "acme")) // 传递租户ID
.call().content();

// 工具中使用上下文
@Tool(description = "查询客户信息")
Customer getCustomer(Long id, ToolContext context) {
String tenantId = context.get("tenantId");
return customerRepo.findByTenantAndId(tenantId, id);
}

异常处理:自定义工具执行异常处理器。

1
2
3
4
5
6
7
8
9
@Bean
ToolExecutionExceptionProcessor exceptionProcessor() {
return e -> {
if (e.getCause() instanceof SQLException) {
return "数据库错误: " + e.getMessage();
}
return "工具调用失败: " + e.getMessage();
};
}

8 工具与其他组件集成

与对话记忆(ChatMemory)集成:

1
2
3
4
5
6
7
ChatMemory memory = MessageWindowChatMemory.builder().build();
ChatClient client = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build())
.defaultTools(new DateTimeTools())
.build();
// 对话中自动包含历史工具调用记录
String response = client.prompt("之前设置的闹钟时间是?").call().content();

与检索增强(RAG)集成:工具可调用向量存储检索上下文。

1
2
3
4
@Tool(description = "检索相关文档")
List<Document> searchDocs(@ToolParam("查询关键词") String query) {
return vectorStore.search(query, 5);
}

9 支持的模型与最佳实践

支持模型:OpenAI、Anthropic Claude 3、Azure OpenAI、Mistral AI、Ollama 等。

最佳实践:

  • 描述清晰:工具描述需明确用途和参数要求,避免模型误用。
  • 参数必填性:合理设置 @ToolParam(required = false),防止模型虚构参数。
  • 安全考虑:工具不应暴露敏感操作,结果直接返回时需验证权限。
  • 性能优化:批量处理工具调用,减少模型交互次数。

7-Tool Calling
http://www.zivjie.cn/2025/11/22/spring框架/springAI/SpringAi框架/7-Tool Calling/
作者
Francis
发布于
2025年11月22日
许可协议