1. Spring AI工具配置概述
在构建AI驱动的应用程序时,工具调用(Tool Calling)是一个关键功能,它允许AI模型与外部系统和服务进行交互。Spring AI 1.x提供了灵活的工具配置机制,支持全局默认配置和运行时动态配置两种模式。
工具调用的核心价值在于:
- 扩展AI模型的能力边界,使其能够执行预定义的操作
- 实现与业务系统的无缝集成
- 提供可控的执行环境,确保AI行为符合预期
Spring AI的工具配置系统设计考虑了以下关键因素:
- 声明式与编程式API的平衡
- 配置的继承与覆盖规则
- 执行上下文的管理
- 结果处理的灵活性
2. 全局默认工具配置
2.1 默认工具的定义方式
Spring AI支持三种定义默认工具的方式:
- 注解式声明:使用
@Tool注解标记工具方法
@Component class DateTimeTools { @Tool(description = "Get current date and time") public String getCurrentDateTime() { return LocalDateTime.now().toString(); } }- 函数式注册:通过
Function接口定义工具
@Bean @Description("Get weather by location") public Function<WeatherRequest, WeatherResponse> weatherFunction() { return request -> weatherService.getWeather(request); }- 编程式构建:使用
ToolCallback接口
@Bean public ToolCallback customTool() { return MethodToolCallback.builder() .name("userLookup") .description("Find user by ID") .toolMethod(ReflectionUtils.findMethod(UserService.class, "findById", Long.class)) .toolObject(userService) .build(); }2.2 默认工具的注册机制
全局默认工具可以通过以下方式注册:
- ChatClient构建器:
ChatClient.builder(chatModel) .defaultTools(new DateTimeTools(), weatherFunction) .build();- ChatModel配置:
OllamaChatModel.builder() .ollamaApi(ollamaApi) .defaultOptions(ToolCallingChatOptions.builder() .toolCallbacks(toolCallbacks) .build()) .build();2.3 默认工具的作用域
全局默认工具具有以下特点:
- 生命周期与创建它们的ChatClient或ChatModel实例绑定
- 对所有通过该实例执行的对话请求可见
- 适合那些在多个对话中需要重复使用的工具
注意:过度使用全局工具可能导致工具污染问题,特别是在多租户场景下。建议仔细评估工具的作用范围。
3. 运行时动态工具配置
3.1 动态工具绑定方式
Spring AI提供了多种运行时绑定工具的方法:
- 直接工具实例:
ChatClient.create(chatModel) .prompt("Set alarm for 10 minutes from now") .tools(new AlarmTools()) .call();- 工具名称解析:
ChatClient.create(chatModel) .prompt("What's the weather in Berlin?") .tools("weatherFunction") .call();- 动态选项配置:
ChatOptions options = ToolCallingChatOptions.builder() .toolCallbacks(ToolCallbacks.from(new StockQuoteTool())) .build(); new Prompt("Get AAPL stock price", options);3.2 动态工具的优先级
当同时存在默认工具和运行时工具时:
- 同名工具:运行时工具完全覆盖默认工具
- 不同名工具:两者都会被保留
- 执行顺序:按工具定义的顺序调用
覆盖行为示例:
// 默认工具 ChatClient client = ChatClient.builder(chatModel) .defaultTools(new BasicCalculator()) .build(); // 运行时覆盖 client.prompt("Calculate 2+2") .tools(new ScientificCalculator()) // 覆盖BasicCalculator .call();3.3 动态工具的应用场景
动态工具特别适合以下情况:
- 临时性工具需求
- 用户特定工具
- 上下文敏感型工具
- 需要隔离的工具执行
4. 工具配置高级特性
4.1 工具上下文传递
Spring AI支持通过ToolContext传递额外上下文信息:
ChatClient.create(chatModel) .prompt("Get customer details") .tools(new CustomerTools()) .toolContext(Map.of("tenantId", "acme-corp")) .call();在工具实现中访问上下文:
@Tool public Customer getCustomer(Long id, ToolContext context) { String tenant = context.get("tenantId"); return customerRepo.find(id, tenant); }4.2 结果处理控制
可以通过以下方式控制工具执行结果的处理:
- 直接返回模式:
@Tool(returnDirect = true) public Report generateReport(Params params) { return reportService.generate(params); }- 自定义结果转换器:
@Tool(resultConverter = CustomConverter.class) public DataSet queryData(Query query) { return db.query(query); }- 元数据配置:
ToolMetadata.builder() .returnDirect(true) .resultConverter(new CustomResultConverter()) .build();4.3 工具输入模式
Spring AI支持丰富的参数定义方式:
- 参数注解:
@Tool public void placeOrder( @ToolParam(description = "Product ID") String productId, @ToolParam(description = "Quantity", required = false) Integer qty ) { // 实现逻辑 }- JSON Schema定义:
ToolDefinition.builder() .inputSchema(""" { "type": "object", "properties": { "location": {"type": "string"}, "unit": {"type": "string", "enum": ["C","F"]} }, "required": ["location"] } """) .build();- 记录类型:
public record WeatherRequest( @JsonPropertyDescription("City name") String location, @JsonProperty(required = false) Unit unit ) {}5. 工具执行与生命周期
5.1 执行流程控制
Spring AI提供了对工具执行流程的细粒度控制:
- 执行资格判断:
@Bean ToolExecutionEligibilityPredicate customPredicate() { return (options, response) -> { // 自定义判断逻辑 return checkExecutionConditions(); }; }- 执行管理器:
@Bean ToolCallingManager toolCallingManager() { return ToolCallingManager.builder() .executionEligibilityPredicate(customPredicate()) .build(); }5.2 生命周期钩子
可以通过以下方式介入工具生命周期:
- 前置处理器:
@Bean ToolExecutionListener preProcessor() { return new ToolExecutionListener() { @Override public void beforeExecution(ToolExecutionRequest request) { // 执行前逻辑 } }; }- 后置处理器:
@Bean ToolExecutionListener postProcessor() { return new ToolExecutionListener() { @Override public void afterExecution(ToolExecutionResult result) { // 执行后逻辑 } }; }6. 最佳实践与常见问题
6.1 配置策略建议
- 工具组织原则:
- 按功能域分组工具
- 区分核心工具与扩展工具
- 使用清晰的命名规范
- 性能优化:
- 轻量级工具方法
- 异步执行耗时操作
- 合理使用缓存
- 安全考虑:
- 实施权限检查
- 参数验证
- 敏感操作审计
6.2 常见问题排查
- 工具未调用:
- 检查工具描述是否清晰
- 验证输入模式是否匹配
- 确认工具可见性
- 参数解析失败:
- 检查JSON Schema定义
- 验证参数类型
- 确保必需参数已提供
- 执行上下文丢失:
- 确认ToolContext正确传递
- 检查线程边界
- 验证序列化/反序列化
6.3 调试技巧
- 日志配置:
logging.level.org.springframework.ai.tool=DEBUG- 诊断端点(Spring Boot Actuator):
@Bean @Endpoint(id = "aitools") public ToolRegistryEndpoint toolEndpoint() { return new ToolRegistryEndpoint(); }- 测试工具:
@Test void testToolExecution() { ToolTester.builder() .tool(myTool) .input("{\"param\":\"value\"}") .expectOutput("expectedResult") .verify(); }7. 实际应用案例
7.1 电商助手实现
@Component class ECommerceTools { private final ProductRepo productRepo; private final OrderService orderService; @Tool(description = "Search products by keywords") public List<Product> searchProducts( @ToolParam(description = "Search keywords") String keywords, @ToolParam(description = "Maximum results") int limit ) { return productRepo.search(keywords, limit); } @Tool(description = "Place new order", returnDirect = true) public OrderConfirmation placeOrder( @ToolParam(description = "Product ID") String productId, @ToolParam(description = "Quantity") int quantity, ToolContext context ) { String userId = context.get("userId"); return orderService.placeOrder(userId, productId, quantity); } }7.2 数据分析流程
@Bean Function<AnalysisRequest, AnalysisResult> dataAnalysisTool() { return request -> { // 1. 数据准备 Dataset data = dataService.load(request.datasetId()); // 2. 执行分析 AnalysisResult result = analyzer.analyze(data, request.parameters()); // 3. 生成报告 return reportGenerator.generate(result); }; } // 配置为默认工具 ChatClient.builder(chatModel) .defaultTools("dataAnalysisTool") .build();7.3 动态工具切换
public ToolCallback getContextualTool(User user) { if (user.hasRole("ADMIN")) { return adminTools; } else if (user.hasPermission("REPORT")) { return reportTools; } return basicTools; } // 在控制器中使用 @GetMapping("/ask") public String askQuestion(@RequestParam String query, @AuthenticationPrincipal User user) { ToolCallback tool = getContextualTool(user); return ChatClient.create(chatModel) .prompt(query) .tools(tool) .call() .content(); }8. 性能优化建议
- 工具懒加载:
@Bean @Lazy public ExpensiveTool expensiveTool() { return new ExpensiveTool(); }- 工具缓存:
@Tool @Cacheable("weatherData") public WeatherData getWeather(String location) { return weatherApi.fetch(location); }- 批量处理:
@Tool(description = "Batch process items") public BatchResult processItems( @ToolParam(description = "Item IDs") List<String> ids ) { return processor.processBatch(ids); }- 异步工具:
@Bean @Description("Async data fetch") public Supplier<CompletableFuture<Data>> asyncDataFetcher() { return () -> dataService.fetchAsync(); }9. 安全实践
- 权限检查:
@Tool public SensitiveData getData(@ToolParam String id, ToolContext context) { SecurityUtils.checkAccess(context.get("user"), id); return dataRepo.find(id); }- 输入验证:
@Tool public void updateRecord( @ToolParam @Valid @Size(max=100) String input ) { // 处理逻辑 }- 审计日志:
@Bean ToolExecutionListener auditLogger() { return new ToolExecutionListener() { @Override public void afterExecution(ToolExecutionResult result) { auditLog.log(result); } }; }10. 未来演进方向
- 工具版本管理:
@Tool(version = "2.0") public ImprovedResult improvedTool() { // 新版本实现 }- 工具依赖声明:
@Tool(dependsOn = {"preProcessor", "validator"}) public Result compositeTool() { // 依赖其他工具 }- 动态模式生成:
ToolDefinition.dynamicBuilder() .fromOpenApiSpec(apiSpec) .build();- 工具市场集成:
@Bean ToolRegistry toolMarketplace() { return new RemoteToolRegistry("https://market.example.com/api/tools"); }