Tool Calling详解

Spring AI Tool Calling 详解

概述

Tool Calling(工具调用)是 Spring AI 框架中让 AI 模型与外部世界交互的核心能力。它允许 AI 模型在对话过程中识别需要调用外部工具的时机,生成结构化的调用参数,执行工具后将结果融入对话。

💡 提示: 与 Function Calling 的关系
Tool Calling 是 Spring AI 对各大模型厂商 Function Calling 能力的统一封装,提供了更加 Spring 友好的编程模型。


核心架构

整体流程

1
2
3
4
5
6
7
8
9
10
┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ 用户请求 │────►│ ChatClient │────►│ ChatModel │────►│ AI 模型 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Tools │◄────│ToolCalling │◄────│ 工具调用 │
│ (工具实现) │ │ Manager │ │ 意图 │
└─────────────┘ └─────────────┘ └─────────────┘

关键组件

组件 职责
@Tool 注解,将方法标记为可被 AI 调用的工具
@ToolParam 注解,描述工具参数的元信息
ToolCallback 接口,工具执行的核心抽象
ToolDefinition 工具定义(名称、描述、输入 Schema)
ToolCallingManager 管理工具执行生命周期
ChatClient 客户端,注册和调用工具的入口

Tool Specification(工具规范)

Spring AI 提供两种定义工具的方式:Declarative(声明式)Programmatic(编程式)

对比总结

维度 Declarative(声明式) Programmatic(编程式)
实现方式 @Tool + @ToolParam 注解 MethodToolCallback / FunctionToolCallback
Schema 生成 自动(基于方法签名) 手动定义 inputSchema
灵活性 低(依赖注解属性) 高(完全控制所有配置)
适用场景 大多数常规场景 动态工具、复杂定制需求
代码量

ToolDefinition 接口

无论哪种方式,最终都会生成 ToolDefinition,这是 AI 模型理解工具的核心:

1
2
3
4
5
public interface ToolDefinition {
String name(); // 工具唯一名称
String description(); // 工具描述(AI 据此判断何时调用)
String inputSchema(); // 参数的 JSON Schema
}

定义工具的三种方式

方式一:@Tool 注解(Declarative,推荐)

最简洁的声明式方式,Spring AI 自动生成 JSON Schema。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

@Component
public class WeatherService {

@Tool(description = "获取指定城市的天气信息")
public String getWeather(
@ToolParam(description = "城市名称,如:北京、上海") String city,
@ToolParam(description = "温度单位:celsius 或 fahrenheit") String unit) {
// 实际调用天气 API
return "当前 " + city + " 天气:晴,温度 25°C";
}
}

