Files
SuperBizAgent-java/knowledge_base/domain/spring-ai-tool-best-practices.md
zhuyongxin 3956426c97 docs(knowledge): 添加测试知识库文档
新增 6 个知识库文档,用于测试 L0+L1 混合检索功能:

API 类:
- payment-errors.md - 支付网关错误码定义

领域知识类:
- spring-ai-tool-best-practices.md - Spring AI 工具定义最佳实践

基础设施类:
- redis-config.md - Redis 缓存配置指南
- mysql-connection-pool.md - MySQL 连接池配置
- flyway-best-practices.md - Flyway 数据库迁移最佳实践

故障排查类:
- fault-diagnosis-process.md - 故障诊断流程规范

所有文档均包含:
- 标准 frontmatter 元数据 (title, keywords, summary, category)
- 实用配置示例和代码片段
- 支持 L0 精确匹配的关键词
2026-06-24 16:19:10 +08:00

6.7 KiB
Raw Permalink Blame History


title: Spring AI 工具定义最佳实践 keywords: [Spring AI, @Tool, 工具定义, Agent, 函数调用] summary: 如何为 Spring AI Agent 定义高质量的工具(Tool),包括命名、描述、参数设计和错误处理 category: domain

Spring AI 工具定义最佳实践

工具定义基础

基本注解

@Component
public class MyTools {
    
    @Tool(description = "查询用户信息。参数 userId: 用户ID(必填)")
    public UserInfo getUserInfo(String userId) {
        // 实现
    }
}

关键要素

  1. @Component - 让 Spring 扫描到
  2. @Tool - 标记为 Agent 可调用的工具
  3. description - 告诉 Agent 这个工具做什么

描述(Description)编写规范

好的描述

@Tool(description = "查询知识库文档。优先精确匹配关键词,未命中或多个匹配时自动补充语义相关片段。" +
        "参数 query: 查询关键词,例如 'ERR_TIMEOUT'、'支付网关超时'")
public LookupResult lookupKnowledge(String query) { ... }

要点:

  • ✅ 说明工具用途(查询知识库)
  • ✅ 说明工作机制(精确匹配 → 语义补充)
  • ✅ 说明参数含义和示例

差的描述

@Tool(description = "查询文档")  // ❌ 太简略
public LookupResult lookup(String q) { ... }

参数设计

参数命名

// ✅ 好的命名 - 语义清晰
public Result search(String query, int maxResults, String category)

// ❌ 差的命名 - 缩写难懂
public Result search(String q, int max, String cat)

参数类型

// ✅ 使用明确的类型
public UserInfo getUser(String userId)
public List<Order> getOrders(LocalDate startDate, LocalDate endDate)

// ❌ 使用 Object 或 Map
public Object getUser(Map<String, Object> params)  // Agent 不知道传什么

可选参数处理

@Tool(description = "查询订单。参数 status: 订单状态(可选,不传则查所有)")
public List<Order> getOrders(
    @Nullable String status  // 使用 @Nullable 标注
) {
    if (status == null) {
        return orderRepository.findAll();
    }
    return orderRepository.findByStatus(status);
}

返回值设计

使用明确的返回类型

// ✅ 好的返回类型
public class LookupResult {
    private boolean found;
    private PrimaryResult primary;
    private SupplementResult supplement;
}

// ❌ 返回 String - Agent 难以解析
public String lookup(String query) {
    return "找到文档: xxx";  // 非结构化
}

返回错误信息

public LookupResult lookup(String query) {
    if (query == null || query.isEmpty()) {
        return LookupResult.builder()
            .found(false)
            .error("查询关键词不能为空")
            .build();
    }
    // 正常逻辑
}

错误处理

优雅降级

@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("系统错误,请稍后重试");
    }
}

不要抛出未捕获的异常

// ❌ 不要这样做
@Tool(description = "查询用户")
public UserInfo getUser(String userId) {
    return userService.findById(userId);  // 可能抛出异常,Agent 无法处理
}

可观测性

日志规范

@Tool(description = "查询订单")
public List<Order> getOrders(String userId) {
    String requestId = UUID.randomUUID().toString().substring(0, 8);
    long startTime = System.currentTimeMillis();
    
    log.info("[{}] 收到订单查询请求: userId={}", requestId, userId);
    
    try {
        List<Order> 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;
    }
}

性能优化

设置合理的超时

@Tool(description = "查询大数据集")
public DataResult queryBigData(String query) {
    // 设置超时保护
    return CompletableFuture
        .supplyAsync(() -> heavyQuery(query))
        .orTimeout(5, TimeUnit.SECONDS)
        .exceptionally(ex -> DataResult.timeout())
        .join();
}

避免返回超大数据

// ✅ 分页或限制数量
@Tool(description = "查询用户列表(最多返回 100 条)")
public List<User> listUsers(int page, int size) {
    size = Math.min(size, 100);  // 强制上限
    return userService.findAll(PageRequest.of(page, size));
}

// ❌ 返回全量数据
public List<User> listAllUsers() {
    return userService.findAll();  // 可能几万条
}

工具组合示例

查询 + 操作的组合

@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", "用户主动取消")

常见陷阱

❌ 工具做太多事

// 不要把整个业务流程塞进一个工具
@Tool(description = "处理订单")
public void processOrder(String orderId) {
    // 查询订单
    // 验证库存
    // 扣减库存
    // 创建物流单
    // 发送通知
    // ...  太多步骤,Agent 无法介入
}

✅ 拆分成多个工具

@Tool(description = "查询订单") 
public Order getOrder(String orderId) { ... }

@Tool(description = "验证库存")
public StockResult checkStock(String productId, int quantity) { ... }

@Tool(description = "创建物流单")
public ShipmentResult createShipment(String orderId) { ... }

❌ 描述不准确

@Tool(description = "查询用户")  
public UserInfo getUser(String query) {
    // 实际上支持按 userId、email、手机号查询
    // 但描述没说清楚,Agent 不知道
}

✅ 描述完整

@Tool(description = "查询用户信息。支持按 userId、email 或手机号查询。" +
        "参数 query: 用户ID、邮箱或手机号")
public UserInfo getUser(String query) { ... }