白歌在白板上画了一个流程:"用户说'帮我查明天的课程',AI 知道自己查不到,于是调用 @Tool 标注的 getSchedule 方法,拿到结果后再组织语言回复用户。这就是 Tool Calling——AI 的'手'。"
Tool 注解与函数声明
定义与作用
@Tool 是 Spring AI 的核心注解,将一个 Java 方法标记为"AI 可调用工具"。当 AI 判断用户问题需要调用某个工具时,会输出函数名和参数,Spring AI 自动执行对应 Java 方法并将结果返回给 AI。
@Tool(description = "获取指定城市天气")
public String getWeather(String city) { ... }
// ↑ ↑
// 方法名 = AI 调用的函数名 参数 = AI 根据 JSON Schema 传递
核心原理:完整的 Tool Calling 请求-响应循环
图释:整个流程包含两轮 AI 调用。第一轮:AI 分析用户意图后决定调用工具,返回函数名和参数。第二轮:收到工具执行结果后,AI 将其转化为自然语言回复。
完整示例一:简单参数工具
场景说明
小崔需要为飞翔科技学生管理系统添加课程查询功能——AI 根据学生输入查询课程信息。
关键代码
@Component
public class CourseTools {
@Tool(description = "根据课程编号查询课程详细信息,包括课程名称、教师、学分、上课时间")
public String getCourseInfo(String courseCode) {
// 模拟数据库查询
return switch (courseCode.toUpperCase()) {
case "CS101" -> "课程:计算机组成原理 | 教师:白歌 | 学分:4 | 时间:周一 8:00-10:00";
case "CS201" -> "课程:数据结构与算法 | 教师:小崔 | 学分:3 | 时间:周三 10:00-12:00";
case "MATH301" -> "课程:高等数学 | 教师:李眉 | 学分:5 | 时间:周二 14:00-16:00";
default -> "未找到课程编号 " + courseCode;
};
}
@Tool(description = "根据教师姓名查询该教师本学期所有课程安排")
public String getTeacherSchedule(String teacherName) {
return switch (teacherName) {
case "白歌" -> "白歌本学期课程:\n- CS101 计算机组成原理(周一 8:00-10:00)\n- CS302 操作系统(周四 14:00-16:00)";
case "小崔" -> "小崔本学期课程:\n- CS201 数据结构与算法(周三 10:00-12:00)\n- CS401 软件工程(周五 8:00-10:00)";
default -> teacherName + " 的课程安排未找到";
};
}
}
@Configuration
public class ToolConfig {
@Bean
public ChatClient chatClient(ChatClient.Builder builder,
CourseTools courseTools) {
return builder
.defaultTools(courseTools) // 注册所有 @Tool 方法
.build();
}
}
@RestController
public class CourseController {
private final ChatClient chatClient;
public CourseController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/course/ask")
public String ask(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
运行结果及分析
GET /course/ask?message=帮我查一下 CS101 是什么课
AI 内部决策:需要调用 getCourseInfo("CS101")
↓
执行 getCourseInfo("CS101") → "课程:计算机组成原理 | 教师:白歌 | ..."
↓
AI 回复:「CS101 是计算机组成原理,由白歌老师授课,4 学分。
上课时间是周一上午 8:00-10:00。需要我帮你查白歌老师的其他课程吗?」
关键观察:用户说"帮我查一下 CS101 是什么课"而非"调用 getCourseInfo"——AI 自动理解了意图并选择了正确的工具。
完整示例二:复杂参数(POJO/Record)
场景说明
孔蓝要求会议室预订工具支持复杂参数——一次预订需要城市、日期、时长等多个字段。
关键代码
public record BookingRequest(
String city,
LocalDate checkIn,
int nights,
String roomType
) {}
@Component
public class MeetingRoomTools {
@Tool(description = "预订会议室,需要提供办公楼、日期、时长(小时)和会议人数")
public String bookMeetingRoom(
String building, // 简单参数
@ToolParam(description = "预订日期,格式 yyyy-MM-dd")
LocalDate date, // 特殊类型参数
int hours, // 简单参数
int participants // 简单参数
) {
// 模拟预订系统
String room;
if (participants <= 6) {
room = "小型会议室 A" + (participants % 3 + 1);
} else if (participants <= 20) {
room = "中型会议室 B" + (participants % 2 + 1);
} else {
room = "大型会议室 C1";
}
return String.format(
"已预订 %s 的 %s,日期 %s,时长 %d 小时,人数 %d 人",
building, room, date, hours, participants
);
}
@Tool(description = "查询指定办公楼所有可用会议室")
public String getAvailableRooms(String building) {
return switch (building) {
case "A座" -> "A座可用会议室:A-201(6人)、A-305(12人)、A-101(30人)";
case "B座" -> "B座可用会议室:B-102(4人)、B-301(8人)";
default -> building + " 暂无会议室信息";
};
}
}
@RestController
public class BookingController {
private final ChatClient chatClient;
public BookingController(ChatClient.Builder builder,
MeetingRoomTools meetingRoomTools) {
this.chatClient = builder
.defaultTools(meetingRoomTools)
.build();
}
@GetMapping("/booking/ask")
public String ask(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
运行结果
GET /booking/ask?message=帮我订明天 A 座 2 小时的会议室,8 个人参会
AI 内部决策:
1. 识别需要调用 bookMeetingRoom
2. 从自然语言中提取参数:
- building = "A座"
- date = "2026-06-14" (明天)
- hours = 2
- participants = 8
执行结果:「已预订 A座 的中型会议室 B1,日期 2026-06-14,时长 2 小时,人数 8 人」
AI 回复:「会议室已预订!A座 B1 中型会议室,明天(6月14日),2小时。
8人会议。需要我帮你发送会议邀请吗?」
易错场景与面试考点
易错场景一:工具描述不清晰导致 AI 不调用
// ❌ 错误:描述太泛化
@Tool(description = "获取信息")
public String getInfo(String param) { ... }
问题分析
AI 根据 description 决定是否调用工具。太泛化的描述会让 AI 无法判断何时使用。
// ✅ 正确:描述精确说明功能和使用场景
@Tool(description = "获取指定城市的当前天气信息,包括温度、湿度、风速")
public String getWeather(String city) { ... }
易错场景二:参数类型不匹配
// ❌ 错误:AI 返回 "2026-06-14" 但参数是 LocalDate,可能解析失败
@Tool(description = "预订会议室")
public String bookRoom(LocalDate date, int hours) { ... }
问题分析
Spring AI 自动从参数生成 JSON Schema,LocalDate 会被映射为 string 类型。确保 AI 传参格式与目标类型兼容。可以通过 @ToolParam 注解补充描述。
// ✅ 正确:为参数提供描述
@Tool(description = "预订会议室")
public String bookRoom(
@ToolParam(description = "预订日期,格式 yyyy-MM-dd") LocalDate date,
@ToolParam(description = "时长(小时)") int hours
) { ... }
面试高频题
Q1:AI 如何决定是否调用工具?
AI 模型在推理时会分析用户意图和可用工具列表(含 name + description + JSON Schema)。如果判断某工具能帮助回答问题,模型输出工具名和参数(JSON 格式)。Spring AI 解析 JSON 匹配到 @Tool 方法并执行,结果返回给模型生成最终回复。
Q2:defaultTools 和 functions() 的区别?
defaultTools:注册到 ChatClient.Builder,全局生效,每次调用自动可用。functions(String...):在单次调用时通过 .functions("toolName") 指定,仅当次请求生效。适合需要按场景控制工具可用性的情况。
本章小结
- @Tool 将 Java 方法暴露为 AI 可调用工具,Spring AI 自动生成 JSON Schema
- 支持简单参数(String/int)和复杂参数(LocalDate/Record)
- 完整流程:用户提问 → AI 决定调用 → Spring AI 执行 → 结果返回 → AI 回复
- 工具描述是 AI 使用工具的关键参考,务必精确描述功能与参数