Function Calling允许大型语言模型(LLM)在必要时调用一个或多个可用的工具,这些工具通常由开发者定义。工具可以是任何东西:网页搜索、对外部 API 的调用,或特定代码的执行等。开发者定义好函数工具,将工具注册给大模型,大模型通过工具的描述来决定是否调用或者调用哪一个工具,从用户输入中提取函数工具的参数,从而成功调用函数。调用的函数返回结果将作为大模型回答结果的一部分。
接下来我们学习如何实现函数工具的定义与注册
首先我写了一个简单的天气的Service类
@Slf4j
@Service
public class WeatherService {
/**
* 获取某城市某天的天气
* @param city
* @param date
* @return
*/
public String getWeatherByDate(String city,String date) {
log.info("调用函数工具: 城市:{} 时间:{}", city, date);
//TODO 调用天气查询接口
String weather = "阴天";
return date + city + "的天气情况为:" + weather;
}
}
接下来就是函数工具类的定义了,其格式如下:
函数定义如下:
@Configuration
public class FunctionToolConfig {
@Bean
@Description("函数描述....") //AI大模型就是通过该描述来决定是否调用,如何调用函数的
public Function< T request, R response>
weatherFunction1() {
return //实现;
}
接下来我们来解释一下函数定义的关键:
@Description("函数描述....") :AI大模型就是通过该描述来决定是否调用,如何调用函数的,必须要,而且很关键,关系到函数是否调用成功
Function< T request, R response> :含有两个泛型的一个接口
T request:表示的是AI大模型调用函数传入的参数,可以是任何对象类型,会把所有的参数封装转化成T类型
R response:函数响应的类型,也就是return 返回的类型。
函数名:weatherFunction1,这个就是我们将定义好的函数注册到AI大模型的函数名称,比如
public ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultFunctions("weatherFunction1",... )
.build();
}
那我就想,AI大模型怎么知道我们需要哪些参数,就算它根据函数描述可以知道要传入哪些参数,那又怎么知道哪个参数对应哪个参数呢?参数顺序是怎样的呢?
Java 16 引入的 record(记录类),用于定义不可变的数据载体类(data class / POJO),具体细节可以看其他博主的介绍,这里就不多解释:
我可以在WeatherService里面定义一个类Request,用于封装传入的参数,同时解释参数,用于AI大模型的对应传参。
@Slf4j
@Service
public class WeatherService {
public record Request(
@JsonProperty(value = "city", required = true) @JsonPropertyDescription("城市名称") String city,
@JsonProperty(value = "date", required = true) @JsonPropertyDescription("日期") String date) {
}
/**
* 获取某城市某天的天气
* @param city
* @param date
* @return
*/
public String getWeatherByDate(String city,String date) {
log.info("调用函数工具: 城市:{} 时间:{}", city, date);
//TODO 调用天气查询接口
String weather = "阴天";
return date + city + "的天气情况为:" + weather;
}
}
其实上面record的写法就相当于为weatherService创建了一个静态内部类
@Slf4j
@Service
public class WeatherService {
/**
* 获取某城市某天的天气
* @param city
* @param date
* @return
*/
public String getWeatherByDate(String city,String date) {
log.info("调用函数工具: 城市:{} 时间:{}", city, date);
//TODO 调用天气查询接口
String weather = "阴天";
return date + city + "的天气情况为:" + weather;
}
@Data
public static class Request {
@JsonProperty(value = "city", required = true)
@JsonPropertyDescription("城市名称")
private String city;
@JsonProperty(value = "date", required = true)
@JsonPropertyDescription("日期")
private String date;
}
}
当然也不需要一定是内部类,外部也可以,将Request单独创建一个外部类也是可以的。
那么大模型如何通过定义 Request 类来保证大模型传入正确参数呢?
1、Spring AI 使用 Request 类的字段信息(包括字段名、类型、是否必填、描述等),自动生成一个 JSON Schema。
例如会将上述的Requestl类信息转换成JSON Schema
{
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称" },
"date": { "type": "string", "description": "日期" }
},
"required": ["city","date"]
}
2、告诉大模型:这个函数需要哪些参数?
当模型收到用户提问,比如:“2025年7月15日深圳天气怎么样?”
它会查看所有可用的函数工具及其 JSON Schema,发现你需要一个包含 "city" 和 "date" 的参数对象,并且 "city" ,"date"是必填项。于是模型会尝试构造如下 JSON:
{
"name": "getWeatherByDate",
"arguments": {
"city": "深圳",
"date": "2025-07-15"
}
}
3、Spring AI 自动将 JSON 转换为 Java 对象
Spring AI 接收到模型返回的 JSON 后,会使用 Jackson 或其他序列化库,将该 JSON 转换为你的 Request
接下来我们来完整的定义一个查询天气的工具函数:
@Configuration
public class FunctionToolConfig {
@Bean
@Description("天气查询城市某一天的天气情况")
public Function<WeatherService.Request, String> getWeatherByDate(WeatherService weatherService) {
return request->weatherService.getWeatherByDate(request.city(), request.date());
}
}
然后就是函数工具注册给ChatClient:
@GetMapping("/simple/getWeather")
public String simpleChat(String query) {
String response = chatClient.prompt()
.user(query)
.functions("getWeatherByDate")
.call()
.content();
return response;
}
然后我们调用一下接口,最好开启DEBUG日志,这样可以打印请求与响应的过程,方便观察调试
结果为:

控制台日志输出为:

