Spring AI Function Calling深度解析:@Tool注解原理与生产实战
2026/4/30大约 4 分钟
Spring AI Function Calling深度解析:@Tool注解原理与生产实战
适读人群:Java开发者,想深入理解Spring AI工具调用机制
阅读时长:约18分钟
文章价值:@Tool原理剖析 + 源码走读 + 生产最佳实践
Function Calling:LLM从"问答机"到"执行者"的关键
Function Calling(工具调用)赋予了LLM调用外部工具的能力,是构建AI Agent的基础。没有Function Calling,LLM只能基于训练数据回答问题;有了它,LLM可以查询实时数据库、调用API、执行代码。
Function Calling工作原理:
Spring AI的@Tool注解原理
Spring AI通过@Tool注解将Java方法自动转换为LLM可调用的工具描述。
JSON Schema自动生成
Java方法 → JSON Schema示例:
// Java方法定义
@Tool(description = "查询指定股票的实时价格")
public StockPrice getStockPrice(
@ToolParam(description = "股票代码,如AAPL、TSLA") String symbol,
@ToolParam(description = "货币单位,默认USD") String currency
) {
return stockApiClient.getPrice(symbol, currency);
}
// 自动生成的JSON Schema(发送给LLM)
/*
{
"name": "getStockPrice",
"description": "查询指定股票的实时价格",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "股票代码,如AAPL、TSLA"
},
"currency": {
"type": "string",
"description": "货币单位,默认USD"
}
},
"required": ["symbol", "currency"]
}
}
*/四种工具注册方式对比
// 方式1:方法级@Tool(最常用)
@Component
public class WeatherTools {
@Tool(description = "查询天气")
public String getWeather(String city) { ... }
}
// 注册到ChatClient
ChatClient client = builder
.defaultTools(new WeatherTools())
.build();
// 方式2:函数Bean(Spring Bean方式)
@Bean("getCurrentWeather")
@Description("查询实时天气")
public Function<WeatherRequest, WeatherResponse> weatherFunction() {
return req -> weatherService.getWeather(req.city());
}
// 注册(通过函数名)
ChatClient client = builder
.defaultTools("getCurrentWeather")
.build();
// 方式3:动态工具(运行时注册,适合多租户)
String result = chatClient.prompt()
.user("查询苹果股价")
.tools(new StockTool()) // 仅在本次请求中有效
.call()
.content();
// 方式4:ToolCallback接口(最灵活)
ToolCallback customTool = ToolCallback.builder()
.name("customSearch")
.description("自定义搜索工具")
.inputSchema("""{"type":"object","properties":{"query":{"type":"string"}}}""")
.callFunction(input -> searchService.search(input))
.build();工具调用完整流程源码追踪
关键源码位置(Spring AI 1.0):
org.springframework.ai.model.tool.DefaultToolCallingChatModel
→ handleToolCalls(ChatResponse, ToolCallbackContext)
→ toolCallback.call(toolInput)
→ appendToolResultToHistory()
→ model.call(updatedPrompt) // 递归调用直到无tool_calls高级用法:工具调用最大次数控制
// 防止无限工具调用循环
ChatOptions options = OpenAiChatOptions.builder()
.withModel("gpt-4o")
// 注:Spring AI通过最大Token间接控制
.withMaxCompletionTokens(4000)
.build();
// 业务层面的最大调用次数
@Service
public class SafeToolAgentService {
private static final int MAX_TOOL_CALLS = 10;
public String executeWithLimit(String task) {
AtomicInteger callCount = new AtomicInteger(0);
// 包装工具,记录调用次数
ToolCallCountWrapper wrapper = new ToolCallCountWrapper(
agentTools, callCount, MAX_TOOL_CALLS
);
return chatClient.prompt()
.user(task)
.tools(wrapper)
.call()
.content();
}
}工具安全性:防止危险操作
@Component
public class SecureAgentTools {
// ✅ 安全:只读操作
@Tool(description = "查询数据库中的产品信息(只读)")
public List<Product> queryProducts(String category) {
return productRepository.findByCategory(category);
}
// ⚠️ 需要鉴权:写操作
@Tool(description = "更新商品价格(需要管理员权限)")
public String updatePrice(String productId, double newPrice) {
// 工具内部进行权限校验
if (!securityContext.hasRole("ADMIN")) {
return "操作拒绝:没有管理员权限";
}
productService.updatePrice(productId, newPrice);
return "价格更新成功";
}
// ❌ 危险操作需要二次确认
@Tool(description = "删除订单(谨慎:不可逆操作)")
public String deleteOrder(String orderId) {
// 记录操作审计日志
auditService.log("DELETE_ORDER", orderId, getCurrentUser());
// 软删除而非物理删除
orderService.softDelete(orderId);
return "订单已标记删除,24小时内可联系客服恢复";
}
}工具调用的可观测性
// 自定义Advisor监控工具调用
@Component
public class ToolCallObservabilityAdvisor implements CallAroundAdvisor {
private final MeterRegistry meterRegistry;
@Override
public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
AdvisedResponse response = chain.nextAroundCall(request);
// 统计工具调用次数和耗时
response.response().getResult().getOutput().getToolCalls()
.forEach(toolCall -> {
meterRegistry.counter("ai.tool.calls",
"tool_name", toolCall.name(),
"status", "success"
).increment();
});
return response;
}
}