--- title: Spring AI 工具定义最佳实践 keywords: [Spring AI, Tool, 工具定义, Agent, 函数调用] summary: 如何为 Spring AI Agent 定义高质量的工具(Tool),包括命名、描述、参数设计和错误处理 category: domain --- # Spring AI 工具定义最佳实践 ## 工具定义基础 ### 基本注解 ```java @Component public class MyTools { @Tool(description = "查询用户信息。参数 userId: 用户ID(必填)") public UserInfo getUserInfo(String userId) { // 实现 } } ``` ### 关键要素 1. **@Component** - 让 Spring 扫描到 2. **@Tool** - 标记为 Agent 可调用的工具 3. **description** - 告诉 Agent 这个工具做什么 ## 描述(Description)编写规范 ### 好的描述 ```java @Tool(description = "查询知识库文档。优先精确匹配关键词,未命中或多个匹配时自动补充语义相关片段。" + "参数 query: 查询关键词,例如 'ERR_TIMEOUT'、'支付网关超时'") public LookupResult lookupKnowledge(String query) { ... } ``` **要点**: - ✅ 说明工具用途(查询知识库) - ✅ 说明工作机制(精确匹配 → 语义补充) - ✅ 说明参数含义和示例 ### 差的描述 ```java @Tool(description = "查询文档") // ❌ 太简略 public LookupResult lookup(String q) { ... } ``` ## 参数设计 ### 参数命名 ```java // ✅ 好的命名 - 语义清晰 public Result search(String query, int maxResults, String category) // ❌ 差的命名 - 缩写难懂 public Result search(String q, int max, String cat) ``` ### 参数类型 ```java // ✅ 使用明确的类型 public UserInfo getUser(String userId) public List getOrders(LocalDate startDate, LocalDate endDate) // ❌ 使用 Object 或 Map public Object getUser(Map params) // Agent 不知道传什么 ``` ### 可选参数处理 ```java @Tool(description = "查询订单。参数 status: 订单状态(可选,不传则查所有)") public List getOrders( @Nullable String status // 使用 @Nullable 标注 ) { if (status == null) { return orderRepository.findAll(); } return orderRepository.findByStatus(status); } ``` ## 返回值设计 ### 使用明确的返回类型 ```java // ✅ 好的返回类型 public class LookupResult { private boolean found; private PrimaryResult primary; private SupplementResult supplement; } // ❌ 返回 String - Agent 难以解析 public String lookup(String query) { return "找到文档: xxx"; // 非结构化 } ``` ### 返回错误信息 ```java public LookupResult lookup(String query) { if (query == null || query.isEmpty()) { return LookupResult.builder() .found(false) .error("查询关键词不能为空") .build(); } // 正常逻辑 } ``` ## 错误处理 ### 优雅降级 ```java @Tool(description = "查询用户信息") public UserInfo getUser(String userId) { try { return userService.findById(userId); } catch (UserNotFoundException e) { log.warn("用户不存在: userId={}", userId); return UserInfo.notFound(userId); // 返回特殊对象,不抛异常 } catch (Exception e) { log.error("查询用户失败: userId={}", userId, e); return UserInfo.error("系统错误,请稍后重试"); } } ``` ### 不要抛出未捕获的异常 ```java // ❌ 不要这样做 @Tool(description = "查询用户") public UserInfo getUser(String userId) { return userService.findById(userId); // 可能抛出异常,Agent 无法处理 } ``` ## 可观测性 ### 日志规范 ```java @Tool(description = "查询订单") public List getOrders(String userId) { String requestId = UUID.randomUUID().toString().substring(0, 8); long startTime = System.currentTimeMillis(); log.info("[{}] 收到订单查询请求: userId={}", requestId, userId); try { List orders = orderService.findByUserId(userId); long elapsed = System.currentTimeMillis() - startTime; log.info("[{}] 查询完成: count={}, time={}ms", requestId, orders.size(), elapsed); return orders; } catch (Exception e) { log.error("[{}] 查询失败: userId={}", requestId, userId, e); throw e; } } ``` ## 性能优化 ### 设置合理的超时 ```java @Tool(description = "查询大数据集") public DataResult queryBigData(String query) { // 设置超时保护 return CompletableFuture .supplyAsync(() -> heavyQuery(query)) .orTimeout(5, TimeUnit.SECONDS) .exceptionally(ex -> DataResult.timeout()) .join(); } ``` ### 避免返回超大数据 ```java // ✅ 分页或限制数量 @Tool(description = "查询用户列表(最多返回 100 条)") public List listUsers(int page, int size) { size = Math.min(size, 100); // 强制上限 return userService.findAll(PageRequest.of(page, size)); } // ❌ 返回全量数据 public List listAllUsers() { return userService.findAll(); // 可能几万条 } ``` ## 工具组合示例 ### 查询 + 操作的组合 ```java @Component public class OrderTools { @Tool(description = "查询订单详情") public OrderDetail getOrder(String orderId) { ... } @Tool(description = "取消订单") public CancelResult cancelOrder(String orderId, String reason) { ... } @Tool(description = "申请退款") public RefundResult refund(String orderId, Double amount) { ... } } ``` **Agent 使用场景**: 1. 用户:"帮我查一下订单 12345" 2. Agent 调用 `getOrder("12345")` 3. 用户:"帮我取消这个订单" 4. Agent 调用 `cancelOrder("12345", "用户主动取消")` ## 常见陷阱 ### ❌ 工具做太多事 ```java // 不要把整个业务流程塞进一个工具 @Tool(description = "处理订单") public void processOrder(String orderId) { // 查询订单 // 验证库存 // 扣减库存 // 创建物流单 // 发送通知 // ... 太多步骤,Agent 无法介入 } ``` ### ✅ 拆分成多个工具 ```java @Tool(description = "查询订单") public Order getOrder(String orderId) { ... } @Tool(description = "验证库存") public StockResult checkStock(String productId, int quantity) { ... } @Tool(description = "创建物流单") public ShipmentResult createShipment(String orderId) { ... } ``` ### ❌ 描述不准确 ```java @Tool(description = "查询用户") public UserInfo getUser(String query) { // 实际上支持按 userId、email、手机号查询 // 但描述没说清楚,Agent 不知道 } ``` ### ✅ 描述完整 ```java @Tool(description = "查询用户信息。支持按 userId、email 或手机号查询。" + "参数 query: 用户ID、邮箱或手机号") public UserInfo getUser(String query) { ... } ```