Files
SuperBizAgent-java/docs/learning/07-Tool定义方式对比与优化.md
zhuyongxin 60be51f4a5 docs: 重构文档结构,分离学习笔记和 MVP 架构设计
**变更概述:**
- 将 MVP 架构设计文档独立到项目根目录 `mvp/`
- 整理 `docs/` 为纯学习和分析文档目录
- 按类型分类:learning(学习)、analysis(分析)、reports(报告)、guides(指南)

**目录结构:**
```
mvp/                          # MVP 架构设计(独立)
├── README.md                 # 数据库设计总览
├── architecture/             # 架构文档
│   ├── agent-architecture-mvp.md
│   ├── implementation-plan.md
│   └── ...
└── tables/                   # 数据表设计

docs/                         # 学习和分析文档
├── learning/                 # 学习笔记(00-08 编号)
├── analysis/                 # 分析笔记 + 重构计划
├── reports/                  # 临时报告
└── guides/                   # 指南文档
```

**详细变更:**
- docs/README.md → mvp/README.md(数据库设计入口)
- docs/architecture/ → mvp/architecture/(架构设计)
- docs/tables/ → mvp/tables/(数据表设计)
- docs/学习笔记-*.md → docs/learning/07-*.md, 08-*.md
- docs/项目学习路径.md → docs/learning/00-*.md
- docs/功能分析报告.md → docs/analysis/
- docs/修复报告-*.md → docs/reports/
- docs/日志配置*.md → docs/guides/ 或 docs/reports/
- docs/design/ → docs/analysis/(问题分析和重构计划)
2026-06-23 14:14:51 +08:00

17 KiB
Raw Permalink Blame History

Tool 定义方式对比与优化建议

文档日期: 2026-05-31
参考文档: https://java2ai.com/docs/frameworks/agent-framework/tutorials/tools
项目: SuperBizAgent-java


📋 Spring AI Agent Framework 的 6 种 Tool 定义方式

方式 类型 难度 类型安全 动态性 最佳场景
1. @Tool 注解 声明式 ⭐ ✅ ❌ 静态工具、类组织
2. MethodToolCallback 编程式 ⭐⭐⭐ ✅ ✅ 动态构建、反射
3. FunctionToolCallback 函数式 ⭐⭐ ✅ ✅ 函数式逻辑
4. @Bean 函数 Spring式 ⭐ ❌ ✅ Spring 应用
5. ToolCallback 接口 自定义 ⭐⭐⭐⭐ ✅ ✅ 高度定制
6. MCP ToolCallback 外部进程 ⭐⭐ ✅ ✅ 外部服务

🔍 项目当前使用方式

方式1:@Tool 注解(主要方式)

使用位置:

  • DateTimeTools.java
  • InternalDocsTools.java
  • QueryMetricsTools.java
  • QueryLogsTools.java

代码示例:

@Component
public class InternalDocsTools {
    
    @Autowired
    private VectorSearchService vectorSearchService;  // ← 依赖注入
    
    @Value("${rag.top-k:3}")
    private int topK;  // ← 配置注入
    
    @Tool(description = "Use this tool to search internal documentation...")
    public String queryInternalDocs(
        @ToolParam(description = "Search query") String query) {  // ← 参数注解
        
        List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
        return objectMapper.writeValueAsString(results);
    }
}

注入方式(ChatService.java:93-101):

public Object[] buildMethodToolsArray() {
    if (queryLogsTools != null) {
        return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools, queryLogsTools};
    } else {
        return new Object[]{dateTimeTools, internalDocsTools, queryMetricsTools};
    }
}

// 在 ReactAgent 中使用
ReactAgent.builder()
    .methodTools(buildMethodToolsArray())  // ← 传入 @Tool 注解的对象
    .build();

方式6:MCP ToolCallback(外部工具)

使用位置:

  • 腾讯云 CLS 日志查询(真实模式)
  • 其他外部 MCP 服务

代码示例(ChatService.java:106-111):

@Autowired(required = false)
private ToolCallbackProvider tools;  // ← MCP 工具提供者

public ToolCallback[] getToolCallbacks() {
    if (tools == null) {
        return new ToolCallback[0];
    }
    return tools.getToolCallbacks();
}

// 在 ReactAgent 中使用
ReactAgent.builder()
    .methodTools(buildMethodToolsArray())  // Java 工具
    .tools(getToolCallbacks())             // MCP 工具
    .build();

