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

576 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<SearchResult> 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<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 函数定义工具** ⭐⭐
**适用场景**:工具逻辑简单、无状态、偏函数式
**改造示例**:
**改造前**(当前方式):
```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<String> 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<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 需要解析
**当前代码**:
```java
@Tool(description = "...")
public String queryInternalDocs(String query) {
List<SearchResult> results = vectorSearchService.searchSimilarDocuments(query, topK);
return objectMapper.writeValueAsString(results); // ← 手动序列化
}
```
**优化方案**:返回结构化对象(Spring AI 自动序列化)
```java
@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️⃣ **工具设计原则**
```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<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 官方文档**
- 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 注解方式**,逐步应用上述优化,而不是全面重构。