让 AI 真正调用工具函数!
本章通过几个核心示例,带你掌握 Spring AI 的 Function Calling 功能。 你将学会如何使用 @Tool 注解定义工具函数,让 AI 能够自动调用这些函数获取实时数据。
@Tool 和 @ToolParam 定义工具函数本章内容是 Spring AI 的核心功能,Function Calling 是 AI 应用开发的关键技术,请仔细学习!
// 只能进行简单对话
String answer = chatModel.call("现在几点?");
// ❌ AI 无法自动调用工具函数// 配置工具后,AI 可以自动调用
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(toolFunctionConfig) // ⭐ 关键
.build();
String answer = chatClient.prompt()
.user("现在几点?")
.call()
.content();
// ✅ AI 会自动调用 getCurrentTime() 工具Spring AI 提供了 @Tool 注解来定义工具函数:
@Tool - 标记方法为工具函数@ToolParam - 描述参数用途@Component - 将类注册为 Spring Bean创建文件:src/main/java/com/example/chatbot/config/ToolFunctionConfig.java
package com.example.chatbot.config;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 工具函数配置
*
* 使用 @Tool 注解定义 AI 可以调用的工具函数
*/
@Component
public class ToolFunctionConfig {
/**
* 获取当前时间
*/
@Tool(description = "获取当前的日期和时间,返回格式化的时间字符串")
public String getCurrentTime() {
LocalDateTime now = LocalDateTime.now();
String formatted = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("🔧 工具调用: getCurrentTime() -> " + formatted);
return formatted;
}
/**
* 查询数据库
*/
@Tool(description = "查询数据库,获取用户、订单等数据的统计信息")
public String queryDatabase(
@ToolParam(description = "查询的内容,如'用户数量'、'今日订单'、'活跃用户'等") String query) {
System.out.println("🔧 工具调用: queryDatabase(\"" + query + "\")");
String result = switch (query.toLowerCase()) {
case "用户数量", "有多少用户", "用户总数" -> "系统目前有 10,523 个注册用户";
case "今日订单", "今天的订单", "订单数量" -> "今天共有 156 个订单";
case "活跃用户", "在线用户" -> "当前有 342 个活跃用户在线";
default -> "未找到相关数据";
};
System.out.println("✅ 返回: " + result);
return result;
}
/**
* 文件操作
*/
@Tool(description = "执行文件操作,如查看文件大小、检查文件是否存在等")
public String fileOperation(
@ToolParam(description = "操作类型,如'查看大小'、'检查存在'、'最后修改时间'") String operation,
@ToolParam(description = "文件名") String filename) {
System.out.println("🔧 工具调用: fileOperation(" + operation + ", " + filename + ")");
String result = switch (operation.toLowerCase()) {
case "查看大小", "文件大小", "size" -> "文件 " + filename + " 的大小为 2.5 MB";
case "检查存在", "是否存在", "exists" -> "文件 " + filename + " 存在";
case "最后修改时间", "修改时间", "modified" -> "文件 " + filename + " 最后修改时间: 2024-11-21 10:30:00";
default -> "不支持的操作: " + operation;
};
System.out.println("✅ 返回: " + result);
return result;
}
/**
* 发送邮件
*/
@Tool(description = "发送邮件给指定收件人,返回发送结果")
public String sendEmail(
@ToolParam(description = "收件人邮箱") String to,
@ToolParam(description = "邮件主题") String subject,
@ToolParam(description = "邮件正文") String body) {
System.out.println("🔧 工具调用: sendEmail(" + to + ", \"" + subject + "\")");
String mailId = "MAIL-" + System.currentTimeMillis();
String result = "邮件已发送给 " + to + ",邮件ID: " + mailId;
System.out.println("✅ 返回: " + result);
return result;
}
}# 编译项目
mvn compile预期输出:
[INFO] BUILD SUCCESS@Component 注册为 Spring Bean@Tool 标记工具函数@ToolParam 描述参数创建文件:src/test/java/com/example/chatbot/knowledge/Test07_ToolAnnotation.java
package com.example.chatbot.knowledge;
import com.example.chatbot.config.ToolFunctionConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
/**
* @Tool 注解测试
*
* 测试使用 @Tool 注解定义的工具函数
*/
@SpringBootTest(classes = com.example.chatbot.AiChatbotApplication.class)
@DisplayName("知识点7:@Tool 注解")
public class Test07_ToolAnnotation {
@Autowired
private ChatModel chatModel;
@Autowired
private ToolFunctionConfig toolFunctionConfig;
// 测试方法将在这里添加
}# 编译测试类
mvn test-compile预期输出:
[INFO] BUILD SUCCESS学习如何使用 ChatClient 配置工具函数,让 AI 能够自动调用。
@Test
@DisplayName("7.0 使用 ChatClient 配置工具函数")
public void test00_ChatClientWithTools() {
System.out.println("\n========== 测试 7.0:使用 ChatClient 配置工具函数 ==========");
System.out.println("\n📝 创建配置了工具的 ChatClient");
// 创建配置了工具的 ChatClient
org.springframework.ai.chat.client.ChatClient chatClient =
org.springframework.ai.chat.client.ChatClient.builder(chatModel)
.defaultTools(toolFunctionConfig)
.build();
System.out.println("✅ ChatClient 创建成功,已配置工具函数");
// 测试1:获取时间
System.out.println("\n🧪 测试1:让 AI 获取当前时间");
String question1 = "现在几点了?请告诉我准确的时间";
System.out.println("👤 问题: " + question1);
System.out.println("⏰ 观察是否有 '🔧 工具调用' 日志...\n");
String answer1 = chatClient.prompt()
.user(question1)
.call()
.content();
System.out.println("🤖 回答: " + answer1);
boolean hasTime = answer1.matches(".*\\d{1,2}[点:]\\d{1,2}.*") ||
answer1.matches(".*\\d{4}-\\d{2}-\\d{2}.*");
System.out.println(hasTime ? "✅ 包含时间信息 - AI 可能调用了工具!" : "❌ 没有时间信息");
// 测试2:查询数据库
System.out.println("\n🧪 测试2:让 AI 查询数据库");
String question2 = "系统有多少用户?";
System.out.println("👤 问题: " + question2);
System.out.println("⏰ 观察是否有 '🔧 工具调用' 日志...\n");
String answer2 = chatClient.prompt()
.user(question2)
.call()
.content();
System.out.println("🤖 回答: " + answer2);
boolean hasUserCount = answer2.contains("10,523") || answer2.contains("10523");
System.out.println(hasUserCount ? "✅ 包含用户数量 - AI 可能调用了工具!" : "❌ 没有用户数量");
// 测试3:发送邮件
System.out.println("\n🧪 测试3:让 AI 发送邮件");
String question3 = "请给 zhangsan@example.com 发送一封邮件,主题是\"会议提醒\",内容是\"明天下午3点开会\"";
System.out.println("👤 问题: " + question3);
System.out.println("⏰ 观察是否有 '🔧 工具调用' 日志...\n");
String answer3 = chatClient.prompt()
.user(question3)
.call()
.content();
System.out.println("🤖 回答: " + answer3);
System.out.println(answer3.contains("邮件") ? "✅ 包含邮件发送结果 - AI 可能调用了工具!" : "❌ 没有邮件发送结果");
System.out.println("\n💡 说明:");
System.out.println("- 使用 ChatClient.builder(chatModel).defaultTools(tools).build()");
System.out.println("- 这样配置后,AI 就能自动调用工具函数了");
System.out.println("- 如果看到 '🔧 工具调用' 日志,说明成功!");
System.out.println("\n✅ 测试通过");
}mvn test -Dtest=Test07_ToolAnnotation#test00_ChatClientWithTools========== 测试 7.0:使用 ChatClient 配置工具函数 ==========
📝 创建配置了工具的 ChatClient
✅ ChatClient 创建成功,已配置工具函数
🧪 测试1:让 AI 获取当前时间
👤 问题: 现在几点了?请告诉我准确的时间
⏰ 观察是否有 '🔧 工具调用' 日志...
🔧 工具调用: getCurrentTime() -> 2024-11-29 14:30:45
🤖 回答: 现在是 2024-11-29 14:30:45
✅ 包含时间信息 - AI 可能调用了工具!
🧪 测试2:让 AI 查询数据库
👤 问题: 系统有多少用户?
⏰ 观察是否有 '🔧 工具调用' 日志...
🔧 工具调用: queryDatabase("用户数量")
✅ 返回: 系统目前有 10,523 个注册用户
🤖 回答: 系统目前有 10,523 个注册用户
✅ 包含用户数量 - AI 可能调用了工具!
🧪 测试3:让 AI 发送邮件
👤 问题: 请给 zhangsan@example.com 发送一封邮件,主题是"会议提醒",内容是"明天下午3点开会"
⏰ 观察是否有 '🔧 工具调用' 日志...
🔧 工具调用: sendEmail(zhangsan@example.com, "会议提醒")
✅ 返回: 邮件已发送给 zhangsan@example.com,邮件ID: MAIL-1700000000000
🤖 回答: 邮件已发送给 zhangsan@example.com,邮件ID: MAIL-1700000000000
✅ 包含邮件发送结果 - AI 可能调用了工具!
✅ 测试通过ChatClient 配置工具后,AI 能自动调用函数🔧 工具调用 日志说明函数被成功调用
前面的示例已经分别演示了时间查询、数据库查询、文件操作、发送通知等工具函数的定义方式。
在真实项目中,更推荐直接通过 ChatClient.defaultTools(...) 暴露这些工具,
由大模型在对话中按需选择调用,而不是再写一个额外的"汇总测试"去逐个打印结果。
因此,本节不再提供额外单元测试代码,只作为一个小结: 只要单个工具函数已经通过各自的测试验证,且成功注册到 ChatClient, 就可以认为整个工具集是可被大模型正常调用的。
运行完整测试类,验证所有工具函数功能:
mvn test -Dtest=Test07_ToolAnnotationTests run: 2, Failures: 0, Errors: 0, Skipped: 0
✅ 所有测试通过!| 测试编号 | 测试名称 | 核心知识点 |
|---|---|---|
| 7.0 | 使用 ChatClient 配置工具函数 | ChatClient.defaultTools() |
| 7.1 | 工具函数小结 | 最佳实践 |
请完成下面的练习,巩固本章的“Function Calling / 工具调用”核心机制与工程化落地方式
⚠️ 提示:高频点集中在 ChatClient.defaultTools()、@Tool 描述质量、参数设计、以及“模型如何选择工具”的可观测性(日志/trace)。
@Tool 方法 validateEmail(String to);当邮箱不合法时返回明确的错误信息。
然后在对话提示词中引导模型“先校验邮箱,再发送邮件”。观察日志是否出现两次工具调用。
@Tool(description = "校验邮箱格式是否正确,返回校验结果")
public String validateEmail(@ToolParam(description = "收件人邮箱") String to) {
boolean ok = to != null && to.matches("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
return ok ? "OK" : "INVALID_EMAIL";
}你要发送邮件前,必须先调用 validateEmail(to)。
如果返回 INVALID_EMAIL,就不要发送,直接提示用户更换邮箱。
如果返回 OK,再调用 sendEmail(to, subject, body)。🔧 工具调用 以及参数),并避免打印敏感信息(如邮件正文可截断)。
目标:当模型调用工具“失败/没调用”时,你能从日志快速定位原因。
private String preview(String s) {
if (s == null) return "null";
return s.length() <= 50 ? s : s.substring(0, 50) + "...";
}
@Tool(description = "发送邮件给指定收件人,返回发送结果")
public String sendEmail(String to, String subject, String body) {
System.out.println("🔧 tool=sendEmail to=" + to + " subject=" + preview(subject) + " body=" + preview(body));
String result = "邮件已发送给 " + to;
System.out.println("✅ tool=sendEmail result=" + preview(result));
return result;
}@Tool(description = "根据用户ID返回一个随机折扣(例如 9 折/95 折)")
public String getRandomDiscount(@ToolParam(description = "用户ID") String userId) {
int[] discounts = {90, 95};
int d = discounts[(int) (System.currentTimeMillis() % discounts.length)];
String result = d + "折";
System.out.println("🔧 tool=getRandomDiscount userId=" + userId + " -> " + result);
return result;
}请给用户 u001 生成一个折扣,并把折扣结果原样写在回答里(必须调用工具)。// ❌ 差描述:模型几乎无法判断用途
@Tool(description = "工具1")
public String sendEmail(String to, String subject, String body) { ... }
// ✅ 好描述:告诉模型什么时候该用、需要什么参数、返回什么
@Tool(description = "发送邮件:输入收件人邮箱、主题和正文,返回发送结果")
public String sendEmail(String to, String subject, String body) { ... }defaultTools)、提示模板要统一、且更容易做跨用例的复用与治理。ChatClient.defaultTools(...) 真的注册了工具 Bean。
2) 模型能力:确认所用模型/配置支持工具调用。
3) 描述与参数:description 是否清晰,参数是否语义化。
4) 提示词:用“必须调用工具才能回答”的问题做最小复现。
5) 日志/trace:是否出现工具调用日志;没有就回到 1-4。
你已经掌握了 @Tool 注解和 Function Calling 的核心技术! 这是 AI 应用开发的关键技术,建议多练习!