✅ 当前方式的优缺点分析

优点 ✅

优点 说明
代码清晰 @Tool 注解一目了然,易于理解
类型安全 编译时检查,减少运行时错误
依赖注入 完美集成 Spring 生态(@Autowired, @Value)
易于测试 工具类可以独立单元测试
配置灵活 通过 @Value 读取配置(如 topK, mockEnabled)
状态管理 工具类可以有成员变量(如 httpClient, objectMapper)
生命周期 支持 @PostConstruct 初始化(如 QueryMetricsTools.init())

缺点 ❌

缺点 影响 是否需要优化
工具数组需要手动管理 每增加一个工具,需要修改 buildMethodToolsArray() ⚠️ 可优化
工具名称为常量字符串 TOOL_QUERY_PROMETHEUS_ALERTS 容易拼写错误 ⚠️ 可优化
无法动态启用/禁用工具 必须在编译时确定工具列表 ⚠️ 可优化(已有 Mock 模式)
工具发现不够智能 需要手动添加到数组,无法自动扫描 ⚠️ 可优化

🚀 优化方案

优化1:自动扫描 @Tool 注解 ⭐⭐⭐(推荐)

问题:每次新增工具类,都需要在 ChatService 中手动添加。

解决方案:自动扫描所有带 @Component 且包含 @Tool 方法的 Bean。

@Service
public class ChatService {
    
    @Autowired
    private ApplicationContext applicationContext;  // ← Spring 上下文
    
    /**
     * 自动扫描所有工具类
     * 无需手动维护工具列表
     */
    public Object[] buildMethodToolsArray() {
        List<Object> tools = new ArrayList<>();
        
        // 1. 获取所有 Spring Bean
        Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Component.class);
        
        for (Object bean : beans.values()) {
            // 2. 检查是否包含 @Tool 方法
            boolean hasTool = Arrays.stream(bean.getClass().getMethods())
                .anyMatch(m -> m.isAnnotationPresent(Tool.class));
            
            if (hasTool) {
                // 3. 根据配置决定是否添加
                if (shouldIncludeTool(bean)) {
                    tools.add(bean);
                    logger.info("🔧 自动注册工具: {}", bean.getClass().getSimpleName());
                }
            }
        }
        
        return tools.toArray();
    }
    
    /**
     * 判断是否应该包含某个工具(基于配置)
     */
    private boolean shouldIncludeTool(Object bean) {
        // 特殊处理:QueryLogsTools 只在 Mock 模式下启用
        if (bean instanceof QueryLogsTools) {
            return queryLogsTools != null;
        }
        return true;
    }
}

优点:

  • ✅ 新增工具类无需修改 ChatService
  • ✅ 自动发现所有工具
  • ✅ 保留配置化的启用/禁用逻辑

缺点:

  • ⚠️ 性能开销(启动时扫描一次,可接受)
  • ⚠️ 可能注册不需要的工具(需要过滤逻辑)

优化2:使用 @Bean 函数定义工具 ⭐⭐

适用场景:工具逻辑简单、无状态、偏函数式

改造示例:

改造前(当前方式):

@Component
public class DateTimeTools {
    @Tool(description = "Get the current date and time")
    public String getCurrentDateTime() {
        return LocalDateTime.now()...toString();
    }
}

改造后(@Bean 函数):

@Configuration
public class ToolsConfiguration {
    
    @Bean("getCurrentDateTime")
    @Description("Get the current date and time in the user's timezone. " +
                 "IMPORTANT: Time changes constantly. Always call this tool...")
    public Supplier<String> getCurrentDateTime() {
        return () -> LocalDateTime.now()
            .atZone(LocaleContextHolder.getTimeZone().toZoneId())
            .toString();
    }
}

// 使用
ReactAgent.builder()
    .toolNames("getCurrentDateTime")  // ← 直接使用工具名
    .build();

优点:

  • ✅ 更简洁(适合简单工具)
  • ✅ 函数式风格
  • ✅ Spring 自动发现和注册

缺点:

  • ❌ 无法使用成员变量(Supplier 无状态)
  • ❌ 工具名称为字符串,非类型安全
  • ❌ 不适合需要依赖注入的复杂工具(如 InternalDocsTools)

结论:不推荐全面改造,因为项目的工具大多需要依赖注入(VectorSearchService、httpClient 等)。


优化3:工具元数据统一管理 ⭐⭐⭐

