本文将对 Spring AI 框架对函数调用实现的源码以及背后的思想进行分析,并使用 Spring AI实现函数调用的功能。
函数调用是大模型的基础能力,在ReAct提示词框架、智能体Agent开发,都会用到函数调用,这是一个非常基础的知识点,大家务必掌握。
Added at 2025/02/12
随着 Spring AI 框架的不断完善,Spring AI function calling 废弃,被Tool Calling 概念取代。新入门者可以直接阅读 # Spring AI 框架在升级,Function Calling 废弃,被 Tool Calling 取代。
一、大模型为什么要函数调用
-
预训练,
没有最新信息,GPT将无法感知。比如GPT3.5训练截止日期为2022年4月大语言模型(LLMs)是预训练的,而且一次训练的成本非常昂贵。据网络数据说GPT4一次训练大约需要25000个A100GPU。再加上机器、电费等等 需消耗6300万美金。
函数调用功能可以增强大模型连接到外部数据能力,包括信息检索、数据库操作、知识图谱搜索与推理。弥补大模型最新数据的缺失。
-
大模型是语言模型,
无真逻辑。比如进行数学计算,无法每次准确计算出结果。函数调用在弥补大模型无真逻辑问题上,通过注册自己的函数,将大模型连接到外部系统的API,比如调用现成的计算器等工具计算。
因此函数调用能力是大模型连接外部世界的重要手段,弥补大模型的不足。函数调用功能可以增强模型推理效果或进行其他外部操作,包括信息检索、数据库操作、知识图谱搜索与推理、操作系统、触发外部操作等工具调用场景。
可以使用更加形象的一句话总结为:函数调用就像给大模型创造了手脚和眼,可以接触到外部世界数据或者使用外部世界工具!
二、哪些大模型支持函数调用
我们仅看Spring AI框架集成的大模型里,哪些支持函数调用,对于未集成的我们不做讨论。在大模型选型时,大模型是否支持函数调用一般都是需要考虑的重点。
- Open AI.
gpt-4o同时支持并行函数调用gpt-4o-2024-05-13同时支持并行函数调用gpt-4-turbo同时支持并行函数调用gpt-4-turbo-2024-04-09同时支持并行函数调用gpt-4-turbo-preview同时支持并行函数调用gpt-4-0125-preview同时支持并行函数调用gpt-4-1106-preview同时支持并行函数调用gpt-4gpt-4-0613gpt-3.5-turbogpt-3.5-turbo-0125同时支持并行函数调用gpt-3.5-turbo-1106同时支持并行函数调用gpt-3.5-turbo-0613
- VertexAI Gemini.
- Azure OpenAI.
- Mistral AI.
Mistral SmallMistral LargeMixtral 8x22B
- Anthropic Claude.
claude-3-5-sonnet-20241022claude-3-opusclaude-3-sonnetclaude-3-haiku
- MiniMax.
- ZhiPu AI.
三、Spring AI 函数调用框架设计
3.1、框架设计流程
Spring AI 框架函数调用设计框架如下;
将详细介绍一下其执行流程;
- 将定义的函数(函数名/方法/参数/方法描述/参数描述))以 Prompt 发送给 AI Model。
- AI Model 根据函数相关描述信息以及用户的输入,返回函数所需要的参数。
- Spring AI 根据 AI Model 返回的参数进行函数调用(内部函数/外部函数),并将函数执行的结果发送给 AI Model。
- AI Model 接收到函数执行结果,通过进一步的加工,最终生成消息返回给客户端。
从整个流程上看 AI Model 本身并不执行函数的调用,而仅仅是根据方法的描述,返回方法所需要的返回值,函数调用仅发生在调用端侧。
3.2、类之间的协作关系

