一、Function Call 是什么
Function Call 是让 AI 调用外部工具的功能。简单说,就是 AI 可以调用你写的 Java 方法,执行实际的业务逻辑。
举个例子:
- 用户问:”查询我的航班信息,预订号是 101,乘客姓名是徐庶”
- AI 分析后,调用
queryFlightInfo("101", "徐庶") 工具
- 工具返回航班信息
- AI 根据返回结果生成回复:”您的航班信息如下:…”
Function Call 的优势:
- 扩展能力:AI 可以调用外部工具,不再局限于文本生成
- 实时数据:可以获取实时数据,如天气、股票、航班信息等
- 业务集成:可以调用业务系统,实现真正的业务逻辑
什么时候需要 Function Call:
- 需要查询数据库
- 需要调用外部 API
- 需要执行业务操作(增删改查)
- 需要获取实时信息
二、工具定义
使用 @Tool 注解定义工具方法,AI 就能识别并调用这个方法。
最简单的例子:
1 2 3 4 5 6 7 8 9
| @Service public class ToolService {
@Tool(description = "查询航班信息") public String queryFlightInfo(String bookingNumber, String passengerName) { return "航班信息:..."; } }
|
关键点:
@Tool 注解标记这是一个工具方法
description 参数很重要,AI 根据这个描述判断是否需要调用
- 方法必须是 public 的
- 返回值通常是 String,AI 会读取这个返回值
2.2 description 的重要性
description 是 AI 判断是否调用工具的唯一依据,所以描述要准确、清晰。
好的描述:
1 2 3 4
| @Tool(description = "查询航班信息,需要提供预订号和乘客姓名") public String queryFlightInfo(String bookingNumber, String passengerName) { }
|
不好的描述:
1 2 3 4
| @Tool(description = "查询") public String queryFlightInfo(String bookingNumber, String passengerName) { }
|
描述要点:
- 清晰说明工具的功能
- 说明需要什么参数
- 说明返回什么结果
- 避免歧义
2.3 参数设计
工具方法的参数设计很重要,影响 AI 是否能正确调用。
实际案例:
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
| @Service public class ToolService {
@Resource private FlightBookingService flightBookingService;
@Tool(description = "查询航班信息,需要提供预订号和乘客姓名") public String queryFlightInfo( @ToolParam String bookingNumber, @ToolParam String passengerName) { FlightBooking booking = flightBookingService .findBookingByBookingNumberAndName(bookingNumber, passengerName); if (booking != null) { return "航班信息:" + booking; } return "未找到航班信息"; }
@Tool(description = "退票/取消预定,需要提供预订号和乘客姓名") public String cancelBooking( String bookingNumber, String passengerName) { flightBookingService.cancelBookingByBookingNumberAndName( bookingNumber, passengerName); return "退票成功"; } }
|
参数设计要点:
- 类型明确:使用明确的类型(String、int、double 等),避免 Object
- 数量适中:建议不超过 5 个参数
- 命名清晰:参数名要能表达含义
- @ToolParam:虽然不强制,但建议使用,提高可读性
参数类型支持:
- 基本类型:String、int、double、boolean 等
- 包装类型:Integer、Double、Boolean 等
- 日期类型:LocalDate、LocalDateTime 等
- 复杂对象:需要 AI 能理解的结构
2.4 返回值设计
返回值通常是 String,AI 会读取这个返回值并生成回复。
好的返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Tool(description = "查询航班信息") public String queryFlightInfo(String bookingNumber, String passengerName) { FlightBooking booking = flightBookingService .findBookingByBookingNumberAndName(bookingNumber, passengerName); if (booking != null) { return String.format( "预订号:%s,乘客:%s,出发地:%s,目的地:%s,出发日期:%s,状态:%s", booking.getId(), booking.getPassengerName(), booking.getFrom(), booking.getTo(), booking.getDepartureDate(), booking.getStatus() ); } return "未找到航班信息,请检查预订号和乘客姓名是否正确"; }
|
返回值设计要点:
- 信息完整:包含 AI 需要的信息
- 格式清晰:便于 AI 理解和处理
- 错误提示:失败时返回有意义的错误信息
三、工具集成
工具定义好后,需要配置 ToolCallbackProvider 来注册工具。
配置方式:
1 2 3 4 5 6 7 8 9 10
| @SpringBootApplication public class ToolsApplication { @Bean public ToolCallbackProvider toolCallbackProvider(ToolService toolService) { return MethodToolCallbackProvider.builder() .toolObjects(toolService) .build(); } }
|
关键点:
MethodToolCallbackProvider:基于方法反射的工具提供者
toolObjects:传入包含 @Tool 注解的对象
- 可以传入多个对象:
.toolObjects(service1, service2, service3)
3.2 ChatClient 集成
在 ChatClient 中配置工具:
1 2 3 4 5 6 7 8 9
| @Autowired private ToolService toolService; @Autowired private ToolCallbackProvider toolCallbackProvider;
ChatClient chatClient = ChatClient.builder(chatModel) .defaultTools(toolService) .defaultToolCallbacks(toolCallbackProvider) .build();
|
注意:
defaultTools 和 defaultToolCallbacks 都要配置
defaultTools 告诉 AI 有哪些工具可用
defaultToolCallbacks 提供工具的实际执行逻辑
3.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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| @RestController @RequestMapping("/api/chat") @CrossOrigin(origins = "*") public class ChatController {
@Autowired private ChatModel chatModel;
@Autowired private ChatMemory chatMemory; @Autowired private ToolService toolService; @Autowired private ToolCallbackProvider toolCallbackProvider;
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamChat(@RequestParam String message) { if (message == null || message.trim().isEmpty()) { return Flux.error(new IllegalArgumentException("message参数不能为空")); }
ChatClient chatClient = ChatClient.builder(chatModel) .defaultSystem(""" #角色 你是一个专业的航空客服助手,名为'南方国际航空'的客服代表 你的职责是帮助客户处理航班预订相关的问题,包括查询航班信息、取消预订等。请用友好、专业的态度回复客户。 #要求 1、在涉及增删改(除了查询)function-call前,必须等用户回复"确认"后再调用tools 2、请讲中文 今天的日期是 {currentDate} """) .defaultAdvisors( PromptChatMemoryAdvisor.builder(chatMemory).build(), new SimpleLoggerAdvisor() ) .defaultTools(toolService) .defaultToolCallbacks(toolCallbackProvider) .build();
return chatClient.prompt() .user(message) .system(p -> p.param("currentDate", LocalDate.now())) .stream() .content() .map(content -> content == null ? "" : content); } }
|
四、实际案例:航空客服助手
4.1 业务场景
假设我们要做一个航空客服助手,需要支持:
数据模型:
1 2 3 4 5 6 7 8 9
| public class FlightBooking { private String id; private String passengerName; private LocalDate departureDate; private String from; private String to; private BookingStatus status; private BookingClass bookingClass; }
|
4.2 工具实现
查询工具:
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
| @Service public class ToolService {
@Resource private FlightBookingService flightBookingService;
@Tool(description = "查询航班信息,需要提供预订号和乘客姓名") public String queryFlightInfo( @ToolParam String bookingNumber, @ToolParam String passengerName) { FlightBooking booking = flightBookingService .findBookingByBookingNumberAndName(bookingNumber, passengerName); if (booking != null) { return String.format( "预订号:%s,乘客:%s,出发地:%s,目的地:%s,出发日期:%s,状态:%s", booking.getId(), booking.getPassengerName(), booking.getFrom(), booking.getTo(), booking.getDepartureDate(), booking.getStatus() ); } return "未找到航班信息,请检查预订号和乘客姓名是否正确"; } }
|
取消工具:
1 2 3 4 5 6 7 8 9 10 11
| @Tool(description = "退票/取消预定,需要提供预订号和乘客姓名") public String cancelBooking( String bookingNumber, String passengerName) { boolean success = flightBookingService .cancelBookingByBookingNumberAndName(bookingNumber, passengerName); if (success) { return "退票成功"; } return "退票失败,请检查预订号和乘客姓名是否正确"; }
|
4.3 系统提示词设计
系统提示词要明确告诉 AI 如何使用工具:
1 2 3 4 5 6 7 8 9 10 11 12
| .defaultSystem(""" #角色 你是一个专业的航空客服助手,名为'南方国际航空'的客服代表 你的职责是帮助客户处理航班预订相关的问题,包括查询航班信息、取消预订等。请用友好、专业的态度回复客户。
#要求 1、在涉及增删改(除了查询)function-call前,必须等用户回复"确认"后再调用tools 2、请讲中文 3、如果用户的问题无法通过工具解决,请引导用户联系人工客服
今天的日期是 {currentDate} """)
|
提示词要点:
- 明确角色:告诉 AI 你是谁
- 明确规则:什么时候可以调用工具,什么时候需要确认
- 错误处理:无法解决时如何处理
4.4 完整代码
ToolService:
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
| package org.study.ai.service;
import jakarta.annotation.Resource; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; import org.study.ai.model.FlightBooking;
@Service public class ToolService {
@Resource private FlightBookingService flightBookingService;
@Tool(description = "查询航班信息,需要提供预订号和乘客姓名") public String queryFlightInfo( @ToolParam String bookingNumber, @ToolParam String passengerName) { FlightBooking booking = flightBookingService .findBookingByBookingNumberAndName(bookingNumber, passengerName); if (booking != null) { return String.format( "预订号:%s,乘客:%s,出发地:%s,目的地:%s,出发日期:%s,状态:%s", booking.getId(), booking.getPassengerName(), booking.getFrom(), booking.getTo(), booking.getDepartureDate(), booking.getStatus() ); } return "未找到航班信息,请检查预订号和乘客姓名是否正确"; }
@Tool(description = "退票/取消预定,需要提供预订号和乘客姓名") public String cancelBooking( String bookingNumber, String passengerName) { boolean success = flightBookingService .cancelBookingByBookingNumberAndName(bookingNumber, passengerName); if (success) { return "退票成功"; } return "退票失败,请检查预订号和乘客姓名是否正确"; } }
|
FlightBookingService:
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
| @Service public class FlightBookingService {
private final ConcurrentHashMap<String, FlightBooking> bookings = new ConcurrentHashMap<>();
@PostConstruct public void init() { addBooking(new FlightBooking("101", "徐庶", LocalDate.of(2025, 7, 15), "重庆", "成都", BookingStatus.CONFIRMED, BookingClass.ECONOMY)); addBooking(new FlightBooking("102", "诸葛", LocalDate.of(2025, 7, 16), "武汉", "南京", BookingStatus.CONFIRMED, BookingClass.BUSINESS)); }
public FlightBooking findBookingByBookingNumberAndName(String bookingNumber, String passengerName) { FlightBooking booking = bookings.get(bookingNumber); if (booking != null && booking.getPassengerName().equals(passengerName)) { return booking; } return null; }
public boolean cancelBookingByBookingNumberAndName(String bookingNumber, String passengerName) { FlightBooking booking = findBookingByBookingNumberAndName(bookingNumber, passengerName); if (booking != null) { booking.setStatus(BookingStatus.CANCELLED); return true; } return false; } }
|
五、工具调用流程
完整的工具调用流程:
1. 用户提问
1
| 用户:查询我的航班信息,预订号是 101,乘客姓名是徐庶
|
2. AI 分析
AI 分析问题,判断需要调用 queryFlightInfo 工具。
3. 工具调用
AI 生成工具调用请求(内部处理):
1 2 3 4 5 6 7
| { "function": "queryFlightInfo", "arguments": { "bookingNumber": "101", "passengerName": "徐庶" } }
|
4. 执行工具
Spring AI 执行工具方法:
1
| queryFlightInfo("101", "徐庶")
|
5. 返回结果
工具执行结果返回给 AI:
1
| 预订号:101,乘客:徐庶,出发地:重庆,目的地:成都,出发日期:2025-07-15,状态:CONFIRMED
|
6. 生成回复
AI 根据工具执行结果生成最终回复:
1 2 3 4 5 6 7
| 根据您提供的信息,我查询到您的航班信息如下: - 预订号:101 - 乘客姓名:徐庶 - 出发地:重庆 - 目的地:成都 - 出发日期:2025年7月15日 - 状态:已确认
|
实际对话示例:
1 2 3 4 5 6 7 8 9
| 用户:查询我的航班信息,预订号是 101,乘客姓名是徐庶 AI:[调用 queryFlightInfo("101", "徐庶")] AI:根据您提供的信息,我查询到您的航班信息如下: 预订号:101 乘客:徐庶 出发地:重庆 目的地:成都 出发日期:2025年7月15日 状态:已确认
|
六、常见问题
6.1 AI 不调用工具
问题: AI 不调用工具,直接回复。
原因:
description 描述不准确
- 系统提示词没有引导 AI 使用工具
- 工具未正确注册
解决:
- 检查
description 是否清晰准确
- 在系统提示词中明确告诉 AI 什么时候使用工具
- 确认
defaultTools 和 defaultToolCallbacks 都已配置
6.2 AI 调用工具但参数错误
问题: AI 调用了工具,但参数值不对。
原因:
解决:
- 使用明确的参数类型(String、int 等)
- 参数名称要清晰(如
bookingNumber 而不是 id)
- 在
description 中说明参数的含义
6.3 工具执行失败
问题: 工具执行时报错。
原因:
解决:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Tool(description = "查询航班信息") public String queryFlightInfo(String bookingNumber, String passengerName) { try { if (bookingNumber == null || bookingNumber.trim().isEmpty()) { return "预订号不能为空"; } if (passengerName == null || passengerName.trim().isEmpty()) { return "乘客姓名不能为空"; } FlightBooking booking = flightBookingService .findBookingByBookingNumberAndName(bookingNumber, passengerName); if (booking != null) { return "航班信息:" + booking; } return "未找到航班信息"; } catch (Exception e) { return "查询失败:" + e.getMessage(); } }
|
6.4 工具返回值 AI 不理解
问题: 工具返回了结果,但 AI 回复不准确。
原因:
解决:
- 返回格式化的字符串,包含完整信息
- 使用清晰的格式,便于 AI 理解
七、最佳实践
7.1 工具设计原则
单一职责
每个工具方法只做一件事:
1 2 3 4 5 6 7 8 9 10
| @Tool(description = "查询航班信息") public String queryFlightInfo(...) { }
@Tool(description = "取消航班预订") public String cancelBooking(...) { }
@Tool(description = "查询或取消航班") public String flightOperation(...) { }
|
参数验证
在工具方法中进行参数验证:
1 2 3 4 5 6 7 8 9 10
| @Tool(description = "查询航班信息") public String queryFlightInfo(String bookingNumber, String passengerName) { if (bookingNumber == null || bookingNumber.trim().isEmpty()) { return "预订号不能为空"; } if (passengerName == null || passengerName.trim().isEmpty()) { return "乘客姓名不能为空"; } }
|
错误处理
完善的错误处理:
1 2 3 4 5 6 7 8 9 10
| @Tool(description = "查询航班信息") public String queryFlightInfo(String bookingNumber, String passengerName) { try { return result; } catch (Exception e) { log.error("查询航班信息失败", e); return "查询失败,请稍后重试"; } }
|
7.2 系统提示词设计
明确规则
在系统提示词中明确工具使用规则:
1 2 3 4
| #要求 1、查询操作可以直接调用工具 2、增删改操作必须等用户确认后再调用工具 3、如果工具返回错误,要向用户说明原因
|
角色定义
明确 AI 的角色和职责:
1 2 3
| #角色 你是一个专业的航空客服助手 你的职责是帮助客户处理航班预订相关的问题
|
7.3 日志记录
在工具方法中记录日志,便于排查问题:
1 2 3 4 5 6 7
| @Tool(description = "查询航班信息") public String queryFlightInfo(String bookingNumber, String passengerName) { log.info("查询航班信息,预订号:{},乘客姓名:{}", bookingNumber, passengerName); log.info("查询结果:{}", result); return result; }
|
八、总结
- Function Call 让 AI 可以调用外部工具,扩展 AI 的能力
- @Tool 注解:使用
@Tool 定义工具,description 很重要
- 工具注册:需要配置
ToolCallbackProvider 并在 ChatClient 中注册
- 参数设计:类型明确、数量适中、命名清晰
- 返回值设计:信息完整、格式清晰、错误提示明确
- 系统提示词:明确角色、规则、错误处理方式
- 最佳实践:单一职责、参数验证、错误处理、日志记录
下一篇文章会介绍 MCP 协议,这是 Spring AI 提供的标准化工具调用协议。