问题:工具名称定义为常量,但未被使用,容易不一致。

当前代码:

public class QueryMetricsTools {
    /** 工具名常量,用于动态构建提示词 */
    public static final String TOOL_QUERY_PROMETHEUS_ALERTS = "queryPrometheusAlerts";
    
    @Tool(description = "...")
    public String queryPrometheusAlerts() {  // ← 方法名就是工具名
        // ...
    }
}

问题:TOOL_QUERY_PROMETHEUS_ALERTS 从未被使用,可能会过时。

优化方案:使用 @Tool(name = ...) 明确指定工具名

public class QueryMetricsTools {
    public static final String TOOL_NAME = "queryPrometheusAlerts";
    
    @Tool(
        name = TOOL_NAME,  // ← 明确指定工具名(可选,默认为方法名)
        description = "Query active alerts from Prometheus..."
    )
    public String queryPrometheusAlerts() {
        // ...
    }
}

或者:移除无用的常量

public class QueryMetricsTools {
    // 删除未使用的常量
    // public static final String TOOL_QUERY_PROMETHEUS_ALERTS = "queryPrometheusAlerts";
    
    @Tool(description = "...")
    public String queryPrometheusAlerts() {  // 方法名即工具名
        // ...
    }
}

优化4:工具分组与条件注册 ⭐⭐

问题:工具启用逻辑分散在多处(@Autowired(required = false), buildMethodToolsArray())

优化方案:使用 @ConditionalOnProperty 统一管理

// Mock 模式的日志查询工具
@Component
@ConditionalOnProperty(name = "cls.mock-enabled", havingValue = "true")
public class QueryLogsTools {
    @Tool(description = "...")
    public String queryLogs(...) {
        // Mock 实现
    }
}

// 真实模式的工具由 MCP 提供,无需 Java 实现

优点:

  • ✅ 配置化启用/禁用
  • ✅ 无需 @Autowired(required = false)
  • ✅ Spring 自动管理生命周期

修改后的 ChatService:

@Service
public class ChatService {
    
    @Autowired
    private List<Object> toolBeans;  // ← Spring 自动注入所有工具类
    
    @Autowired(required = false)
    private ToolCallbackProvider tools;
    
    public Object[] buildMethodToolsArray() {
        return toolBeans.stream()
            .filter(bean -> hasToolMethod(bean))  // 过滤出包含 @Tool 方法的 Bean
            .toArray();
    }
    
    private boolean hasToolMethod(Object bean) {
        return Arrays.stream(bean.getClass().getMethods())
            .anyMatch(m -> m.isAnnotationPresent(Tool.class));
    }
}

优化5:工具返回类型结构化 ⭐⭐

问题:工具返回值都是 String(JSON),LLM 需要解析

当前代码:

@Tool(description = "...")
public String queryInternalDocs(String query) {
    List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
    return objectMapper.writeValueAsString(results);  // ← 手动序列化
}

优化方案:返回结构化对象(Spring AI 自动序列化)

@Tool(description = "...")
public InternalDocsResponse queryInternalDocs(String query) {
    List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
    return new InternalDocsResponse(results);  // ← 返回 POJO
}

@Data
public class InternalDocsResponse {
    private List<SearchResult> results;
    private int totalCount;
    private String status;
    
    public InternalDocsResponse(List<SearchResult> results) {
        this.results = results;
        this.totalCount = results.size();
        this.status = "success";
    }
}

优点:

  • ✅ 类型安全
  • ✅ LLM 自动解析
  • ✅ 更清晰的数据结构

缺点:

  • ⚠️ 需要定义额外的 DTO 类
  • ⚠️ Spring AI 需要支持(当前版本可能只支持 String)

验证:查看 Spring AI 文档确认是否支持非 String 返回值。


🎯 推荐的优化优先级

短期优化(1-2周)

优化项 优先级 难度 收益
优化3:移除未使用的工具名常量 🔴 高 ⭐ 低 代码整洁
优化4:使用 @ConditionalOnProperty 🔴 高 ⭐⭐ 中 配置简化
优化1:自动扫描工具类 🟡 中 ⭐⭐⭐ 中 易扩展

中期优化(1个月)

优化项 优先级 难度 收益
优化5:工具返回类型结构化 🟡 中 ⭐⭐ 中 类型安全
添加工具单元测试 🟡 中 ⭐⭐ 中 质量保障
工具性能监控 🟢 低 ⭐⭐ 中 可观测性

