# 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` **代码示例**: ```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 results = vectorSearchService.searchSimilarDocuments(query, topK); return objectMapper.writeValueAsString(results); } } ``` **注入方式**(`ChatService.java:93-101`): ```java 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`): ```java @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。 ```java @Service public class ChatService { @Autowired private ApplicationContext applicationContext; // ← Spring 上下文 /** * 自动扫描所有工具类 * 无需手动维护工具列表 */ public Object[] buildMethodToolsArray() { List tools = new ArrayList<>(); // 1. 获取所有 Spring Bean Map 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 函数定义工具** ⭐⭐ **适用场景**:工具逻辑简单、无状态、偏函数式 **改造示例**: **改造前**(当前方式): ```java @Component public class DateTimeTools { @Tool(description = "Get the current date and time") public String getCurrentDateTime() { return LocalDateTime.now()...toString(); } } ``` **改造后**(@Bean 函数): ```java @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 getCurrentDateTime() { return () -> LocalDateTime.now() .atZone(LocaleContextHolder.getTimeZone().toZoneId()) .toString(); } } // 使用 ReactAgent.builder() .toolNames("getCurrentDateTime") // ← 直接使用工具名 .build(); ``` **优点**: - ✅ 更简洁(适合简单工具) - ✅ 函数式风格 - ✅ Spring 自动发现和注册 **缺点**: - ❌ 无法使用成员变量(`Supplier` 无状态) - ❌ 工具名称为字符串,非类型安全 - ❌ 不适合需要依赖注入的复杂工具(如 `InternalDocsTools`) **结论**:**不推荐全面改造**,因为项目的工具大多需要依赖注入(`VectorSearchService`、`httpClient` 等)。 --- ### **优化3:工具元数据统一管理** ⭐⭐⭐ **问题**:工具名称定义为常量,但未被使用,容易不一致。 **当前代码**: ```java public class QueryMetricsTools { /** 工具名常量,用于动态构建提示词 */ public static final String TOOL_QUERY_PROMETHEUS_ALERTS = "queryPrometheusAlerts"; @Tool(description = "...") public String queryPrometheusAlerts() { // ← 方法名就是工具名 // ... } } ``` **问题**:`TOOL_QUERY_PROMETHEUS_ALERTS` 从未被使用,可能会过时。 **优化方案**:使用 `@Tool(name = ...)` 明确指定工具名 ```java public class QueryMetricsTools { public static final String TOOL_NAME = "queryPrometheusAlerts"; @Tool( name = TOOL_NAME, // ← 明确指定工具名(可选,默认为方法名) description = "Query active alerts from Prometheus..." ) public String queryPrometheusAlerts() { // ... } } ``` **或者**:移除无用的常量 ```java public class QueryMetricsTools { // 删除未使用的常量 // public static final String TOOL_QUERY_PROMETHEUS_ALERTS = "queryPrometheusAlerts"; @Tool(description = "...") public String queryPrometheusAlerts() { // 方法名即工具名 // ... } } ``` --- ### **优化4:工具分组与条件注册** ⭐⭐ **问题**:工具启用逻辑分散在多处(`@Autowired(required = false)`, `buildMethodToolsArray()`) **优化方案**:使用 `@ConditionalOnProperty` 统一管理 ```java // 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`**: ```java @Service public class ChatService { @Autowired private List 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 需要解析 **当前代码**: ```java @Tool(description = "...") public String queryInternalDocs(String query) { List results = vectorSearchService.searchSimilarDocuments(query, topK); return objectMapper.writeValueAsString(results); // ← 手动序列化 } ``` **优化方案**:返回结构化对象(Spring AI 自动序列化) ```java @Tool(description = "...") public InternalDocsResponse queryInternalDocs(String query) { List results = vectorSearchService.searchSimilarDocuments(query, topK); return new InternalDocsResponse(results); // ← 返回 POJO } @Data public class InternalDocsResponse { private List results; private int totalCount; private String status; public InternalDocsResponse(List 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️⃣ **工具设计原则** ```java // ✅ 好的工具设计 @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️⃣ **工具描述规范** ```java // ✅ 好的描述 @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️⃣ **工具错误处理** ```java @Tool(description = "...") public String queryInternalDocs(String query) { try { // 参数验证 if (query == null || query.trim().isEmpty()) { return buildErrorResponse("Query cannot be empty", "INVALID_INPUT"); } // 业务逻辑 List 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 官方文档** - Tool 定义:https://java2ai.com/docs/frameworks/agent-framework/tutorials/tools - ReactAgent:https://java2ai.com/docs/frameworks/agent-framework/tutorials/react-agent 2. **Spring AI 官方文档** - Function Calling:https://docs.spring.io/spring-ai/reference/api/functions.html 3. **项目现有工具类** - `DateTimeTools.java` - 最简单的工具示例 - `InternalDocsTools.java` - 依赖注入示例 - `QueryMetricsTools.java` - 配置注入 + 状态管理示例 --- ## ✅ 总结 ### 当前方式:**@Tool 注解 + 手动注册** ✅ **评价**:**已经是很好的选择**,适合当前项目规模和复杂度。 **理由**: 1. ✅ 工具需要依赖注入(`VectorSearchService`, `httpClient` 等) 2. ✅ 工具需要配置注入(`@Value`) 3. ✅ 工具需要生命周期管理(`@PostConstruct`) 4. ✅ 工具逻辑组织在类中,易于维护 --- ### 推荐的改进方向: 1. **短期**:移除未使用的常量,使用 `@ConditionalOnProperty` 2. **中期**:自动扫描工具类,减少手动维护 3. **长期**:根据实际需求考虑函数式改造或自定义 ToolCallback --- **结论**:**保持当前的 @Tool 注解方式**,逐步应用上述优化,而不是全面重构。