随着大型语言模型(LLM)能力的飞速发展,它们在自然语言理解、文本生成、对话交互等方面展现出了令人惊叹的表现。然而,LLM 本身并不具备执行外部操作的能力,比如访问网页、调用第三方 API、执行精确数学运算等。在实际应用中,我们往往需要将 LLM 与各种工具(Tool)进行结合,让模型在生成文本时,能够在必要时发出“调用工具”的指令,由外部程序执行该工具,并将结果反馈给模型,以获得准确、可执行的输出。
“工具调用”或“功能调用”(Function Calling)正是为了解决这一需求而设计的一种机制。它允许我们在向模型发送请求时,声明一组可用的工具(或函数),并在模型的回复中,通过特殊的字段告知“我想调用哪个工具,传入什么参数”。随后,应用程序负责执行该工具,将执行结果再送回给模型,模型据此继续生成最终回复。
本文将从原理、示例、API 概览、Spring AI 集成、具体代码示例以及最佳实践等多个角度,深入解析 Function Calling 的使用方法,帮助读者在自己的项目中顺利落地这一强大功能。
弥补模型自身能力的局限
降低 Prompt 复杂度,提升可维护性
squareRoot
的工具,你需要的时候就调用它”。支持多样化的应用场景
下面以一个简单的数学计算示例,说明 Function Calling 在消息交互中的具体工作流程。
User: What is the square root of 475695037565?
AI: The square root of 475695037565 is approximately 689710.
{
"messages": [
{ "role": "user", "content": "What is the square root of 475695037565?" }
],
"functions": [
{
"name": "squareRoot",
"description": "Returns the square root of a given number",
"parameters": {
"type": "object",
"properties": {
"x": { "type": "number", "description": "The number to take the square root of" }
},
"required": ["x"]
}
}
]
}
{
"role": "assistant",
"content": null,
"function_call": {
"name": "squareRoot",
"arguments": { "x": 475695037565 }
}
}
function_call
,调用相应的工具(如内部实现或外部 API),得到结果 689706.486532
。{
"messages": [
{ "role": "user", "content": "What is the square root of 475695037565?" },
{
"role": "assistant",
"function_call": {
"name": "squareRoot",
"arguments": { "x": 475695037565 }
}
},
{ "role": "tool", "name": "squareRoot", "content": "689706.486532" }
]
}
The square root of 475695037565 is 689706.486532.
通过上述流程,模型能够借助外部工具得到精确结果,并在对话中正确呈现。
要让 LLM 知道可调用哪些工具,需要在请求中提供每个工具的名称(name)、描述(description)和调用签名(parameters)。常见字段如下:
getWeather
、searchWeb
。以 OpenAI Chat API 为例,请求体中的 functions
字段示例如下:
"functions": [
{
"name": "getWeather",
"description": "Get the weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["C", "F"],
"description": "Temperature unit, Celsius or Fahrenheit"
}
},
"required": ["location"]
}
}
]
当模型生成回答时,如果它判断需要调用 getWeather
,就会在响应中输出 function_call
而非直接文本。
在 Java 生态中,Spring AI(或类似框架)极大简化了工具注册和调用流程。我们只需将函数定义为 Spring Bean,并通过注解或配置将其暴露给 ChatModel。
@Configuration
public class FunctionConfig {
@Bean
@Description("Get the weather in location") // 函数描述,帮助模型选择调用
public Function weatherFunction() {
return new MockWeatherService();
}
}
weatherFunction
)即作为函数名传给模型。@Description
注解为该函数提供说明,模型据此判断何时调用。下面是一个模拟天气服务的简单实现,硬编码返回温度:
public class MockWeatherService implements Function {
public enum Unit { C, F }
public record Request(String location, Unit unit) {}
public record Response(double temp, Unit unit) {}
@Override
public Response apply(Request request) {
// 简单返回固定温度,真实场景可调用第三方天气 API
return new Response(30.0, Unit.C);
}
}
在构建 ChatClient/ChatModel 时,将函数 Bean 名称传入:
ChatClient chatClient = ChatClient.builder(dashScopeChatModel)
.defaultFunctions("weatherFunction") // 注册函数
.build();
ChatResponse response = chatClient.prompt()
.user("What's the weather like in Boston?")
.call()
.chatResponse();
当模型判断需要调用天气服务时,框架会自动将 function_call
转为对 weatherFunction
Bean 的调用,并将结果封装回模型继续生成对话。
结合 Spring AI,整个 Function Calling 流程可分为以下几个步骤:
函数定义:在 Spring 应用上下文中,通过 @Bean
定义 Function
,并用 @Description
或 @JsonClassDescription
注解提供描述。
函数注册:在创建 ChatClient/ChatModel 时,将 Bean 名称通过 defaultFunctions(...)
或 withFunctionCallbacks(...)
传入模型选项。
构造 Prompt 请求:请求中包含用户消息和 functions
列表(由框架自动根据已注册 Bean 生成)。
模型响应 Function Call:模型在生成时,若判断需要调用某个工具,就输出 function_call
而非文本回复。
执行函数:框架捕获 function_call
,将其中的 JSON 参数反序列化为对应 Bean 的 Request
对象,调用 Bean(即 Function.apply
),得到 Response
对象。
反馈结果给模型:将函数调用结果作为新的消息(role="tool"
)附加到消息列表中,再次调用模型,让它基于工具返回值生成最终文本回复。
整个过程对开发者透明,大大简化了与模型的交互和工具调用逻辑。
除了新建 Bean,也可以将现有的 Service 方法通过函数调用暴露给模型。以下示例展示如何将一个已有的 MockOrderService
注册为 Function Call。
@Service
public class MockOrderService {
public Response getOrder(Request request) {
String productName = "尤尼克斯羽毛球拍";
return new Response(
String.format("%s 的订单编号为 %s, 购买的商品为: %s",
request.userId, request.orderId, productName)
);
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public record Request(
@JsonProperty(required = true, value = "orderId")
@JsonPropertyDescription("订单编号, 比如1001***")
String orderId,
@JsonProperty(required = true, value = "userId")
@JsonPropertyDescription("用户编号, 比如2001***")
String userId
) {}
public record Response(String description) {}
}
@Configuration
public class FunctionCallConfiguration {
@Bean
@Description("根据用户编号和订单编号查询订单信息")
public Function getOrderFunction(
MockOrderService mockOrderService) {
// 将 Service 方法引用作为 Function Bean
return mockOrderService::getOrder;
}
}
ChatClient chatClient = ChatClient.builder(dashScopeChatModel)
.defaultFunctions("getOrderFunction")
.build();
ChatResponse response = chatClient.prompt()
.user("帮我查询订单, 用户编号为1001, 订单编号为2001")
.call()
.chatResponse();
String content = response.getResult().getOutput().getContent();
System.out.println("模型回复: " + content);
这样,模型在收到查询订单的请求时,就会自动触发对 getOrderFunction
的调用,并将结果融入最终回复。
在某些场景下,我们希望根据上下文动态选择可用函数,而非在应用启动时一次性注册所有函数。Spring AI 支持通过 FunctionCallbackWrapper
在 Prompt 级别动态注入函数。
// 构造 FunctionCallbackWrapper 列表
List> callbacks = List.of(
new FunctionCallbackWrapper<>(
"CurrentWeather", // name
"Get the weather in location", // description
new MockWeatherService() // function 实现
)
);
var promptOptions = OpenAiChatOptions.builder()
.withFunctionCallbacks(callbacks)
.build();
ChatResponse response = chatModel.call(
new Prompt(List.of(new UserMessage("What's the weather like in Tokyo?")),
promptOptions)
);
这样,每次调用时都可灵活传入不同的函数集合,无需预先在 Spring 容器中定义 Bean。
函数描述要精准:使用 @Description
或 @JsonClassDescription
为函数和参数添加清晰说明,帮助模型准确选择。
参数模式要完整:在 JSON Schema 中为每个参数提供 type
、description
,并对必填字段使用 required
,避免模型生成错误调用。
合理拆分工具:将功能独立的操作拆分为不同工具,避免“万能”函数导致模型调用混乱。
调用结果校验:在将工具执行结果反馈给模型前,最好进行必要的校验或清洗,防止异常数据影响后续生成。
安全与权限控制:对敏感操作(如发送邮件、数据库写入)要做好鉴权与审计,避免模型被滥用。
限流与降级:对外部 API 调用要做好限流,遇到失败时可设计降级方案,让模型给出合理的错误提示。
“工具调用(Function Calling)”为 LLM 提供了与外部系统、API、代码逻辑无缝对接的能力,大大拓展了模型在实际应用中的边界。通过在请求中声明工具、让模型在合适时机发起调用、由应用程序执行并将结果反馈给模型,整个流程犹如一次透明的 RPC 调用,让模型既保留了强大的自然语言生成能力,又具备了执行外部操作的可能。
在 Java/Spring 生态中,借助 Spring AI、FunctionCallbackWrapper 等框架,我们只需将函数以 Bean 形式定义,并通过注解或配置进行注册,便可轻松实现 Function Calling。无论是简单的数学运算、天气查询,还是复杂的业务系统交互,都能通过这种方式优雅地集成到对话流程中。
希望本文能帮助读者全面了解并掌握 Function Calling 的原理与实践,快速在自己的项目中落地这一功能,打造更智能、更实用的 AI 应用。未来,随着 LLM 与工具生态的不断完善,我们将见证更加丰富、多样的智能化场景落地,值得持续关注与探索。