五、函数调用示例
将演示一个实时查询天气温度的程序。因为对于大模型来说,无法获取最新的数据。本文使用OpenAI大模型进行演示。
5.1、定义函数
为了快速方便快速实现演示,获取天气实现模拟返回气温,此处仅模拟返回北京、天津、南京城市温度,其它城市返回温度为0。
package org.ivy.func;
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import java.util.function.Function;
public class MockWeatherService implements Function<MockWeatherService.Request, MockWeatherService.Response> {
/**
* Weather Function request. 该类主要告诉大模型需要从提示词中提取的 * 参数有哪些,然后将提取的参数返回,并调用apply方法。
*/
@JsonInclude(Include.NON_NULL)
@JsonClassDescription("Weather API request")
public record Request(@JsonProperty(required = true, value = "location") @JsonPropertyDescription("The city and state e.g. 北京") String location,
@JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) {
}
/**
* Temperature units.
*/
public enum Unit {
C, F
}
/**
* Weather Function response. 该类函数执行返回的结果,并将该结果
* 发送给大模型。
*/
public record Response(double temp, Unit unit) {
}
@Override
public Response apply(Request request) {
System.out.println("function called :" + request);
double temperature = 0;
if (request.location().contains("北京")) {
temperature = 15;
} else if (request.location().contains("天津")) {
temperature = 10;
} else if (request.location().contains("南京")) {
temperature = 30;
}
return new Response(temperature, Unit.C);
}
}
在此处有一点非常重要,Request 中的 @JsonPropertyDescription("The city and state e.g. 北京")中的 e.g 如果你写“北京” 会返回中文城市名称,如果写的beijing,则会返回拼音城市名称。
5.2、将函数注册到 Spring 容器
package org.ivy.config;
import org.ivy.func.MockWeatherService;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public FunctionCallback weatherFunctionInfo() {
return FunctionCallbackWrapper.builder(new MockWeatherService())
.withName("WeatherInfo")
.withDescription("Get the weather in location")
.withResponseConverter((response) -> "" + response.temp() + response.unit())
.build();
}
}
5.3、定义接口
package org.ivy.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.Map;
@RestController
public class FunctionCallingController {
private final OpenAiChatModel openAiChatModel;
@Value("classpath:weather.st")
private org.springframework.core.io.Resource weather;
public FunctionCallingController(OpenAiChatModel openAiChatModel) {
this.openAiChatModel = openAiChatModel;
}
/**
* 没有函数调用,看看返回结果
*
* @return 返回天气情况
*/
@GetMapping("/noFunc")
public Flux<String> noFunc(String prompt) {
ChatClient chatClient = ChatClient.builder(openAiChatModel).build();
return chatClient.prompt(new PromptTemplate(weather, Map.of("prompt", prompt)).create())
.stream()
.content();
}
/**
* 调用函数,看看返回结果
*
* @return 天气状况
*/
@GetMapping("/func")
public Flux<String> func(String prompt) {
UserMessage userMessage = new UserMessage(prompt + " 你可以调用函数:'WeatherInfo'");
ChatClient chatClient = ChatClient.builder(openAiChatModel).build();
return chatClient.prompt(new Prompt(
List.of(userMessage),
OpenAiChatOptions.builder()
.withFunction("WeatherInfo")
.build()
)
).stream()
.content();
}
}
定义两个方法,一个无函数调用,一个有函数调用,对比两者效果。
5.4、验证效果
在验证时如果发现无法使用函数调用,首先检查检查代码是否正确,如果代码正确还是不正确的话,然后在确认选择的大模型是否支持函数调用,最后就有一个非常隐蔽的问题:是否使用的代理中转,有些代理中转是无法实现函数调用的。
我们先验证没有函数调用的情况,如下图所示:
开始一通胡说八道,所以对于需要最新的,准确的回答,大模型比较难做到,因为它是基于推理进行的。我们改一下提示词,不知道的时候,让回答不知道
问题: {prompt}, 如果你无法获取到最新的真实有效的数据,请回答:抱歉
在验证有函数调用的情况,如下图所示:

六、总结
本篇文章主要对大模型中函数调用的概念、作用、解决的问题进行讲解。并对 Spring AI 框架实现的源码进行分析,目前分析的还不透彻,但不耽误开发程序。最后Spring AI接入OpenAI大模型进行演示了无函数与有函数调用的区别。
几个关键的注解:
- @Description("") 注册函数时使用,告诉大模型函数调用的时机,但是需要大模型非常聪明,像gpt-3.5-turbo 就无法根据描述判断是否函数调用。
- @JsonClassDescription 定义请求类使用,比如Request,告诉大模型该类的作用。比如
@JsonClassDescription("Weather API request") - @JsonPropertyDescription 定义请求类中的请求参数的描述