摘要
在上一篇 《大模型 Agent 知识体系:Java 开发者视角下的原理、架构与选型边界》文章中,我们明确了工具层是 Agent 连接真实业务系统的唯一桥梁,而支撑工具层的底层核心,就是大模型的 Function Calling(函数调用)能力。没有 Function Calling,大模型只能输出自然语言,永远无法真正操作业务接口、数据库与第三方服务,Agent 也就成了 "只会说话不会干活" 的空壳。
承接上文,本文从 Java 后端工程视角出发,先厘清 Function Calling 的本质定义与层级定位;再深入 Spring AI 框架底层,拆解函数调用的完整调度机制、内部组件与三种调用模式;随后从基础到进阶,覆盖 @Tool 注解定义、工具动态注册、拦截增强、结构化 POJO 输出、流式响应处理等核心开发能力,帮助读者从 "跑通 Demo" 升级到 "落地生产"。
一、Function Calling 本质与体系定位
很多 Java 开发者刚接触 Spring AI 时,会陷入两个典型认知误区:要么把 Function Calling 当成 "高级 Prompt 技巧",认为只是让大模型输出 JSON;要么觉得工具调用就是 "把方法名告诉大模型,让它自己调",忽略参数校验、权限管控等工程风险。
事实上,Function Calling 是大模型厂商专门训练的原生结构化输出能力,是整个 Agent 技术栈的基石 ------ReAct 循环里的每一次 Action 行动,本质都是一次 Function Calling;Spring AI Alibaba 的 Skill 组件,底层也是对 Function Calling 的封装与增强。
对于后端开发,学习 Function Calling 的核心目标不是跑通 Hello World,而是搞懂三层问题:原理层清楚链路、开发层掌握用法、工程层守住红线。
1.1 本质定义
Function Calling(函数调用)是大语言模型的一项原生能力:开发者预先向大模型描述一组可用工具的名称、功能、入参规范(JSON Schema),大模型根据用户意图,自主判断是否需要调用工具、调用哪一个工具、传入什么参数,并输出严格结构化的调用指令,而非直接返回自然语言回答。
通俗来说:普通对话模式下,大模型输出 "答案";函数调用模式下,大模型输出 "指令"------ 告诉 Java 程序 "请调用 XX 方法,传入 XX 参数"。
1.2 与 Agent、ReAct 的层级关系
很多人混淆这三者,这里用层级图明确边界:

核心结论:
- Agent 是完整系统,包含规划、工具、记忆、治理四大维度;
- ReAct 是 Agent 的主流运行模式,通过循环迭代完成复杂任务;
- Function Calling 是 ReAct 中 Action 环节的底层实现技术,是大模型与 Java 代码之间的标准化通信协议;
- 业务 Tool 方法是最终执行者,由 Function Calling 触发调用。
关键认知:不是所有 Function Calling 都是 Agent,但所有 LLM-Agent 都离不开 Function Calling。单次工具调用只是 "大模型调接口",加上思考 - 观察循环迭代,才是真正的 Agent。
1.3 为什么不能用 Prompt 拼 JSON 替代 Function Calling
不少初学者会尝试用提示词引导大模型输出 JSON,认为和 Function Calling 效果一致。实际上二者在生产可用性上有量级差距:
|----------|--------------------------|------------------------------|
| 对比维度 | 纯 Prompt 引导输出 JSON | 原生 Function Calling 能力 |
| 格式稳定性 | 极易出现语法错误、字段缺失、多余文本,容错成本高 | 模型专门训练,输出格式合规率极高,生产级稳定 |
| 工具选择准确率 | 多工具场景下容易选错、漏选,语义匹配偏差大 | 工具匹配准确率显著更高,多工具场景优势明显 |
| 参数生成质量 | 易出现参数类型错误、幻觉参数、不符合业务约束 | 严格遵循 JSON Schema 约束,参数合规性强 |
| 框架兼容性 | 需要自行解析、异常处理、重试兜底 | Spring AI 等框架原生支持,自动调度、反射、回填 |
| 生产可用性 | 仅适合简单 Demo 与极简单场景 | 企业级落地的标准方案 |
二、底层调度:Spring AI 函数调用完整执行链路
不了解框架链路,导致开发者遇到问题只能黑盒排查。下面先从宏观时序讲清全链路,再深入 Spring AI 内部,拆解核心组件与执行细节。
2.1 宏观全链路时序
从用户发起请求到拿到最终结果,一次完整的函数调用包含 7 个核心阶段:
2.2 框架内部核心组件与执行细节
Spring AI 并没有魔法,整个工具调用过程由几个核心组件协同完成,理解它们的职责,遇到问题就能精准定位。
- Tool 注解解析器
应用启动时,扫描所有标注 @Tool 注解的方法,提取方法名、Javadoc 描述、参数名称与类型,自动生成符合 OpenAI 规范的 JSON Schema,注册到工具上下文。
注意:方法注释和参数注释非常重要,它们会直接进入工具描述,决定大模型对工具的理解准确率。
- FunctionCallback / ToolCallback
这是工具的统一抽象接口,负责封装工具的元数据与执行逻辑。Spring AI 支持两种注册方式:
- 注解式:@Tool 标注普通 Java 方法,框架自动生成 Callback(业务开发最常用);
- 编程式:手动实现 FunctionCallback 接口,适合复杂工具的自定义控制。
- 工具调度器(DefaultToolService)
是整个调用流程的中枢,负责:
- 把工具元数据拼接到大模型请求中;
- 解析大模型返回的调用指令,匹配到对应的 ToolCallback;
- 通过反射执行 Java 方法,捕获异常并格式化返回结果;
- 将执行结果回填到对话上下文,触发下一轮大模型推理。
- 参数类型转换器
负责将大模型返回的 JSON 参数,转换为 Java 方法的实际参数类型,支持基础类型、字符串、集合、自定义 POJO 等。类型不匹配是高频报错点,本质就是转换器解析失败。
2.3 函数调用的三种模式
Spring AI 支持三种工具调用模式,对应不同业务场景:
|----------|------------------------|-----------------------------------------|
| 模式 | 行为 | 适用场景 |
| AUTO(默认) | 大模型自主判断:是否调用工具、调用哪个工具 | 绝大多数通用场景,让大模型灵活决策 |
| NONE | 强制禁止调用任何工具,大模型直接返回文本回答 | 纯对话场景、不需要工具的问答环节 |
| FORCED | 强制调用指定工具,大模型必须执行该工具 | 确定要调用某个工具的场景,比如固定流程中的工具执行节点,减少模型选错工具的概率 |
三、基础实战:@Tool 注解快速定义业务工具
Spring AI 1.0 提供了高度抽象的工具能力,核心通过 @Tool 注解实现,开发者只需关注业务逻辑,无需手动处理 JSON Schema 与反射调用。
3.1 环境依赖
基于 Spring Boot 3.x + Spring AI 1.0,Maven 核心依赖:
XML
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
3.2 定义业务工具类
核心规则:
- 类无需特殊注解,方法上添加 @Tool;
- 方法与参数的 Javadoc 会被框架读取,作为工具描述,务必写清晰;
- 参数使用明确类型,不建议用泛型、原生 Map 等模糊类型。
java
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
/**
* 订单业务工具集
* 供 Agent 智能体调用的业务能力封装
*/
@Component
public class OrderTools {
/**
* 根据订单号查询订单售后状态
* @param orderId 订单编号,字符串类型
* @return 订单售后状态描述
*/
@Tool(name = "queryOrderStatus", description = "根据订单编号查询订单的售后处理状态")
public String queryOrderStatus(String orderId) {
// 真实业务:调用订单微服务、查询数据库
if ("1001".equals(orderId)) {
return "订单1001,售后状态:未处理,提交时间2026-08-20";
}
return "订单" + orderId + ",售后状态:已完成";
}
/**
* 提交售后加急工单
* @param orderId 订单编号
* @param reason 加急原因
* @return 工单提交结果
*/
@Tool(name = "submitUrgentWorkOrder", description = "为指定订单提交售后加急工单,需要订单号和加急原因")
public String submitUrgentWorkOrder(String orderId, String reason) {
// 真实业务:写入工单表、触发流程、发送通知
return "加急工单提交成功,订单号:" + orderId + ",加急原因:" + reason + ",工单号WO-" + System.currentTimeMillis();
}
}
3.3 注册工具并发起调用
将工具注册到 ChatClient,即可实现带工具能力的对话,框架自动完成调度:
java
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AgentTestController {
private final ChatClient chatClient;
public AgentTestController(ChatClient.Builder builder, OrderTools orderTools) {
this.chatClient = builder
.defaultTools(orderTools) // 注册业务工具
.build();
}
@GetMapping("/chat/tool")
public String chatWithTool(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
3.4 调用效果
- 输入:帮我查一下订单1001的售后状态
大模型自动识别需要调用 queryOrderStatus 工具,执行后基于结果返回自然语言答复。
- 输入:订单1001还没处理,帮我提交一个加急工单,原因是客户催单
大模型自动调用 submitUrgentWorkOrder 工具,提取两个参数并执行。
整个过程开发者无需手动解析 JSON、无需判断调用哪个工具,Spring AI 全自动完成调度,这也是框架的核心价值。
四、进阶开发:工具高级能力与结构化输出
基础用法只能满足 Demo 需求,真实业务中还需要动态权限、拦截增强、稳定结构化输出等进阶能力。
4.1 工具高级用法
(1)工具分组与场景化加载
全量工具堆砌会消耗大量 Token、降低选择准确率,生产环境建议按业务场景分组,单次请求只加载对应场景的工具。
java
// 按场景动态传入工具,而非全局注册
public String chatWithSceneTools(String message, String scene) {
List<Object> tools = switch (scene) {
case "order" -> List.of(orderTools);
case "user" -> List.of(userTools);
default -> List.of();
};
return chatClient.prompt()
.user(message)
.tools(tools) // 本次请求专属工具
.call()
.content();
}
(2)按用户权限动态注册工具
多租户、多角色场景下,不同用户能调用的工具范围不同,不能全局暴露所有工具。最佳实践是根据当前登录用户动态组装工具列表:
java
public String chatWithPermission(String message, LoginUser user) {
List<Object> allowedTools = new ArrayList<>();
if (user.hasPermission("order:query")) {
allowedTools.add(orderTools);
}
if (user.hasPermission("workorder:create")) {
allowedTools.add(workOrderTools);
}
return chatClient.prompt()
.user(message)
.tools(allowedTools)
.call()
.content();
}
(3)工具调用拦截增强
通过 Spring AOP 或自定义 ToolCallback 包装层,可以对所有工具调用做统一增强:
- 统一日志记录:记录调用的工具名、入参、耗时、结果、异常;
- 统一鉴权:在工具执行前校验用户权限;
- 统一脱敏:工具返回结果自动脱敏后再送入大模型;
- 统一限流:对高频调用的工具做流量控制。
4.2 结构化输出进阶
很多同学会产生疑问:Agent 最终交付给用户的不是自然语言文本吗,为什么还需要结构化 POJO 输出? 这里要区分两层输出:
- 内部流转输出:Agent 思考、任务规划、工具参数、业务数据解析,是框架与 Java 程序之间交互,适合结构化 POJO,保证机器可解析;
- 面向用户最终输出:循环结束后,大模型基于全部中间结果,整理为人类可读的自然语言文本返回。
本章介绍的结构化输出能力,主要服务于内部机器流转,对应 ReAct 循环中Thought思考、Action行动、Observation观察环节,并不直接作为最终应答返回用户。
(1)原生 entity () 方法:最简洁的 POJO 返回
Spring AI 1.0 提供了更优雅的结构化输出方式,直接调用 entity() 方法,框架自动注入格式指令并解析结果,无需手动创建 Parser:
java
public OrderAfterSaleResult getStructuredResult(String orderId) {
return chatClient.prompt()
.user("查询订单 " + orderId + " 的售后状态,返回结构化结果")
.tools(orderTools)
.call()
.entity(OrderAfterSaleResult.class); // 直接返回 POJO 对象
}
(2)嵌套 POJO 与枚举类型
结构化输出支持复杂对象,包括嵌套实体、枚举、集合,完全适配业务模型:
java
@Data
public class OrderAfterSaleResult {
private String orderId;
private OrderStatus status; // 枚举类型
private Boolean needUrgent;
private List<String> operationRecords; // 集合类型
private UserInfo operator; // 嵌套对象
}
(3)与校验注解联动
实体类上的 jakarta.validation 注解不仅可以做业务校验,还能辅助大模型理解参数约束,降低幻觉参数概率:
java
@Data
public class WorkOrderRequest {
@NotNull(message = "订单号不能为空")
@Pattern(regexp = "^O\\d{4,}$", message = "订单号格式错误")
private String orderId;
@NotBlank
@Size(max = 200, message = "原因不超过200字")
private String reason;
}
4.3 流式响应中的函数调用处理
SSE 流式场景下,工具调用是静默执行的:大模型输出思考过程时是流式的,遇到工具调用时会暂停输出,等工具执行完成后再继续流式返回结果。 Spring AI 原生支持该模式,只需将 call() 替换为 stream(),框架会自动处理工具调用的暂停与恢复:
java
public Flux<String> streamChatWithTool(String message) {
return chatClient.prompt()
.user(message)
.tools(orderTools)
.stream()
.content();
}
工程建议:前端需要兼容 "思考过程流式输出 + 工具调用静默执行" 的节奏,避免用户误以为卡顿。
五、企业级治理:生产环境的风控体系
Function Calling 跑通 Demo 很简单,但线上落地有大量容易忽略的工程风险。以下六大治理维度,是 Java 后端必须守住的生产红线。
5.1 细粒度权限控制
工具直接对接业务系统,权限失控会造成严重风险。推荐与 Spring Security 体系打通,实现三层权限管控:
- 工具可见性权限 :不同角色能看到的工具列表不同,无权限的工具根本不注册到本次请求中;
- 数据范围权限 :即使能调用工具,也只能查询自己权限范围内的数据,比如普通用户只能查自己的订单;
- 操作类型权限 :只读工具放开,写操作工具必须额外校验权限,且默认关闭。
5.2 可观测性埋点
Agent 系统不能是黑盒,必须具备完整的可观测能力:
- 指标监控 :对接 Micrometer + Prometheus,统计工具调用次数、成功率、平均耗时、异常率、Token 消耗,配置阈值告警;
- 全链路追踪 :透传 TraceId,把工具调用节点接入链路追踪系统,方便排查慢调用、异常调用;
- 审计日志 :全量记录 "谁、在什么时间、调用了哪个工具、传入什么参数、返回什么结果",满足合规审计要求。
5.3 熔断与降级
工具调用依赖外部接口、数据库、第三方服务,一旦下游故障,会拖垮整个 Agent 链路。
- 集成 Sentinel / Resilience4j,对每个工具配置独立的超时、重试、熔断规则;
- 工具熔断后,向大模型返回明确的错误信息,由大模型决定是否换路径或告知用户;
- 核心场景配置降级方案,比如数据库查询工具故障时,降级为只返回知识库中的通用答案。
5.4 幂等性保障
涉及写操作的工具(提交工单、修改数据、发送通知),必须保证幂等。大模型可能因为重试逻辑重复调用同一工具,导致业务重复执行。 两种标准落地方案:
- 业务主键幂等 :基于订单号、工单号等业务主键做去重,同一主键重复调用直接返回首次结果;
- 幂等号幂等 :工具入参携带唯一幂等号,执行前先校验幂等号是否已处理过。
java
// 基于业务主键的幂等控制示例
public String submitWorkOrder(String orderId, String reason) {
String idempotentKey = "workorder:" + orderId;
if (redisTemplate.hasKey(idempotentKey)) {
return redisTemplate.opsForValue().get(idempotentKey).toString();
}
// 执行业务逻辑
String result = doSubmit(orderId, reason);
redisTemplate.opsForValue().set(idempotentKey, result, 24, TimeUnit.HOURS);
return result;
}
5.5 多层参数校验
绝对不能信任大模型传入的参数,幻觉参数、非法参数是常态。建议建立三层校验体系:
- 基础格式校验 :使用 jakarta.validation 做非空、格式、长度、范围校验;
- 业务规则校验 :比如订单号是否存在、用户是否有权限查询该订单;
- 白名单校验 :枚举值、状态值严格限定在允许范围内,防止越权参数。
校验失败时返回结构化的错误描述,大模型可以读懂错误并修正参数重试,这也是 ReAct 循环中纠错能力的基础。
5.6 数据安全与脱敏
工具返回的业务数据可能包含敏感信息,直接送入大模型会有数据泄露风险。
- 工具返回结果做统一脱敏:手机号、身份证、地址、金额等敏感字段脱敏后再送入大模型;
- 核心敏感数据优先使用私有化部署大模型,避免数据流出企业内网;
- 入参侧做敏感词过滤,防止用户通过工具调用窃取数据。
六、场景实战与排错指南
6.1 实战场景:NL2SQL 数据查询工具
我们以最常见的业务场景 ------ 自然语言转 SQL 查询数据,完整实现一个生产可用的工具,串联前面讲到的所有知识点。
java
@Component
public class DataQueryTools {
/**
* 执行销售数据查询SQL
* @param sql 待执行的查询SQL语句
* @return 查询结果JSON字符串
*/
@Tool(name = "executeSalesQuery", description = "执行销售数据的查询SQL,仅支持SELECT查询,禁止写入操作")
public String executeSalesQuery(String sql) {
// 1. 安全校验:只允许 SELECT 语句
if (!sql.trim().toLowerCase().startsWith("select")) {
return "错误:仅支持SELECT查询语句,禁止执行写入操作";
}
// 2. 慢查询限制:增加超时时间
// 3. 权限校验:只允许查询销售库的指定表
try {
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
return JSON.toJSONString(result);
} catch (Exception e) {
return "SQL执行失败:" + e.getMessage();
}
}
}
使用时,用户只需说 "帮我查一下上个月华东区的销售总额",大模型会自动生成 SQL、调用该工具执行,再基于返回结果整理成自然语言答复。配合参数校验、权限控制、熔断降级,即可直接用于生产环境。
6.2 高频问题排查清单
|--------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------|
| 问题现象 | 常见原因 | 解决办法 |
| 大模型总是不调用工具,直接回答 | 1. 工具描述不清晰,模型没理解用途;2. 提示词冲突;3. 模式被设为 NONE | 1. 优化工具 description,明确用途与适用场景;2. 检查调用模式是否为 AUTO;3. 在系统提示词中明确 "优先使用工具获取准确数据" |
| 工具参数解析失败、类型转换报错 | 1. 参数类型不匹配,比如用了复杂泛型;2. 参数名和方法名不对应;3. 大模型生成了不符合 Schema 的参数 | 1. 参数使用简单明确的类型,避免复杂泛型;2. 参数命名清晰,补充注释说明;3. 开启参数校验,返回明确错误让模型重试 |
| 工具执行后,大模型没有继续推理,直接返回原始结果 | 1. 返回结果格式异常;2. 上下文长度不足,结果被截断 | 1. 工具返回结构化字符串,不要返回二进制或特殊格式;2. 控制返回结果长度,过长时做摘要 |
| 多工具场景下,模型总是选错工具 | 1. 工具描述相似度太高;2. 工具数量过多 | 1. 差异化编写工具描述,明确各自适用边界;2. 按场景分组加载工具,减少单次工具数量 |
| 同样的请求,有时调工具有时不调 | 大模型本身的随机性导致 | 调低温度参数(temperature 0~0.3),提升决策稳定性 |
6.3 工具描述优化技巧
工具描述的质量直接决定调用准确率,三个优化原则:
- 明确职责边界 :写清楚 "能做什么、不能做什么",比如 "仅用于查询订单状态,不支持修改";
- 说明入参含义 :参数注释不要只写 "订单号",补充格式、范围等约束;
- 说明返回格式 :让大模型知道返回结果是什么形式,便于后续处理。
七、选型边界:单次调用 vs ReAct Agent
7.1 判断标准
适合用单次 Function Calling 的场景
- 任务路径固定:用户提问 → 调用一次工具 → 返回结果,不需要多步迭代;
- 工具数量少:1~3 个工具,且调用逻辑简单,不需要动态规划;
- 实时性要求高:希望快速返回结果,不能接受多轮大模型调用的时延;
- 需求稳定:业务逻辑固定,不需要自主探索与策略调整。
典型场景:客服查询订单、查询物流、简单数据查询、固定格式的信息提交。
需要升级为 ReAct Agent 的场景
- 任务路径不确定:下一步做什么取决于上一步的结果,无法预先枚举全部分支;
- 需要多工具动态串联:需要自主选择多个工具、多次调用,动态调整执行顺序;
- 具备任务规划需求:需要拆解复杂目标、分步执行、遇到错误自主修正;
- 任务复杂度高:需要思考 - 行动 - 观察多轮迭代才能完成。
典型场景:故障排查、复杂数据分析、调研检索、多步骤办公自动化。
7.2 成本与复杂度对比
|----------|-------------------------|-------------------------|
| 维度 | 单次 Function Calling | ReAct Agent |
| 大模型调用次数 | 2 次(判断 + 总结) | N 次(每轮循环 1 次,通常 3~8 次) |
| 响应时延 | 低,秒级 | 高,数秒到数十秒 |
| Token 成本 | 低 | 高,随循环次数线性增长 |
| 系统复杂度 | 低 | 高,需要循环控制、记忆管理、状态持久化 |
| 稳定性 | 高,路径可控 | 较低,执行路径动态不确定 |
| 泛化能力 | 弱,只能处理预设场景 | 强,可处理开放式复杂任务 |
7.3 渐进式升级路径
推荐所有团队都走渐进式路线,避免上来就过度设计:
- 第一步 :先落地单次 Function Calling,验证业务价值,跑通工具治理体系;
- 第二步 :当出现 "需要多步调用、路径不固定" 的需求时,再手写极简 ReAct 循环,不引入复杂 Agent 框架;
- 第三步 :当 Agent 场景增多、需要统一治理时,再引入 Spring AI Alibaba 等企业级框架。
核心原则:能简单就不要复杂。技术选型的目标不是用最炫的技术,而是用最低成本解决业务问题。
八 、 核心复习要点
- 层级定位 :Agent 是系统,ReAct 是范式,Function Calling 是底层通信协议;单次工具调用不是 Agent,加上思考 - 观察循环才是。
- 底层机制 :Spring AI 通过 @Tool 扫描生成 JSON Schema,由工具调度器负责解析指令、反射调用、结果回填;三种调用模式 AUTO/NONE/FORCED 适配不同场景。
- 开发进阶 :生产环境按场景分组加载工具、按权限动态注册工具;结构化输出优先用 entity() 原生方法,支持嵌套对象与校验注解。
- 生产治理 :六大红线 ------ 权限细粒度控制、可观测性埋点、熔断降级、写操作幂等、多层参数校验、数据脱敏,绝对不能信任大模型的输入。
- 选型原则 :简单场景用单次函数调用,复杂场景再升级 Agent;走渐进式落地路线,避免过度设计。
结语
Function Calling 看起来只是 "让大模型调方法",但它是大模型从 "聊天玩具" 走向 "业务生产力" 的关键一步,也是整个 Agent 技术大厦的第一块砖。
对于 Java 后端开发者,我们的核心工作不是去优化大模型的调用准确率,而是构建一层坚固的工程防护壳:让大模型在我们划定的权限、参数、超时、安全边界内,稳定、可控、安全地调用业务能力。这也是后续所有 Agent 开发的核心准则。
到这里,我们已经掌握了单次工具调用的全部底层能力与治理方法。而真正的 Agent,是把这个过程包装成循环:思考 → 调用工具 → 观测结果 → 再思考,直到任务完成。
下一篇,我们将用今天掌握的 Function Calling 能力,不依赖任何高级 Agent 框架,手写一个完整的极简 ReAct Agent 循环,亲手把 "单次调用" 变成 "智能迭代",彻底搞懂 Agent 内部每一步的流转逻辑。
📚 我的技术博客导航:点击进入一站式查看所有干货