⚠️ 重要: @Tool 注解属性

  • description:工具描述,AI 模型根据此决定何时调用(必填
  • returnDirect:是否直接返回结果给用户,跳过模型后处理(默认 false

方式二:MethodToolCallback(Programmatic)

当需要更精细控制时使用,可完全自定义 ToolDefinition

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
27
28
29
30
31
32
33
import org.springframework.ai.tool.MethodToolCallback;
import org.springframework.ai.tool.ToolDefinition;
import org.springframework.ai.tool.ToolDefinitions;
import org.springframework.util.ReflectionUtils;

// 方式 A:完全手动构建
Method method = WeatherService.class.getMethod("getWeather", String.class, String.class);

ToolCallback toolCallback = MethodToolCallback.builder()
.toolDefinition(ToolDefinition.builder()
.name("getWeather")
.description("获取指定城市的天气信息")
.inputSchema("""
{
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
""")
.build())
.toolMethod(method)
.toolObject(new WeatherService())
.build();

// 方式 B:从方法自动生成 ToolDefinition,再自定义部分属性
Method method = ReflectionUtils.findMethod(DateTimeTools.class, "getCurrentDateTime");
ToolDefinition toolDefinition = ToolDefinitions.builder(method)
.name("currentDateTime") // 覆盖默认名称
.description("获取用户时区的当前日期时间") // 覆盖默认描述
.build();

方式三:FunctionToolCallback(函数式)

使用 FunctionSupplierConsumerBiFunction 等函数式接口定义工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义请求/响应类型
public record WeatherRequest(String location, Unit unit) {}
public record WeatherResponse(double temp, Unit unit) {}
public enum Unit { C, F }

// 实现 Function 接口
public class WeatherFunction implements Function<WeatherRequest, WeatherResponse> {
@Override
public WeatherResponse apply(WeatherRequest request) {
// 调用天气 API
return new WeatherResponse(25.0, Unit.C);
}
}

// 构建 ToolCallback
ToolCallback weatherTool = FunctionToolCallback.builder("getWeather", new WeatherFunction())
.description("获取指定位置的天气信息")
.inputType(WeatherRequest.class)
.build();

// 注册到 ChatClient
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(weatherTool)
.build();

方式四:实现 ToolCallback 接口(完全自定义)

适合封装 MCP 工具或构建复杂的 Agent 应用。

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
27
28
29
30
public class CustomToolCallback implements ToolCallback {

@Override
public ToolDefinition getToolDefinition() {
return ToolDefinition.builder()
.name("customTool")
.description("自定义工具")
.inputSchema("{...}")
.build();
}

@Override
public ToolMetadata getToolMetadata() {
return ToolMetadata.builder()
.returnDirect(false)
.build();
}

@Override
public String call(String toolInput) {
// 解析 toolInput (JSON),执行逻辑,返回结果
return "执行结果";
}

@Override
public String call(String toolInput, ToolContext toolContext) {
// 带上下文的执行,可访问会话历史等信息
return call(toolInput);
}
}

注册和使用工具

方式一:Builder 级别注册(全局默认)

所有通过该 ChatClient 发起的请求都可使用这些工具。

1
2
3
4
5
6
7
8
9
10
@Configuration
public class AiConfig {

@Bean
public ChatClient chatClient(ChatModel chatModel, WeatherService weatherService) {
return ChatClient.builder(chatModel)
.defaultTools(weatherService) // 注册默认工具
.build();
}
}

方式二:请求级别注册(按需使用)

针对特定请求注册工具,会覆盖默认工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class ChatService {

private final ChatClient chatClient;
private final WeatherService weatherService;
private final StockService stockService;

public String chat(String userMessage) {
return chatClient.prompt()
.user(userMessage)
.tools(weatherService, stockService) // 本次请求可用的工具
.call()
.content();
}
}

方式三:通过名称引用(需预先注册)

1
2
3
4
5
6
7
8
9
10
11
// 预先注册工具
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultToolCallbacks(weatherToolCallback)
.build();

// 通过名称引用
String response = chatClient.prompt()
.user("北京天气如何?")
.toolNames("getWeather")
.call()
.content();

参数处理

@ToolParam 注解详解

1
2
3
4
5
6
7
8
9
10
11
12
public record BookingRequest(
@ToolParam(description = "出发城市") String from,
@ToolParam(description = "目的城市") String to,
@ToolParam(description = "出发日期,格式:yyyy-MM-dd") String date,
@ToolParam(description = "舱位类型", required = false) String cabinClass
) {}

@Tool(description = "查询航班信息")
public List<Flight> searchFlights(BookingRequest request) {
// 支持复杂对象作为参数
return flightService.search(request);
}

📝 注意: 参数规则

  • 默认所有参数为必需,除非显式标记 required = false
  • Spring AI 自动根据 Java 类型生成 JSON Schema
  • 支持基本类型、对象、集合等

自动 Schema 生成

Spring AI 根据方法签名自动生成输入 Schema:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Java 方法
@Tool(description = "Set a user alarm")
void setAlarm(@ToolParam(description = "Time in ISO-8601 format") String time)

// 自动生成的 JSON Schema
{
"type": "object",
"properties": {
"time": {
"type": "string",
"description": "Time in ISO-8601 format"
}
},
"required": ["time"]
}

returnDirect 机制

默认行为

工具执行结果返回给 AI 模型,模型整合后再响应用户。

1
用户 → 模型 → 调用工具 → 工具结果 → 模型整合 → 用户

returnDirect = true

工具结果直接返回给用户,跳过模型后处理。

1
用户 → 模型 → 调用工具 → 工具结果 → 用户(直接返回)

适用场景

  • RAG 检索结果无需再加工
  • 某些工具作为 Agent 推理循环的终止点
  • 减少不必要的 Token 消耗
1
2
3
4
@Tool(description = "获取图书馆营业时间", returnDirect = true)
public String getLibraryHours() {
return "营业时间:周一至周五 9:00-20:00,周末 10:00-18:00";
}

⚠️ 警告: 多工具调用时的注意事项
如果一次请求调用多个工具,所有工具都必须设置 returnDirect = true 才会直接返回。否则结果仍会发送给模型。


Dynamic Tool Discovery(动态工具发现)

当应用注册了大量工具时(几十甚至上百个),将所有工具定义发送给 LLM 会消耗大量 Token 并影响性能。Spring AI 提供了动态工具发现机制来解决这个问题。

核心思想

1
2
3
传统方式:注册 100 个工具 → 全部发送给 LLM → Token 爆炸 💥

动态发现:注册 100 个工具 → 只发送 ToolSearchTool → LLM 按需搜索 → 按需加载

工作流程

sequenceDiagram
    participant U as 用户
    participant C as ChatClient
    participant S as ToolSearcher
    participant L as LLM

    Note over C,S: 启动时:索引所有工具,但不发送给 LLM
    U->>C: "阿姆斯特丹天气如何?"
    C->>L: Prompt + ToolSearchTool 定义
    L->>C: 调用 searchTools("weather")
    C->>S: 搜索匹配的工具
    S->>C: 返回 getWeather 工具定义
    C->>L: 补充 getWeather 工具定义
    L->>C: 调用 getWeather("Amsterdam")
    C->>U: "阿姆斯特丹当前天气..."

实现方式

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@SpringBootApplication
public class Application {

@Bean
CommandLineRunner demo(ChatClient.Builder builder, ToolSearcher toolSearcher) {
return args -> {
// 构建 ToolSearchToolCallAdvisor
var advisor = ToolSearchToolCallAdvisor.builder()
.toolSearcher(toolSearcher)
.build();

ChatClient chatClient = builder
.defaultTools(new MyTools()) // 注册大量工具,但不立即发送给 LLM
.defaultAdvisors(advisor) // 启用动态工具搜索
.build();

var answer = chatClient.prompt("""
帮我规划今天在阿姆斯特丹穿什么。
请推荐现在营业的服装店。
""").call().content();

System.out.println(answer);
};
}

static class MyTools {
@Tool(description = "获取指定位置和时间的天气")
public String weather(String location,
@ToolParam(description = "格式:YYYY-MM-DDTHH:mm") String atTime) {
// 实现
}

@Tool(description = "获取指定位置和时间营业的服装店")
public List<String> clothing(String location,
@ToolParam(description = "格式:YYYY-MM-DDTHH:mm") String openAtTime) {
// 实现
}

@Tool(description = "获取指定位置的当前日期时间")
public String currentTime(String location) {
// 实现
}

// ... 可能有上百个工具
}
}

关键组件

组件 职责
ToolSearcher 索引所有工具,提供搜索能力
ToolSearchToolCallAdvisor Advisor,拦截请求并启用动态搜索
ToolSearchTool 暴露给 LLM 的元工具,用于搜索其他工具

适用场景

  • 工具数量多(>10 个)
  • 单次对话通常只用少数几个工具
  • 需要优化 Token 消耗和响应速度

工具执行生命周期

ToolCallingManager

ToolCallingManager 是管理工具执行的核心接口:

1
2
3
4
5
6
7
8
public interface ToolCallingManager {

// 从配置中解析工具定义
List<ToolDefinition> resolveToolDefinitions(ToolCallingChatOptions chatOptions);

// 执行模型请求的工具调用
ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResponse);
}

内部执行 vs 用户控制执行

内部执行(默认)

Spring AI 自动处理工具调用循环:

1
2
3
4
5
6
String response = chatClient.prompt()
.user("北京和上海的天气如何?")
.tools(weatherService)
.call()
.content();
// Spring AI 自动:调用工具 → 返回结果给模型 → 获取最终响应

用户控制执行

手动管理工具调用,适合需要自定义逻辑的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(new WeatherService())
.internalToolExecutionEnabled(false) // 禁用内部执行
.build();

Prompt prompt = new Prompt(List.of(new UserMessage("北京天气如何?")), chatOptions);
ChatResponse response = chatModel.call(prompt);

// 手动检查和执行工具调用
while (response.hasToolCalls()) {
ToolExecutionResult result = toolCallingManager.executeToolCalls(prompt, response);
// 自定义处理逻辑...
prompt = new Prompt(result.conversationHistory(), chatOptions);
response = chatModel.call(prompt);
}

完整实战示例

场景:智能客服系统

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Component
public class CustomerServiceTools {

private final OrderRepository orderRepository;
private final ProductRepository productRepository;

@Tool(description = "根据订单号查询订单状态")
public OrderInfo queryOrder(
@ToolParam(description = "订单号,格式:ORD-XXXXXXXX") String orderId) {
return orderRepository.findById(orderId)
.map(order -> new OrderInfo(order.getStatus(), order.getEstimatedDelivery()))
.orElseThrow(() -> new RuntimeException("订单不存在"));
}

@Tool(description = "查询商品库存")
public StockInfo checkStock(
@ToolParam(description = "商品SKU编码") String sku,
@ToolParam(description = "仓库代码", required = false) String warehouse) {
return productRepository.checkStock(sku, warehouse);
}

@Tool(description = "提交退款申请", returnDirect = true)
public String submitRefund(
@ToolParam(description = "订单号") String orderId,
@ToolParam(description = "退款原因") String reason) {
// 处理退款逻辑
String refundId = refundService.submit(orderId, reason);
return "退款申请已提交,退款单号:" + refundId + ",预计 3-5 个工作日到账。";
}
}

@RestController
@RequestMapping("/api/chat")
public class ChatController {

private final ChatClient chatClient;

public ChatController(ChatModel chatModel, CustomerServiceTools tools) {
this.chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个专业的客服助手,请礼貌地帮助用户解决问题。")
.defaultTools(tools)
.build();
}

@PostMapping
public String chat(@RequestBody ChatRequest request) {
return chatClient.prompt()
.user(request.message())
.call()
.content();
}
}

与其他技术的关系

1
2
3
4
5
6
7
┌─────────────────────────────────────────────────────────────┐
│ Spring AI Tool Calling │
│ (Java 生态的 AI 工具调用统一抽象层) │
├─────────────────────────────────────────────────────────────┤
│ 各厂商 Function Calling │
│ OpenAI / Claude / 通义千问 / Gemini / 文心一言 等 │
└─────────────────────────────────────────────────────────────┘

Spring AI Tool Calling 的价值:

  1. 统一 API:屏蔽各厂商 Function Calling 的实现差异
  2. Spring 友好:注解驱动、依赖注入、与 Spring 生态无缝集成
  3. 类型安全:自动 JSON Schema 生成,编译期检查
  4. 可扩展:通过 ToolCallback 接口对接任意工具来源(如 MCP)

总结

要点 说明
定义工具 声明式 @Tool 或编程式 MethodToolCallback / FunctionToolCallback
注册工具 Builder 级别(全局)或请求级别(按需)
动态发现 ToolSearchToolCallAdvisor 实现按需加载,优化 Token
执行控制 默认内部自动循环,可切换为手动控制
返回策略 returnDirect=true 可跳过模型后处理
核心接口 ToolCallback 是扩展点,ToolCallingManager 管理生命周期

相关链接

  • Agent 工具调用技术详解 - Function Calling / MCP / Skills 对比
  • WebFlux 简介 - 响应式编程基础(Spring AI 支持响应式)

Tool Calling详解
https://zmmmmy.github.io/2026/01/28/Tool Calling详解/
作者
ZhiMy
发布于
2026年1月28日
许可协议