Java Tool Calling
Java Tool Calling 的核心是把 Java 方法注册成模型可调用的工具,用 mock 数据先跑通“模型选择工具、后端执行工具、模型生成最终回答”的链路。
Java Tool Calling 是什么
Java Tool Calling,中文可以理解为“Java 工具调用”。
它指的是在 Java 项目里把方法暴露为大模型可选择的工具。
模型不会直接执行 Java 方法。
流程是:
Java 定义工具方法
-> Spring AI 生成工具定义
-> 模型选择工具并生成参数
-> Spring AI 调用 Java 方法
-> 方法结果回填给模型
-> 模型生成最终回答
结论:
Java Tool Calling 本质上是把业务方法包装成模型可理解、后端可执行的工具。
适合做工具的方法
适合:
查询订单
查询库存
查询用户信息
创建工单
发送通知
计算价格
不适合直接暴露:
删除数据
批量修改
高风险支付
无权限校验的内部接口
不可回滚操作
学习阶段先做 mock。
Mock,中文一般翻译为“模拟数据”或“模拟对象”。
先不用真实数据库,避免把重点搞复杂。
注册 Java 方法为 Tool
Spring AI 可以用 @Tool 注解把方法声明为工具。
示例:
import org.springframework.ai.tool.annotation.Tool;
public class OrderTools {
@Tool(description = "根据订单号查询订单状态")
public String queryOrder(String orderId) {
return "订单 " + orderId + " 当前状态:已发货";
}
}
重点是 description。
它会影响模型什么时候选择这个工具。
描述要写清楚:
工具用途
输入参数
适用场景
不适用场景
订单查询 mock
先定义订单结果:
public record OrderInfo(
String orderId,
String status,
String trackingNo
) {
}
工具类:
import org.springframework.ai.tool.annotation.Tool;
import java.util.Map;
public class OrderTools {
private static final Map<String, OrderInfo> ORDERS = Map.of(
"A10001", new OrderInfo("A10001", "已发货", "SF123456"),
"A10002", new OrderInfo("A10002", "待发货", "")
);
@Tool(description = "根据订单号查询订单状态。只用于查询订单发货、物流和当前处理状态。")
public OrderInfo queryOrder(String orderId) {
OrderInfo orderInfo = ORDERS.get(orderId);
if (orderInfo == null) {
return new OrderInfo(orderId, "未查询到订单", "");
}
return orderInfo;
}
}
用户问题:
帮我查一下订单 A10001 发货了吗?
期望模型选择 queryOrder。
库存查询 mock
库存结果:
public record StockInfo(
String skuId,
String productName,
int availableStock
) {
}
工具类:
import org.springframework.ai.tool.annotation.Tool;
import java.util.Map;
public class StockTools {
private static final Map<String, StockInfo> STOCKS = Map.of(
"SKU-001", new StockInfo("SKU-001", "机械键盘", 12),
"SKU-002", new StockInfo("SKU-002", "无线鼠标", 0)
);
@Tool(description = "根据商品 SKU 查询可售库存。只用于库存数量查询。")
public StockInfo queryStock(String skuId) {
StockInfo stockInfo = STOCKS.get(skuId);
if (stockInfo == null) {
return new StockInfo(skuId, "未知商品", 0);
}
return stockInfo;
}
}
用户问题:
SKU-001 还有库存吗?
期望模型选择 queryStock。
在 ChatClient 中使用工具
Service 示例:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class AiToolService {
private final ChatClient chatClient;
public AiToolService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("""
你是一个售后客服助手。
如果用户询问订单状态,优先使用订单查询工具。
如果用户询问库存,优先使用库存查询工具。
不要编造订单或库存结果。
""")
.build();
}
public String chat(String message) {
return chatClient.prompt()
.user(message)
.tools(new OrderTools(), new StockTools())
.call()
.content();
}
}
这里每次请求都把工具传给模型。
如果是全局默认工具,也可以在构建 ChatClient 时配置默认工具。
学习阶段先用 .tools(...) 更直观。
Controller 示例
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/tool-chat")
public class ToolChatController {
private final AiToolService aiToolService;
public ToolChatController(AiToolService aiToolService) {
this.aiToolService = aiToolService;
}
@PostMapping
public ChatResponse chat(@RequestBody ChatRequest request) {
String answer = aiToolService.chat(request.message());
return new ChatResponse(answer);
}
}
请求:
curl -X POST http://localhost:8080/api/tool-chat \
-H "Content-Type: application/json" \
-d '{"message":"帮我查一下订单 A10001 发货了吗?"}'
期望结果:
订单 A10001 已发货,物流单号是 SF123456。
工具参数校验
模型生成的参数不能直接信任。
订单号可以先做简单校验:
private void validateOrderId(String orderId) {
if (orderId == null || orderId.isBlank()) {
throw new IllegalArgumentException("订单号不能为空");
}
if (!orderId.matches("[A-Z][0-9]{5}")) {
throw new IllegalArgumentException("订单号格式不正确");
}
}
在工具里使用:
@Tool(description = "根据订单号查询订单状态。订单号格式示例:A10001。")
public OrderInfo queryOrder(String orderId) {
validateOrderId(orderId);
return ORDERS.getOrDefault(orderId, new OrderInfo(orderId, "未查询到订单", ""));
}
后面接真实业务系统时,还要加:
用户身份
订单归属校验
接口限流
审计日志
异常处理
调试工具调用日志
调试时要看清楚几件事:
模型有没有选择工具
选择的是哪个工具
参数是什么
工具是否执行成功
工具返回了什么
最终回答是否引用了工具结果
工具方法里可以先加日志:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderTools {
private static final Logger log = LoggerFactory.getLogger(OrderTools.class);
@Tool(description = "根据订单号查询订单状态")
public OrderInfo queryOrder(String orderId) {
log.info("queryOrder called, orderId={}", orderId);
return new OrderInfo(orderId, "已发货", "SF123456");
}
}
日志不要打印敏感信息。
真实项目里要避免打印:
手机号
身份证
详细地址
API Key
完整用户隐私数据
工具调用失败怎么处理
常见失败:
参数缺失
参数格式错误
业务接口超时
订单不存在
库存服务不可用
模型选错工具
处理方式:
参数错误:返回明确错误
订单不存在:返回未查询到
接口超时:返回稍后重试
模型选错:优化工具描述和 Prompt
工具异常不要直接把堆栈返回给模型或用户。
可以返回业务可理解的信息:
return new OrderInfo(orderId, "查询失败,请稍后重试", "");
常见问题
为什么模型没有调用工具
先检查:
模型是否支持 Tool Calling
调用时是否传入 tools
工具 description 是否清楚
用户问题是否真的需要工具
system prompt 是否说明了工具使用规则
为什么调用错工具
可能是工具描述太模糊。
比如:
订单工具
库存工具
不如写成:
根据订单号查询订单状态。只用于订单发货、物流、售后进度查询。
根据 SKU 查询商品可售库存。只用于库存数量查询。
mock 有什么意义
mock 可以先验证 Tool Calling 链路。
不需要一开始就连数据库、权限和真实接口。
先把链路跑通,再替换为真实业务服务。
练习清单
完成几件事情:
能用 @Tool 注册 Java 方法
能写订单查询 mock 工具
能写库存查询 mock 工具
能把工具传给 ChatClient
能用 curl 触发工具调用
能在日志里看到工具调用参数
知道工具参数要校验
知道工具异常要兜底
建议目录:
ai-agent-study
├── java
│ └── tool-calling-demo
│ ├── ToolChatController.java
│ ├── AiToolService.java
│ ├── tools
│ │ ├── OrderTools.java
│ │ └── StockTools.java
│ └── dto
│ ├── OrderInfo.java
│ └── StockInfo.java
└── docs
└── java-tool-calling.md
小结
本节的结论:
Java Tool Calling 先用 @Tool 和 mock 数据跑通,再逐步接真实业务接口。
最小链路:
用户问题
-> ChatClient
-> 模型选择工具
-> Java 方法执行
-> 工具结果回填
-> 模型生成回答
工具调用进入真实业务前,必须补权限、校验、日志和异常兜底。


Comments | 0 条评论