如果函数没有或者只有一个参数,那我们就可以不需要创建一个Request类了
@Service
public class WeatherService {
/**
* 获取今天的时间
* @return
*/
public String getToday(){
LocalDate today = LocalDate.now();
log.info("调用函数工具: 今天的日期{}", today);
return "今天是" + today+ "哦,嘿嘿";
}
/**
* 获取某城市今天的天气
* @param city
* @return
*/
public String getTodayWeather(String city) {
log.info("调用函数工具: {}", city);
LocalDate today = LocalDate.now();
//TODO 调用天气查询接口
String weather = "晴天";
return "今天是" + today + "," + city + "的天气情况为:" + weather;
}
}
定义函数时我们可以:
@Configuration
public class FunctionToolConfig {
@Bean
@Description("获取今日的日期")
public Function<Void, String> getToday(WeatherService weatherService) {
return v -> weatherService.getToday();
}
@Bean
@Description("天气查询城市今天的天气")
public Function<String, String> getTodayWeather(WeatherService weatherService) {
return weatherService::getTodayWeather;
}
}
当然函数的请求参数可以封装传入,那函数的响应参数也是可以封装响应的
@Slf4j
@Service
public class WeatherService {
public record Request(
@JsonProperty(value = "city") @JsonPropertyDescription("城市名称") String city,
@JsonProperty(value = "date") @JsonPropertyDescription("日期") String date) {
}
public record Response(
@JsonProperty(value = "date") @JsonPropertyDescription("日期") String date,
@JsonProperty(value = "city") @JsonPropertyDescription("城市名称") String city,
@JsonProperty(value = "weather") @JsonPropertyDescription("天气情况") String weather) {
}
/**
* 获取某城市某天的天气
* @param city
* @param date
* @return
*/
public Response getWeatherByDate(String city,String date) {
log.info("调用函数工具: 城市:{} 时间:{}", city, date);
//TODO 调用天气查询接口
String weather = "阴天";
Response response = new Response(date, city, weather);
log.info("返回结果:{}", response);
return response;
}
}
@Configuration
public class FunctionToolConfig {
@Bean
@Description("天气查询城市某一天的天气情况")
public Function<WeatherService.Request, WeatherService.Response> getWeatherByDate(WeatherService weatherService) {
return request->weatherService.getWeatherByDate(request.city(), request.date());
}
}
接下来我们来学习另一种 函数工具的定义FunctionCallback
@Component
public class GetWeatherByDateFunction implements FunctionCallback {
private final WeatherService weatherService;
private static final ObjectMapper mapper = new ObjectMapper();
public GetWeatherByDateFunction(WeatherService weatherService) {
this.weatherService = weatherService;
}
/**
* 定义函数名称
* @return
*/
@Override
public String getName() {
return "getWeatherByDate1";
}
/**
* 函数描述
* @return
*/
@Override
public String getDescription() {
return "根据城市和日期查询天气信息";
}
/**
* 函数调用
* @param functionInput
* @return
*/
@Override
public String call(String functionInput) {
try {
Request request = mapper.readValue(functionInput, Request.class);
return weatherService.getWeatherByDate(request.getCity(), request.getDate()).toString();
} catch (Exception e) {
throw new RuntimeException("解析函数调用参数失败", e);
}
}
/**
* 输入参数的 JSON Schema
* @return
*/
@Override
public String getInputTypeSchema() {
try {
return mapper.writeValueAsString(mapper.generateJsonSchema(Request.class));
} catch (Exception e) {
throw new RuntimeException("生成 JSON Schema 失败", e);
}
}
}
两个实体类:
@Data
public class Request {
@JsonProperty(value = "city", required = true)
@JsonPropertyDescription("城市名称")
private String city;
@JsonProperty(value = "date", required = true)
@JsonPropertyDescription("日期")
private String date;
}
@Data
public class Response {
@JsonProperty(value = "date")
@JsonPropertyDescription("日期")
private String date;
@JsonProperty(value = "city")
@JsonPropertyDescription("城市名称")
private String city;
@JsonProperty(value = "weather")
@JsonPropertyDescription("天气情况")
private String weather;
}
Function<T t,R r>和 FunctionCallback实现函数定义的区别:
function<T t,R r>:
✅优点:
- 简洁直观,适合快速开发。
- 不需要额外类或接口。
- 易于测试和集成。
❌ 缺点:
- 无法提供详细的 JSON Schema,模型可能无法准确理解参数结构。
- 不支持复杂参数类型(如嵌套对象)
- 无法自定义错误处理、参数转换逻辑
- 不便于扩展功能(如记录调用日志、权限校验)
FunctionCallback:
✅ 优点:
- 可以定义完整的函数元信息(名称、描述、参数结构)
- 支持 JSON Schema 自动生成
- 支持多种调用方式(Map、JSON 字符串)
- 可扩展性强,便于封装通用逻辑(如日志、异常处理)
- 更适用于多平台(OpenAI / DashScope / 自定义模型)
❌ 缺点:
- 相对繁琐,需要实现多个接口方法。
- 对新手来说学习成本略高。
Function<T, R> 更轻量,适合快速开发;FunctionCallback 更强大,适合生产环境。两者都可用于注册函数工具,但 FunctionCallback 提供了更强的控制能力和兼容性
&spm=1001.2101.3001.5002&articleId=149357698&d=1&t=3&u=2dfadc4343014de086952fc978c05291)
641

被折叠的 条评论
为什么被折叠?



