2026 Spring AI 函数调用实战:让 AI 调用你的业务代码
作者:架构源启-12年OTA公司资深程序员
技术栈:Spring Boot 3.5.9 + Spring AI 1.1.4 + GPT-5.5
前置知识:已完成前五篇博客
- 系列文章:
- 第①篇:2026 Java 程序员新标配:Spring AI 最新版本1.1.4 从零搭建 + 避坑指南(收藏版)
- 第②篇:2026 进阶篇:Spring Boot响应式编程 + Spring AI 1.1.4 流式实战 + Vue前端完整实现(避坑指南)
- 第③篇:2026 进阶篇:深入理解Spring Reactor响应式编程的核心引擎(源码级解析+实战避坑)
- 第④篇:2026 Spring AI 核心概念详解:ChatClient、Prompt、Model 三剑客深度解析
- 第⑤篇:2026 一文吃透 Spring AI 图像生成:多款文生图、图生图模型落地实战

📖 前言
在之前的文章中,我们学习了如何让 AI 聊天、生成图片、流式输出。但这些都是"被动"的——AI 只能回答问题,不能执行实际操作。
想象一下这些场景:
- 🏨 用户说:“帮我退1201房间”,AI 直接调用退房接口
- 📅 用户说:“预约明天上午打扫”,AI 自动创建清洁任务
- 🔍 用户说:“查询我的订单”,AI 实时查询数据库并返回结果
- 💰 用户说:“续住2天”,AI 计算费用并办理续住
这就是 Function Calling(函数调用) 的魅力!它让 AI 从"问答机器人"升级为"智能助手"。
本文你将学到
✅ Function Calling 的核心原理
✅ Spring AI 函数注册机制
✅ 酒店预订场景完整实战
✅ 意图识别与参数提取
✅ 多函数组合调用
✅ 错误处理与重试机制
✅ 实战:智能酒店助手
准备好了吗?让我们开始吧!🚀
🎯 一、Function Calling 是什么?
1.1 传统方式的局限
传统流程:
用户输入 → AI 理解 → 返回文本 → 程序解析 → 执行业务
问题:
- ❌ 需要复杂的自然语言处理
- ❌ 意图识别准确率低
- ❌ 参数提取困难
- ❌ 容易出错
1.2 Function Calling 的优势
新流程:
用户输入 → AI 理解 → 选择函数 → 提取参数 → 调用函数 → 返回结果
优势:
- ✅ AI 自动选择要调用的函数
- ✅ AI 自动提取参数
- ✅ 类型安全
- ✅ 结构化输出
- ✅ 更准确的意图识别
1.3 工作原理:四步闭环
Function Calling 并不是模型直接“运行”了你的代码,而是一个**“建议-执行-反馈”**的协作过程:
第一步:定义工具 (Definition)
开发者需要向模型提供一组可用的“工具”描述。这些描述通常包括:
- 函数名:如
get_weather。 - 功能描述:如“获取指定城市的当前天气”。
- 参数结构:如
location(字符串, 必填),unit(枚举: celsius/fahrenheit)。 - 注意:此时模型只知道有哪些工具可用,但不知道如何执行。
第二步:模型决策 (Decision)
当用户输入一个问题(例如:“孝感今天冷吗?”)时,模型会结合上下文和工具描述进行推理:
- 如果模型认为自己无法直接回答(因为它没有实时天气数据),它会决定调用工具。
- 模型不会直接返回文本,而是返回一个特殊的 JSON 对象,包含它建议调用的函数名和提取出的参数。
- 输出示例:
{"name": "get_weather", "arguments": {"location": "Xiaogan", "unit": "celsius"}}
第三步:外部执行 (Execution)
这是最关键的一步:模型本身不执行代码。
- 你的应用程序(后端代码)接收到模型返回的 JSON。
- 程序解析 JSON,识别出要调用
get_weather函数。 - 程序在本地或通过 API 真正执行这个函数,并获取结果(例如:“北京,15°C,多云”)。
第四步:结果回填 (Response)
- 程序将函数的执行结果作为一个新的“消息”发送回给模型。
- 模型看到这个结果后,结合原始问题,生成最终的自然语言回复给用户:“北京今天气温 15°C,有点凉,建议穿件外套。”
1.4 为什么需要 Function Calling?
在没有 Function Calling 之前,模型存在以下局限:
- 知识截止:模型无法知道训练数据之后的新闻、股价或天气。
- 幻觉问题:模型可能会编造事实。通过调用权威 API,可以确保信息的准确性。
- 行动能力弱:模型只能“说”,不能“做”。Function Calling 让模型能操作数据库、发送邮件或控制智能家居。
1.5 进阶视野:Function Calling 与 MCP 的关联
如果你关注 AI 领域的最新动态,可能听说过 MCP (Model Context Protocol)。它与 Function Calling 有着千丝万缕的联系:
- Function Calling 是“基石”:它是大模型与外部世界交互的底层技术标准。无论是 OpenAI 还是 Anthropic,都在使用这种机制。
- MCP 是“通用插座”:MCP 是一个开放协议,旨在标准化模型如何连接到各种数据源(如数据库、文件系统)和工具。在 MCP 架构中,模型正是通过 Function Calling 来触发 MCP 服务器提供的各种能力。
- 从“私有”到“生态”:目前的 Function Calling 通常需要开发者为每个应用手动定义工具。而 MCP 的目标是建立一个庞大的工具生态,让你的 Spring AI 应用可以像插 USB 一样,即插即用各种现成的 MCP 服务(如 GitHub、PostgreSQL、Slack 等)。
总结:你现在学习的 Function Calling 是通往更高级 AI Agent 架构的必经之路。Spring AI 社区也在积极跟进 MCP 标准,未来你将能通过更简洁的方式集成这些强大的外部能力。
🔧 二、Spring AI 函数注册
2.1 定义函数
方式一:使用 @Bean 注册
@Configuration
public class HotelFunctionConfig {
/**
* 退房函数
*/
@Bean
public FunctionCallback checkOutFunction() {
return FunctionCallback.builder()
.name("checkOut")
.description("办理酒店退房手续")
.function("checkOut", (String roomNo) -> {
// 调用业务逻辑
System.out.println("办理退房,房间号: " + roomNo);
return "房间 " + roomNo + " 已成功退房";
})
.inputType(String.class)
.build();
}
/**
* 续住函数
*/
@Bean
public FunctionCallback extendStayFunction() {
return FunctionCallback.builder()
.name("extendStay")
.description("办理酒店续住")
.function("extendStay", (ExtendStayRequest request) -> {
System.out.println("办理续住,房间号: " + request.getRoomNo()
+ ", 天数: " + request.getDays());
return "房间 " + request.getRoomNo()
+ " 已续住 " + request.getDays() + " 天";
})
.inputType(ExtendStayRequest.class)
.build();
}
}
方式二:使用注解
@Component
public class HotelFunctions {
@Tool(description = "办理酒店退房手续")
public String checkOut(
@ToolParam(description = "房间号") String roomNo) {
// 业务逻辑
return "房间 " + roomNo + " 已成功退房";
}
@Tool(description = "办理酒店续住")
public String extendStay(
@ToolParam(description = "房间号") String roomNo,
@ToolParam(description = "续住天数") int days) {
// 业务逻辑
return "房间 " + roomNo + " 已续住 " + days + " 天";
}
@Tool(description = "查询房间状态")
public RoomStatus queryRoomStatus(
@ToolParam(description = "房间号") String roomNo) {
// 查询数据库
return new RoomStatus(roomNo, "occupied", "2026-05-05");
}
}
2.2 配置 ChatClient
@RestController
@RequestMapping("/hotel")
public class HotelAssistantController {
private final ChatClient chatClient;
public HotelAssistantController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("""
你是酒店智能助手小智。
你可以帮客人办理:退房、续住、查询、打扫、预订。
当需要执行操作时,使用相应的函数。
""")
.build();
}
}
💡 三、酒店预订场景实战
3.1 需求分析
我们要实现一个酒店智能助手,支持以下功能:
- 退房:
checkOut(roomNo) - 续住:
extendStay(roomNo, days) - 查询:
queryRoomStatus(roomNo) - 打扫:
scheduleCleaning(roomNo, time) - 预订:
bookRoom(roomType, checkInDate, days)
3.2 定义数据模型
@Data
public class ExtendStayRequest {
private String roomNo;
private Integer days;
}
@Data
public class RoomStatus {
private String roomNo;
private String status; // occupied, vacant, cleaning
private String checkOutDate;
}
@Data
public class CleaningRequest {
private String roomNo;
private String time; // 如 "明天上午10点"
}
@Data
public class BookingRequest {
private String roomType; // single, double, suite
private String checkInDate;
private Integer days;
}
3.3 实现函数
@Component
@Slf4j
public class HotelFunctions {
@Autowired
private HotelService hotelService;
@Tool(description = "办理酒店退房手续")
public String checkOut(
@ToolParam(description = "房间号,如 1201") String roomNo) {
try {
log.info("办理退房,房间号: {}", roomNo);
hotelService.checkOut(roomNo);
return "房间 " + roomNo + " 已成功退房。祝您旅途愉快!";
} catch (Exception e) {
log.error("退房失败", e);
return "退房失败: " + e.getMessage();
}
}
@Tool(description = "办理酒店续住")
public String extendStay(
@ToolParam(description = "房间号") String roomNo,
@ToolParam(description = "续住天数") int days) {
try {
log.info("办理续住,房间号: {}, 天数: {}", roomNo, days);
// 查询当前退房日期
RoomStatus status = hotelService.queryRoomStatus(roomNo);
// 计算新的退房日期
LocalDate newCheckOut = LocalDate.parse(status.getCheckOutDate())
.plusDays(days);
// 办理续住
hotelService.extendStay(roomNo, days);
return String.format("房间 %s 已续住 %d 天,新的退房日期是 %s",
roomNo, days, newCheckOut);
} catch (Exception e) {
log.error("续住失败", e);
return "续住失败: " + e.getMessage();
}
}
@Tool(description = "查询房间状态")
public RoomStatus queryRoomStatus(
@ToolParam(description = "房间号") String roomNo) {
try {
log.info("查询房间状态,房间号: {}", roomNo);
return hotelService.queryRoomStatus(roomNo);
} catch (Exception e) {
log.error("查询失败", e);
throw new RuntimeException("查询房间状态失败");
}
}
@Tool(description = "预约房间打扫")
public String scheduleCleaning(
@ToolParam(description = "房间号") String roomNo,
@ToolParam(description = "打扫时间,如 明天上午10点") String time) {
try {
log.info("预约打扫,房间号: {}, 时间: {}", roomNo, time);
hotelService.scheduleCleaning(roomNo, time);
return String.format("已预约房间 %s 在 %s 进行打扫", roomNo, time);
} catch (Exception e) {
log.error("预约打扫失败", e);
return "预约打扫失败: " + e.getMessage();
}
}
@Tool(description = "预订酒店房间")
public String bookRoom(
@ToolParam(description = "房型:single, double, suite") String roomType,
@ToolParam(description = "入住日期,格式 YYYY-MM-DD") String checkInDate,
@ToolParam(description = "入住天数") int days) {
try {
log.info("预订房间,房型: {}, 入住日期: {}, 天数: {}",
roomType, checkInDate, days);
// 查询可用房间
List<String> availableRooms = hotelService.findAvailableRooms(
roomType, checkInDate, days);
if (availableRooms.isEmpty()) {
return "抱歉,该时间段没有可用的" + getRoomTypeName(roomType);
}
// 预订第一个可用房间
String roomNo = availableRooms.get(0);
hotelService.bookRoom(roomNo, checkInDate, days);
return String.format("已成功预订 %s(%s),入住日期 %s,共 %d 天",
roomNo, getRoomTypeName(roomType), checkInDate, days);
} catch (Exception e) {
log.error("预订失败", e);
return "预订失败: " + e.getMessage();
}
}
private String getRoomTypeName(String roomType) {
return switch (roomType.toLowerCase()) {
case "single" -> "单人间";
case "double" -> "双人间";
case "suite" -> "套房";
default -> roomType;
};
}
}
3.4 控制器
@RestController
@RequestMapping("/hotel")
public class HotelAssistantController {
private final ChatClient chatClient;
public HotelAssistantController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("""
你是酒店智能助手小智。
你可以帮客人办理:退房、续住、查询、打扫、预订。
当需要执行操作时,使用相应的函数。
回答要友好、专业、简洁。
""")
.build();
}
@PostMapping("/chat")
public Map<String, Object> chat(@RequestBody ChatRequest request) {
try {
// 调用 AI(自动处理函数调用)
ChatResponse response = chatClient.prompt()
.user(request.getMessage())
.call();
// 获取回复
String reply = response.getResult().getOutput().getContent();
// 返回结果
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("reply", reply);
result.put("sessionId", request.getSessionId());
return result;
} catch (Exception e) {
log.error("聊天失败", e);
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", "服务暂时不可用");
return error;
}
}
}
注意:Spring AI 会自动处理函数调用,无需手动干预!
🎨 四、高级功能
4.1 多轮对话中的函数调用
@PostMapping("/conversation")
public Map<String, Object> conversation(@RequestBody ConversationRequest request) {
// 构建消息历史
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是酒店智能助手"));
// 添加历史消息
for (MessageHistory history : request.getHistory()) {
if ("user".equals(history.getRole())) {
messages.add(new UserMessage(history.getContent()));
} else if ("assistant".equals(history.getRole())) {
messages.add(new AssistantMessage(history.getContent()));
}
}
// 添加当前消息
messages.add(new UserMessage(request.getCurrentMessage()));
// 调用 AI(支持函数调用)
ChatResponse response = chatClient.prompt()
.messages(messages)
.call();
String reply = response.getResult().getOutput().getContent();
// 保存对话历史
saveConversation(request.getSessionId(), messages, reply);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("reply", reply);
return result;
}
4.2 并行函数调用
某些场景下,AI 可能需要同时调用多个函数:
// 用户说:"帮我查1201和1202房间的状态"
// AI 会并行调用两次 queryRoomStatus
@Tool(description = "批量查询房间状态")
public List<RoomStatus> queryMultipleRooms(
@ToolParam(description = "房间号列表") List<String> roomNos) {
return roomNos.stream()
.map(roomNo -> hotelService.queryRoomStatus(roomNo))
.collect(Collectors.toList());
}
4.3 条件函数调用
@Tool(description = "根据情况选择合适的操作")
public String handleGuestRequest(
@ToolParam(description = "用户需求描述") String request) {
// 分析需求
if (request.contains("退房")) {
return checkOut(extractRoomNo(request));
} else if (request.contains("续住")) {
return extendStay(extractRoomNo(request), extractDays(request));
} else if (request.contains("查询")) {
return queryRoomStatus(extractRoomNo(request)).toString();
} else {
return "抱歉,我不太理解您的需求。您可以这样说:\n" +
"- 帮我退1201房间\n" +
"- 我想续住2天\n" +
"- 查询1201房间状态";
}
}
4.4 函数调用日志
@Component
public class FunctionCallLogger {
@EventListener
public void onFunctionCall(FunctionCallEvent event) {
log.info("函数调用: {}({}), 耗时: {}ms",
event.getFunctionName(),
event.getArguments(),
event.getDuration());
}
}
⚠️ 五、常见问题与解决方案
问题1:函数未被调用
现象:AI 没有调用注册的函数
原因:
- 函数描述不清晰
- 提示词未说明可以使用函数
- 模型不支持 Function Calling
解决方案:
// 1. 优化函数描述
@Tool(description = "办理酒店退房手续。当用户想要退房时使用此函数。")
// 2. 在系统提示词中说明
.defaultSystem("""
你是酒店智能助手。
你可以使用以下函数来帮助用户:
- checkOut: 办理退房
- extendStay: 办理续住
- queryRoomStatus: 查询房间状态
当用户请求相关服务时,请调用相应的函数。
""")
// 3. 使用支持 Function Calling 的模型
.model("gpt-4o") // 或 gpt-3.5-turbo
问题2:参数提取错误
现象:AI 提取的参数不正确
解决方案:
// 1. 提供更详细的参数描述
@ToolParam(description = "房间号,必须是4位数字,如 1201")
// 2. 添加参数验证
@Tool(description = "办理退房")
public String checkOut(@ToolParam(description = "房间号") String roomNo) {
// 验证房间号格式
if (!roomNo.matches("\\d{4}")) {
return "房间号格式错误,请输入4位数字,如 1201";
}
// 业务逻辑
return hotelService.checkOut(roomNo);
}
问题3:函数执行失败
现象:函数调用后返回错误
解决方案:
@Tool(description = "办理退房")
public String checkOut(@ToolParam(description = "房间号") String roomNo) {
try {
// 业务逻辑
hotelService.checkOut(roomNo);
return "退房成功";
} catch (RoomNotFoundException e) {
return "房间 " + roomNo + " 不存在,请检查房间号";
} catch (RoomNotOccupiedException e) {
return "房间 " + roomNo + " 当前无人入住,无法退房";
} catch (Exception e) {
log.error("退房失败", e);
return "退房失败,请稍后重试或联系前台";
}
}
问题4:多次调用同一函数
现象:AI 反复调用同一个函数
原因:返回值不够明确
解决方案:
// 返回更明确的结果
@Tool(description = "办理退房")
public String checkOut(@ToolParam(description = "房间号") String roomNo) {
hotelService.checkOut(roomNo);
// 明确告知已完成
return String.format(
"✅ 退房已完成\n" +
"房间号:%s\n" +
"退房时间:%s\n" +
"祝您旅途愉快!",
roomNo,
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
);
}
📊 六、性能优化建议
6.1 函数缓存
对于查询类函数,可以使用缓存减少重复调用。
@Component
public class HotelFunctions {
@Autowired
private HotelService hotelService;
/**
* 查询房间状态(带缓存)
* 缓存5分钟,避免频繁查询数据库
*/
@Tool(description = "查询房间状态")
@Cacheable(value = "room-status", key = "#roomNo", unless = "#result == null")
public RoomStatus queryRoomStatus(
@ToolParam(description = "房间号") String roomNo) {
log.info("Querying room status for: {}", roomNo);
return hotelService.queryRoomStatus(roomNo);
}
/**
* 清除房间状态缓存
* 在退房、续住等操作后调用
*/
@CacheEvict(value = "room-status", key = "#roomNo")
public void clearRoomStatusCache(String roomNo) {
log.info("Cleared cache for room: {}", roomNo);
}
}
缓存配置:
spring:
cache:
type: redis
redis:
time-to-live: 300000 # 5分钟过期
key-prefix: "hotel:room:"
效果对比:
| 场景 | 无缓存 | 有缓存 | 提升 |
|---|---|---|---|
| 首次查询 | 200ms | 200ms | - |
| 重复查询 | 200ms | 5ms | 40倍 |
| 数据库压力 | 高 | 低 | 95%降低 |
6.2 异步函数调用
对于耗时操作(如发送短信、邮件),使用异步执行。
@Component
public class NotificationFunctions {
@Autowired
private SmsService smsService;
@Autowired
private EmailService emailService;
/**
* 发送确认短信(异步)
*/
@Tool(description = "发送确认短信给用户")
@Async("notificationExecutor")
public CompletableFuture<String> sendConfirmationSms(
@ToolParam(description = "手机号") String phone,
@ToolParam(description = "消息内容") String message) {
log.info("Sending SMS to: {}", phone);
try {
smsService.send(phone, message);
log.info("SMS sent successfully");
return CompletableFuture.completedFuture("短信已发送");
} catch (Exception e) {
log.error("Failed to send SMS", e);
return CompletableFuture.completedFuture("短信发送失败");
}
}
/**
* 发送确认邮件(异步)
*/
@Tool(description = "发送确认邮件给用户")
@Async("notificationExecutor")
public CompletableFuture<String> sendConfirmationEmail(
@ToolParam(description = "邮箱地址") String email,
@ToolParam(description = "邮件主题") String subject,
@ToolParam(description = "邮件内容") String content) {
log.info("Sending email to: {}", email);
try {
emailService.send(email, subject, content);
log.info("Email sent successfully");
return CompletableFuture.completedFuture("邮件已发送");
} catch (Exception e) {
log.error("Failed to send email", e);
return CompletableFuture.completedFuture("邮件发送失败");
}
}
}
线程池配置:
@Configuration
public class AsyncConfig {
@Bean("notificationExecutor")
public Executor notificationExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("notification-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
启用异步:
@SpringBootApplication
@EnableAsync // 启用异步支持
public class HotelApplication {
public static void main(String[] args) {
SpringApplication.run(HotelApplication.class, args);
}
}
6.3 批量处理
当需要处理多个相似任务时,使用批量操作提高效率。
@Component
public class BatchFunctions {
@Autowired
private HotelService hotelService;
/**
* 批量办理退房
*/
@Tool(description = "批量办理多个房间的退房手续")
public String batchCheckOut(
@ToolParam(description = "房间号列表,如 [\"1201\", \"1202\", \"1203\"]")
List<String> roomNos) {
log.info("Batch check-out for {} rooms", roomNos.size());
List<String> results = roomNos.parallelStream()
.map(roomNo -> {
try {
hotelService.checkOut(roomNo);
return "✅ " + roomNo + " 退房成功";
} catch (Exception e) {
log.error("Check-out failed for room: {}", roomNo, e);
return "❌ " + roomNo + " 退房失败: " + e.getMessage();
}
})
.collect(Collectors.toList());
int successCount = results.stream()
.filter(r -> r.startsWith("✅"))
.count();
return String.format(
"批量退房完成\n" +
"总计: %d 间\n" +
"成功: %d 间\n" +
"失败: %d 间\n\n" +
"详情:\n%s",
roomNos.size(),
successCount,
roomNos.size() - successCount,
String.join("\n", results)
);
}
/**
* 批量查询房间状态
*/
@Tool(description = "批量查询多个房间的状态")
public String batchQueryRoomStatus(
@ToolParam(description = "房间号列表") List<String> roomNos) {
List<RoomStatus> statuses = roomNos.parallelStream()
.map(roomNo -> {
try {
return hotelService.queryRoomStatus(roomNo);
} catch (Exception e) {
log.error("Query failed for room: {}", roomNo, e);
return new RoomStatus(roomNo, "error", null);
}
})
.collect(Collectors.toList());
// 格式化为表格
StringBuilder sb = new StringBuilder();
sb.append("房间状态查询结果:\n\n");
sb.append("| 房间号 | 状态 | 退房日期 |\n");
sb.append("|--------|------|----------|\n");
for (RoomStatus status : statuses) {
sb.append(String.format("| %s | %s | %s |\n",
status.getRoomNo(),
getStatusText(status.getStatus()),
status.getCheckOutDate() != null ? status.getCheckOutDate() : "-"
));
}
return sb.toString();
}
private String getStatusText(String status) {
return switch (status) {
case "occupied" -> "入住中";
case "vacant" -> "空闲";
case "cleaning" -> "打扫中";
case "error" -> "查询失败";
default -> status;
};
}
}
性能对比:
| 方式 | 10个房间 | 100个房间 | 说明 |
|---|---|---|---|
| 串行处理 | 2s | 20s | 逐个处理 |
| 并行处理 | 0.5s | 2s | 并行流 |
| 批量SQL | 0.2s | 0.5s | 一次性查询 |
6.4 函数调用日志
记录所有函数调用,便于审计和问题排查。
@Component
@Slf4j
public class FunctionCallLogger {
@Autowired
private AuditRepository auditRepository;
/**
* 监听函数调用事件
*/
@EventListener
@Async("auditExecutor")
public void onFunctionCall(FunctionCallEvent event) {
AuditLog auditLog = AuditLog.builder()
.timestamp(LocalDateTime.now())
.functionName(event.getFunctionName())
.arguments(maskSensitiveData(event.getArguments())) // 脱敏
.duration(event.getDuration())
.success(event.isSuccess())
.errorMessage(event.getErrorMessage())
.userId(getCurrentUserId())
.build();
auditRepository.save(auditLog);
// 记录日志
if (event.isSuccess()) {
log.info("Function call: {}({}) - {}ms",
event.getFunctionName(),
maskSensitiveData(event.getArguments()),
event.getDuration());
} else {
log.error("Function call failed: {}({}) - {}ms - {}",
event.getFunctionName(),
maskSensitiveData(event.getArguments()),
event.getDuration(),
event.getErrorMessage());
}
}
/**
* 脱敏敏感数据
*/
private String maskSensitiveData(String arguments) {
if (arguments == null) return "";
// 隐藏手机号
arguments = arguments.replaceAll("\\d{3}\\d{4}\\d{4}", "$1****$3");
// 隐藏身份证
arguments = arguments.replaceAll("\\d{6}\\d{8}\\d{4}", "$1********$3");
return arguments;
}
private String getCurrentUserId() {
// 从 SecurityContext 获取当前用户ID
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth != null ? auth.getName() : "anonymous";
}
}
审计日志实体:
@Entity
@Table(name = "audit_logs")
@Data
@Builder
public class AuditLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDateTime timestamp;
@Column(length = 100)
private String functionName;
@Column(length = 2000)
private String arguments;
private Long duration; // 毫秒
private Boolean success;
@Column(length = 500)
private String errorMessage;
@Column(length = 50)
private String userId;
}
查询审计日志:
@RestController
@RequestMapping("/admin/audit")
public class AuditController {
@Autowired
private AuditRepository auditRepository;
@GetMapping("/logs")
public Page<AuditLog> getAuditLogs(
@RequestParam(required = false) String functionName,
@RequestParam(required = false) Boolean success,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Specification<AuditLog> spec = Specification.where(null);
if (functionName != null) {
spec = spec.and((root, query, cb) ->
cb.equal(root.get("functionName"), functionName));
}
if (success != null) {
spec = spec.and((root, query, cb) ->
cb.equal(root.get("success"), success));
}
return auditRepository.findAll(spec,
PageRequest.of(page, size, Sort.by("timestamp").descending()));
}
}
6.5 熔断降级
防止某个函数故障影响整个系统。
@Component
public class ResilientHotelFunctions {
@Autowired
private HotelService hotelService;
/**
* 带熔断的退房函数
*/
@Tool(description = "办理酒店退房手续")
@CircuitBreaker(name = "checkOutService", fallbackMethod = "checkOutFallback")
@Retry(name = "checkOutService", fallbackMethod = "checkOutRetryFallback")
public String checkOut(@ToolParam(description = "房间号") String roomNo) {
log.info("Processing check-out for room: {}", roomNo);
try {
hotelService.checkOut(roomNo);
return String.format(
"✅ 退房成功\n" +
"房间号:%s\n" +
"时间:%s\n" +
"祝您旅途愉快!",
roomNo,
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
);
} catch (Exception e) {
log.error("Check-out failed", e);
throw e; // 抛出异常触发熔断/重试
}
}
/**
* 重试降级
*/
public String checkOutRetryFallback(String roomNo, Exception e) {
log.warn("Check-out retry failed for room: {}", roomNo, e);
return "抱歉,系统繁忙,请稍后重试或联系前台办理退房。";
}
/**
* 熔断降级
*/
public String checkOutFallback(String roomNo, Exception e) {
log.error("Check-out circuit breaker opened for room: {}", roomNo, e);
return "抱歉,退房服务暂时不可用,请直接联系前台办理。电话:400-xxx-xxxx";
}
}
Resilience4j 配置:
resilience4j:
circuitbreaker:
instances:
checkOutService:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 5
retry:
instances:
checkOutService:
max-attempts: 3
wait-duration: 1s
exponential-backoff-multiplier: 2
监控面板:
management:
endpoints:
web:
exposure:
include: health,metrics,circuitbreakers
health:
circuitbreakers:
enabled: true
访问 http://localhost:8080/actuator/circuitbreakers 查看熔断器状态。
📝 七、总结与最佳实践
7.1 核心要点回顾
函数注册
- ✅ 使用
@Tool注解或FunctionCallbackBean - ✅ 提供清晰的函数描述
- ✅ 详细说明每个参数的含义
提示词设计
- ✅ 在系统提示词中说明可用函数
- ✅ 指导 AI 何时使用函数
- ✅ 提供示例对话
错误处理
- ✅ 函数内部捕获异常
- ✅ 返回友好的错误信息
- ✅ 记录详细日志
7.2 设计原则
- 单一职责:每个函数只做一件事
- 明确命名:函数名和参数名要清晰
- 详细描述:description 要具体
- 健壮性:处理好异常情况
- 可测试:编写单元测试
7.3 安全注意事项
- 🔒 验证所有输入参数
- 🔒 实施权限控制
- 🔒 记录审计日志
- 🔒 限制函数调用频率
- 🔒 防止注入攻击
🔮 八、下一步学习路径
恭喜你已经掌握了 Function Calling 的核心技能!接下来可以学习:
关注账号,后续持续更新内容
- [第7篇] Spring AI RAG 实战 - 基于知识库的智能问答
- [第8篇] Spring AI 多模型路由 - 动态切换多个模型
- [第9篇] Spring AI 微服务架构 - 生产级架构设计
实践项目
- 🏨 完善酒店智能助手
- 🛒 开发电商客服机器人
- 📅 构建智能日程管理助手
- 💼 实现企业OA智能助手
💬 互动环节
有问题?
欢迎在评论区留言!
觉得有用?
- ⭐ 点赞支持
- 💾 收藏备用
- 🔄 分享给朋友
下一篇预告:《Spring AI RAG 实战:基于知识库的智能问答系统》

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)