长期优化(3个月+)

优化项 优先级 难度 收益
优化2:部分工具改为 @Bean 函数 🟢 低 ⭐⭐ 中 函数式风格
实现自定义 ToolCallback(高度定制) 🟢 低 ⭐⭐⭐⭐ 高 特殊需求

📊 对比表:当前方式 vs 推荐方式

维度 当前方式 推荐方式(优化后)
工具发现 手动添加到数组 自动扫描 @Tool 注解
启用/禁用 @Autowired(required = false) + 条件判断 @ConditionalOnProperty
工具名管理 未使用的常量 方法名即工具名
代码行数 ~100 行 ~50 行
易扩展性 ⭐⭐ ⭐⭐⭐⭐
维护成本 ⭐⭐⭐ ⭐

💡 最佳实践建议

1️⃣ 工具设计原则

// ✅ 好的工具设计
@Component
public class WeatherTools {
    
    @Tool(description = "Get current weather for a location. Returns temperature, humidity, and conditions.")
    public String getCurrentWeather(
        @ToolParam(description = "City name, e.g., 'Beijing', 'London'") String city) {
        
        // 清晰的输入验证
        if (city == null || city.trim().isEmpty()) {
            return "{\"error\": \"City name is required\"}";
        }
        
        // 结构化的返回值
        WeatherData data = weatherService.getWeather(city);
        return objectMapper.writeValueAsString(data);
    }
}

// ❌ 不好的工具设计
@Tool(description = "Get weather")  // ← 描述不够详细
public String getWeather(String c) {  // ← 参数名不明确
    return weatherService.get(c);  // ← 返回值不规范
}

2️⃣ 工具命名规范

规范 示例 说明
动词开头 getCurrentDateTime, queryInternalDocs 明确动作
驼峰命名 queryPrometheusAlerts Java 规范
避免缩写 queryMetrics ✅, queryMtr ❌ 可读性
包含主语 queryInternalDocs ✅, query ❌ 明确查询对象

3️⃣ 工具描述规范

// ✅ 好的描述
@Tool(description = 
    "Query active alerts from Prometheus alerting system. " +
    "Returns all currently firing alerts with labels, annotations, state, and values. " +
    "Use this when you need to check alert status, investigate conditions, or monitor system health.")
public String queryPrometheusAlerts() { }

// ❌ 不好的描述
@Tool(description = "Get alerts")  // ← 太简短
public String queryPrometheusAlerts() { }

描述应包含:

  1. What:工具的功能
  2. Returns:返回值类型
  3. When to use:使用场景

4️⃣ 工具错误处理

@Tool(description = "...")
public String queryInternalDocs(String query) {
    try {
        // 参数验证
        if (query == null || query.trim().isEmpty()) {
            return buildErrorResponse("Query cannot be empty", "INVALID_INPUT");
        }
        
        // 业务逻辑
        List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
        
        // 成功响应
        return buildSuccessResponse(results);
        
    } catch (Exception e) {
        logger.error("Tool execution failed", e);
        // 返回结构化错误(而不是抛异常)
        return buildErrorResponse("Query failed", e.getMessage());
    }
}

private String buildErrorResponse(String message, String details) {
    return String.format(
        "{\"status\": \"error\", \"message\": \"%s\", \"details\": \"%s\"}",
        message, details
    );
}

📚 参考资料

  1. Spring AI Alibaba Agent Framework 官方文档

  2. Spring AI 官方文档

  3. 项目现有工具类

    • DateTimeTools.java - 最简单的工具示例
    • InternalDocsTools.java - 依赖注入示例
    • QueryMetricsTools.java - 配置注入 + 状态管理示例

✅ 总结

当前方式:@Tool 注解 + 手动注册 ✅

评价:已经是很好的选择,适合当前项目规模和复杂度。

理由:

  1. ✅ 工具需要依赖注入(VectorSearchService, httpClient 等)
  2. ✅ 工具需要配置注入(@Value)
  3. ✅ 工具需要生命周期管理(@PostConstruct)
  4. ✅ 工具逻辑组织在类中,易于维护

推荐的改进方向:

  1. 短期:移除未使用的常量,使用 @ConditionalOnProperty
  2. 中期:自动扫描工具类,减少手动维护
  3. 长期:根据实际需求考虑函数式改造或自定义 ToolCallback

结论:保持当前的 @Tool 注解方式,逐步应用上述优化,而不是全面重构。