**变更概述:** - 将 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/(问题分析和重构计划)
17 KiB
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.javaInternalDocsTools.javaQueryMetricsTools.javaQueryLogsTools.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() { }
描述应包含:
- What:工具的功能
- Returns:返回值类型
- 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
);
}
📚 参考资料
-
Spring AI Alibaba Agent Framework 官方文档
-
Spring AI 官方文档
- Function Calling:https://docs.spring.io/spring-ai/reference/api/functions.html
-
项目现有工具类
DateTimeTools.java- 最简单的工具示例InternalDocsTools.java- 依赖注入示例QueryMetricsTools.java- 配置注入 + 状态管理示例
✅ 总结
当前方式:@Tool 注解 + 手动注册 ✅
评价:已经是很好的选择,适合当前项目规模和复杂度。
理由:
- ✅ 工具需要依赖注入(
VectorSearchService,httpClient等) - ✅ 工具需要配置注入(
@Value) - ✅ 工具需要生命周期管理(
@PostConstruct) - ✅ 工具逻辑组织在类中,易于维护
推荐的改进方向:
- 短期:移除未使用的常量,使用
@ConditionalOnProperty - 中期:自动扫描工具类,减少手动维护
- 长期:根据实际需求考虑函数式改造或自定义 ToolCallback
结论:保持当前的 @Tool 注解方式,逐步应用上述优化,而不是全面重构。